Skip to content

SQLMAP 人工辅助注入简介

Posted in 每刻,知识分享

以前在平时比赛的时候很少用 SQLMAP,一是感觉这样显得自己好像什么都不会,二是真实原因,并不是很会用这个东西,直接测基本是测不出来的,也懒得用。

然而现在渐渐的纠正了自己的这一认识,实习期间也偶尔会遇到需要 SQLMAP 取一些测试数据的时候,这时候研究了一下才念起了 SQLMAP 的好来,原来经过指导还是很聪明的工具,当然也有被折磨疯的时候,因为有时候就是教不会,原因还得继续探索,然而这个过程仍然收获了一些东西,记录下来分享一下。

  • SQLMAP 辅助注入的参数
    • 指定数据库系统
    • 指定注入技术
    • 指定对返回值的判断依据
    • 指定 UNION 查询具体的列数
  • TAMPER 模块的编写
    • 何时需要用 TAMPER
    • 编写规范
  • 中转脚本的编写
    • 何时需要用中转脚本
    • 编写规范
  • 如何判断后端数据库

SQLMAP 辅助注入的参数

指定数据库系统

参数很好记,就是 --dbms=DBMS 这样的形式,可以跳过大量的检测,比如已经确定后端用的数据库是 MySQL 的话,可以直接指定该参数为--dbms=mysql

指定注入技术

共有六个可选项,参数写法为--technique=TECH,根据 SQLMAP 手册,该参数可能存在的值有 B、T、E、U、S,分别对应基于布尔值的盲注,基于延时的盲注,基于错误回显的注入,联查注入,以及堆查询注入。还有一个选项是 Q ,我不是很懂做什么用的,懂得同学求教一下。

指定对返回值的判断依据

有四个常用选项,都是用于布尔注入的

--string=true-string ,当页面返回中包含这个 true-string 时,让 SQLMAP 认定这个返回为 True
--not-string=false-string,当页面返回中包含这个 false-string 时,让 SQLMAP 认定这个返回为 False
--regexp=regexp,当页面与 regexp 所示的表达式存在匹配时,认定为 True
--code=HTTP_CODE,当 HTTP 响应的 CODE 为 HTTP_CODE 时,认定为 True。

这就用在 SQLMAP 自己判断不出来页面返回的区别时,我们手动指定返回内容对应的真假来帮助 SQLMAP 识别出来到底发生了什么。

指定 UNION 查询具体的列数

也是一个节约时间的选项

--union-cols=range,比如可以写 --union-cols=8-10,那么在检测 UNION 注入的时候就只会检查 8 到 10 列这个范围了。

TAMPER 模块的编写

何时需要用 TAMPER

这是调教 SQLMAP 的关键一步,因为我们实际情况遇到的总是千奇百怪的过滤,虽然 SQLMAP 已经自带了许多 TAMPER 能绕过一些常见过滤,然而还有很多时候会遇到许多畸形的过滤,我们也就需要自己编写 TAMPER 了。

编写规范

我们来看一下 SQLMAP tamper 目录下最小的一个例子

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOWEST

def dependencies():
    pass

def tamper(payload, **kwargs):
    """
    Slash escape quotes (' and ")

    >>> tamper('1" AND SLEEP(5)#')
    '1\\\\" AND SLEEP(5)#'
    """

    return payload.replace("'", "\\'").replace('"', '\\"')

显而易见, tamper 函数是我们需要关注的主要函数,其第一个参数为 SQLMAP 使用的 PAYLOAD,从注释也可以看出来,我们仅仅只是编写了一个能够处理当前字符串的函数而已,也就是说把绕过过滤的方法写进去就好喽,着实是很简单的,没什么规范可言,简而言之就是瞎写。

中转脚本的编写

何时需要用中转脚本

其实有了中转脚本就不需要使用 TAMPER 了,因为中转脚本也可以处理 PAYLOAD 来绕过后端的过滤,那么为什么要用中转脚本呢?不知你是否有遇到过这种情况,这里明显有注入然而 SQLMAP 就是判断不出来,那你和 SQLMAP 两个总有一个是傻逼吧???

为了证明你不是傻逼,你需要写一个中转脚本向 SQLMAP 证明你不是傻逼他是傻逼,因为 SQLMAP 对页面回显有时候判断的很捉鸡,我们需要帮他处理一下结果来符合他内部的匹配规则。

比如说一个页面返回了 JSON,JSON 其中某个参数的变化意味着注入的返回值为 True 还是 False,然而 SQLMAP 就是判断不出来,那还能怎么办,也很绝望,只好写一个脚本把这部分提取出来,然后只显示这部分,来让 SQLMAP 感受到区别,或者检测某个标志,发现了这个标志就返回 1,否则返回 0,也有不错的效果。

编写规范

这个哪里有什么编写规范……

放一个 PHP 的示例,接受一个参数,我们在后端构造好完整的参数,通过 cURL 发出去即可。

<?php
$type = $_POST['type'];
$content = "productType=Y&uuid=xxxxxxxxxx&pageNum=1&orderType=" . $type;
$header = array("Content-Type: application/x-www-form-urlencoded",
        "X-Requested-With: XMLHttpRequest",
        "User-Agent: Mozilla/5.0 (X11; U; Linux i686; fr; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4",
        "Content-Length: ".strlen($content));
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://xxxxxxxxxx");
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
$result=curl_exec($ch);
curl_close($ch);
if(strstr($result, "code")){
    echo "error";
}else if(strstr($result, "500")){
    echo 1;
}else{
    echo 0;
}
?> 

大致是如此啦。

如何判断后端数据库

在刚才指定的 --dbms 参数中,我们如何先手动判断出来后端数据库系统呢?之前在小密圈分享过,在这里也分享一下吧。

a. MySQL 的判断。

(1)如果说既能够使用— –作为注释标志,又能够使用#做注释标志,那么基本可以确定是 MySQL

(2)行间注释 /*!version command*/,这样的语法是 MySQL 专属,其中 version 是一个数字,如果 MySQL 的版本大于这个数字的话就会把 command 作为 SQL 语句的一部分。

/*!50000SELECT*/

如果 MySQL 的版本大于5.00.00的话就会把 SELECT 识别出来。这个特性同样可以用来探测 MySQL 的版本号

(3) 既可以使用 SUBSTR 函数又可以使用 SUBSTRING 函数,也可以基本确定是 MySQL。

(4) 能够使用 trim 函数、 rtrim 函数、 ltrim 函数,但是不能使用 btrim 函数(Postgresql 以及 AWS Redshift SQL 所有),可以基本确定后端使用了 MySQL 。

(5) 一些 MySQL 特有的函数,比如 conv()、load_file()(读取文件的函数需要权限)、benchmark() 等,还有一些能够返回整型结果的函数,比如 connection_id(),last_insert_id(),row_count()。

(6) MySQL 独有的数据库也可作为判断依据,即 information_schema, mysql。

(7) 构造除 0,即 1/0 等,若是 MySQL 则会返回 NULL。

b. SQL Server 的判断

(1) 能够利用 waitfor delay time做到延时注入,可以确定是 SQL Server。

(2) 若能够做到 UNION 注入,那么直接查看 @@version 的值是一个判断数据库的更好的选择(这在 MySQL 中也可使用)

(3)几个特殊的表也可用于判断。

master..sysmessages、master..sysservers

(4)如果能够使用 TOP 语法,很有可能是 SQL Server

SELECT TOP 1 column_name FROM table_name;

(5)能够使用 + 号连接字符串,可以判定是 SQL Server

SELECT ‘a’ + ‘a’;

(6)能够通过 @@pack_received 与 @@rowcount 返回整型结果的,可以判定是 SQL Server

c. ORACLE 的判断

(1) oracle版本的探测方法

select version from v$instance;

(2)支持 minus select 是 ORACLE 的显著特征

(3) ORACLE 支持使用 || 作为字符串连接符,当然 MySQL 在 sql_mode 为 ANSI 时也支持,但是这需要手动设置,还有 PostgreSQL 也支持使用 || 连接字符串。

(4)ORACLE 特有函数 BITAND(),可用于判断,如 BITAND(1,1) 会返回 1。

d. PostgreSQL

(1)PostgreSQL 有一个区别于其他 SQL 数据库的特殊操作符,::,官方称之为 typecast,即他可以用于声明类型。
SELECT 1::CHAR;会将 1 作为字符串类型,若使用SELECT 1.1::INT; 则会将 1.1 转化为 1,则该特性可用于判断后端是否为PostgreSQL

(2)PostgreSQL 支持使用 || 连接字符串。

(3) PostgreSQL 中有开平方根和开立方跟与阶乘的操作符

SELECT |/ 25.0;结果是5
SELECT ||/ 27.0;结果是3

(4) PostgreSQL 特有函数 pg_sleep(),若产生延时则可确定是 PostgreSQL

3 Comments

  1. 写的非常到位..
    对多个数据库的特性也了解的非常多啊..大佬
    就是sqlmap 的tamper 部分我有个疑问
    比如 检测的payload 和 出数据的payload 是不同的。
    如果我 写的tamper针对检测的payload,这时候就会出现 sqlmap能检测出来是什么注入了。但是出数据的时候的payload还是会被过滤掉。所以有没有好的方法一次性解决?
    是需要在一个tamper里面解决两个不同应用的payload的过滤还是写两个tamper一起使用(因为我python很渣)
    eg:
    原payload: 1′ AND 1=1 OR ‘fuckwaf’=’fuckwaf’
    现payload: 1’AND(1=1)OR’fuckwaf’=’fuckwaf’ (绕过waf)

    这是Boolean的注入。我写了tamper使sqlmap跑出注入点后,会发生sqlmap进行跑数据的payload还是存在空格的情况(waf检测空格和其他字符,只能关键词之间用括号来绕过)导致跑不出库名or表名or字段。

    疑问:难道还要针对语句再写tamper吗?(因为sqlmap出库名、表面、字段、 数据的payload都不一样..)

    我的想法是..直接新增一个sqlmap的检测语法和出数据的语法(绕过waf的方法添加到这里面),但是python不熟练..还在苦逼google和看博文、看源码当中…

    2017年9月11日
    |Reply
    • Melody
      Melody

      不好意思现在才回复,sqlmap 只检测到 payload 后,攻击向量就会使用这个 payload 来做,你要做成通用的 tamper 才行,的确是要针对语句再构造的,而且要确保语法准确,不能用空格还是建议找个能代替空格的会比较舒适

      2017年9月24日
      |Reply

Leave a Reply

Your email address will not be published. Required fields are marked *