Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
Weblogic-CVE-2020-2551-To-Internet — 인터넷에서 사용할 CVE-2020-2551 POC | Kitploit
도구/GitHubGitHub/dido1960/weblogic-cve-2020-2551-to-internet
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationPayload Development
GitHubdido1960/weblogic-cve-2020-2551-to-internet

Weblogic-CVE-2020-2551-To-Internet

인터넷에서 사용할 CVE-2020-2551 POC

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유
저장소 보기
2276년 전Kitploit 검토 완료

Weblogic-CVE-2020-2551-To-Internet

인터넷에서 사용할 수 있는 CVE-2020-2551 POC

  • 테스트 POC (외부 네트워크 테스트에 사용 가능)

    python CVE-2020-2551.py [HOST] [IP]

  • codebase를 수정하여 원격 클래스 로딩 시도 (실패)

    python CVE-2020-TEST.py [HOST] [IP]

weblogic CVE-2020-2551 취약점 & 외부 네트워크 POC 구성에 대한 간단한 고찰

(安全客에 처음 게재, 원문 링크)

0x00 기본 개념

이 취약점을 학습하려면 CORBA와 RMI 같은 사전 지식이 필요하다.

간단히 요약하면 다음과 같다:

CORBA는 OMG가 제정한 분산 애플리케이션을 위한 기술 표준으로, IDL을 사용하여 크로스 언어를 지원하며 클라이언트와 서버 간에는 IIOP 프로토콜로 통신한다.

RMI는 또 다른 분산 애플리케이션 기술로, JAVA에서는 JNDI로 간단히 적용할 수 있으며 클라이언트와 서버는 JRMP 프로토콜로 통신한다. 다만 weblogic에서 RMI는 T3 프로토콜을 사용하며, 이와 관련해 이전에도 많은 취약점이 공개된 바 있다.

RMI-IIOP는 RMI와 CORBA 각각의 장점을 결합하여 IIOP 프로토콜을 통해 RMI 애플리케이션을 배포한다.

공식 문서에서도 언급한다:

RMI 서버 객체는 IIOP 프로토콜을 사용할 수 있으며, 모든 언어로 작성된 CORBA 클라이언트 객체와 통신할 수 있다.

0x01 RMI-IIOP

weblogic은 잠시 제쳐두고, RMI-IIOP 인스턴스를 작성하는 방법을 먼저 살펴보자.

클라이언트 코드는 Java의 RMI, JNDI, LDAP, JRMP, JMX, JMS에 관한 이야기 (상)의 테스트 프로젝트를 참고할 수 있다. HelloClient와 HelloServer를 직접 컴파일해도 되고, 테스트 프로젝트에 미리 컴파일된 것을 사용해도 된다.

명령줄에서 이름 서버를 시작한다 (Java 기본 제공):

root@kitploit:~
start orbd -ORBInitialPort 1050

명령줄에서 서버 HelloServer를 시작하고 원격 디버깅을 구성한다. IDEA로 원격 디버깅하는 방법은 여기 서두에 언급된 방법을 참고할 수 있다.

root@kitploit:~
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 HelloServer

물론 원격 디버깅 없이 결과를 바로 확인해도 된다. 그냥 시작하면 된다.

root@kitploit:~
java HelloServer

명령줄에서 클라이언트를 시작한다.

root@kitploit:~
Java HelloClient 

이때 계산기가 실행된다. 원격 디버깅에 성공하면 다음과 같은 호출 스택을 볼 수 있다.

EvilMessage.readObejct()에서 명령이 실행된다.

여담: weblogic의 설치와 디버깅에 관한 글

그렇다면 weblogic의 RMI-IIOP는 어떨까? Java의 RMI-IIOP에 관하여라는 글에서 weblogic RMI-IIOP의 활용에 대해 언급하고 있으며, 이를 바탕으로 몇 가지 연구를 진행했다. Using WebLogic’s RMI over IIOP에는 weblogic이 RMI-IIOP 클라이언트를 사용하는 몇 가지 방식이 소개되어 있다:

  1. 독립 RMI 클라이언트 (JNDI를 사용하며 weblogic의 어떤 것도 사용하지 않음)
  2. WebLogic 클라이언트
  3. J2EE 클라이언트
  4. CORBA/IDL 클라이언트

앞의 두 방식의 차이는 단지 JNDI_FACTORY 설정의 차이인 것으로 보인다.

이전에 weblogic T3 역직렬화를 연구할 때 weblogic에 Helloserver 애플리케이션을 배포한 적이 있으며, sayhello() 메서드를 활용할 수 있다. 두 가지 JNDI_FACTORY를 설정하여 호출해 보았고, 두 번째 JNDI_FACTORY로 sayHello() 메서드를 성공적으로 호출했다.

그래서 weblogic T3 프로토콜의 POC를 수정했다. 사실 RMI를 IIOP로 바꾸기만 한 것인데, jtaTransactionManager 익스플로잇 체인이 성공적으로 실행되어 로컬 jrmplisten으로 jrmp 요청을 보냈다.

트래픽을 살펴보면 remove() 메서드 호출 시 remove__java_lang_Object 요청이 전송되며, 트래픽에 악성 데이터가 있지만 aced 매직 헤더는 발견되지 않았다.

아마 서버에서 특별한 파싱을 거친 후 데이터를 역직렬화하는 것으로 추측된다. 호출 스택을 보면 후반부 실행 체인이 앞서 본 네이티브 RMI-IIOP의 실행 체인과 매우 유사하다. 앞에서는 CDRInputStream.read_value()에서 트리거되었고, 여기서는 weblogic의 IIOPInputStream.read_value()에서 트리거된다. (read_value 지점은 19년 발표 자료에서도 언급되었다.)

여기서 요청은 먼저 clusterableServerRef.invoke()가 처리하며, 각기 다른 invoker에 따라 this.invoker.invoke()를 호출한다. 그리고 여기서 Mejb_dj5nps_HomeImpl_WLSkel.invoke()가 호출되는데, "remove"이므로 case 6 분기로 들어가 IIOPInputStream.readObject()를 호출한다. read_value() 메서드에서 IIOPInputStream 데이터를 파싱하여 역직렬화를 트리거한다. 이것이 remove() 메서드를 이용하는 POC이다.

0x02 CVE-2020-2551

Lucifaer님의 분석 글에서는 bind() 메서드를 이용한 익스플로잇을 언급했는데, 이는 인터넷에서 주류로 사용되는 방식이기도 하다. 호출 스택을 따라가 보자.

앞서와 마찬가지로 요청은 먼저 clusterableServerRef.invoke()가 처리하고, 각기 다른 invoker에 따라 this.invoker.invoke()를 호출한다. 여기서 CobraServerRef.invoke()가 호출된 다음, _NamingContextAnyImplBase._invoke()에서 va1이 "bind_any"이므로 case 0 분기로 들어가 IIOPInputStream.read_any() 메서드를 호출한다. 이후에도 IIOPInputStream.read_value()를 호출하여 역직렬화를 트리거한다. 앞서 트래픽에서 aced 매직 헤더를 볼 수 없었던 이유는 IIOPInputStream에 별도의 파싱 방식이 있기 때문이다. IIOPInputStream의 hex-value 형식은 다음과 같으며, 여기에는 클래스 이름과 필드 정보가 포함된다:

결국 악성 클래스의 readObejct() 메서드가 호출된다.

패치를 살펴보니 2015년 T3 역직렬화 익스플로잇 패치와 같은 위치임을 발견했다.

WebLogic CVE-2020-2551 취약점 분석 테스트에서 CVE-2020-2551이 필터링하는 클래스 위치도 weblogic.iiop.Utils 클래스에 있음을 확인했다.

그러나 로컬 테스트에서 weblogic10.3.6에 2015년 패치를 적용했을 때 isBlacklisted() 함수가 트리거되지 않았다. (하지만 MsgAbbrevInputStream과 InboundMsgAbbrev에서 모두 isBlacklisted()를 호출하여 블랙리스트 검증을 수행하는데, 이상하게도...)

이번 CVE-2020-2551 패치에서는 weblogic.iiop.Utils.LoadClass()에 필터링을 수행하는 verifyclassermitted() 메서드가 추가되었다.

블랙리스트가 악성 클래스를 필터링하는데, 여기에는 JtaTransactionManager의 부모 클래스인 com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager도 포함된다. 이 클래스는 weblogic에 기본 포함되어 있어 매우 위험하다. 패치를 보면서 한 가지 생각이 떠올랐다. 606행의 검증이 LoadClass() 이후에 이루어지므로, className을 로드할 때 클래스 로딩을 통해 악성 정적 코드 블록을 실행하면 방어를 우회할 수 있지 않을까? 이에 대해서는 나중에 다시 다루겠다.

0x03 IIOP 프로토콜 모사를 통한 POC 구성

JAVA 프로그램으로 작성된 POC에는 네트워크 문제가 있다. 로컬 weblogic 서비스를 공격하는 것은 가능하지만, docker 컨테이너나 외부 네트워크 머신을 공격하는 것은 불가능하다. 이 문제를 다룬 분석 글은 다음과 같다:

Weblogic CVE-2020-2551 POC 네트워크 문제 해결 완전 가이드

WebLogic CVE-2020-2551에 대한 잡담

이제 POC를 디버깅해 보자. 앞서 설명한 remove 방식을 참고해도 되고 Y4er의 방식을 참고해도 된다. 앞의 두 글에서는 두 가지 해결 방법을 언급했다:

  • weblogic.jar 패키지를 수정하고 다시 패킹하기
  • IIOP 프로토콜 모사하기

두 가지 모두 시도해 보았다. weblogic을 다시 패킹한 후 java.lang.NoSuchMethodError: weblogic.security.subject.SubjectManager.installCESubjectManager 오류가 발생했지만 해결 방법을 찾지 못했다.

그래서 IIOP 프로토콜 모사를 시도했다. 먼저 POC에 중단점을 설정하고 디버깅했다.

new InitialContext(env) 시점에 EndPointImpl.sendReceive()에서 두 개의 패킷을 송수신하는 것을 발견했다.

LocateReply에는 IOR 정보가 포함되어 있다. 여기서 IOR이 무엇인지 이해해야 한다. IOR은 RMI-IIOP 클라이언트가 IIOP 프로토콜로 서버 객체와 상호작용할 때 IIOP 통신에 필요한 host와 port를 제공하는 역할을 한다. 또한 빨간 상자 부분의 Object_key는 서버의 서로 다른 객체를 구분하는 데 사용된다.

IIOP 프로토콜을 모사할 때 중점적으로 봐야 할 것은 Object_key이다. host와 ip는 사실상 영향이 없다. 처음 테스트를 시작했을 때는 모든 패킷을 그대로 재전송했는데, resolve_any를 보내면 location forward가 반환되었다.

GIOP 공식 문서에 따르면 location forward가 발생하면 Object_key가 변경될 수 있음을 의미하며, 요청에 따라 반환되는 Object_key가 다를 수 있다. (여기서 말하는 Object_key는 데이터 패킷의 key address이다.) 앞서 언급했듯이 이 Object_key는 IIOP 프로토콜 사용 시 어떤 객체와 통신할지를 구분하는 데 사용되며, 이 값은 LocateReply에서 동적으로 가져와야 한다.

마지막으로 여기서는 remove()를 모사하지 않고 bind() 메서드가 보내는 IIOP 요청을 모사하기로 했다. 요청이 상대적으로 적기 때문이다. 로컬에서 정상적으로 익스플로잇할 때의 데이터 패킷을 살펴보자.

LocateRequest를 보내고 data를 수신한 뒤, 정규식을 통해 LocateReply의 key address를 얻는다.

악성 jrmp 서버(rmi://...) 주소를 수동으로 설정하고 1초 간격으로 bind_any 패킷을 보낸다. 여기서 익스플로잇 체인이 보내는 jrmp 요청은 DGCClient를 사용하지 않기 때문에 JEP290의 영향을 받지 않으며, jrmplisten을 통해 익스플로잇할 수 있다.

POC는 docker 환경에서 테스트에 성공했다. vulhub의 SSRF 환경을 사용할 수 있다. IP를 호스트 머신으로 설정하면 docker가 호스트의 jrmp 요청을 성공적으로 획득한다. 구체적인 코드는 Github에 있다.

0x04 가설 검증

앞서 codebase를 이용해 원격 코드를 로드하여 탐지를 우회하는 아이디어를 언급했다. JNDI 공격을 연구해 본 사람이라면 codebase로 원격 클래스의 위치를 지정할 수 있다는 것을 알고 있을 것이다. codebase를 제어할 수 있고 프로그램이 원격 클래스 로딩을 허용한다면, 원격 악성 클래스를 로드해 정적 코드 블록의 악성 코드를 실행할 수 있다.

코드를 읽어 보면 weblogic.iiop.Utils.lodaClass()의 두 번째 인자가 codebase를 나타낸다. 이 인자는 IIOPInputStream.read_value()에서 읽히며 var8 인자이다. 1659행에서 readIndirectingRepositoryId(var8)를 호출하고, 최종적으로 weblogic.iiop.Utils.lodaClass()를 호출한다. 1644행과 1659행의 코드를 실행하려면 (va4r & 1)=1, (va4 & 6)=2가 필요하므로 var4의 값은 3이다.

readIndirectingRepositoryId에서 getClassFromId까지의 호출 스택이며, 마지막에 304행의 loadclass()가 실행된다.

bind_any 데이터 패킷을 살펴보면, 사실 GIOP Header와 GIOP Request를 조합하여 만든다. GIOP Request에는 key address(LocateReply와 일치), ServiceContextList, stub_data가 포함된다. var4의 값은 stub_data의 \x7f\xff\xff\x02이므로 (va4r & 1)=0, (va4 & 6)=2가 되어 1644행의 codebase 설정 코드는 실행되지 않는다.

우리는 아래 첫 번째 박스의 \x7f\xff\xff\x02를 \x00\x00\x00\x03으로 수정하고, 두 번째 박스에 codebase 길이와 값 정보를 추가했다. 구체적인 코드는 Github에 있다. 보면 알 수 있듯이 정렬(alignment) 작업도 수행했다. 이것이 함정인데, 이후 클래스 정보를 읽기 전에 다음 바이트 위치가 4의 배수인지 확인하기 때문이다. 4의 배수가 아니면 일부 바이트를 무시한다. 예를 들어 다음 바이트 위치가 1이면 3바이트를 무시하고 위치 4의 바이트부터 읽는다. 이 위치는 전체 bind_any 패킷 기준이며, 바이트가 4의 배수가 아니면 0으로 채운다.

또 하나의 문제가 있다. 아까 readIndirectingRepositoryId에서 getClassFromId까지의 호출 스택을 보면 중간에 findClassInfo() 함수를 거친다. 여기서 어떤 클래스가 이미 로드된 적이 있으면 클래스 ID 정보가 저장되고, findClassInfo() 호출 시 클래스 정보를 바로 반환하여 weblogic.iiop.Utils.getClassFromID() 함수로 들어가지 않는다.

그래서 테스트할 때마다 클래스 이름을 바꿔야 한다.

어쨌든 마지막에는 IIOP 프로토콜 모사를 통해 codebase 값을 수정하고 weblogic.iiop.Utils.getClassFromId() 함수를 실행하는 데 성공했다.

불행하게도 RMIURLClassFinder를 가져올 때 NULL이 반환되었고, RMIEnvironment.getEnvironment().isNetworkClassLoadingEnabled() 함수는 false를 반환했다.

원인은 ServerMBeanImpl의 _NetworkClassLoadingEnable 파라미터 값이 False이기 때문이다.

이 파라미터가 어느 weblogic 구성 파일에서 설정되었는지 확인하고 싶었지만 찾지 못했다.

요약

사실 이 취약점을 공부하면서 Java 역직렬화, RMI, JNDI 등 많은 사전 지식이 필요하다는 것을 깨달았다. 관련 학습은 이 글 모음을 참고하면 된다. 마지막에 codebase를 수정한 익스플로잇은 실패했지만, 얻은 것이 많았다.

도구 다운로드