禁止JVM執行外部命令Runtime.exec–由ApacheCommonsCollections漏洞引發的思考

中介軟體小哥發表於2016-01-13

該文章來自阿里巴巴技術協會(ATA)精選集

Apache Commons Collections遠端程式碼執行漏洞

最近出來一個比較嚴重的漏洞,在使用了Apache Commons Collections的Java應用,可以遠端程式碼執行。包括最新版的WebLogic、WebSphere、JBoss、Jenkins、OpenNMS這些大名鼎鼎的Java應用。

這個漏洞的嚴重的地方在於,即使你的程式碼裡沒有使用到Apache Commons Collections裡的類,只要Java應用的Classpath裡有Apache Commons Collections的jar包,並且開放了允許Java序列化協議的埠,都可以遠端程式碼執行。

參考:

這個漏洞的演示很簡單,只要在maven依賴裡增加

        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>

再執行下面的java程式碼:

        Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class },
                        new Object[] { "getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class },
                        new Object[] { null, new Object[0] }),
                new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "calc" }) };

        Transformer transformedChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain);

        Map.Entry onlyElement = (Entry) outerMap.entrySet().iterator().next();
        onlyElement.setValue("foobar");

這個漏洞的根本問題並不是Java序列化的問題,而是Apache Commons Collections允許鏈式的任意的類函式反射呼叫。攻擊者通過允許Java序列化協議的埠,把攻擊程式碼上傳到伺服器上,再由Apache Commons Collections裡的TransformedMap來執行。

這裡不對這個漏洞多做展開,可以看上面的參考文章。

如何簡單的防止Java程式呼叫外部命令?

從這個漏洞,引發了很久之前的一個念頭:**如何簡單的防止Java程式呼叫外部命令?**

java相對來說安全性問題比較少。出現的一些問題大部分是利用反射,最終用Runtime.exec(String cmd)函式來執行外部命令的。**如果可以禁止JVM執行外部命令,未知漏洞的危害性會大大降低,可以大大提高JVM的安全性。**

換而言之,就是如何禁止Java執行Runtime.exec(String cmd)之類的函式。

在Java裡有一套Security Policy,但是實際上用的人比較少。因為配置起來太麻煩了。
參考:

從文件裡可以知道,Java裡並沒有直接禁止Rumtine.exec 函式執行的許可權。

禁止檔案執行的許可權在java.io.FilePermission裡。如果想禁止所有外部檔案執行,可以在下面的配置檔案中把execute刪除:

grant {
  permission java.io.FilePermission "<<ALL FILES>>", "read, write, delete, execute";
};

但是**Java許可權機制是白名單的**,還有一大堆的許可權要配置上去,非常複雜。
從tomcat的配置就知道了。http://tomcat.apache.org/tomcat-7.0-doc/security-manager-howto.html
所以Tomcat預設是沒有啟用Security Policy的,可以通過在命令加上-security引數來啟用。

catalina.sh start -security

那麼有沒有簡單的辦法可以在程式碼裡禁止Java執行外部命令?

研究了下,通過擴充套件SecurityManager可以簡單實現:

        SecurityManager originalSecurityManager = System.getSecurityManager();
        if (originalSecurityManager == null) {
            // 建立自己的SecurityManager
            SecurityManager sm = new SecurityManager() {
                private void check(Permission perm) {
                    // 禁止exec
                    if (perm instanceof java.io.FilePermission) {
                        String actions = perm.getActions();
                        if (actions != null && actions.contains("execute")) {
                            throw new SecurityException("execute denied!");
                        }
                    }
                    // 禁止設定新的SecurityManager,保護自己
                    if (perm instanceof java.lang.RuntimePermission) {
                        String name = perm.getName();
                        if (name != null && name.contains("setSecurityManager")) {
                            throw new SecurityException("System.setSecurityManager denied!");
                        }
                    }
                }

                @Override
                public void checkPermission(Permission perm) {
                    check(perm);
                }

                @Override
                public void checkPermission(Permission perm, Object context) {
                    check(perm);
                }
            };

            System.setSecurityManager(sm);
        }

只要在Java程式碼裡簡單加上面那一段,就可以禁止執行外部程式了。

Java序列化的本身的蛋疼之處

其實Java自身的序列化機制就比較蛋疼,可以參考Effective Java裡的。

http://allenlsy.com/NOTES-of-Effective-Java-10/

並非禁止外部程式執行,Java程式就安全了

要注意的是,如果可以任意執行Java程式碼,還可以做很多事情,比如寫入ssh金鑰,從而可以遠端登陸,參考最近的Redis未授權訪問漏洞:https://www.sebug.net/vuldb/ssvid-89715

總結

禁止JVM執行外部命令,是一個簡單有效的提高JVM安全性的辦法。但是以前沒有見到有相關的內容,有點奇怪。

可以考慮在程式碼安全掃描時,加強對Runtime.exec相關程式碼的檢測。在JVM層面對所有Runtime.exec打日誌。

有些開源庫喜歡用Runtime.exec來執行命令獲取網路卡mac等操作,個人表示相當的蛋疼,不會使用這樣子的程式碼。


相關文章