1.drops之前的文檔 SQLMAP進階使用介紹過SQLMAP的高級使用方法,網上也有幾篇介紹過SQLMAP源碼的文章曾是土木人,都寫的非常好,建議大家都看一下。
2.我準備分幾篇文章詳細的介紹下SQLMAP的源碼,讓想了解的朋友們熟悉一下SQLMAP的原理和一些手工注入的語句,今天先開始第一篇:流程篇。
3.之前最好了解SQMAP各個選項的意思,可以參考sqlmap用戶手冊和SQLMAP目錄doc/README.pdf
4.內容中如有錯誤或者沒有寫清楚的地方,歡迎指正交流。有部分內容是參考上面介紹的幾篇文章的,在此一并說明,感謝他們。
1.我用的IDE是PyCharm。
2.在菜單欄Run->Edit Configurations。點擊左側的“+”,選擇Python,Script中選擇sqlmap.py的路徑,Script parameters中填入注入時的命令,如下圖。
3.打開sqlmap.py,開始函數是main函數,在main函數處下斷點。
4.右鍵Debug 'sqlmap',然后程序就自動跳到我們下斷點的main()函數處,后面可以繼續添加斷點進行調試。如下圖,左邊紅色的代表跳轉到下一個斷點處,上面紅色的表示跳到下一句代碼處
5.另外,如果要在代碼中加中文注釋,需要在開始處添加以下語句:#coding:utf-8。
我這里用的版本是:1.0-dev-nongit-20150614
miin()函數開始73行:
#!python
paths.SQLMAP_ROOT_PATH = modulePath()
setPaths()
進入common.py中的setPaths()函數后,就可以看到這個函數是定義SQLMAP路徑和文件的,類似于:
#!python
paths.SQLMAP_EXTRAS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "extra")
paths.SQLMAP_PROCS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "procs")
paths.SQLMAP_SHELL_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "shell")
paths.SQLMAP_TAMPER_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "tamper")
paths.SQLMAP_WAF_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "waf")
接下來的78行函數initOptions(cmdLineOptions),包含了三個函數,作用如流程圖所示,設置conf,KB,參數. conf會保存用戶輸入的一些參數,比如url,端口
kb會保存注入時的一些參數,其中有兩個是比較特殊的kb.chars.start和kb.chars.stop,這兩個是隨機字符串,后面會有介紹。
#!python
_setConfAttributes()
_setKnowledgeBaseAttributes()
_mergeOptions(inputOptions, overrideOptions)
102行的start函數,算是檢測開始的地方.start()函數位于controller.py中。
#!python
if conf.direct:
initTargetEnv()
setupTargetEnv()
action()
return True
首先這四句,意思是,如果你使用-d選項,那么sqlmap就會直接進入action()函數,連接數據庫,語句類似為:
#!python
python sqlmap.py -d "mysql://admin:[email protected]:3306/testdb" -f --banner --dbs --user
#!python
if conf.url and not any((conf.forms, conf.crawlDepth)):
kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None))
上面代碼會把url,methos,data,cookie加入到kb.targets,這些參數就是我們輸入的
接下來從274行的for循環中,可以進入檢測環節
#!python
for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
此循環先初始化一些一些變量,然后判斷之前是否注入過,如果沒有注入過,testSqlInj=True,否則testSqlInj=false。后面會進行判斷是否檢測過。
#!python
def setupTargetEnv():
_createTargetDirs()
_setRequestParams()
_setHashDB()
_resumeHashDBValues()
_setResultsFile()
_setAuthCred()
372行setupTargetEnv()函數中包含了5個函數,這些函數作用是
1.創建輸出結果目錄
2.解析請求參數
3.設置session信息,就是session.sqlite。
4.恢復session的數據,繼續掃描。
5.存儲掃描結果。
6.添加認證信息
其中比較重要的就是session.sqlite,這個文件在sqlmap的輸出目錄中,測試的結果都會保存在這個文件里。
#!python
checkWaf()
if conf.identifyWaf:
identifyWaf()
377行checkWaf()是檢測是否有WAF,檢測方法是NMAP的http-waf-detect.nse,比如頁面為index.php?id=1,那現在添加一個隨機變量index.php?id=1&aaa=2,設置paoyload類似為AND 1=1 UNION ALL SELECT 1,2,3,table_name FROM information_schema.tables WHERE 2>1-- ../../../etc/passwd
,如果沒有WAF,頁面不會變化,如果有WAF,因為payload中有很多敏感字符,大多數時候頁面都會發生改變。
接下來的conf.identifyWaf代表sqlmap的參數--identify-waf,如果指定了此參數,就會進入identifyWaf()函數,主要檢測的waf都在sqlmap的waf目錄下。
當然檢測的方法都比較簡單,都是查看返回的數據庫包種是否包含了某些特征字符。如:
#!python
__product__ = "360 Web Application Firewall (360)"
def detect(get_page):
retval = False
for vector in WAF_ATTACK_VECTORS:
page, headers, code = get_page(get=vector)
retval = re.search(r"wangzhan\.360\.cn", headers.get("X-Powered-By-360wzb", ""), re.I) is not None
if retval:
break
return retval
if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) \
and (kb.injection.place is None or kb.injection.parameter is None):
回到start函數,385行會判斷是否注入過,如果還沒有測試過參數是否可以注入,則進入if語句中。如果之前測試過,則不會進入此語句。
#!python
for place in parameters:
# Test User-Agent and Referer headers only if
# --level >= 3
skip = (place == PLACE.USER_AGENT and conf.level <
skip |= (place == PLACE.REFERER and conf.level < 3)
# Test Host header only if
# --level >= 5
skip |= (place == PLACE.HOST and conf.level < 5)
# Test Cookie header only if --level >= 2
skip |= (place == PLACE.COOKIE and conf.level < 2)
這中間sqlmap給了我們一些注釋,可以看到,level>=3時,會測試user-agent,referer,level>=5時,會測試HOST,level>=2時,會測試cookie。當然最終的測試判斷還要在相應的xml中指定,后面會介紹。
#!python
check = checkDynParam(place, parameter, value)
480行的checkDynParam()函數會判斷參數是否是動態的,比如index.php?id=1,通過更改id的值,如果參數是動態的,頁面會不同。
#!python
check = heuristicCheckSqlInjection(place, parameter)
502行有個heuristicCheckSqlInjection()函數,翻譯過來是啟發性sql注入測試,其實就是先進行一個簡單的測試,設置一個payload,然后解析請求結果。
heuristicCheckSqlInjection()在checks.py中,821行開始如下:
#!python
if conf.prefix or conf.suffix:
if conf.prefix:
prefix = conf.prefix
if conf.suffix:
suffix = conf.suffix
randStr = ""
while '\'' not in randStr:
randStr = randomStr(length=10, alphabet=HEURISTIC_CHECK_ALPHABET)
kb.heuristicMode = True
payload = "%s%s%s" % (prefix, randStr, suffix)
payload = agent.payload(place, parameter, newValue=payload)
page, _ = Request.queryPage(payload, place, content=True, raise404=False)
kb.heuristicMode = False
parseFilePaths(page)
result = wasLastResponseDBMSError()
首先conf.prefix和conf.suffix代表用戶指定的前綴和后綴;在while '\'' not in randStr
中,隨機選擇'"', '\'', ')', '(', ',', '.'中的字符,選10個,并且單引號要在。接下來生成一個payload,類似u'name=PAYLOAD_DELIMITER\__1)."."."\'."__PAYLOAD_DELIMITER'。其中PAYLOAD_DELIMITER\__1和__PAYLOAD_DELIMITER是隨機字符串。請求網頁后,調用parseFilePaths進行解析,查看是否爆出絕對路徑,而wasLastResponseDBMSError是判斷response中是否包含了數據庫的報錯信息。
#!python
value = "%s%s%s" % (randomStr(), DUMMY_XSS_CHECK_APPENDIX, randomStr())
payload = "%s%s%s" % (prefix, "'%s" % value, suffix)
payload = agent.payload(place, parameter, newValue=payload)
page, _ = Request.queryPage(payload, place, content=True, raise404=False)
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
if value in (page or ""):
infoMsg = "heuristic (XSS) test shows that %s parameter " % paramType
infoMsg += "'%s' might be vulnerable to XSS attacks" % parameter
logger.info(infoMsg)
kb.heuristicMode = False
上面的代碼是從888行開始,DUMMY_XSS_CHECK_APPENDIX = "<'\">",如果輸入的字符串在頁面中返回了,會提示可能存在XSS漏洞。
接下來,我們回到start函數中,繼續看下面的代碼。
#!python
if testSqlInj:
......
injection = checkSqlInjection(place, parameter, value)
在502行判斷testSqlInj,如果為true,就代表之前沒有檢測過,然后就會到checkSqlInjection,checkSqlInjection()才是真正開始測試的函數,傳入的參數是注入方法如GET,參數名,參數值。我們跟進。
checkSqlInjection()在checks.py中,91行開始
#!python
paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place
tests = getSortedInjectionTests()
paramType是注入的類型,如GET。tests是要測試的列表,如下圖所示,包含了每個測試項的名稱,這些數據都是和/sqlmap/xml/payloads/目錄下每個xml相對應的。
#!python
if conf.dbms is None:
if not injection.dbms and PAYLOAD.TECHNIQUE.BOOLEAN in injection.data:
if not Backend.getIdentifiedDbms() and kb.heuristicDbms is False:
kb.heuristicDbms = heuristicCheckDbms(injection)
if kb.reduceTests is None and not conf.testFilter and (intersect(Backend.getErrorParsedDBMSes(), \
SUPPORTED_DBMS, True) or kb.heuristicDbms or injection.dbms):
msg = "it looks like the back-end DBMS is '%s'. " % (Format.getErrorParsedDBMSes() or kb.heuristicDbms or injection.dbms)
msg += "Do you want to skip test payloads specific for other DBMSes? [Y/n]"
kb.reduceTests = (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) if readInput(msg, default='Y').upper() == 'Y' else []
if kb.extendTests is None and not conf.testFilter and (conf.level < 5 or conf.risk < 3) \
and (intersect(Backend.getErrorParsedDBMSes(), SUPPORTED_DBMS, True) or \
kb.heuristicDbms or injection.dbms):
msg = "for the remaining tests, do you want to include all tests "
msg += "for '%s' extending provided " % (Format.getErrorParsedDBMSes() or kb.heuristicDbms or injection.dbms)
msg += "level (%d)" % conf.level if conf.level < 5 else ""
msg += " and " if conf.level < 5 and conf.risk < 3 else ""
msg += "risk (%d)" % conf.risk if conf.risk < 3 else ""
msg += " values? [Y/n]" if conf.level < 5 and conf.risk < 3 else " value? [Y/n]"
kb.extendTests = (Backend.getErrorParsedDBMSes() or [kb.heuristicDbms]) if readInput(msg, default='Y').upper() == 'Y' else []
101行開始,這段代碼主要是判斷DBMS類型,首先,如果用戶沒有手工指定dbms,則會根據頁面報錯或者bool類型的測試,找出DBMS類型,找出后,會提示是否跳過測試其他的DBMS。然后,對于測試出來的DBMS,是否用所有的payload來測試。
140行if stype == PAYLOAD.TECHNIQUE.UNION:會判斷是不是union注入,這個stype就是payload文件夾下面xml文件中的stype,如果是union,就會進入,然后配置列的數量等,今天先介紹流程,union注入以后會介紹。
#!python
if conf.tech and isinstance(conf.tech, list) and stype not in conf.tech:
debugMsg = "skipping test '%s' because the user " % title
debugMsg += "specified to test only for "
debugMsg += "%s techniques" % " & ".join(map(lambda x: PAYLOAD.SQLINJECTION[x], conf.tech))
logger.debug(debugMsg)
continue
177行,就是用戶提供的--technique,共有六個選項BEUSTQ,但是現在很多文檔,包括SQLMAP的官方文檔都只給了BEUST的解釋說明,少個inline_query,相當于查詢語句中再加入一個查詢語句。
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(內聯查詢)
接下來,就是生成payload的過程。288行:
#!python
fstPayload = agent.cleanupPayload(test.request.payload, origValue=value if place not in (PLACE.URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER) else None)
test.request.payload為'AND [RANDNUM]=[RANDNUM]'(相應payload.xml中的request值)。根據此代碼,生成一個隨機字符串,如fstPayload=u'AND 2876=2876'。
302行:
#!python
for boundary in boundaries:
injectable = False
if boundary.level > conf.level and not (kb.extendTests and intersect(payloadDbms, kb.extendTests, True)):
continue
循環遍歷boundaries.xml中的boundary節點,如果boundary的level大于用戶提供的level,則跳過,不檢測。
307行:
#!python
clauseMatch = False
for clauseTest in test.clause:
if clauseTest in boundary.clause:
clauseMatch = True
break
if test.clause != [0] and boundary.clause != [0] and not clauseMatch:
continue
whereMatch = False
for where in test.where:
if where in boundary.where:
whereMatch = True
break
if not whereMatch:
continue
首先,循環遍歷test.clause(payload中的clause值),如果clauseTest在boundary的clause中,則設置clauseMatch = True,代表此條boundary可以使用。 接下來循環匹配where(payload中的where值),如果存在這樣的where,設置whereMatch = True。如果clause和where中的一個沒有匹配成功,都會結束循環,進入下一個payload的測試。
#!python
prefix = boundary.prefix if boundary.prefix else ""
suffix = boundary.suffix if boundary.suffix else ""
ptype = boundary.ptype
prefix = conf.prefix if conf.prefix is not None else prefix
suffix = conf.suffix if conf.suffix is not None else suffix
comment = None if conf.suffix is not None else comment
上面是設置payload的前綴和后綴,如果用戶設置了,則使用用戶設置的,如果沒有,則使用boundary中的。
352行:
#!python
for where in test.where:
if where == PAYLOAD.WHERE.ORIGINAL or conf.prefix:
......
elif where == PAYLOAD.WHERE.NEGATIVE:
......
elif where == PAYLOAD.WHERE.REPLACE:
......
這里的where是payload中的where值,共有三個值,where字段我理解的意思是,以什么樣的方式將我們的payload添加進去。
1:表示將我們的payload直接添加在值得后面[此處指的應該是檢測的參數的值] 如我們寫的參數是id=1,設置
2:表示將檢測的參數的值更換為一個整數,然后將payload添加在這個整數的后面。 如我們寫的參數是id=1,設置
3:表示將檢測的參數的值直接更換成我們的payload。 如我們寫的參數是id=1,設置
最終在389行:
#!python
boundPayload = agent.prefixQuery(fstPayload, prefix, where, clause)
boundPayload = agent.suffixQuery(boundPayload, comment, suffix, where)
reqPayload = agent.payload(place, parameter, newValue=boundPayload, where=where)
組合前綴、后綴、payload等,生成請求的reqPayload。
這其中有個cleanupPayload()函數,其實就是將一些值進行隨機化。如下圖,例如kb.chars.start,kb.chars.stop,這兩個變量是在基于錯誤的注入時,隨機產生的字符串。
在398行:
#!python
for method, check in test.response.items():
check = agent.cleanupPayload(check, origValue=value if place not in (PLACE.
URI, PLACE.CUSTOM_POST, PLACE.CUSTOM_HEADER) else None)
if method == PAYLOAD.METHOD.COMPARISON:
def genCmpPayload():
sndPayload = agent.cleanupPayload(test.response.comparison,
origValue=value if place not in (PLACE.URI, PLACE.CUSTOM_POST,
PLACE.CUSTOM_HEADER) else None)
boundPayload = agent.prefixQuery(sndPayload, prefix, where, clause)
boundPayload = agent.suffixQuery(boundPayload, comment, suffix,
where)
cmpPayload = agent.payload(place, parameter,
newValue=boundPayload, where=where)
return cmpPayload
kb.matchRatio = None
kb.negativeLogic = (where == PAYLOAD.WHERE.NEGATIVE)
Request.queryPage(genCmpPayload(), place, raise404=False)
falsePage = threadData.lastComparisonPage or ""
trueResult = Request.queryPage(reqPayload, place, raise404=False)
truePage = threadData.lastComparisonPage or ""
if trueResult:
falseResult = Request.queryPage(genCmpPayload(), place,
raise404=False)
if not falseResult:
infoMsg = "%s parameter '%s' seems to be '%s' injectable " % (
paramType, parameter, title)
logger.info(infoMsg)
injectable = True
if not injectable and not any((conf.string, conf.notString, conf.
regexp)) and kb.pageStable:
trueSet = set(extractTextTagContent(truePage))
falseSet = set(extractTextTagContent(falsePage))
candidates = filter(None, (_.strip() if _.strip() in (kb.
pageTemplate or "") and _.strip() not in falsePage and _.strip()
not in threadData.lastComparisonHeaders else None for _ in (
trueSet - falseSet)))
if candidates:
conf.string = candidates[0]
infoMsg = "%s parameter '%s' seems to be '%s' injectable (with
--string=\"%s\")" % (paramType, parameter, title, repr(conf.
string).lstrip('u').strip("'"))
logger.info(infoMsg)
injectable = True
elif method == PAYLOAD.METHOD.GREP:
try:
page, headers = Request.queryPage(reqPayload, place, content=True,
raise404=False)
output = extractRegexResult(check, page, re.DOTALL | re.
IGNORECASE) \
or extractRegexResult(check, listToStrValue( \
[headers[key] for key in headers.keys() if key.lower() !=
URI_HTTP_HEADER.lower()] \
if headers else None), re.DOTALL | re.IGNORECASE) \
or extractRegexResult(check, threadData.lastRedirectMsg[1]
\
if threadData.lastRedirectMsg and threadData.
lastRedirectMsg[0] == \
threadData.lastRequestUID else None, re.DOTALL | re.
IGNORECASE)
if output:
result = output == "1"
if result:
infoMsg = "%s parameter '%s' is '%s' injectable " % (
paramType, parameter, title)
logger.info(infoMsg)
injectable = True
except SqlmapConnectionException, msg:
debugMsg = "problem occurred most likely because the "
debugMsg += "server hasn't recovered as expected from the "
debugMsg += "error-based payload used ('%s')" % msg
logger.debug(debugMsg)
elif method == PAYLOAD.METHOD.TIME:
trueResult = Request.queryPage(reqPayload, place,
timeBasedCompare=True, raise404=False)
if trueResult:
# Confirm test's results
trueResult = Request.queryPage(reqPayload, place,
timeBasedCompare=True, raise404=False)
if trueResult:
infoMsg = "%s parameter '%s' seems to be '%s' injectable " % (
paramType, parameter, title)
logger.info(infoMsg)
injectable = True
elif method == PAYLOAD.METHOD.UNION:
configUnion(test.request.char, test.request.columns)
if not Backend.getIdentifiedDbms():
if kb.heuristicDbms is None:
warnMsg = "using unescaped version of the test "
warnMsg += "because of zero knowledge of the "
warnMsg += "back-end DBMS. You can try to "
warnMsg += "explicitly set it using option '--dbms'"
singleTimeWarnMessage(warnMsg)
else:
Backend.forceDbms(kb.heuristicDbms)
if unionExtended:
infoMsg = "automatically extending ranges for UNION "
infoMsg += "query injection technique tests as "
infoMsg += "there is at least one other (potential) "
infoMsg += "technique found"
singleTimeLogMessage(infoMsg)
reqPayload, vector = unionTest(comment, place, parameter, value,
prefix, suffix)
if isinstance(reqPayload, basestring):
infoMsg = "%s parameter '%s' is '%s' injectable" % (paramType,
parameter, title)
logger.info(infoMsg)
injectable = True
# Overwrite 'where' because it can be set
# by unionTest() directly
where = vector[6]
kb.previousMethod = method
上面這部分代碼非常多,通過for循環遍歷payload中的
所以,上面的代碼可以分為:
1.method為PAYLOAD.METHOD.COMPARISON:bool類型盲注 2.method為PAYLOAD.METHOD.GREP:基于錯誤的sql注入 3.mehtod為PAYLOAD.METHOD.TIME:基于時間的盲注 4.method為PAYLOAD.METHOD.UNION:union聯合查詢
請注意,上面這四種方法,和之前說的六種注入方法不是一個概念,這里的是payload中的response代碼,而注入用的是request代碼。通過比較request的結果和response的結果,確定是否可以注入。以后的文章會介紹怎么比較的。。
checkSqlInjectiond的關鍵部分就到這里了,后面就是把注入的數據保存起來。馬上會介紹讀取的時候。
前面具體介紹了Payload的生成方法,這里再總結一下條件:
1.sqlmap會實現讀取payloads文件夾下xml文件中的每個test元素,然后循環遍歷。
2.此時還會遍歷boundaries.xml文件。
3.當且僅當某個boundary元素的where節點的值包含test元素where節點的值,clause節點的值包含test元素的clause節點的值,該boundary才能和當前的test匹配,從而進一步生成payload。
4.where字段有三個值1:表示將我們的payload直接添加在值得后面[此處指的應該是檢測的參數的值] 如我們寫的參數是id=1,設置
5.最終的payload = url參數 + boundary.prefix+test.payload+boundary.suffix
在start()的617行是action()函數,位于Action.py中,此函數是判斷用戶提供的參數,然后提供相應的函數。
#!python
if conf.getDbs:
conf.dumper.dbs(conf.dbmsHandler.getDbs())
if conf.getTables:
conf.dumper.dbTables(conf.dbmsHandler.getTables())
if conf.commonTables:
conf.dumper.dbTables(tableExists(paths.COMMON_TABLES))
sqlmap注入的結果會保存在輸出目錄的session.sqlite文件匯總,此文件是sqlite數據庫,可以使用SQLiteManager打開。
回到controller.py中的start函數。第602行
#!python
_saveToResultsFile()
_saveToHashDB()
_showInjections()
_selectInjection()
這四個函數的作用就是保存結果保存結果、保存session、顯示注入結果,包括類型,payload等。
前面介紹過會判斷testSqlInj的值,如果為True,代表沒有測試過,會進入checkSqlInjection()函數,如果測試過,那么testSqlInj為false,就會跳過checkSqlInjection()。
比如我們選擇--current-db時,通過action()進入到conf.dumper.currentDb(conf.dbmsHandler.getCurrentDb())。進入到databases.py的getCurrentDb中。
#!python
query = queries[Backend.getIdentifiedDbms()].current_db.query
這是獲取相應的命令,比如mysql的命令是database().一直跟蹤函數到use.py的346行
#!python
if not value and not abortedFlag:
output = _oneShotUnionUse(expression, unpack)
value = parseUnionPage(output)
_onehotUninoUse就是讀取session文件,獲取已經注入過的數據,如果session中沒有,代表沒有請求過,則重新請求獲取數據。output此時是獲取的網頁的源碼。
#!python
retVal = hashDBRetrieve("%s%s" % (conf.hexConvert, expression), checkConf=True)
_onehotUninoUse的第一行,就是從session中獲取數據,跟蹤進hashdb.py的regrieve函數
#!python
def hashKey(key):
key = key.encode(UNICODE_ENCODING) if isinstance(key, unicode) else repr(key)
retVal = int(hashlib.md5(key).hexdigest()[:12], 16) #注釋:hash的算法,對應數據庫中的id。md5后,轉換為10進制,就是session中的id
return retVal
def retrieve(self, key, unserialize=False):
retVal = None
if key and (self._write_cache or os.path.isfile(self.filepath)):
hash_ = HashDB.hashKey(key)
retVal = self._write_cache.get(hash_)
if not retVal:
while True:
try:
for row in self.cursor.execute("SELECT value FROM storage WHERE id=?", (hash_,)):
retVal = row[0]
except sqlite3.OperationalError, ex:
if not "locked" in ex.message:
raise
except sqlite3.DatabaseError, ex:
errMsg = "error occurred while accessing session file '%s' ('%s'). " % (self.filepath, ex)
errMsg += "If the problem persists please rerun with `--flush-session`"
raise SqlmapDataException, errMsg
else:
break
return retVal if not unserialize else unserializeObject(retVal)
通過HashDB.hashKey()計算id,然后到session.sqlite中找記錄,那么key是怎么生成的呢?
在common.py中有個hashDBRetrieve(),
#!python
def hashDBRetrieve(key, unserialize=False, checkConf=False):
_ = "%s%s%s" % (conf.url or "%s%s" % (conf.hostname, conf.port), key, HASHDB_MILESTONE_VALUE)
retVal = conf.hashDB.retrieve(_, unserialize) if kb.resumeValues and not (checkConf and any((conf.flushSession, conf.freshQueries))) else None
if not kb.inferenceMode and not kb.fileReadMode and any(_ in (retVal or "") for _ in (PARTIAL_VALUE_MARKER, PARTIAL_HEX_VALUE_MARKER)):
retVal = None
return retVal
此函數用于生成hash的key,生成方法為url+'None'+命令+HASHDB_MILESTONE_VALUE,比如u'http://127.0.0.1:80/biweb/archives/detail.phpNoneDATABASE()JHjrBugdDA'。此key經過int(hashlib.md5(key).hexdigest()[:12], 16),就是對應session中的id
最終在session.sqlite中根據id,就能夠找到記錄。
如上圖,獲取到的記錄其實就是一個網頁的源代碼,另外可以看到current-db的前后有幾個字符串,這個字符串就是kb.chars.start和kb.chars.stop
回到_oneShotUnionUse中,如果session中沒有記錄,則會重新進行請求,獲取數據
#!python
vector = kb.injection.data[PAYLOAD.TECHNIQUE.UNION].vector
kb.unionDuplicates = vector[7]
kb.forcePartialUnion = vector[8]
query = agent.forgeUnionQuery(injExpression, vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6], None, limited)
where = PAYLOAD.WHERE.NEGATIVE if conf.limitStart or conf.limitStop else vector[6]
payload = agent.payload(newValue=query, where=where)
最終的值通過解析session中的記錄value = parseUnionPage(output),找到kb.chars.start和kb.chars.stop中間的值,就是結果。
還有很多東西沒有寫出來,希望后面的幾篇文章能夠寫好。花了好久的時間,調試、碼字,不知道又沒有人能看到最后。。