sqlmap Bool型&延時型 檢測策略分析
sqlmap Bool型&延時型 檢測策略分析
0x00 預備-queryPage()
首先先講一個核心的函式:queryPage()。這個函式在以下分析中貫穿始終。其被用於請求頁面, 同時具有多種返回值以適配多種檢測策略,見下圖:
下面分析一下這幾個return的情況。
第一個return:1237行,如果傳入timeBasedCompare=True,則return wasLastResponseDelayed(),這個語句會在延時型注入中用到。函式wasLastResponseDelayed()的返回值為一個布林值,如果上次請求有延遲則為True,反之為False。這個函式的具體的實現見延時型檢測策略的判斷依據部分。
第二個return:1249行,其中content和reponse都是函式引數傳過來的,預設為False,如果為true則返回這次請求的響應體、響應頭、響應碼。
第三個return:1252行,getRatioValue也為函式引數傳來的,如果為True,則返回一個元組,兩個元素分別為兩個comparison函式的返回值,此元組為(布林值, ratio)(注:ratio為兩次請求的響應內容的重合率,被用作判斷頁面是否變化的依據)。comparison函式被應用在Bool型盲注中,其具體實現邏輯見bool型檢測策略的判斷依據部分。
第四個return:1254行,為上一個return的else條件,直接返回ratio值。
0x01 bool型檢測策略
首先bool型檢測之前,程式會提前檢測頁面是否穩定(checkStability()),即測試兩個引數相同的請求的響應是否相同,如果不同則說明頁面中有可能存在類似於時間戳的東西。如果動態內容對頁面改動較大(ratio<0.98,ratio為呼叫quick_ratio的值,此函式原理下方說明),則呼叫findDynamicContent函式,去定位頁面中動態內容的位置,並將其定位存在kb中。
然後到了bool型檢測程式碼段,先上圖

首先看到475行呼叫了queryPage函式,其payload引數位置傳入了getCmpPayload(),getCmpPayload這個函式主要是根據每個payload對應的comparison標籤構造CmpPayload(用於與payload標籤進行比較,比如payload為1=1,那麼CmpPaload就是類似為1=2)。可以看到這一步程式沒有取queryPage的返回值,因為其主要作用是設定threadData中的lastPage之類的值,用於下一次True請求與之比較。緊接著就會發現476行直接把threadData的幾個last變數傳給了falsePage\falseHeaders\falseCode,方便下面比較。
接著看到480-482行傳送了True邏輯的請求,並將其響應也賦值給對應的truePage\trueHeaders\trueCode。這裡queryPage的返回值賦值給了trueResult,這個函式的返回值已經在第0部分中說了,由於這裡沒有傳入什麼多餘的引數,因此進入的是第四個return,即一個布林值,代表較ori_page,這個介面是否發生了變化。
然後在484行進行if判斷,當true與false響應不同時進入(bool注入可能存在的基礎)。進來後再次進行一次false判斷用於防止誤報,如果comparison返回的還是false,那就基本上確定為存在漏洞。反之,如果這次返回了True,與第一次的結果不同,那麼還要進行下一次防止誤報,嘗試瞭解為什麼會結果不同。
這就來到了505行:
這裡的防誤報原理主要是提取響應中的文字內容,然後將響應內容中的純文字內容拿出來進行單獨對比(即去掉script、css、註釋、html等標籤),同理,如果有True響應中有而False中沒有的字串,則確認為有漏洞。
然後剩下的525行之後,如果確認存在漏洞需要進行的內容,就是輸出一些bool型注入的判斷依據字元之類的東西,這裡就不在跟進了。如下圖:
判斷依據
comparison()函式原理:
經檢視程式碼可以發現,其主要呼叫的是_comparison(),因此下面對這個函式進行詳細的分析。
首先以kb.pageTamplate作為與本次請求對比的響應,這個變數是程式在初始化階段呼叫checkConnection()設定的,其請求中不包含payload。
然後在根據上面的動態內容位置,去掉kb.pageTamplate和本次請求響應中的動態內容。見89-90行
最後呼叫difflib庫的quick_ratio方法,計算兩者的頁面相似比率,將之賦值給ratio。見139行
程式事先定義了兩個常量:
LOWER_RATIO_BOUND==0.02 UPPER_RATIO_BOUND==0.98
然後設定kb.matchRatio,當ratio在兩者中間時,程式會將ratio賦值給kb.matchRatio。見143-146行
最後return時,此函式分為了以下幾種情況:
1.當ratio>0.98時返回True,即判斷為頁面相同;
2.ratio<0.02時判斷為頁面不同;
3.return (ratio - kb.matchRatio) > DIFF_TOLERANCE(0.05),kb.matchRatio是一個在0.02與0.98之間的值,在上面已經說了,是在一次請求中使用ratio賦值的。這個return的判斷可以轉換成ratio>kb.matchRatio+0.05,也就是說ratio必須大於之前的一次ratio至少0.05才行,同時如果ratio>0.98也是直接返回True的(第一次的條件)。
quick_ratio()
統計字元的個數(比如字母a有29個等等),然後拿匹配的字母數*2/兩個對比字串的字元總數
案例
下面看一下在使用正確的boundary突破邊界之後,True請求和False請求的最終對比介面是什麼。
首先是一個正常請求的響應(www.test.com/index.php?id=2):
第一個框是直接返回url中的引數值,即id=2。
第二個框是時間戳,即保證在每次響應這一部分都是變化的。
第三個框是“you are in”,表示可以在資料庫中找到資料,看一下原始碼:
下面先看一下True請求的響應。
可以看到,第一個框處只有id=,沒有id的值,這是因為在進行對比之前,為了避免“payload本身就是不同的”這種影響,實現就已經去掉了這些反射型的引數(類似於xss,即頁面直接返回了使用者可控的引數)。
再False的響應。
這裡也沒有第一個框,跟True響應的情況相同。同時這裡也沒有第三個框,因為這個False語句構建了一個邏輯假的SQL語句,直接導致程式無法在資料庫中找到資料。
0x02 延時型
598行開始進行延時型判斷,600行呼叫queryPage函式,返回值為一個布林值,True表示這次請求產生了延遲(見‘判斷依據’部分),code為http響應碼。
接著進入603行的if語句,如果上次存在延時則進入(由於payload中使用了[SLEEPTIME],在queryPage中將之替換成了一個值,所以如果存在漏洞則一定會有延時)。
如果進入了這個語句塊則再次進行一次時延時間為0的請求,如果還是產生時延則說明為誤報。如果沒有時延則在611行再進行一次有時延時間的請求,結果如果還是True(與603行結果一致),則確定為存在漏洞。
判斷依據
判斷本次請求是否產生延遲呼叫的是wasLastResponseDelayed函式,先放個圖:
可以看到,首先計算標準差:deviation。然後在2402行計算lowerStdLimit,值為平均數+7*標準差,以這個值看做一個閾值,作為最大延遲時間(小於0.5時看做0.5)。大於這個數的請求都看做是發生延時的請求。+-7*標準差能保證99.9999999997440%的未延時請求能落在這個區間裡。
使用標準差的原因
為什麼不是平均值呢,因為標準差更能反映一組資料的離散程度。比如熊的平均體重是140kg,標準差為5kg,那麼一隻熊的重量可能在135-145kg(根據標準差計算)之間,也有可能在120-160kg(根據平均數猜測)之間。因此僅僅檢視所有熊的平均重量就不可能知道單個熊的重量,但是標準差可以告訴你單個熊的可靠重量範圍。這就是這裡使用標準差的意義。
參考:
1.How Self-Tuning Threshold Baseline is Computed
2.標準差的意義