sqlmap 源碼閱讀與流程分析

0x01 前言

還是代碼功底太差,所以想嘗試閱讀 sqlmap 源碼一下,并且自己用 golang 重構,到后面會進行 ysoserial 的改寫;以及 xray 的重構,當然那個應該會很多參考 cel-go 項目

0x02 環境準備

sqlmap 的項目地址:https://github.com/sqlmapproject/sqlmap 用 pycharm 打斷點調試,因為 vscode 用來調試比較麻煩。

因為要動調,所以需要一個 sql 注入的靶場,這里直接選用的是 sql-labs,用 docker 起

docker pull acgpiano/sqli-labs
docker run -dt --name sqli-lab -p [PORT]:80 acgpiano/sqli-labs:latest

最后還需要重新配置一下數據庫,然后才能以 sqli-labs 為靶場進行測試。

這里也掛一下 sqlmap 對應的一些基礎操作 ———— https://www.cnblogs.com/hongfei/p/3872156.html

直接在 pycharm 的 Debug 下進行調試,設置參數如下,開始調試

-u "http://81.68.120.14:3333/Less-1/?id=1" -technique=E --dbs

0x03 sqlmap 源碼閱讀

在開始之前我們有必要確認一下?sqlmap 運行的流程圖,很重要!這樣有助于我們進一步分析源碼。

1. 初始化

在?sqlmap.py?的 main 函數下斷點,開始調試

在沒有對 URL 進行發包/探測的時候 sqlmap 會先對一些環境、依賴、變量來做一些初始化的處理

往下,通過?cmdLineParser()?獲取參數,cmdLineParser()?通過?argparse?庫進行 CLI 的打印與獲取,類似的一個小項目我之前也有接觸過 https://github.com/Drun1baby/EasyScan

往下?initOptions(cmdLineOptions)?解析命令行參數

init?函數:初始化

在?init()?函數中通過調用各種函數進行參數的設置、payload 的加載等,有興趣的師傅可以點進去閱讀一下。

其中這三個相對比較重要,是用來加載 payload 的 ————?loadBoundaries()、loadPayloads()、_loadQueries(),

loadBoundaries()  // 加載閉合符集合
loadPayloads()    // 加載 payload 集合
_loadQueries()    // 加載查詢語句,在檢測到注入點之后后續進行數據庫庫名字段名爆破會用到的語句

下個斷先點調試一下?loadBoundaries()?函數

首先,會去加載?paths.BOUNDARIES_XML,也就是?data/xml/boundaries.xml

接著進入解析 XML 文件的部分,跟進?parseXmlNode(root)

最終添加到 conf 對象的 tests 屬性里

  • ??loadPayloads()?函數與?_loadQueries()?函數大體上也是如此,都是做了解析 xml 文件的工作,再將內容保存到 conf 對象的 tests 屬性里。像?loadPayloads()?函數,最后在?conf.tests?里面可以很清晰的看到 payloads

此時我們還可以看一下?conf?是什么

conf 屬性中主要存儲了一些目標的相關信息(hostname、path、請求參數等等)以及一些配置信息,init 加載的 payload、請求頭 header、cookie 等

init()?函數執行完畢后,就會來到?start()?函數進行項目的正式運行。

初始化功能點小結

簡單概括一下初始化部分的代碼做了什么事

  • ? 獲取命令行參數并處理
  • ? 初始化全局變量 conf 以及 kb
  • ? 獲取并解析幾個 xml 文件,完成閉合工作、payloads 加載工作
  • ? 設置 HTTP 相關配置,如 HTTP Header,UA,Session 等

2. URL 處理

f8 下來,先到的是?threadData = getCurrentThreadData(),繼續往下走,到?result = f(*args, **kwargs)?代碼塊,跟進一下

代碼邏輯此時來到了?/lib/controller/controller.py?下,往下走,是不會進到?conf.direct?和?conf.hashFile?中的,會直接進入到?kb.targets.add()?的代碼邏輯里面。

此處的 kb 變量的作用是共享一些對象,其實本質上是保存了注入時的一些參數。kb.targets?添加了我們輸入的參數,如圖

往下看,大體上是做了一些類似類似打印日志、賦值、添加 HTTP Header 等工作,這一部分代碼我們就不看了,直接看最關鍵的這一部分代碼?parseTargetUrl()。

跟進

一開始先進行了這一判斷

if re.search(r"://\[.+\]", conf.url) and not socket.has_ipv6

判斷?http://?的開頭形式是否正確,以及 socket 是否為 ipv6 協議,如果為 ipv6 協議,那么 sqlmap 并不支持。

接著判斷

if not re.search(r"^(http|ws)s?://", conf.url, re.I):

判斷是 http 開頭還是 https 開頭,又或者是否是 ws/wss 開頭,如果沒有這些開頭,則就從端口判斷,這里我認為或許可以加上 80 與 8080 端口。

繼續往下看,進行了 url 的拆分、host 的拆分,并將這些內容保存到 conf 里面的對應屬性,后續也是一些基礎的判斷與賦值,這里不再贅述。

總而言之是在對 URL 進行剖析與拆解,最后這些東西都是放到?conf?里面的

3. 如果這個網站已經被注入過,生成注入檢測的payload

核心代碼在?controller.py?的第 434 行,需跟進;此處我們可以設置對?kb.injections?的變量監測。先跟進?setupTargetEnv()?函數

  • ??setupTargetEnv()?函數調用了如下圖所示的七個函數

我們跟進最主要的?_resumeHashDBValues()?函數,首先調用了?hashDBRetrieve()?函數,設置檢索

出來,到第 476 行,這一次又調用了?hashDBRetrieve()?函數,傳參是?HASHDB_KEYS.KB_INJECTIONS,意思就是以?KB_INJECTIONS?作為 KEY 進行檢索。跟進發現函數先將需要注入的 URL 信息放到了?_?這個變量中,并將基礎信息用?|?符號隔開。

跟進?retrieve()?函數,這個函數做了生成 payload 的工作,具體是怎么生成的我們繼續往下看

第 95 行,這里很重要,執行了 SQL 語句,并通過 Hash 加密,加密方式是 base64Pickle 序列化

最終反序列化解密 Payload,說實話這里沒看懂是怎么生成的,看上去僅僅是執行了一個 SQL 語句,后面看其他師傅的文章的時候并沒有把這一段單獨拉出來說,payloads 其實都放在 xml 當中。

接著再循環一次,生成一個 payload

在生成完所有 payload 之后會先對目標進行一次探測,如果 Connection refused 則返回 False

這里生成的 payload 只是很基礎的一部分,并非是

4. WAF 檢測

解析完 URL 之后對目標進行探測,往下看,位置是?controller.py?的第 439 行,第 448 行有?checkWaf()?的函數,很明顯就是要做 WAF 檢測的功能。

先會判斷這一目標是否存在 WAF,如果存在 WAF 的話,會進行字符的相關 fuzz,當然此處建議對一個存在 WAF 的目標進行測試。值得注意的是,如果這個目標你已經探測過存在 waf,且已知 waf 歸屬廠商的情況下,就不會走到 payload 那一段代碼邏輯當中去,相關的業務代碼在?hashDBRetrieve()?下,此處不再展開,比較容易。

如果存在 WAF,則會生成用于 fuzz 的 payload,這個 payload 是基于這個 NMAP 的?http-waf-detect.nse?———— https://seclists.org/nmap-dev/2011/q2/att-1005/http-waf-detect.nse

設置 payload 類似于?"9283 AND 1=1 UNION ALL SELECT 1,NULL,'<script>alert("XSS")</script>',table_name FROM information_schema.tables WHERE 2>1--/**/; EXEC xp_cmdshell('cat ../../../etc/passwd')#",如果沒有 WAF,頁面不會變化,如果有 WAF,因為 payload 中有很多敏感字符,大多數時候頁面都會發生改變。

接下來的?conf.identifyWaf?代表 sqlmap 的參數?--identify-waf,如果指定了此參數,就會進入?identifyWaf()?函數,主要檢測的 waf 都在 sqlmap 的 waf 目錄下。不過新版的 sqlmap 已經將這一參數的功能自動放到里面了,無需再指定參數

  • ? 這里的 payload 先經過處理后賦值給 value,再將 value 作為參數傳入?queryPage()?請求中,跟進

在經過很長一段的數據處理與判斷代碼后,我們到第 1531 行,如圖,跟進;getPage()?函數的作用是獲取界面的一些信息,如 url,ua,host 等,通過輸出比對 payload,為判斷 waf 類型提供信息。

  • ? 獲取基本信息

這些基礎信息最后都會保存在 response 系列的 message 當中

getPage()?函數中調用了?processResponse()?函數做響應結果的處理,跟進

往下看,到 401 行開始,后續的代碼進行了 Waf 的識別

跟進?identYwaf.non_blind_check(),是通過正則表達式來對頁面進行匹配,對應的規則在?thirdparty/identywaf/data.json?中

同時 sqlmap 不光通過規則庫來進行判斷,也會通過頁面相似度來判斷是否存在?waf/ips

如果相似度小于設定的 0.5 那么就判定為有 waf 攔截

WAF 注入總結

總結一下就是兩點,一種方法是通過正則匹配的檢測,另外一種方法是根據頁面相似度來檢測,我自己應該很難寫出來 waf 檢測的東西;屆時再做嘗試。

5. 注入檢測之啟發式注入

從?checkWaf()?函數里面出來,先到第 457 行,檢測網站是否穩定(因為有些網站一測試可能就炸了)對應此 info

[INFO] testing if the target URL content is stable

繼續往下走到第 471 行,會先判斷參數是否可以注入,這里與命令的參數 ——?--level?掛鉤

在前文環境準備的時候我們采用的方式是報錯注入,如果不這么做,直接指定參數?--dbs,無法進入到啟發式注入里面。我們接著看代碼,往下直到第 581 行,調用的?heuristicCheckSqlInjection()?函數,意思是啟發性注入。

  • ? 啟發式注入做了哪些工作

1、數據庫版本的識別?2、絕對路徑獲取?3、XSS 的測試

數據庫版本的識別

首先會從?HEURISTIC_CHECK_ALPHABET?中隨機抽取10個字符出現構造 Payload,當然里面的都不是些普通的字符,而且些特殊字符,當我們進行 SQL 注入測試的時候會很習慣的在參數后面加個分號啊什么的,又或者是其他一些特殊的字符,出現運氣好的話有可能會暴出數據的相關錯誤信息,而那個時候我們就可以根據所暴出的相關錯誤信息去猜測當前目標的數據庫是什么。

并且最后生成的這個 payload 是能夠閉合的

實際找個網站測試,如圖,這就是報出的 SQL 數據庫錯誤

判斷在?lib/request/connect.py?的 1532 行

接著跟進?processResponse()?函數,這里和 waf 對比用的同一種方式,不再詳細說明

其中?processResponse()?會調用到?./lib/parse/html.py?中的?htmlParser()?函數,這一個函數就是根據不同的數據庫指紋去識別當前的數據庫究竟是什么。

最終實現這一功能的其實是?HTMLHandler?這個類,errors.xml?文件內容如圖

這一配置文件的比較簡單,其實也就是一些對應數據庫的正則。sqlmap 在解析?errors.xml?的時候,然后根據?regexp?中的正則去匹配當前的頁面信息然后去確定當前的數據庫。這一步和 WAF 比對類似。

到此 sqlmap 就可以確定數據的版本了,從而選擇對應的測試 Payload,后續我們會看到這是根據莫索引將 payloads 排序,然后選取對應數據庫信息的 payloads 進行測試。減少 sqlmap 的掃描時間。

  • ? 最后這個 DBMS 探測對應的是這一段信息

獲取絕對路徑與 XSS 探測

相比指紋識別,獲取絕對路徑的功能模塊相對簡單,利用正則匹配尋找出絕對路徑。

XSS 的探測也比較簡單,這里就不作代碼分析了

6. 注入檢測之正式注入

從啟發式注入里面出來,到第 592 行,進行正式的注入檢測,跟進

到第 130 行,獲取所有的 payload,后續會根據數據庫的信息構建索引,將符合索引的 payload 拿去攻擊

往下走,先判斷有沒有做數據庫信息的獲取,如果有則跳過,如果沒有就先進行上一步的啟發式注入

接著根據通過報錯得到的數據庫信息建立索引,將對應最有效的 payload 拿出來。這些 payloads 會進行 while 循環

第 370 行,通過?cleanupPayload()?函數對 payload 進行處理,主要功能其實是做了 payload 的標簽替換

最后替換過的 payload 長這樣

"AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT('qbpxq',(SELECT (ELT(9125=9125,1))),'qxkvq','x'))s), 8446744073709551610, 8446744073709551610)))"

在 sqlmap 中將payload 分為了三部分,上面生成的 fstpayload 就是中間那部分

prefix + payload + suffix 

prefix 和 suffix 就是對應的,閉合前面的結合以及注釋后面的結構,這兩個屬性主要是從 boundary 中進行獲取的,boundary 就是前面加載的?boundaries.xml?配置文件,用來閉合的,所以這里作為了 prefix 和 suffix

最后的拼接

并分別對 prefix 和 suffix 進行 clean,然后進行組合,組合之后的 payload 就是 reqPayload,然后進行請求

  • ? 發出請求最終還是通過?request.queryPage()?來實現的

請求完畢的結果經過?queryPage()?函數來獲取界面,但是頁面結果是由?kb.chars.start?和?kb.chars.stop?包裹著的

當第一次的注入不成功的時候,會不斷變更 prefix,suffix,當 prefix 和 suffix 都變更完畢但還是無法注入時,才會變更 payload,取出另一個 payload 出來,直至?injectable?變量為 true,同時?output=1

并且?injectable=true

7. 爆數據庫等操作

經過上一步正式注入的判斷,得到的?injectable=true?參數,才能進行下一步的爆數據庫操作.

爆庫階段主要是先經過四個函數處理數據后,再調用?action()?函數,跟進。

這里已爆庫為例,先看?--dbs?參數有關的這一塊,核心函數是?getDbs()

先根據后臺數據庫信息,輸出日志

第 133 行,queries?就是存放之前初始化?queries.xml?的變量

首先通過?count(schema_name)?來獲取數據庫的個數,然后再通過?limit num,1?來依次獲取數據庫名,從?queries?變量中獲取語句之后就會傳遞到?getValue?函數

跟進,前面做了一些基礎的設置和 payload 的處理與賦值,比如第 401 行的?cleanQuery()?函數,將語句轉換為大寫,這里我就不跟進了。直接看關鍵語句,第 451 行,errorUse()?函數

在?errorUse()?中首先通過正則將 payload 中的各個部分都進行了獲取 ,保存到了對應的?field?當中,最終經過一系列處理,取出了 payload 中的?schema_name

跳出?getFields()?函數,往下,將?expression?的值經過 replace 操作,賦值給了?countedExpression,最終得到的值是?'SELECT COUNT(schema_name) FROM INFORMATION_SCHEMA.SCHEMATA'

第 337 行,跟進?_oneShotErrorUse()?函數,在這一個函數中,sqlmap 對目標網站發包,使用的 payload 為?countedExpression,目的是探測數據庫個數(count)

具體業務發包在這里

最后將結果傳入?extractRegexResult()?函數中進行正則提取

多線程的方式進行注入,而?runThreads()?函數調用了?errorThread()?函數,最終的注入業務還是由?errorThread()?函數來完成的

跟進一下?_errorFields()?函數,將每一個表進行 while 循環操作,再通過?limitQuery()?函數設置最后的?Limit?語句

最后成功?--dbs

sqlmap 流程分析結束

0x04 小結

sqlmap 的流程分析需要非常重視這張圖,當感覺代碼看不下去的時候看一下這張圖可以事半功倍。

在審計開始之前也可以看一下?utils?文件夾下的 python 文件,總體來說流程并不難,看正則的時候其實挺吃力的。

0x05 Reference

http://wjlshare.com/archives/1733 https://wooyun.js.org/drops/SQLMAP%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90Part1.%E6%B5%81%E7%A8%8B%E7%AF%87.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/73674.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/73674.shtml
英文地址,請注明出處:http://en.pswp.cn/web/73674.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

vscode連接服務器失敗問題解決

文章目錄 問題描述原因分析解決方法徹底刪除VS Code重新安裝較老的版本 問題描述 vscode鏈接服務器時提示了下面問題&#xff1a; 原因分析 這是說明VScode版本太高了。 https://code.visualstudio.com/docs/remote/faq#_can-i-run-vs-code-server-on-older-linux-distribu…

企業網站源碼HTML成品網站與網頁代碼模板指南

在當今數字化時代&#xff0c;企業網站已成為展示品牌形象、吸引客戶和提供在線服務的重要工具。對于許多企業來說&#xff0c;使用現成的HTML網站源碼模板是快速搭建網站的高效方式。本文將詳細介紹企業網站源碼、HTML成品網站以及網頁代碼模板的相關內容&#xff0c;幫助你快…

計算機網絡 - OSI 七層模型

OSI 七層模型 OSI&#xff08;Open System Interconnection&#xff0c;開放系統互聯&#xff09;模型由 ISO&#xff08;國際標準化組織&#xff09; 制定&#xff0c;目的是為不同計算機網絡系統之間的通信提供一個標準化的框架。它將網絡通信劃分為 七個層次&#xff0c;每…

flutter-實現瀑布流布局及下拉刷新上拉加載更多

文章目錄 1. 效果預覽2. 結構分析3. 完整代碼4. 總結 1. 效果預覽 在 Flutter 應用開發中&#xff0c;瀑布流布局常用于展示圖片、商品列表等需要以不規則但整齊排列的內容。同時&#xff0c;下拉刷新和上拉加載更多功能&#xff0c;能夠極大提升用戶體驗&#xff0c;讓用戶方…

在 Ubuntu 下通過 Docker 部署 Nginx 服務器

1. Docker 和 Nginx 簡介以及實驗環境 Docker 是一個開源的容器化平臺&#xff0c;允許開發者將應用程序及其依賴項打包成一個輕量級的、可移植的容器。通過 Docker&#xff0c;開發者可以在任何支持 Docker 的環境中運行應用&#xff0c;從而實現一致的開發和生產環境。Docke…

IoT平臺實時監測機器人狀態的實現方案

通過IoT平臺實時監測機器人狀態的實現方案與可執行路徑 一、整體架構設計 #mermaid-svg-6xMlDfFSZM4Wc8tA {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-6xMlDfFSZM4Wc8tA .error-icon{fill:#552222;}#mermaid-sv…

mybatis里in關鍵字拼接id問題

我們一般會把ids集合用StrUtil.join(‘,’)轉成"1,2,3"這種形式 然后放入in中 我們會這么寫: select id, nick_name, icon from tb_user where id in (#{ids}) order by FIELD(id, #{ids})結果發現sql執行是這樣的: select id, nick_name, icon from tb_user where…

4.用 Excel 錄入數據

一 用 Excel 錄入數據的兩種方式 用鼠標鍵盤錄入數據和從網上爬取數據。 二 用鼠標鍵盤錄入數據 1.錄入數據的規范 橫著錄入數據&#xff08;橫著一條條錄入數據&#xff09;。 2.使用快捷鍵進行數據錄入 tab 鍵和 enter 鍵。 tab 鍵&#xff1a;向右移動一個單元格。 tab 鍵…

C++類與對象-3.23筆記

今天學習了類的概述和寫類的基本框架 在嗶哩嗶哩學習的這個老師的C面向對象高級語言程序設計教程&#xff08;118集全&#xff09;講的很不錯&#xff08;真的&#xff01;&#xff01;&#xff01;&#xff09;&#xff0c;C語言也是在這個老師的帶領下學習的 #include<io…

Android讀寫權限分析

Android系統使用的是Linux內核&#xff0c;所以Android系統沿用了linux系統的那一套文件讀寫權限。 目錄 1&#xff0c;權限解讀1.1&#xff0c;權限分為三種類型&#xff1a;1.2&#xff0c;權限針對的三類對象&#xff1a;1.3&#xff0c;文件和目錄的權限區別1.3.1&#xf…

Python二分查找【清晰易懂】

1. 二分查找是什么&#xff1f; 想象你在玩“猜數字”游戲&#xff1a; 對方心里想一個 1~100 的數字&#xff0c;你每次猜一個數&#xff0c;對方會告訴你是“大了”還是“小了”。 最快的方法&#xff1a;每次都猜中間的數&#xff01;比如第一次猜50&#xff0c;如果大了&…

關于Qt的各類問題

目錄 1、問題&#xff1a;Qt中文亂碼 2、問題&#xff1a;啟動時避免ComBox控件出現默認值 博客會不定期的更新各種Qt開發的Bug與解決方法,敬請關注! 1、問題&#xff1a;Qt中文亂碼 問題描述&#xff1a;我在設置標題時出現了中文亂碼 this->setWindowTitle("算法…

關于我對接了deepseek之后部署到本地將數據存儲到mysql的過程

寫在前面 今天寫一下使用nodejs作為服務端&#xff0c;vue作為客戶端&#xff0c;mysql的數據庫&#xff0c;對接deepseek的全過程&#xff0c;要實現一個很簡單的效果就是&#xff0c;可以自由的詢問&#xff0c;然后可以將詢問的過程存儲到mysql的數據庫中。 文檔對接 deeps…

游戲引擎學習第182天

回顧和今天的計劃 昨天的進展令人驚喜&#xff0c;原本的調試系統已經被一個新的系統完全替換&#xff0c;新系統不僅能完成原有的所有功能&#xff0c;還能捕獲完整的調試信息&#xff0c;包括時間戳等關鍵數據。這次的替換非常順利&#xff0c;效果很好。 今天的重點是在此基…

CSS終極指南:從基礎到高級實踐

目錄 一、CSS基礎概念與核心語法 1.1 CSS的本質與作用 1.2 CSS語法結構 二、CSS與HTML結合的四種方式 2.1 內聯樣式&#xff08;Inline Style&#xff09; 2.2 內部樣式表&#xff08;Internal Style Sheet&#xff09; 2.3 外部樣式表&#xff08;External Style Sheet…

MATLAB 2024b深度學習新特性全面解析與DeepSeek大模型集成開發

MATLAB 2024b深度學習工具箱通過架構創新與功能強化&#xff0c;為科研創新和行業應用提供了全棧式解決方案。 第一&#xff1a;MATLAB 2024b深度學習工具箱新特性 1、MATLAB Deep Learning Toolbox 2、實時腳本&#xff08;Live Script&#xff09;與交互控件&#xff08…

.NET開源的智能體相關項目推薦

一、AntSK 由AIDotNet團隊開發的人工智能知識庫與智能體框架&#xff0c;支持多模型集成和離線部署能力。 核心能力&#xff1a; ? 支持OpenAI、Azure OpenAI、星火、阿里靈積等主流大模型&#xff0c;以及20余種國產數據庫&#xff08;如達夢&#xff09; ? 內置語義內核&a…

Qt彈出新窗口并關閉(一個按鈕)

參考&#xff1a;Qt基礎 練習&#xff1a;彈出新窗口并關閉的兩種實現方式&#xff08;兩個按鈕、一個按鈕&#xff09;_qt打開一個窗口另一個關閉-CSDN博客 實現&#xff1a; 一個按鈕&#xff0c;點擊一次&#xff0c;按鈕的名字從open window變為close window&#xff0c;…

PHP中yield關鍵字的使用

PHP版本>5.5 原理&#xff1a;yield關鍵字會生成一個Generator類的對象&#xff0c;PHP通過Generator實例計算出下一次迭代的值&#xff0c;再次返回一個Generator對象并停止循環&#xff08;即循環一次執行一次&#xff09;。 理解&#xff1a;使用在for/foreach/while循…

SpringBoot集成騰訊云OCR實現身份證識別

OCR身份證識別 官網地址&#xff1a;https://cloud.tencent.com/document/product/866/33524 身份信息認證&#xff08;二要素核驗&#xff09; 官網地址&#xff1a;https://cloud.tencent.com/document/product/1007/33188 代碼實現 引入依賴 <dependency><…