红日代审Day4-strpos函数缺陷
strpos
haystack在该字符串中进行查找。
needle要搜索的字符串。
offset如果提供了此参数,搜索会从字符串该字符数的起始位置开始统计。 如果是负数,搜索会从字符串结尾指定字符数开始。
返回值
返回 needle 存在于
haystack字符串起始的位置(独立于offset)。 同时注意字符串位置是从0开始,而不是从1开始的。如果没找到 needle,将返回 **
false**。警告:此函数可能返回布尔值 **
false**,但也可能返回等同于false的非布尔值。请阅读 布尔类型章节以获取更多信息。应使用 === 运算符来测试此函数的返回值。
demo:
1 |
|
Demo分析
我们看到 第11行 和 第12行 ,程序通过格式化字符串的方式,使用 xml 结构存储用户的登录信息。实际上这样很容易造成数据注入。然后 第21行 实例化 Login 类,并在 第16行 处调用 login 方法进行登陆操作。在进行登录操作之前,代码在 第8行 和 第9行 使用 strpos 函数来防止输入的参数含有 <** 和 **> 符号,猜测开发者应该是考虑到非法字符注入问题。
而strpos 函数返回查找到的子字符串的下标。如果字符串开头就是我们要搜索的目标,则返回下标 0 ;如果搜索不到,则返回 false 。在这道题目中,开发者只考虑到 strpos 函数返回 false 的情况,却忽略了匹配到的字符在首位时会返回 0 的情况,因为 false 和 0 的取反均为 true 。这样我们就可以在用户名和密码首字符注入 < 符号,从而注入xml数据。我们尝试使用以下 payload ,观察 strpos 函数的返回结果:
1 | user=<"><injected-tag%20property="&pass=<injected-tag> |
如上图所示,很明显是可以注入xml数据的。
实例分析
环境:DeDecms V5.7SP2正式版
审计
该CMS存在未修复的任意用户密码重置漏洞。漏洞的触发点在 member/resetpassword.php 文件中,由于对接收的参数 safeanswer 没有进行严格的类型判断,导致可以使用弱类型比较绕过。我们来看看相关代码:
针对上面的代码做个分析,当 $dopost 等于 safequestion 的时候,通过传入的 $mid 对应的 id 值来查询对应用户的安全问题、安全答案、用户id、电子邮件等信息。跟进到 第11行 ,当我们传入的问题和答案非空,而且等于之前设置的问题和答案,则进入 sn 函数。然而这里使用的是 == 而不是 === 来判断,所以是可以绕过的。假设用户没有设置安全问题和答案,那么默认情况下安全问题的值为 0 ,答案的值为 null (这里是数据库中的值,即 $row[‘safequestion’]=”0” 、 $row[‘safeanswer’]=null )。当没有设置 safequestion 和 safeanswer 的值时,它们的值均为空字符串。第11行的if表达式也就变成了 if(‘0’ == ‘’ && null == ‘’) ,即 if(false && true) ,所以我们只要让表达式 $row[‘safequestion’] == $safequestion 为 true 即可。下图是 null == ‘’ 的判断结果:
我们可以利用 php弱类型 的特点,来绕过这里 $row[‘safequestion’] == $safequestion 的判断,如下:
通过测试找到了三个的payload,分别是 0.0 、 0. 、 0e1 ,这三种类型payload均能使得 $row[‘safequestion’] == $safequestion 为 true ,即成功进入 sn 函数。跟进 sn 函数,相关代码在 member/inc/inc_pwd_functions.php 文件中,具体代码如下:
在 sn 函数内部,会根据id到pwd_tmp表中判断是否存在对应的临时密码记录,根据结果确定分支,走向 newmail 函数。假设当前我们第一次进行忘记密码操作,那么此时的 $row 应该为空,所以进入第一个 if(!is_array($row)) 分支,在 newmail 函数中执行 INSERT 操作,相关操作代码位置在 member/inc/inc_pwd_functions.php 文件中,关键代码如下:
该代码主要功能是发送邮件至相关邮箱,并且插入一条记录至 dede_pwd_tmp 表中。而恰好漏洞的触发点就在这里,我们看看 第13行 至 第18行 的代码,如果 ($send == ‘N’) 这个条件为真,通过 ShowMsg 打印出修改密码功能的链接。 第17行 修改密码链接中的 $mid 参数对应的值是用户id,而 $randval 是在第一次 insert 操作的时候将其 md5 加密之后插入到 dede_pwd_tmp 表中,并且在这里已经直接回显给用户。那么这里拼接的url其实是:
1 | http://127.0.0.1/member/resetpassword.php?dopost=getpasswd&id=$mid&key=$randval |
继续跟进一下 dopost=getpasswd 的操作,相关代码位置在 member/resetpassword.php 中:
在重置密码的时候判断输入的用户id是否执行过重置密码,如果id为空则退出;如果 $row 不为空,则会执行以下操作内容,相关代码在 member/resetpassword.php 中。
上图代码会先判断是否超时,如果没有超时,则进入密码修改页面。在密码修改页面会将 $setp 赋值为2。
由于现在的数据包中 $setp=2 ,因此这部分功能代码实现又回到了 member/resetpassword.php 文件中。
上图代码 第6行 判断传入的 $key 是否等于数据库中的 $row[‘pwd’] ,如果相等就完成重置密码操作,至此也就完成了整个攻击的分析过程。
漏洞利用
我们分别注册 test1 , test2 两个账号
第一步访问 payload 中的 url
1 | http://127.0.0.1/dedecms/member/resetpassword.php?dopost=safequestion&safequestion=0.0&safeanswer=&id=9 |
这里 test2 的id是9
通过抓包获取到 key 值。
去掉多余的字符访问修改密码链接
1 | http://192.168.31.240/dedecms/member/resetpassword.php?dopost=getpasswd&id=9&key=OTyEGJtg |
最后成功修改密码,我将密码修改成 123456 ,数据库中 test2 的密码字段也变成了 123456 加密之后的值。
CTF例题练习
环境搭建
题目链接: https://pan.baidu.com/s/1pHjOVK0Ib-tjztkgBxe3nQ 密码: 59t2
WP
题目为QCTF 2018中的一道题目,场景就是一个彩票系统,每位用户初始情况下有20$,由用户输入一个7位数,系统也会随机生成一个7位数。然后逐位数字进行比较,位数相同的个数越多,奖励的钱也越多。当你的钱足够买flag的时候,系统就会给你flag。
我们来看一下后台代码是如何进行比较的,比较代码在 buy.php 文件中:
在上图中看到表单的部分( 代码4-8行 ),调用了 js/buy.js 文件,应该是用来处理上面的表单的,我们具体看一下 js 代码:
在 第10行 处看到,程序将表单数据以 json 格式提交到服务器端,提交页面为 api.php ,我们转到该文件看看:
这里主要是对数字进行比较,注意 第13行 用的是 == 操作符对数据进行比较,这里会引发安全问题。因为用户的数据是以 json 格式传上来的,如果我们传一个数组,里面包含7个 true 元素,这样在比较的时候也是能相等的。因为 == 运算符只会判断两边数据的值是否相等,并不会判断数据的类型。而语言定义,除了 0、false、null 以外均为 true ,所以使用 true 和数字进行比较,返回的值肯定是 true 。只要后台生成的随机数没有数字0,我们传入的payload就能绕过每位数字的比较。我们发送几次payload后,就可以买到flag了。
































