社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  NGINX

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

船山信安 • 2 月前 • 82 次点击  

前言

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

历史漏洞

漏洞分析

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

  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路径等相关配置


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


所以攻击者只需要将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


成功调用cmd执行ping命令

迟迟未修的RCE漏洞

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

漏洞分析

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


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


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


但shell中是可以正常执行的


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


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

漏洞复现

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

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":[]}

未知攻,焉知防

该接口的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", "
    "
    );
    }

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


用户输入恶意代码:


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

总结

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



来源:【https://xz.aliyun.com/】,感谢【n*o

由于群已满,各位大佬可以加浪师父的微信,再加群。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/167324
 
82 次点击