IDEA配置Java远程调试,以CVE-2024-4956为例

背景

学习代码审计,看到一些Java的漏洞,想要动手调试,复现漏洞搭建环境可以使用docker快速创建,了解到Java可以远程调试,本文记录学习Java远程调试环境搭建的过程。

远程调试的原理

如下图(图源:doc.oracle.com):

JPDA
首先需要明白上述些许名词的含义:

  • JDPA: Java Platform Debugger Architecture,直译Java平台调试架构,是Java为应用程序提供调试服务的一套框架。层次分明的结构提供了跨平台的特性,包含三层分别是JVM TI、JDWP、JDI
  • JVM TI: Java VM Tool Interface,Java虚拟机工具接口,是由VM(即Java虚拟机)实现的一组本地API,定义了VM必须提供的用于调试的服务。
  • JDWP: Java Debug Wire Protocol,Java调试线路协议,定义了后端与前端之间传输的信息和请求的格式。但JDWP没有定义传输机制
  • JDI: Java Debug Interface,Java调试接口,定义了用户代码级别的信息和请求。

也就是说,JDPA定义了一个框架,该框架包含三大模块,分别是后端的JVM TI、前端的JDI、以及定义了中间信息格式的JDWP。当我们调试某程序时,程序在VM(即Java虚拟机后续不在赘述)中运行,且VM实现了JVM TI,调试器后端即通过JVM TI与VM通信获取运行时的各种响应信息。

JDWP定义了调试器后端与调试器前端之间的通信格式,调试器后端将响应信息按照JDWP的规定包装后发送给调试器前端,还记得前面说的“JDWP没有定义传输机制”吗,这就意味着可以使用多种传输机制,例如可以是我们远程调试时使用的套接字。

现在已经明确了,JVM TI用于在VM运行时(调试时)收集调试关注的信息(响应),将响应按照JDWP打包后可以通过多种方式传输至调试器前端,包含套接字。调试器前端通过实现JDI,规定代码级别的请求,例如在何处断点,并将该信息同样打包成JDWP格式,以控制调试器后端。

像IDEA与Eclipse,都实现了JDI与自己UI界面来控制调试器的后端,进而操控VM,获取运行时的调试信息。

Demo:CVE-2024-4956远程调试

CVE-2024-4956是Nexus Repository 3的一个任意文件读取漏洞。我是用的docker镜像是官方的sonatype/nexus3:3.68.0-java8

首先创建一个IDEA空项目,创建一个远程JVM调试配置,IDEA实现了调试器的客户端,且会自动帮我们生成JVM的启动命令行参数,如下:

创建与配置JVM远程调试
这里调试器模式有两种,附加到远程JVM和;拷贝启动参数,-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

  • agentlib:jdwp:这是指定使用Java调试线程库的前缀。
  • transport=dt_socket:这表明调试数据将通过套接字(Socket)传输。
  • server=y:表示Java应用程序将作为调试服务器运行,调试器可以远程连接到这个服务器。
  • uspend=n:表示Java虚拟机(JVM)启动时不会暂停,即使调试器还未连接,程序也会继续运行。如果设置为suspend=y,则JVM会在启动时暂停,直到调试器连接后才继续执行。
  • address=5005:这是调试服务器监听的端口号,调试器需要连接到这个端口进行远程调试。这里设置为5005,也可以选择任何未被占用的端口。

第二步,修改VM的启动参数,添加启用远程调试。install4j是一个用于打包Java应用程序的工具,该镜像使用了install4j的环境变量INSTALL4J_ADD_VM_PARAMS,我们可以通过该环境变量修改启动参数。

docker inspect 镜像id

# 原始环境变量
INSTALL4J_ADD_VM_PARAMS=-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m -Djava.util.prefs.userRoot=/nexus-data/javaprefs
# 修改后的环境变量(即将idea中copy出的参数附加)
INSTALL4J_ADD_VM_PARAMS=-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m -Djava.util.prefs.userRoot=/nexus-data/javaprefs -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

docker通过-e选项指定启动的环境变量,于是得到容器的启动命令如下:

docker run -d -p 8081:8081 -p 5005:5005 --name nexus_3.68.0 -e INSTALL4J_ADD_VM_PARAMS="-Xms2703m -Xmx2703m -XX:MaxDirectMemorySize=2703m -Djava.util.prefs.userRoot=/nexus-data/javaprefs -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" sonatype/nexus3:3.68.0-java8

第三步把jar包copy出来,附加到IDEA。要确保本地与远程的要调试部分的代码是一样的,这样我们在IDEA本地打断点,调试前端获取断点信息发送到调试后端,调试后端才能正确解析。

一开始我查到的文章,这里写的比较粗略,我一度一位本地是个空项目都能调试了,我在想,那怎么打断点呢?一些文章写要保证本地与远程的源码一样,看过文档我觉得“只要保证打断点部分的代码一样就可以了”,为验证该想法下面我做了实验:

实验:

  1. 在本地写一个web服务,打包成jar包,8090端口提供web服务
  2. 在服务器运行该jar包,为了方便我这里也使用了docker容器里的java环境,5006端口调试
  3. 本地配置IDEA调试客户端环境,连接服务器5006端口的调试端口
  4. 修改本地源码,下断点,访问web服务,观察是否还能正确触发断点
# Dockerfile
FROM vulhub/java:8u221-jdk
COPY ./apptest.jar /tmp/app.jar
EXPOSE 8090
ENTRYPOINT java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5006 -jar /tmp/app.jar
// apptest.jar的源码
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;public class MainClass {public static void main(String[] args) throws Exception {HttpServer server = HttpServer.create(new InetSocketAddress(8090), 0);server.createContext("/test", new MyHandler());server.setExecutor(null); // creates a default executorserver.start();}static class MyHandler implements HttpHandler {@Overridepublic void handle(HttpExchange t) throws IOException {String response = "This is the response";t.sendResponseHeaders(200, response.length());OutputStream os = t.getResponseBody();os.write(response.getBytes());os.close();}}
}
# 构建镜像
docker build -t test:v1.0 .
# 启动容器
docker run -d -p 8090:8090 -p 5006:5006 test:v1.0
# 配置IDEA调试客户端

idea成功连接调试后端
随后修改了response的值,甚至是response变量的名称,发现在访问/test路径时,依旧可以触发断点,如下图所示;因此不需要保证本地源码与远程源码的“完全一致”,这点也很好理解,JDWP规定的信息也必然不是像“xx行xx变量有断点”此类的信息,源码被翻译为字节码,只要保证字节码时对应的,即可正确匹配(我觉得)。后续有深入研究再来探讨该问题。

修改变量名称仍可触发断点
手动设置值

CVE-2024-4956漏洞分析

如上配置好调试环境,把jar包copy出来,在IDEA中导入,项目结构=》模块=》依赖=》小加号“jar或目录”如下图:

导入jar包
我这里因为是看了别人的分析,知道漏洞点位于哪里,所以直接从docker容器里复制的特定jar包出来的。看了其他师傅的分析,get了一个小技巧:

# 将目录下的所有 jar 都复制到同一目录下, 方便 IDEA 添加依赖
mkdir ../all-lib
find . -name "*.jar" -exec cp {} ../all-lib/ \;

从官方给出的临时解决方案开始分析:

官方给出的临时解决方案
告诉我们要删除jetty.xml中的<Set name="resourceBase"><Property name="karaf.base"/>/public</Set>行,之后通过访问robots.txt来观察,若是404代表临时解决方案生效。

nexus对静态资源文件的获取有如下三种方法,优先级从1到3;目的都是获取路径,再检查请求的文件是否存在于这些路径中:

  1. getFileIfOnFileSystem,该方法从系统定义的环境变量或系统属性中获取路径,再从这些路径中get文件。默认为空。
  2. this.resourcePaths本身就是一个哈希表,是系统维护的一批路径,通过调试可以发现有2012条。
  3. this.servletContext,调用了Jetty的WebAppContext获取资源文件。
    获取资源文件
    2012条
    方法1默认为空,常规的静态资源文件通过方法2获取,在2中无法命中的交给3即jetty处理。问题即出在3处,即jetty的处理中。

访问不存在的a.txt

	// servletContext.getResource(path)public Resource getResource(String path) throws MalformedURLException {if (path != null && path.startsWith("/")) {if (this._baseResource == null) {return null;} else {try {Resource resource = this._baseResource.addPath(path);return this.checkAlias(path, resource) ? resource : null;} catch (Exception var3) {Exception e = var3;LOG.ignore(e);return null;}}} else {throw new MalformedURLException(path);}}// this._baseResource.addPath(path);public Resource addPath(String subPath) throws IOException {if (URIUtil.canonicalPath(subPath) == null) {throw new MalformedURLException(subPath);} else {return "/".equals(subPath) ? this : new PathResource(this, subPath);}}

如上是getResource与addPath的源码,首先会判断传入的path值是否为空,是否以/开头,之后与_baseResource“拼接”,即addPath方法,_baseResource的path属性为:“/opt/sonatype/nexus/public”,即将会从public路径下寻找匹配的文件。

addPath中为防止路径穿越的问题,做了处理,即canonicalPath函数,对传入的subPath进行“标准化”,具体逻辑如下:

// URIUtil.canonicalPath(subPath)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 < end; ++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);int dots = 1;++i;for(; i < end; ++i) {char c = path.charAt(i);switch (c) {case '.':if (dots > 0) {++dots;} else if (slash) {dots = 1;} else {canonical.append('.');}slash = false;continue;case '/':if (doDotsSlash(canonical, dots)) {return null;}slash = true;dots = 0;continue;}while(dots-- > 0) {canonical.append('.');}canonical.append(c);dots = 0;slash = false;}if (doDots(canonical, dots)) {return null;} else {return canonical.toString();}}} else {return path;}}// (doDotsSlash(canonical, dots))private static boolean doDotsSlash(StringBuilder canonical, int dots) {switch (dots) {case 0:canonical.append('/');break;case 1:return false;case 2:if (canonical.length() < 2) {return true;}canonical.setLength(canonical.length() - 1);canonical.setLength(canonical.lastIndexOf("/") + 1);return false;default:while(true) {if (dots-- <= 0) {canonical.append('/');break;}canonical.append('.');}}return false;}
  1. 先检查传入的路径path,既不是null也非空;
  2. 之后进入第一个label68循环,在该循环中对路径的每一个字符进行遍历,出现/.之前时跳出label68的循环。
  3. 下面判断循环是“正常结束”还是“提前跳出”,“正常结束”即i==end; “提前跳出”即遇到“出现/.之前”的情况。“正常结束”则返回path。
  4. 若是“提前跳出”,则维护一个dots变量标识点的数量并新建一个字符串,并将不包含该.在内的往前所有字符,保存至新字符串中,称之为标准字符串;接下来从下一个位置开始继续遍历字符串。
  5. dots一开始将被初始化为1,新的遍历将跳过该.,直接读取下一个字符,此时进入一个switch判断该字符:1.若为.:先判断dos,若dots大于0则dots自增并将slash变量置为false。若dots不大于0判断slash是否为true,若为true,dots置为1;2.若为/,判断doDotsSlash函数的值,若为true返回null,否则将slash置为true且清零dots。3.若为正常字符则将前面的点号都追加上,将此正常字符也追加。
  6. 下面来看doDotsSlash函数的逻辑,接受两个参数,标准字符串和点的数量dots,若点的数量为0,直接追加一个/,返回false;若点的数量为1,返回false;若点的数量为2,则将标准字符串长度减一(删掉最后一个字符),再寻找标准字符串中的最后一个/,将之后的都删掉。返回false;若点的数量大于2,则向标准字符串追加/,最后追加/,返回false。
  7. 只有一种情doDotsSlash会返回true,则标准化字符串返回null,即已经有两个/,且标准化字符串的长度小于2,即全是.

上面以“流水账”的形式走了一遍代码的流程,可以看出,标准化函数canonicalPath,已经在避免路径穿越的情况发生;第一次遍历path中的每个字符串,当遇到/之后有.时,跳过该.将前面的保存为新的标准化字符串,认为是没有问题的;对后续的字符串特殊处理,进入第二个遍历,遇到.前点的前面没有斜线也没有点时,认为时“良性”直接追加点号;否则将数量dots加1;

当再次遇到斜线后,前面无点、有一个点、有2个以上点时,都将点追加至标准字符串即可。只有当斜线前面点的数量恰好为2时,删除最后一个斜线后的所有字符,这两个连续的点也将被跳过。

写到这里的时候我在想这么严格的过滤,这怎么绕过?

但是addPath中,只是一个通过canonicalPath函数做了个判断,结果是否为null,并没有使用其返回值;之后将传入的路径与基础路径进行拼接,造成了路径穿越。
addPath函数
拼接
如果使用一些很明显的路径穿越payload是会被判null,从而抛出错误的。这就是前面doDotsSlash函数中,dots数量为2且标准化字符串长度小于2的情况。(测了下挺鸡肋的,两个.开头会抛出400错误,这是因为之前已经有了开头必须为/的判断了,因此这里的path前两个字符必定为"//")

第一次调,还有很多稀里糊涂的地方,有疑问评论区交流、多多批评

Reference

https://docs.oracle.com/javase/8/docs/technotes/guides/jpda/architecture.html
https://blog.csdn.net/ywlmsm1224811/article/details/98611454
https://exp10it.io/2024/05/通过-java-fuzzing-挖掘-nexus-repository-3-目录穿越漏洞-cve-2024-4956/
https://xz.aliyun.com/t/14623
https://support.sonatype.com/hc/en-us/articles/29412417068819-Mitigations-for-CVE-2024-4956-Nexus-Repository-3-Vulnerability

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/339597.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库索引的理解

目录 1.索引是什么&#xff0c;解决了什么问题 2.索引付出了什么代价 3.如何使用sql索引&#xff0c;有何注意事项 普通索引&#xff1a; 唯一索引&#xff1a; 主键索引(Primary Key Index)&#xff1a; 删除索引: 创建主键索引的基本语法: 4.索引背后的数据结构 1.索…

玩转Linux进度条

准备工作&#xff1a; 一.关于缓冲区 首先&#xff0c;咱们先来一段有意思的代码&#xff1a; #include<stdio.h> #include<unistd.h> int main() {printf("you can see me");sleep(5);} 你可以在你的本地运行一下&#xff0c;这里我告诉大家运行结果…

如何用python做一个用户登录界面——浔川python社

1 需解决的问题&#xff1a; 1.1如何用python做一个用户登录界面&#xff1f; 1.2需要用到哪些库、模块&#xff1f; 2 问题解决&#xff1a; 2.1 回答 1.1 &#xff1a;合理即可&#xff0c;无标准回答。 2.2 回答 1.2 &#xff1a;tk库&#xff08;缩写&#xff09;、GUL界面…

appium元素定位工具_uiautomatorviewer.bat

特点&#xff1a; uiautomatorviewer是android-sdk自带的元素定位工具uiautomatorviewer只能用于安卓系统&#xff1b;它是通过截屏分析XML布局文件方式&#xff0c;来提供控件信息的查看服务 uiautomatorviewer.bat 基本使用 路径&#xff1a;这个工具是Android SDK中自带&…

项目中统一异常处理

项目中统一异常处理 1.异常处理框架图2.实现 1.异常处理框架图 异常处理除了输出在日志中&#xff0c;还需要提示给用户&#xff0c;前端和后端需要作一些约定&#xff1a; 错误提示信息统一以json格式返回给前端。以HTTP状态码决定当前是否出错&#xff0c;非200为操作异常。…

QML信号连接到c++的槽函数(五)

文章目录 前言一、QML Signal and Handler Event System二、QML信号连接到c++的槽函数代码实例1. 创建一个QML 工程2. 用C++ 实现一个QML Types3. 代码实例4. 运行结果总结参考资料前言 本文主要介绍,如何将QML 中的信号连接到C++ 中的槽函数 软硬件环境: 硬件:PC 软件:wi…

在国内PMP含金量并不高?

PMP已经在全球194个国家和地区得到广泛认可&#xff0c;自1999年开始在国内实施。PMP被认为是项目管理专业身份的象征&#xff0c;是项目经理最重要的资质。获得PMP证书意味着个人的项目操作水平已经得到了PMI的认可&#xff0c;具备国际专业项目操作者水平&#xff0c;有资格专…

大模型应用之基于Langchain的测试用例生成

一 用例生成实践效果 在组内的日常工作安排中&#xff0c;持续优化测试技术、提高测试效率始终是重点任务。近期&#xff0c;我们在探索实践使用大模型生成测试用例&#xff0c;期望能够借助其强大的自然语言处理能力&#xff0c;自动化地生成更全面和高质量的测试用例。 当前…

[数据集][目标检测]旋风检测数据集VOC+YOLO格式157张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;159 标注数量(xml文件个数)&#xff1a;159 标注数量(txt文件个数)&#xff1a;159 标注类别…

1. MySQL 数据库的基本操作

文章目录 【 1. SQL 的书写规则 】大小写规则常量的表示注释 【 2. RDBMS 术语 】Table 表Filed 域/字段Column 列Record 记录NULL 空值Constraint 约束数据的完整性范式 【 3. 数据库基本操作函数 】3.1 SHOW DATABASES 显示数据库3.2 CREATE DATABASE 创建数据库3.3 ALTER DA…

STM32-14-FSMC_LCD

STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32-11-电容触摸按键 STM32-12-OLED模块 STM32-13-MPU 文章目录 1. 显示器分类2. LCD简…

[Windows] 植物大战僵尸杂交版

游戏包含冒险模式、挑战模式、生存模式三种不同玩法。冒险模式主打关卡闯关&#xff0c;挑战模式则挑战特殊设计的关卡&#xff0c;生存模式结合无尽模式和特殊地图&#xff0c;各具特色。玩家可根据喜好自由选择模式&#xff0c;体验不同的游戏乐趣。快来尝试这款独特的pvz游戏…

6月2(信息差)

&#x1f30d;特斯拉&#xff1a;Model3高性能版预计6月中旬开启首批交付 &#x1f384;微软对开源字体 Cascadia Code 进行重大更新 ✨天猫618加码引爆消费热潮 截至晚9点185个品牌成交破亿 1.瑞士清洁科技公司Librec开发废旧锂离子电池回收技术&#xff0c;可回收电池90%的…

【设计模式】JAVA Design Patterns——Factory Method(虚拟构造器模式)

&#x1f50d;目的 为创建一个对象定义一个接口&#xff0c;但是让子类决定实例化哪个类。工厂方法允许类将实例化延迟到子类 &#x1f50d;解释 真实世界例子 铁匠生产武器。精灵需要精灵武器&#xff0c;而兽人需要兽人武器。根据客户来召唤正确类型的铁匠。 通俗描述 它为类…

IDEA2020.3部署旧的的web工程,报错,参考下面的配置

以下内容&#xff0c;需要仔细核对&#xff0c;有些配置只是针对本项目进行的配置&#xff0c;仅供参考&#xff0c;可以解决一些问题。 File->Project Structure&#xff1a; Tomcat配置&#xff1a; 完成。 详细内容&#xff0c;参考&#xff1a;IDEA2020.3部署旧的的web工…

备战十一届大唐杯国赛预选赛

这次省赛带了太多个省一了&#xff0c;具体可看下面的图片&#xff0c;只放了一部分。目前根据可靠消息&#xff0c;应该还有个预选赛和去年一样&#xff0c;就是还会考一次仿真。如果说通过了就是国二起步然后去北方工业争夺国一国二&#xff0c;没过的话就是国三。 每…

Python实用代码片段分享(三)

在今天的博文中&#xff0c;我们将继续分享一些Python编程中非常实用的代码片段。这些代码片段将帮助你更高效地处理常见任务&#xff0c;从字符转换到数据类型检查&#xff0c;应有尽有。 1. ord函数和chr函数 Python的ord()函数可以返回Unicode字符对应的ASCII码值&#xf…

精准检测,可燃气体报警系统的技术原理与特点

在现代化的工业生产与日常生活中&#xff0c;可燃气体泄露事故频发&#xff0c;给人们的生命和财产安全带来了严重威胁。 因此&#xff0c;可燃气体报警检测系统的应用变得尤为重要。它不仅能够实时监测环境中的可燃气体浓度&#xff0c;还能在发现异常情况时及时报警&#xf…

[leetcode hot150]第五十七题,插入区间

题目&#xff1a; 给你一个 无重叠的 &#xff0c;按照区间起始端点排序的区间列表 intervals&#xff0c;其中 intervals[i] [starti, endi] 表示第 i 个区间的开始和结束&#xff0c;并且 intervals 按照 starti 升序排列。同样给定一个区间 newInterval [start, end] 表示…

【机器学习】——驱动智能制造的青春力量,优化生产、预见故障、提升质量

目录 一.优化生产流程 1.1 数据收集 1.2 数据预处理 1.3 模型训练 1.4 优化建议 1.5 示例代码 二.预测设备故障 2.1 数据收集 2.2 数据预处理 2.3 模型训练 2.4 故障预测 2.5 示例代码 三.提升产品质量 3.1 数据收集 3.2 数据预处理 3.3 模型训练 3.4 质量提升…