PHP代码审计04之strpos函数使用不当
前言
根据红日安全写的文章,学习PHP代码审计的第四节内容,题目均来自PHP SECURITY CALENDAR 2017,讲完题目会用一个实例来加深巩固,这是之前写的,有兴趣可以去看看:
PHP代码审计01之in_array()函数缺陷
PHP代码审计02之filter_var()函数缺陷
PHP代码审计03之实例化任意对象漏洞
漏洞分析
现在咱们看第一题,代码如下:
<?php
class Login {
public function __construct($user, $pass) {
$this->loginViaXml($user, $pass);
}
public function loginViaXml($user, $pass) {
if (
(!strpos($user, '<') || !strpos($user, '>')) &&
(!strpos($pass, '<') || !strpos($pass, '>'))
) {
$format = '<?xml version="1.0"?>' .
'<user v="%s"/><pass v="%s"/>';
$xml = sprintf($format, $user, $pass);
$xmlElement = new SimpleXMLElement($xml);
// Perform the actual login.
$this->login($xmlElement);
}
}
}
new Login($_POST['username'], $_POST['password']);
?>
题目分析
我们来看上面的代码,看第1214行,这里通过格式话字符串的方式,使用XML结构来存储用户的登陆信息,这样很容易造成注入,再看上面的代码,最后一行实例化了这个类,17行调用了login来进行登陆操作。下面到重点了,看代码810行,这里用到了strpos()函数来进行过滤<>符号,这个函数的用法如下:


如下:
<?php
var_dump(strpos("abcd","a"));
var_dump(strpos("abcd","y"));
?>

我们发现,找到子字符串的话就会返回对应的下标,没找到会返回false。而在PHP中,0和false的取反都是true,这点需要我们注意,这道题目就是开发者在使用这个函数时,只考虑了返回false的情况,而没有考虑当首字符匹配时返回0的情况。导致了过滤被绕过从而实施XML攻击。
现在知道了如何绕过,那么构造payload:user=<"><injected-tag%20property="&pass=<injected-tag>
为了更好的理解,来看一下构造这个payload时,strpos()函数的返回结果如下:
$user='<"><injected-tag property="';
$pass='<injected-tag>';
var_dump(strpos($user,'<'));
var_dump(!strpos($user,'<'));
var_dump(strpos($user,'>'));
var_dump(!strpos($user,'>'));

是不是理解了一些呢,现在看上面的代码,思考一下如果我们使用这个payload会返回什么呢。
var_dump((!strpos($user, '<') || !strpos($user, '>')) &&
(!strpos($pass, '<') || !strpos($pass, '>')));

返回了true,其实就是var_dump((true || false) &&(true || false))
成功绕过,可以进行XML注入。
通过上面的学习讲解是不是对strpos()函数了解更深一些了呢?下面咱们看一个实例,这个实例也是设计者没有考虑周全,导致任意用户密码重置。
实例分析
这次的实例是DeDecms V5.7SP2正式版,源码可以从网上下载搭建,这个很容易找到,就不详细说了,上面说了,这个漏洞是任意用户密码重置,下面我们来分析代码,漏洞的触发点在 member/resetpassword.php 文件中,是因为对接收的参数safeanswer没有进行严格的类型判断导致被绕过。下面看代码:

现在来对上面的代码做一个分析,当$dopost==safequestion时,通过$mid对应的id值来查询当前用户的safequestion,safeanswer,userid,email等值,现在来看第84行,这里的意思是当我们传入的安全问题和安全答案等于之前设置的值时,就传入sn()函数,重点来了,注意看,这里用的是双等于来验证,而没有用三等于,所以,这里是可以被绕过的。当用户没有设置安全问题时,那么默认情况安全问题值为0,安全答案值为null,这里指的是数据库中的值,而我们如果传入空值时,那么就是空字符串,84行语句也就变成了if('0' == '' && null == ''),也就是if(false&&true),所以我们只需要让前半部分转为true就可以了,通过测试如下图,都可以和0比较等于true。
if("0"=="0.0"){echo "成功1"."\n";}
if("0"=="0e1"){echo "成功2"."\n";}
if("0"=="0e12"){echo "成功3"."\n";}
if("0"=="0e123"){echo "成功4"."\n";}
if("0"=="0."){echo "成功5"."\n";}
上面的几种payload均能使得 safequestion 为 true,成功进入sn()函数

我们跟进sn()函数,代码如下:

在sn()内部,会根据id到pwd_tmp表中判断是否存在对应的临时密码记录,根据结果确定分支,走向 newmail 函数。现在我们假设第一次来进行忘记密码操作,那么现在的$row的值应该为空,也就会进入if(!is_array($row))分支,然后在newmail()函数中执行INSERT操作,具体代码在这个文件的上面,如图:

这个代码的功能是发送邮件到相关邮箱,并插入一条记录到dede_pwd_tmp表中,漏洞触发点在这里,我们现在看92~95行。如果 ($send == 'N') 这个条件为真,那就跳转到修改页,通过 ShowMsg 打印出修改密码功能的链接。拼接的url为:
http://www.dmsj.com/DedeCMS-V5.7-UTF8-SP2/uploads/member/resetpassword.php?dopost=getpasswd&id=$mid&key=$randval
现在咱们跟进dopost=getpasswd去看看,在member/resetpassword.php文件中,代码如下:

这里用empty()函数来判断$id和$row是否为空,如果不为空的话,就继续向下走,进入if(empty($setp))中,先判断是否超时,如果没超时的话进入修改页面,我圈起来了,现在跟过去看一下具体代码如下:

发现数据包中 $setp=2,所以代码功能又回到了member/resetpassword.php文件中,如下:

分析上面代码,我们发现如果传入的$key和数据库中的$row['pwd']相同时,则完成密码的重置,也完成了攻击的分析过程。
漏洞验证
现在注册两个账号,分别是test123和test456,查看mid的值如下,test456的mid为3。test123的mid为2。

我现在登陆test456账户,访问咱们构造的payload。来修改test123的密码。
http://www.dmsj.com/DedeCMS-V5.7-UTF8-SP2/uploads/member/resetpassword.php?dopost=safequestion&safequestion=0e1&safeanswer=&id=2
我现在登陆的是test456账户,访问url抓包。

获取到了key值,然后来访问修改密码的url:
http://www.dmsj.com/DedeCMS-V5.7-UTF8-SP2/uploads/member/resetpassword.php?dopost=getpasswd&id=2&key=xy8UzeOI

我们发现到了修改密码的页面,直接可以修改密码。我们将密码修改为abcdef,然后登陆test123,发现登陆成功,密码成功被修改。

小结
通过这篇文章的学习与讲解,是不是对strpos()函数和PHP弱类型绕过有了一定的了解了呢?下一篇文章会对escapeshellarg与escapeshellcmd使用不当来进行学习与讲解,一起努力吧!
PHP代码审计04之strpos函数使用不当的更多相关文章
- ***用php的strpos() 函数判断字符串中是否包含某字符串的方法
判断某字符串中是否包含某字符串的方法 if(strpos('www.idc-gz.com','idc-gz') !== false){ echo '包含'; }else{ echo '不包含'; } ...
- PHP strpos() 函数
定义和用法 strpos() 函数返回字符串在另一个字符串中第一次出现的位置. 如果没有找到该字符串,则返回 false. 语法 strpos(string,find,start) 参数 描述 str ...
- 字符串处理——strpos()函数
strpos() 函数返回字符串在另一个字符串中第一次出现的位置. 大小写敏感 如果没有找到该字符串,则返回 false. strpos(string,find,start) string 必需:规 ...
- PHP strlen()函数和strpos()函数
strlen() 函数返回字符串的长度(字符数) 代码: <?php echo strlen("Hello world!"); ?> 上面的代码将输出:12 ...
- php中strpos()函数
1,strpos()函数 mixed strops(]) 返回needle在haystack中首次出现的数字位置,从0开始查找,区分大小写. 参数:haystack,在该字符串中进行查找. needl ...
- php strpos() 函数介绍与使用方法详解
本文主要和大家介绍PHP中mb_strpos的使用技巧,通过使用语法以及实例给大家详细分析了用法,需要的朋友参考学习下.希望能帮助到大家.mb_strpos(PHP 4 >= 4.0.6, PH ...
- php strpos()函数 语法
php strpos()函数 语法 作用:寻找字符串中某字符最先出现的位置.大理石平台怎么选择 语法:strpos(string,find,start) 参数: 参数 描述 string 必需 ...
- PHP代码审计02之filter_var()函数缺陷
前言 根据红日安全写的文章,学习PHP代码审计审计的第二节内容,题目均来自PHP SECURITY CALENDAR 2017,讲完这个题目,会有一道CTF题目来进行巩固,外加一个实例来深入分析,想了 ...
- php基础04:字符串函数
<?php //1.strlen(),strlen() 函数返回字符串的长度,以字符计. echo strlen("hello world"); echo "< ...
随机推荐
- Artwork (Gym - 102346A)【DFS、连通块】
Artwork (Gym - 102346A) 题目链接 算法 DFS,连通块 时间复杂度:O(k*n + k * k) 1.这道题就是让你判断从(0,0)到(m,n),避开中途所有的传感器(传感器的 ...
- 图片压缩工具pngquant
关于图片压缩的,之前看到一个imageOptim,用着不错,也挺好用的,直接打开要压缩的图片或者文件夹,唰唰唰的就开始压缩了,如下图 后来觉得不是很方面,还要打开软件,选择文件夹,然后就又研究了一下, ...
- 使用MATLAB 2019 App Design 工具设计一个 电子日记App
使用MATLAB 2019 App Design 工具设计一个 电子日记App1.1 前言:由于信号与系统课程需要,因此下载了MATLAB软件,加之对新款的执着追求,通过一些渠道,下载了MATLAB ...
- 如何学习iOS开发?iOS Developer Library足矣!
记得上高中的时候,寄信请教二哥学习经验,二哥来信介绍学习经验说:资料书要快速阅读,把书上的题做完,然后再买几套资料书(习题集)继续练习. 这是二哥的经验,因为他自学能力强,可以消化多套资料书. 我仿照 ...
- vue使用vueCropper裁剪功能,代码复制直接使用
//先安装包 npm install vue-cropper --save-dev <template> <div id="merchantInformation" ...
- 用 Java 实现的八种常用排序算法
八种排序算法可以按照如图分类 交换排序 所谓交换,就是序列中任意两个元素进行比较,根据比较结果来交换各自在序列中的位置,以此达到排序的目的. 1. 冒泡排序 冒泡排序是一种简单的交换排序算法,以升序排 ...
- dgraph 使用简介
dgraph 简介 dgraph 使用示例(基于 golang) golang client 安装 创建 schema 数据的 CURD 事务 总结 dgraph 简介 dgraph 是基于 gola ...
- 3.字符设备led驱动
1.硬件原理图 由图可知,led1,led2,led3,led4,分别对应GPB5,GPB6,GPB7,GPB8,由s3c2440芯片手册可得到如下图所示,分别配置GPBCON和GPBDAT即可 2. ...
- 面经手册 · 第13篇《除了JDK、CGLIB,还有3种类代理方式?面试又卡住!》
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 编程学习,先铺宽度还是挖深度? 其实技术宽度与技术深度是相辅相成的,你能了解多少技术 ...
- JSON,数组根据字段分组
function GroupbyName(data, Name) { //data数据源,Name 根据什么字段分组 var map = {}, dest = []; for (var i = 0; ...