三、原始碼閱讀
基本上比較常用的引數都在上面列了一下,當個速查表吧,然後就開始學習sqlmap原始碼之路吧。
本次學習的原始碼版本為 1.3.6.50#dev
目錄結構
首先看一下目錄結構,我們將文件類的檔案排除掉,只看系統類的:
主目錄
檔案/資料夾 說明
data/ 資料庫注入檢測載荷、使用者自定義攻擊載荷、字典、shell命令、資料庫觸發順序等
extra/ 一些額外功能,例如發出聲響(beep)、執行cmd、安全執行、shellcode等
lib/ 包含了sqlmap的多種連線庫,如五種注入型別請求的引數、提權操作等。
plugins/ 資料庫資訊和資料庫通用事項
tamper/ 繞過指令碼
thirdparty/ sqlmap使用的第三方的外掛
sqlmap.conf sqlmap的配置檔案,如各種預設引數(預設是沒有設定引數、可設定預設引數進行批次或者自動化檢測)
sqlmap.py sqlmap主程式檔案
sqlmapapi.py sqlmap的api檔案,可以將sqlmap整合到其他平臺上
swagger.yaml api文件
程式初始化
main()函式首先是執行5個函式,我們先依次看下每個函式的作用。
補丁包:dirtyPatches()
對於程式的一些問題及修復,寫成了補丁函式,優先執行。
首先設定了 httplib 的最大行長度,接下來匯入第三方的 windows 下的 ip地址轉換函式模組,然後對編碼進行了一些替換,把 cp65001 替換為 utf8 避免出現一些互動上的錯誤,這些操作對於 sqlmap 的實際功能影響並不是特別大,屬於保證起使用者體驗和系統設定的正常選項,不需要進行過多關心
解決交叉引用:resolveCrossReferences()
為了消除交叉引用的問題,一些子程式中的函式會被重寫,在這個位置進行賦值
檢查環境:checkEnvironment()
如名,函式的主要作用是檢查環境
首先呼叫modulePath(),獲取程式路徑,判斷程式是否由 py2exe 打包成 exe ,因為打包後無法使用 file 獲得路徑,為了防止亂碼,使用getUnicode返回unicode編碼的路徑
然後使用LooseVersion判斷python版本號,版本號小於1.0則報錯並退出
接下來是對pip安裝環境的補丁,可以看到是一些系統環境變數的設定
這裡面定義個貫穿sqlmap全域性的三個全域性變數,cmdLineOptions,conf ,kb
絕對路徑設定:setPaths()
配置程式檔案的路徑,並且判斷.txt, .xml, .zip為副檔名的檔案是否存在並且是否可讀,這部分將預設的程式全部使用的資訊都載入進來了,在後續的呼叫中就去各個變數、字典裡取就可以了
paths值為一個sqlmap自己封裝的字典型別 AttribDict
為什麼作者要自己封裝一個字典型別呢?首先能看到的就是,可以直接使用訪問屬性的方式訪問鍵值
其次是作者定義了deepcopy,為了解決字典賦值傳遞後淺copy會修改原資料的問題
BANNER展示:banner()
函式判斷執行引數中是否包含 --version 或 --api引數,或者在配置中是否將disableBanner中設定為True,如果都沒有,那就將BANNER字串賦值給 "_"
跨終端顯示顏色則使用了第三方外掛: colorama 庫。跟一下變數BANNER
使用random.sample隨機選擇,然後使用正則替換,再跟一下BANNER,最後我們看到的就是SQLMAP的字元畫了
實際上這部分看出來SQLMAP中間紅色部分是隨機的。。用了這麼久竟然不知道。。
五個函式執行之後進入之後的流程,在 banner 列印之後,就是對命令列引數的操作了
cmdLineOptions 同樣是一個 AttribDict,跟進 cmdLineParser
可以看到,對命令列引數的處理使用了 optparse,首先將獲取到的命令列引數選項進行判斷和拆分以後轉變成dict鍵值對的形式存入到cmdLineOptions,然後傳入initOption進行下一步操作
_setConfAttributes() 函式初始化了一些重要的屬性值為空
_setKnowledgeBaseAttributes() 函式同樣的初始化了一些重要的屬性值到“知識庫”中
_mergeOptions(inputOptions, overrideOptions) 函式主要的作用是將配置項中的引數和命令列獲得的引數選項以及預設選項進行合併,函式執行完畢將會將字典 mergedOptions 的值進行更新
檢查是否透過中間進行標準輸入:
檢查是否呼叫api,如果是將會引入新的包,並重寫sys.stdout和sys.stderr
判斷過後列印法律宣告和時間
然後就是執行 init() 函式進行程式的初始化,對使用者定義引數進行配置和初始化。這部分後面再說。
init() 函式執行過後就是執行測試以及start函式的執行了,我們看到了三個測試函式 smokeTest、vulnTest 和 liveTest 用於測試。
在不進行測試的情況下,匯入包檔案,在這一步才匯入包,會使程式的啟動更快。
然後判斷 conf.profile ,這個程式呼叫的圖形處理,暫時沒弄明白是幹嘛的。預設為空,程式直接走到else,啟用start() 程式,也在後面說。
接下來是一大長串的 except 和最終的 finally
finally中包括了一些提示資訊、清除臨時目錄檔案、清除執行緒、清除配置等等一些檔案
程式的最終結尾又是一個 if 判斷,如果命令列引數裡有sqlmapShell的話重新執行main函式,這實際上用一個死迴圈來完成的類似長連線的效果
命令列引數處理
檔案位置:/lib/core/option.py
init() 函式將命令列引數和配置檔案的選項值同時設定為程式配置
具體做了什麼如下:
def init():
_useWizardInterface() # --wizard 使用者嚮導程式,為了給初學者一個友好的介面
setVerbosity() # -v 設定debug等級
_saveConfig() # --save 將命令列選項儲存到sqlmap配置INI檔案中
_setRequestFromFile() # -r 從檔案中設定http請求
_cleanupOptions() # 清理配置選項
_cleanupEnvironment() # 清理環境
_purge() # 安全刪除(清除)sqlmap資料目錄
_checkDependencies() # 檢測丟失的依賴
_createHomeDirectories() # 在sqlmap的主目錄中建立目錄
_createTemporaryDirectory() # 建立執行的臨時目錄
_basicOptionValidation() # 檢測選項是否有效
_setProxyList() # --proxy 設定代理列表
_setTorProxySettings() # --tor/--tor-type 設定Tor代理
_setDNSServer() # --dns-domain 設定DNS伺服器
_adjustLoggingFormatter() # 調整日誌格式
_setMultipleTargets() # 如果在多個目標中執行,只定義一種引數
_listTamperingFunctions()
_setTamperingFunctions() # --tamper 設定tamper指令碼
_setPreprocessFunctions() # --preprocess 從給定的指令碼載入預處理函式
_setTrafficOutputFP() # 設定記錄http日誌檔案
_setupHTTPCollector() # 清理http收集
_setHttpChunked()
_checkWebSocket() # 檢測websocket-client模組呼叫
parseTargetDirect() # 解析目標DBMS
if any((conf.url, conf.logFile, conf.bulkFile, conf.sitemapUrl, conf.requestFile, conf.googleDork, conf.liveTest)):
# 如果設定了上述選項,配置相關http
_setHostname() # 設定hostname
_setHTTPTimeout() # 設定超時時間
_setHTTPExtraHeaders() # 設定Headers
_setHTTPCookies() # 設定Cookies
_setHTTPReferer() # 設定Referer
_setHTTPHost() # 設定Host
_setHTTPUserAgent() # 設定UserAgent
_setHTTPAuthentication() # 設定HTTPAuthentication
_setHTTPHandlers() # 檢查並設定所有HTTP請求的 HTTP/SOCKS代理。
_setDNSCache() # 設定DNS快取
_setSocketPreConnect()
_setSafeVisit() # 檢查並設定安全訪問選項。
_doSearch() # 使用搜尋平臺搜尋結果並儲存
_setBulkMultipleTargets()
_setSitemapTargets() # 解析SitemapTargets中的目標
_checkTor() # 檢測Tor
_setCrawler() # 設定爬蟲
_findPageForms() # 尋找網頁的表單
_setDBMS() # 強制DBMS選項
_setTechnique()
_setThreads() # 設定執行緒
_setOS() # 強制OS
_setWriteFile() # 寫入檔案
_setMetasploit() # Metasploit相關設定
_setDBMSAuthentication() # 設定資料庫身份認證,來使用另一個身份執行
loadBoundaries() # 載入Boundaries
loadPayloads() # 載入Payloads
_setPrefixSuffix() # 設定前字尾
update() # 更新sqlmap
_loadQueries() # 載入查詢的xml /查詢
初始和解析HTTP相關內容
檔案位置:/lib/controller/controller.py
start() 函式將呼叫函式,檢查URL的連線穩定性,以及對所有的 GET/POST/Cookie/User-Agent 引數進行檢查,檢查他們是否為動態的,以及是否收到SQL隱碼攻擊的影響
接下來是檢測目標點是否存在SQL隱碼攻擊,conf.direct 引數為True,則使用者配置直連資料庫,將繞過下方的流程,直接檢測:
initTargetEnv() 用於初始化目標環境
setupTargetEnv() 用於設定目標環境
action() 函式用於在受影響的URL引數上執行SQL隱碼攻擊,並嘗試提取 DBMS 或 作業系統相關資訊
正常流程是將配置 conf 字典中的目標URL、方法、data、cookie中新增到 kb.targets 中,然後進一步判斷,如果沒有就報錯,然後列印出目標個數
然後是判斷網路連線數
設定HTTP連線的一些引數,都是python資料型別轉換,沒什麼好說的
initTargetEnv() 函式主要就是完成全域性變數 conf 和 kb 的初始化工作
parseTargetUrl() 函式主要透過正則完成針對目標網址的解析工作,如獲取協議名、路徑、埠、請求引數等資訊
測試過的 url 引數資訊會儲存到 kb.testedParams 中,所以在進行test之前,會先判斷當前的url是否已經test過,如果沒test過的話,則 testSqlInj = True,否則 testSqlInj = False,如果值為False ,到後續就不會再執行注入攻擊了,因此接下來這段程式碼就是在判斷目前要測試的點是否測試過,測試過是否為注入點等,並修改 testSqlInj 的值,最後進行判斷是否要跳過這個點
接下來是判斷是否存在多個目標,依次與使用者互動確認是否進行測試
然後執行 setupTargetEnv() 函式,該函式主要包含3個子功能:
建立儲存目標執行結果的目錄和檔案
將 get 或 post 傳送的資料解析成字典形式,並儲存到 conf.paramDict 中
讀取session檔案(如果存在的話),並讀取檔案中的資料,儲存到 kb 變數中
檢查連線、是否有使用者自定義的 String(字元)或 Regexp(正則):
然後是 checkWaf() 函式,檢測是否有WAF,之後再說
檢查空連線(nullConnection)、檢查頁面穩定性,以及對引數、測試列表進行排序
什麼是 nullConnection?根據官方手冊,是一種不用獲取頁面內容就可以知道頁面大小的方法,這種方法在布林盲注中有非常好的效果,可以很好的節省頻寬。
如果啟用 --null-connection,計算頁面相似率就只是很簡單的透過頁面的長度來計算,這部分後面會提到
然後根據引數中的 level 級別判斷是否進行 cookie 、referer、ua的注入
使用函式 checkDynParam() 判斷引數是否是動態,如果不是動態則需要選取其他引數。其核心是給引數另外一個隨機值,然後透過選擇引數及對於頁面的各種規則的判斷,計算出兩個頁面的相似比率,來判定是否為動態。
根據level等級不同的情況判斷是否應該跳過引數
如果引數為動態,將接下來進入檢測SQL隱碼攻擊測試環節,程式將呼叫 heuristcCheckSqlInjection() 函式進行啟發式檢測,再得到 POSITIVE 的結果後,再呼叫 checkSqlInjection() 進行注入點檢查,再次確認有漏洞且不是 FALSE_POSTTIVE後,將injectable 設為True,中間產生的資料放入相應的資料集中,執行下一步,這兩個函式的內容也會稍後詳細說。
如果沒檢查出漏洞點,將根據不同情況提供各種各樣的建議引數
否則將結果進行儲存
二次確認有注入資料後,將與使用者互動確認,並執行 action() 函式,才是開始跑資料了。這部分後續會詳細介紹。
中間又是一大堆的 except 異常處理之後,finally 中顯示了http錯誤程式碼,並提示最大連線限制的情況
最後提示結果檔名