CVE-2022-39197漏洞复现


环境搭建

这里可以直接菜鸟教程上的swing的代码:

import javax.swing.*;

public class CVE extends JFrame{
    private static void createAndShowGUI() {
        // 确保一个漂亮的外观风格
        JFrame.setDefaultLookAndFeelDecorated(true);

        // 创建及设置窗口
        JFrame frame = new JFrame("HelloWorldSwing");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // 添加 "Hello World" 标签
        JLabel label = new JLabel("<html><img src=x></html>");
        frame.getContentPane().add(label);

        // 显示窗口
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        // 显示应用 GUI
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
}

漏洞原理

接下来根据大佬们的描述,关键点在swing的如下三个类中:

HTML
HTMLDocument
HTMLEditorKit

在HTML类中定义了大量的标签,根据大佬的说法定义了标签就会有标签解释器,在HTMLDocument中:

image-20221020203448455

并且这里也可以看到script标签在swing中是不支持的:(所以,网上最开始的一波script直接打xss的人直接被啪啪打脸)

image-20221020203638052

接下来是在HTMLEditorKit的HTMLFactory中的create方法,这个Factory的作用是为html生成view,在它的create方法中会对每一个标签生成一个view。根据大佬的思路,这里比较有意思的是ObjectView类:

Component decorator that implements the view interface for <object> elements.
This view will try to load the class specified by the classid attribute. If possible, the Classloader used to load the associated Document is used. This would typically be the same as the ClassLoader used to load the EditorKit. If the document's ClassLoader is null, Class.forName is used.
If the class can successfully be loaded, an attempt will be made to create an instance of it by calling Class.newInstance. An attempt will be made to narrow the instance to type java.awt.Component to display the object.
This view can also manage a set of parameters with limitations. The parameters to the <object> element are expected to be present on the associated elements attribute set as simple strings. Each bean property will be queried as a key on the AttributeSet, with the expectation that a non-null value (of type String) will be present if there was a parameter specification for the property. Reflection is used to set the parameter. Currently, this is limited to a very simple single parameter of type String.
A simple example HTML invocation is:
       <object classid="javax.swing.JLabel">
       <param name="text" value="sample text">
       </object>

这里的大概意思就是可以通过classid标签来实例化一个类,这里classid是通过createComponent来反射调用实例化一个类:

    /**
     * Create the component.  The classid is used
     * as a specification of the classname, which
     * we try to load.
     */
    protected Component createComponent() {
        AttributeSet attr = getElement().getAttributes();
        String classname = (String) attr.getAttribute(HTML.Attribute.CLASSID);
        try {
            ReflectUtil.checkPackageAccess(classname);
            Class c = Class.forName(classname, true,Thread.currentThread().
                                    getContextClassLoader());
            Object o = c.newInstance();
            if (o instanceof Component) {
                Component comp = (Component) o;
                setParameters(comp, attr);
                return comp;
            }
        } catch (Throwable e) {
            // couldn't create a component... fall through to the
            // couldn't load representation.
        }

        return getUnloadableRepresentation();
    }

然后分析setParameters函数是如何传参的:

    /**
     * Initialize this component according the KEY/VALUEs passed in
     * via the &lt;param&gt; elements in the corresponding
     * &lt;object&gt; element.
     */
    private void setParameters(Component comp, AttributeSet attr) {
        Class k = comp.getClass();
        BeanInfo bi;
        try {
            bi = Introspector.getBeanInfo(k);
        } catch (IntrospectionException ex) {
            System.err.println("introspector failed, ex: "+ex);
            return;             // quit for now
        }
        PropertyDescriptor props[] = bi.getPropertyDescriptors();
      //取出所有的属性,然后循环判断哪些属性名和我们通过标签输入的是一样的。
        for (int i=0; i < props.length; i++) {
            //      System.err.println("checking on props[i]: "+props[i].getName());
            Object v = attr.getAttribute(props[i].getName());
          //判断属性是否可写(具有setXXXX方法)并且属性是否是String
            if (v instanceof String) {
                // found a property parameter
                String value = (String) v;
                Method writer = props[i].getWriteMethod();
                if (writer == null) {
                    // read-only property. ignore
                    return;     // for now
                }
                Class[] params = writer.getParameterTypes();
                if (params.length != 1) {
                    // zero or more than one argument, ignore
                    return;     // for now
                }
                Object [] args = { value };
                try {
                    MethodUtil.invoke(writer, comp, args);
                } catch (Exception ex) {
                    System.err.println("Invocation failed");
                    // invocation code
                }
            }
        }
    }

所以这里对于classid标签的利用,总结下来就是:

1. classid传入需要实例化的类,类必须继承与Component
2. 必须有无参构造方法,貌似是因为newinstant是调用的无参构造方法
3. 必须存在一个setXXX方法的XXX属性
4. setXXX方法的传参数必须是接受一个string类型的参数

(1)这里还是可以用javax.swing.JLabel来举例子,首先是JLabel继承了Component:

image-20221021161223583

(2)这个类有一个无参构造函数:

image-20221021161325088

(3)存在一个setText方法并且有text属性,还是String类型:

image-20221021161556347

恶意类分析

这里网上的大佬是通过自己编写的脚本来实现恶意类遍历的,但是小菜狗水平有限还没写出来,后面再和大家分享,这里最后找到的利用链如下(实话实说,小菜狗没看懂大佬们是怎么通过这个.class文件找到漏洞利用链的):

org.apache.batik.swing.JSVGCanvas-->setURI

不过这里直接构造如下的payload也是会执行失败:

<?xml version="1.0" standalone="no"?>

<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
        "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg width="100%" height="100%" version="1.1"
     xmlns="http://www.w3.org/2000/svg">

    <circle cx="100" cy="50" r="40" stroke="black"
            stroke-width="2" fill="red"/>
    <script>
        java.lang.Runtime.getRuntime().exec("open -na Calculator");
    </script>

</svg>

可以看到请求是正常的:

image-20221022150016953

那么问题就是在我们构造的payload上了:

image-20221022151559035

目前就是检查出cs包中的问题是什么,但是这里很恶心的就是CS的class文件为了反调试都去掉了行号,没有行号之后就不能在方法体内部进行调试,只能调试到方法名,而且这种机制目前并没有什么好的办法解决。(所以,真的很崇拜大佬们当时能够找到这个loadScript方法,小菜狗尝试了一个下午都没有找到,泪崩!)

image-20221022152610016

这里可以看到,通过type默认获取的是text/ecmascript,但是这个是走不通的,好在这里还有一种方式是走的application/java-archive。简单理解就是可以加载一个远程的jar包:

 public void loadScripts() {
        org.apache.batik.script.Window var1 = null;
        NodeList var2 = this.document.getElementsByTagNameNS("http://www.w3.org/2000/svg", "script");
        int var3 = var2.getLength();
        if (var3 != 0) {
            for(int var4 = 0; var4 < var3; ++var4) {
                AbstractElement var5 = (AbstractElement)var2.item(var4);
                String var6 = var5.getAttributeNS((String)null, "type");
                if (var6.length() == 0) {
                    var6 = "text/ecmascript";
                }

                String var13;
                if (var6.equals("application/java-archive")) {
                    try {
                        String var24 = XLinkSupport.getXLinkHref(var5);
                        ParsedURL var25 = new ParsedURL(var5.getBaseURI(), var24);
                        this.checkCompatibleScriptURL(var6, var25);
                        URL var29 = null;

                        try {
                            var29 = new URL(this.docPURL.toString());
                        } catch (MalformedURLException var18) {
                        }

                        DocumentJarClassLoader var26 = new DocumentJarClassLoader(new URL(var25.toString()), var29);
                        URL var28 = var26.findResource("META-INF/MANIFEST.MF");
                        if (var28 != null) {
                            Manifest var30 = new Manifest(var28.openStream());
                            var13 = var30.getMainAttributes().getValue("Script-Handler");
                            if (var13 != null) {
                                ScriptHandler var35 = (ScriptHandler)var26.loadClass(var13).newInstance();
                                if (var1 == null) {
                                    var1 = this.createWindow();
                                }

                                var35.run(this.document, var1);
                            }

                            var13 = var30.getMainAttributes().getValue("SVG-Handler-Class");
                            if (var13 != null) {
                                EventListenerInitializer var36 = (EventListenerInitializer)var26.loadClass(var13).newInstance();
                                if (var1 == null) {
                                    var1 = this.createWindow();
                                }

                                var36.initializeEventListeners((SVGDocument)this.document);
                            }
                        }
                      
                      
                      ........

所以这里继续跟踪一下getXLinkHref函数,可以看到这里可以直接使用href标签:

  public static String getXLinkHref(Element var0) {
        return var0.getAttributeNS("http://www.w3.org/1999/xlink", "href");
    }

然后分析一下这里的判断条件一个有三个,第一个是:

image-20221022155153874

一直跟进到最后发现核心方法如下,这里的意思就是svg和jar的host必须一致,这里直接放在一个位置就能解决。

   public DefaultScriptSecurity(String var1, ParsedURL var2, ParsedURL var3) {
        if (var3 == null) {
            this.se = new SecurityException(Messages.formatMessage("DefaultScriptSecurity.error.cannot.access.document.url", new Object[]{var2}));
        } else {
            String var4 = var3.getHost();
            String var5 = var2.getHost();
            if (var4 != var5 && (var4 == null || !var4.equals(var5)) && !var3.equals(var2) && (var2 == null || !"data".equals(var2.getProtocol()))) {
                this.se = new SecurityException(Messages.formatMessage("DefaultScriptSecurity.error.script.from.different.url", new Object[]{var2}));
            }
        }

第二和第三个条件满足一个即可,主要就是检查配置文件,并且根据配置文件来反射条用恶意类:

image-20221022155914094

到这里,漏洞原理就基本上说完了,目前网上的frada脚本还是很多的,大家也可以参考一下。

总结

这次复现又学到了不少关于java调试,代码审计的新知识。不过缺点是还没有写出来目标java类筛选的脚本(后面不上),frada脚本编写还没有学会。

参考

最新CS RCE(CVE-2022-39197)复现心得分享

最新CS RCE曲折的复现路


文章作者: kento
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 kento !
评论
  目录