phpcms attachment.class.php,PHPCMS漏洞分析合集

本篇详细分析了 PHPCMS 的部分历史漏洞。其中多是以获取到漏洞点为场景,反向挖掘至漏洞触发入口(假设自己发现了漏洞点,模拟如何找寻整个攻击链及其入口点),旨在提高自身代码审计能力。当中包含一些网络上未公开的触发点,以及补丁对比分析与绕过。

v9.6.0任意文件上传

这个漏洞存在于用户注册处。这里有一个可控变量 $_POST[‘info’] 传入了 member_input 类的 get 方法中,跟进该方法。(下图对应文件位置:phpcms/modules/member/index.php)

1cf15cdbe5e94bad97a6c069fc27cece.png

在 get 方法中,我们发现 $data 变量来自 $_POST[‘info’] ,并且我们可以调用 member_input 类的所有方法(对应下图 第47-48行 代码)。(下图对应文件位置:caches/caches_model/caches_data/member_input.class.php)

deb76e78219cda4b904a54cedcfd602c.png

看了一下 member_input 类的所有方法,只有一个 editor 方法比较好利用,而本次漏洞正是利用到这个方法。在这个方法中,调用了 attachment 类的 download 方法。(下图对应文件位置:caches/caches_model/caches_data/member_input.class.php)

93bbe69281e6a7a2673c3dd979d19544.png

在 download 方法中,程序先使用正则对图片 URL 进行匹配,其中 $ext 只允许为gif|jpg|jpeg|bmp|png ,而我们使用

fd22746da52924249aca8c4282cc4d3e.png

接着又使用 fillurl 方法对匹配到的远程图片地址进行处理,其实就是将 # 号之后的字符全部去除,例如

a5c80462049800c54b96035636f4c6d8.png

fillurl 方法处理后,又回到了 download 方法。程序直接调用 copy 函数将远程文件复制到本地(对应下图 161 行代码),远程文件名可预测,后缀名为上边处理后的 URL 文件名后缀,即 php ,最终导致 getshell 。其中 webshell 地址为

583f71075ce0e6d9368c4c3e3a063d88.png

最后我们再来看一下在官方发布的 PHPCMS v9.6.1 中是如何修复这个漏洞的,代码具体如下。可以明确看到,在官方补丁中,对 fileext($file) 获取到的文件后缀进行了黑名单校验。虽然暂时不能直接上传 shell ,但是还是可以上传图片马。如果 CMS 存在任意文件包含或任意文件名修改的漏洞,同样还是可以 getshell ,这里最好再对远程图片的内容进行校验下比较好。(下图对应文件位置:phpcms/libs/classes/attachment.class.php,左半图为PHPCMSv9.6.0,右半图为PHPCMSv9.6.1)

8e7176e8da6871e971c106c9e74a151c.png

实际上,单这个补丁中的正则来说,是可以绕过的,例如: .php%7f ,Windows下会将非法字符替换成空,但是其实后续还有一系列的问题,导致我没绕过。本以为要挖到0day了,我傻乐了半天:)

v9.6.0SQL注入

这个版本的 SQL注入 主要在于程序对解密后的数据没有进行过滤,我们来看一下漏洞文件 phpcms/modules/content/down.php 。在其 init 方法中,从 GET 数据中获取了 a_k 的值,该值若能解密成程序规定格式的字符串,则程序继续运行(这里加解密使用的秘钥必须一致,例如这里秘钥为 pc_base::load_config(‘system’,’auth_key’) )。程序将解密后的数据用 parse_str 函数处理,这里又存在变量覆盖问题。然后将可控变量 $id 带入数据库查询,我们跟进 get_one 方法。

6da7e0d924bfe1afaac60b8c1ce8bff9.png

get_one 方法调用了 sqls 方法,而在 sqls 方法中可以明显看到,未过滤的数据直接拼接进了 SQL 语句中。

ddfa343545b43d34f9fd7117c1a847a5.png

那么现在,我们要解决的问题是:如何构造出加密数据,使得数据能够被正常解密?我们先来看一下 sys_auth 函数的代码,其代码位于 phpcms/libs/functions/global.func.php 中。开头我们可以很明显看到,当我们没有指定加解密用的 key 时,系统默认使用 pc_base::load_config(‘system’,’auth_key’) 作为 key ,这样我们就不用特地去搜索形如 sys_auth(‘xxx’,’ENCODE’,pc_base::load_config(‘system’,’auth_key’)) 的代码段,直接搜索形如 sys_auth(‘可控字符串’,’ENCODE’) 或 sys_auth(‘可控字符串’) 的代码段即可。(这里搜索这种代码段的目的,是为了找到可利用的点将恶意 payload 进行加密,然后传输给开头 phpcms/modules/content/down.php 文件的 init 方法进一步利用)

5f20844e11fec192d9e1140a2b544899.png

通过搜索,会发现在 set_cookie 方法中使用了 sys_auth($value, ‘ENCODE’) ,我们可以寻找是否存在可控的 $value 。

1cf907b077c818f93b320b8ae377d045.png

我们可以搜到 phpcms/modules/wap/index.php 文件,在该文件中 $_GET[‘siteid’] 可控,并且可以通过 cookie 获得加密后的数据,但是这里有 intval 过滤,所以无法放置我们的 payload 。

828974f23684ef0d1231d884f418cd21.png

我们继续寻找,会发现 phpcms/modules/attachment/attachments.php 文件的 swfupload_json 方法有满足我们需要的代码。程序将可控数据放在了 cookie ,其中可控数据中,比较好利用的是 $_GET[‘src’] 。

b20414187b9e5c6cf1498d6cb1aeddb5.png

$_GET[‘src’] 只是经过了 safe_replace 函数的过滤,该函数会将某些字符替换为空,而我们却可以在 payload 中插入这些字符,从而绕过黑名单的过滤。 safe_replace 函数代码如下:

e72b43761a1add01a68c30f80bc9d601.png

貌似现在已经找到了利用链了?别高兴的太早。在调用这个 swfupload_json 方法之前,程序会执行 attachments 类的 __construct 方法,而这个方法中有用户登录状态检测。用于登录状态检测的 $this->userid 可以来自 sys_auth($_POST[‘userid_flash’],’DECODE’) ,即我们让 $_POST[‘userid_flash’] 经过 sys_auth 方法解密之后有东西即可。而这个加密数据,就可以利用我们上面说到的 phpcms/modules/wap/index.php 文件。通过 cookie 获取 $_GET[‘siteid’] 加密后的数据,然后再作为 $_POST[‘userid_flash’] 的值,即可绕过登录检测。

4341ac19c8d4eaf108a25ad101170461.png

绕过登录检测后,我们将 payload 传给 phpcms/modules/attachment/attachments.php 文件 swfupload_json 方法中的 $_GET[‘src’] ,再利用开头 parse_str 函数进行变量覆盖,最终完成整个漏洞链。整个漏洞的利用流程图如下:

51c937a9ace3c3f7d3eab3d26e595736.png

按照默认配置安装的网站搭建好后, WAP 是处于禁用状态,但是这并不影响我们获得加密后的 $_GET[‘siteid’] 。

b1e2253650cfd69464394930b6cfaf57.png

97bfbcd4db0eb031b30e8f079c6c2632.png

我们再来假设,如果网站管理员删除了 WAP 模块的代码,这个洞还能利用吗?我们可以继续来挖掘一下这个漏洞链的其他入口,这也是网络上未公开的一个入口点。上面我们在搜索 set_cookie 方法找可控数据时,会发现 phpcms/modules/mood/index.php 文件的 post 方法可以直接获得一个加密后的数据,这样我们就可以将这个数据,用在漏洞链的第二步:绕过用户登录验证,具体代码如下:

861feaef2c22f413fc987497f57a2b38.png

这里只要按照代码逻辑,构造参数即可。这里可能还要注意本类的 __construct 方法,同样按照逻辑构造参数即可,具体构造这里不再赘述。

f07631f13b27931a857a11fdaff73cbb.png

通过上面这个漏洞链入口,我们便可以进行 报错SQL注入 。比较有意思的是, PHPCMS 会将 admin 登录的 cookie 存储在数据库中,我们可以通过注入获取管理员 cookie ,然后伪造管理员身份利用后台 getshell 。这里如何伪造身份,网络上貌似提及很少,唯一找到一篇文章https://www.secpulse.com/archives/57486.html ,发现作者竟然还少提及了一个关键参数。于是我将伪造的数据包,与正常登录的数据包进行对比,逐个删除 cookie 中的数据,看看少了哪个关键参数。接下来,我们来具体看一下如何伪造 cookie 进入后台。

PHPCMS 专门在数据库中建了一个表来存放 PHPSESSID ,其中也包含管理员的 PHPSESSID ,且登录状态下的 userid 字段会被设为1,注销则为0,具体如下:

456e464ef1158a4cc953cca6c61a6869.png

我们把 PHPSESSID=9t9mrk25ak5sb9v60nc255ql11 加到 cookie 中,直接访问后台,这是发现程序还是会让你登录,估计我们是少了什么,下面来动态调试一下。经过调试,我们会发现程序终止在了 admin 类 __construct 方法的 self::check_admin() 语句中,其具体代码如下:

5f8c9a5fd2405aa3bbd9e2e429eae586.png

可以明显看到,我们原先的 cookie 中少了 $userid 对应的字段,而且要想绕过登录,必须保证 $_SESSION[‘roleid’] 和 $userid 相等且它们两者非空。那么现在,我们只要加上 $userid 对应字段就行了,其值可以从上面漏洞链第一步中的响应包 Set-Cookie 字段获取。这里要注意一个点,一旦管理员注销,我们就无法利用这个点伪造 cookie 了,这也是这个漏洞的鸡肋之处。

最后来看一下官方发布的 PHPCMS v9.6.1 中是如何修复这个漏洞的,补丁如下:

7a194e15c0b2bb74e80ecd5bf45bedef.png

可以看到官方对解密后的数据进行了 safe_replace、intval 双重过滤处理。

v9.6.1任意文件读取

这个版本的 任意文件读取 漏洞和上个版本的 SQL注入 漏洞原理是类似的,且出问题的文件均在 phpcms/modules/content/down.php 中。在该文件的 download 方法中最后一行调用了 file_down 文件下载函数,我们可以看到其第一个参数是要读取的文件路径。

815854f9d11f9ad3e53055e2566bd64c.png

我们再来看看 download 方法中有哪些限制条件。可以看到其开头部分的代码,和上一个版本的 SQL注入 类似,唯一不同的是这里加解密的 key 变成了 $pc_auth_key ,我们等下就要来找找使用 $pc_auth_key 进行加密的可控点。继续看 download 方法,里面对要下载的文件后缀进行了黑名单校验,但是末尾又对 >< 字符进行替换,这就导致后缀名正则可被绕过,例如: .ph

5905f09364825c29ba34998cdd8cd2e6.png

现在我们就要来找找使用 $pc_auth_key 作为加密 key 的可控点。通过搜索关键字,我们可以看到有三处地方。然而前两处地方是不可以利用的,因为都有登录检测。而第三个点就可以利用,我们看其中 $i、$d、$s 作为明文字符串被加密。(下图对应文件位置:phpcms/modules/content/down.php)

1bc25db9669c8701ec6f5998a3447502.png

有了加密字符串,我们如何能够从前台获取呢,这里其实在最后一行包含模板文件时,将加密字符串 $downurl 输出了,这样也就解决了我们获取的问题。

71f9c8995d4d388df81b6bb5b88a06d9.png

那 $i、$d、$s 这三个变量从哪里来?我们往前看,代码有没有相当熟悉?这里只对 $i 进行了 intval 过滤,其他两个变量还是可以利用。而且加密字符串 $a_k 的获取,就和上个版本的 SQL注入 漏洞攻击链的前2步是一样的,这里不再赘述。(下图对应文件位置:phpcms/modules/content/down.php)

ee25d8b9b4ebf678cbb98eb8f56fb724.png

我们在构造 payload 的时候,我们要注意整个攻击过程会经过两次 safe_replace 、两次 parse_str 、一次 str_replace(array(‘’), ‘’,$fileurl) ,而程序对 .. 和 php 字符进行了检测。所以我们要想访问 php 文件或进行路径穿越,后缀可以设置成 ph>p ,路径符可以变成 .>. 。但是 safe_replace 函数会 str_replace(‘>’,’>’,$string) ,所以 > 字符需要编码两次,变成 %25253e 。

0fc5926fdbdc9d2a21f71f6f9034c8d8.png

我们可以将整个漏洞的触发过程整理成下图:

25eed97e09c569cfaad645a205ad6e46.png

最后来看一下官方发布的 PHPCMS v9.6.2 中是如何修复这个漏洞的,补丁如下:

04a43dd5a0225810fb58b709faafa10a.png

可以看到补丁将后缀匹配规则放在离下载文件最近的地方,貌似能防止规则中的文件被读取,但是我们可以利用 windows 的特性,在 windows 下绕过这个正则,这也是网传的一种 PHPCMS v9.6.2任意文件下载 漏洞。

16c6cd5a579d44de853f4b45df718ce8.png

v9.6.2前台SQL注入

这个版本的的注入,是建立在任意文件读取漏洞存在的情况下才可利用。通过任意文件读取漏洞获得加解密的 key 值,我们可以用这个 key 加密我们的 SQL注入payload 。由于程序对解密后的数据并未过滤,最终导致漏洞发生。严格上来讲 v9.6.2 版本的注入只能在 windows 上利用,具体原因在上面的任意文件读取漏洞分析时也说了。下面我们来具体分析一下这个漏洞。

漏洞文件位于 phpcms/modules/member/classes/foreground.class.php ,代码如下图。我们可以明显看到下图第33行,程序直接将解密后的数据未经过滤直接带入查询。而待解密数据 $phpcms_auth 和解密秘钥 $auth_key 均可构造。

760ae04884bb7ab634199b4300fa081f.png

我们先来看一下待解密数据 $phpcms_auth 如何构造。从下图中,可以看出程序将从 cookie 中的 xxx_auth 字段经过 sys_auth 函数解密后,返回给了 $phpcms_auth ,而默认情况下使用 pc_base::load_config(‘system’, ‘auth_key’) 作为加解密的 key 值。

6899b0fb6ffff942511269354801866e.png

而 pc_base::load_config(‘system’, ‘auth_key’) 的值在网站搭建好后,会存储在 caches/configs/system.php 中,我们可以通过任意文件读取来获得这个值。

d512b28c10ef77a7100c3dfad1d3988e.png

现在 $phpcms_auth 已经搞定了,我们再来看看 $auth_key = get_auth_key(‘login’) 如何构造,跟进 get_auth_key 的代码。我们可以看到 $auth_key 由 $prefix、pc_base::load_config(‘system’,’auth_key’)、ip() 三个元素决定。前两个都是已知的,而第三个获取用户IP的函数存在IP伪造的问题,也可以是固定的。

67c0b4889444f8afc95bbfff572c83fe.png

所以 get_auth_key(‘login’) 的值也是我们可以构造的,剩下的事情只要我们将 payload 传给加密函数加密两次即可。我们最后再来看一下 PHPCMS v9.6.3 中是如何修复这个漏洞的,补丁如下:

b5f5c5e992821594f79676971289cf36.png

可以明显看到,补丁将解密后获得的 $userid 进行了强转。

结束语

分析历史漏洞好处在于,可以使自身对这个 CMS 更熟悉,摸清该 CMS 普遍存在的问题,甚至有机会通过 bypass 补丁来发现新的 0day 。有些补丁只是暂时修复了漏洞,安全隐患仍然存在。随着 CMS 功能越来越多,我们可以将新功能中的利用点,结合之前的风险点,打出一条漂亮的攻击链,期待下个 0day 的诞生。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值