Java Web 安全之程式碼審計
資訊保安的75%發生在Web應用而非網路層。本文內容主要以Java Web安全-程式碼審計為中心展開。
一、JavaWeb 安全基礎
1. 何為程式碼審計?
通俗的說Java程式碼審計就是通過審計Java程式碼來發現Java應用程式自身中存在的安全問題,由於Java本身是編譯型語言,所以即便只有class檔案的情況下我們依然可以對Java程式碼進行審計。對於未編譯的Java原始碼檔案我們可以直接閱讀其原始碼,而對於已編譯的class或者jar檔案我們就需要進行反編譯了。
Java程式碼審計其本身並無多大難度,只要熟練掌握審計流程和常見的漏洞審計技巧就可比較輕鬆的完成程式碼審計工作了。但是Java程式碼審計的方式絕不僅僅是使用某款審計工具掃描一下整個Java專案程式碼就可以完事了,一些業務邏輯和程式架構複雜的系統程式碼審計就非常需要審計者掌握一定的Java基礎並具有具有一定的審計經驗、技巧甚至是對Java架構有較深入的理解和實踐才能更加深入的發現安全問題。
本文將分為多章節來講述Java程式碼審計需要掌握的前置知識以及Java程式碼審計的流程、技巧。
2. 準備環境和輔助工具
在開始Java程式碼審計前請自行安裝好Java開發環境,建議使用MacOS、Ubuntu作業系統。
所謂“工欲善其事,必先利其器”,合理的使用一些輔助工具可以極大的提供我們的程式碼審計的效率和質量!
強烈推薦下列輔助工具:
1.Jetbrains IDEA(IDE) 2.Sublime text(文字編輯器) 3.JD-GUI(反編譯) 4.Fernflower(反編譯) 5.Bytecode-Viewer 6.Eclipse(IDE) 7.NetBeans(IDE)
二、反編譯技巧
在滲透測試的時候需要審計的程式碼通常是class檔案或者jar包,那麼我們應該如何審計呢?讓我們先來學習一下什麼是Java原始碼和位元組碼。
1. Java類編譯與反編譯基礎
簡單的說Java原始碼就是未經編譯的.java檔案,我們可以很輕鬆的閱讀其中的程式碼邏輯,而位元組碼.class檔案則是.java檔案經過編譯之後產生的位元組碼檔案,因為.class檔案是編譯後的二進位制檔案所以我們是無法直接閱讀的,只能通過反編譯工具將二進位制檔案轉換成java程式碼或者ASM程式碼。
示例程式碼Test.java:
/** * @author yz */ public class Test { public static void hello() { System.out.println("Hello~"); } public void world() { System.out.println("World!"); } public static void main(String[] args) { hello(); } }
Test.java編譯執行流程:
Test.java 原始碼、位元組碼
由於class檔案的可讀性較差,通常我們需要使用Java反編譯工具來反編譯程式碼。我們通常會使用到JD-GUI、IDEA Fernflower外掛、Bytecode-Viewer、Fernflower、JAD、JBE、JEB 等工具來反編譯class。
其中JD-GUI可能是目前反編譯中使用的最多的工具了,但是個人覺得JD-GUI的反編譯能力遠不如經過IDEA(IDEA應該是使用的改版後的Fernflower),因為IDEA預設支援對jar和class的反編譯,所以我個人強烈推薦使用IDEA來反編譯class程式碼。
當然,反編譯工具很多時候也不是萬能的,JD-GUI經常遇到無法反編譯或反編譯過程中程式直接崩潰的情況,遇到這類情況我們通常可以使用IDEA反編譯試試,如果IDEA也無法反編譯可以使用JBE來載入class檔案讀取程式的位元組碼,如果JBE仍無法讀取類資訊還可以使用JDK自帶的javap命令來讀取class類位元組碼,如果上訴所有的方法都無法反編譯,那麼恐怕是這個類本身就存在無法編譯問題要麼可能就是類檔案被加密處理過。可能你會說java編譯的class不是說不可以加密嗎?沒錯,這裡所說的加密其實是為了保護編譯後的class程式碼不可反編譯,通過實現自定義ClassLoader來loadClass加密後的類方式而已,這種加密方式曾在實戰中也有遇到。
2. 反編譯整個Jar技巧
通常我們在某些特殊的場景下拿到的只是jar檔案,那麼我們應該如何反編譯整個jar包的class檔案呢?
2.1. Fernflower
Fernflower可以很輕鬆的實現jar的完整反編譯,執行如下命令即可: java -jar fernflower.jar jarToDecompile.jar decomp/ 其中jarToDecompile.jar是需要反編譯的jar檔案,decomp是反編譯後的class檔案所存放的目錄。需要注意的是Fernflower如遇無法反編譯的情況可能會生成空的java檔案!
2.2. JD-GUI
JD-GUI是一個帶GUI的反編譯工具,在JD-GUI的選單中點選File→Save All Sources即可反編譯jar。
2.3. IDEA
IDEA預設就支援jar包反編譯,同時還支援class檔名(⇧⌘F)、類方法名稱(⇧⌘O)搜尋。
2.4. Bytecode-Viewer
FernFlower提供了GUI版本Bytecode-Viewer,Bytecode-Viewer提供了直接反編譯的class、jar、zip、apk、dex功能,直接拖拽jar就可以直接對整個jar進行反編譯了。
2.5. Find命令
find命令並不能支援Java反編譯,但是find命令可以非常方便的搜尋經過編譯後的二進位制檔案中的內容,所以有的時候使用find命令通常是最簡單實用的,直接解壓jar包然後使用find命令搜尋: find ./ -type f -name “*.class” |xargs grep XXXX 即可搞定。
2.6 使用Find命令和Fernflower實現批量反編譯jar
當我們只有專案war包且原始碼經過打包後釋出到WEB-INF/lib的情況下,我們不得不去找出待審計原始碼的具體jar檔案並反編譯。遇到這種情況我們可以巧妙的使用find命令來反編譯所有目標的jar包。
這裡以jcms的一個非常老版本為例,jcms最終給客戶部署的war包中原始碼並不是在WEB-INF/classes目錄下,而是將整個jcms系統按模組打包成了多個jar包放在了WEB-INF/lib目錄下。我們可以通過搜尋com.hanweb包名稱來找出所有jar中包含了jcms的檔案並通過Fernflower來反編譯。
java -jar /Users/yz/Desktop/javaweb-decomplier/javaweb-decomplier.jar -dgs=1 $(find /Users/yz/Desktop/jcms/WEB-INF/lib/ -type f -name "*.jar" |xargs grep "com.hanweb" |awk '{print $3}') /Users/yz/jcms-decomplier
依賴的jar: javaweb-decomplier、Intellij java-decompiler。
執行上面的命令後會在jcms-decomplier目錄下看到所有的jar已經被Fernflower反編譯了。
3. IntelliJ IDEA 推薦
IntelliJ IDEA是Jetbrains出品的一款非常強大的Java IDE,IDEA提供了強大的程式碼搜尋、近乎完美的反編譯、動態除錯等功能可以最大程度的輔助我們程式碼審計。
不可以否認,與IDEA相比雖然Eclipse和Netbeans也有與之類似的功能,但是在真正的實戰體驗中個人更傾向於使用IDEA,雖然曾經的我也是一個重度Eclipse開發者。
三、IDEA程式碼搜尋技巧
IDEA的搜尋快捷鍵是:⇧⌘F,使用IDEA提供的搜尋功能可以非常快速的定位漏洞點資訊。
IDEA可以通過自定義搜尋範圍來精確查詢我們需要審計的程式碼。預設搜尋的是所有的位置,不過我們可以點選紅色箭頭指向的…按鈕來細化我們的搜尋範圍。
1. 自定義範圍搜尋
自定義搜尋範圍示例:
自定義搜尋範圍後就可以在搜尋時使用自定義的配置進行範圍搜尋了,有助於我們在挖漏洞的時候縮小程式碼定位範圍。
2. 標記搜尋
搜尋快捷鍵: ⌘O,標記搜尋支援類名、方法名搜尋(包括class或jar檔案中的方法也支援搜尋)。
3. Java呼叫鏈搜尋
當我們審計程式碼的時候發現某個方法或類有漏洞時我們需要定位到漏洞的請求地址(觸發點),複雜業務系統往往會讓我們很難定位到漏洞的觸發點。藉助IDEA的方法呼叫鏈搜尋功能就可以很輕鬆的找出方法的呼叫鏈和觸發點。
選擇類或者方法名→右鍵→Find Useages或者使用快捷鍵⌥F7
四、Java Web基礎
1. Java分層思想
為了更好的管理專案我們通常會採用分層架構的方式來開發Java Web專案,分層設計的好處在於可以非常方便的分清楚包之間的業務邏輯關係。
常見的JavaWeb專案分層:
檢視層(View 檢視)
控制層(Controller、Action 控制層)
服務層(Service)
業務邏輯層BO(business object)
實體層(entity 實體物件、VO(value object) 值物件 、模型層(bean)。
持久層(dao- Data Access Object 資料訪問層、PO(persistant object) 持久物件)
基於Java分層架構的示例專案:
2. Java模組化開發
如今的較為大型的Java Web專案通常都採用了模組化方式開發,藉助於Maven、Gradle依賴管理工具,Java可以非常輕鬆的完成模組化開發。除此之外使用OSGi(Open Service Gateway Initiative 可實現模組熱部署)技術開發來Java動態模組化系統也是較為常見的。
採用模組化開發也會給我們做程式碼審計帶來一定的難度,因為需要在更多的依賴庫中去尋找需要我們審計的程式碼。
使用Maven開發的JavaWeb專案示例:
3. 什麼是Servlet?
Servlet是在Java Web容器上執行的小程式,通常我們用Servlet來處理一些較為複雜的伺服器端的業務邏輯。值得注意的是在Servlet3.0之後(Tomcat7+)可以使用註解方式配置Servlet了。
基於註解的Servlet
Servlet3.0之前的版本都需要在web.xml中配置,Servlet是兩對標籤,由
基於配置實現的Servlet
HttpServlet類
實現一個Servlet很簡單,只需要繼承javax.servlet.http.HttpServlet類並重寫doXXX方法或者service方法就可以了,其中需要注意的是重寫HttpServlet類的service方法可以獲取到上述七種Http請求方法的請求。
4. JSP、Servlet之間的關係
JSP、JSPX檔案是可以直接被Java容器直接解析的動態指令碼,jsp和其他指令碼語言無異,不但可以用於頁面資料展示,也可以用來處理後端業務邏輯。
從本質上說JSP就是一個Servlet,因為jsp檔案最終會被編譯成class檔案,而這個Class檔案實際上就是一個特殊的Servlet。
JSP檔案會被編譯成一個java類檔案,如index.jsp在Tomcat中Jasper編譯後會生成index_jsp.java和index_jsp.class兩個檔案。而index_jsp.java 繼承於HttpJspBase類,HttpJspBase是一個實現了HttpJspPage介面並繼承了HttpServlet的標準的Servlet, jspService方法其實是HttpJspPage介面方法,類似於Servlet中的service方法,這裡的 jspService方法其實就是HttpJspBase的service方法呼叫。
5. 什麼是Filter
Filter是JavaWeb中的過濾器,用於過濾URL請求。通過Filter我們可以實現URL請求資源許可權驗證、使用者登陸檢測等功能。Filter是一個介面,實現一個Filter只需要重寫init、doFilter、destroy方法即可,其中過濾邏輯都在doFilter方法中實現。
Filter和Servlet一樣是Java Web中最為核心的部分,使用Servlet和Filter可以實現後端介面開發和許可權控制,當然使用Filter機制也可以實現MVC框架,Struts2實現機制就是使用的Filter。
Filter的配置類似於Servlet,由
6. Filter和Servlet的總結
對於基於Filter和Servlet實現的簡單架構專案,程式碼審計的重心集中於找出所有的Filter分析其過濾規則,找出是否有做全域性的安全過濾、敏感的URL地址是否有做許可權校驗並嘗試繞過Filter過濾。第二點則是找出所有的Servlet,分析Servlet的業務是否存在安全問題,如果存在安全問題是否可以利用?是否有許可權訪問?利用時是否被Filter過濾等問題,切勿看到Servlet、JSP中的漏洞點就妄下定論,不要忘了Servlet前面很有可能存在一個全域性安全過濾的Filter。
Filter和Servlet都是Java Web提供的API,簡單的總結了下有如下共同點。
1.Filter和Servlet都需要在web.xml或註解(@WebFilter、@WebServlet)中配置,而且配置方式是非常的相似的; 2.Filter和Servlet都可以處理來自Http請求的請求,兩者都有request、response物件; 3.Filter和Servlet基礎概念不一樣,Servlet定義是容器端小程式,用於直接處理後端業務邏輯,而Filter的思想則是實現對Java Web請求資源的攔截過濾; 4.Filter和Servlet雖然概念上不太一樣,但都可以處理Http請求,都可以用來實現MVC控制器(Struts2和Spring框架分別基於Filter和Servlet技術實現的); 5.一般來說Filter通常配置在MVC、Servlet和JSP請求前面,常用於後端許可權控制、統一的Http請求引數過濾(統一的XSS、SQL注入、Struts2命令執行等攻擊檢測處理)處理,其核心主要體現在請求過濾上,而Servlet更多的是用來處理後端業務請求上
7. 初識JavaWeb MVC框架
傳統的開發存在結構混亂易用性差耦合度高可維護性差等多種問題,為了解決這些毛病分層思想和MVC框架就出現了。MVC即模型(Model)、檢視(View)、控制器(Controller), MVC模式的目的就是實現Web系統的職能分工。
截至2018年底,絕大多數的新專案都已然改為了基於Spring Boot的Spring MVC實現,也就是說曾經站在JavaWeb MVC最巔峰的Struts2框架已經逐漸隕落。
7.1 Spring MVC 控制器
在Spring進入了3.0時代,使用Java註解的方式也逐漸的流行了起來,曾經寫一個Spring的控制器我們通常要在xml中宣告Spring bean並配置處理的URL,而在新時代的Spring專案中我們通常用Spring MVC註解就可以輕鬆完成Spring MVC的配置了。
一個基於Spring 註解配置的控制器:
package org.javaweb.codereview.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { @RequestMapping("/index.php") public String index() { return "/index.html"; } }
Spring Controller註解:
@Controller @RestController @RepositoryRestController
Spring MVC請求配置註解:
@RequestMapping @GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping
Spring MVC除了上述6種Http請求處理註解以外還有Spring Data JPA Rest提供的特殊的@RepositoryRestResource註解,@RepositoryRestResource是基於Spring Data JPA REST庫實現的,Spring Data JPA REST提供的API可支援通過JPA查詢資料並處理Http請求服務。
基於XML配置的Spring MVC
對於一些老舊的專案可能還保留了一些基於xml配置的方式Spring MVC專案,這裡只簡單的介紹下如何配置不做過多的描述。基於配置方式的控制器一般是在Controller類中實現了Spring的org.springframework.web.servlet.mvc.Controller介面的handleRequest方法(當然還有其他途徑,如:AbstractCommandController和SimpleFormController但都已經過時了)。
TestController.java示例程式碼:
package org.javaweb.codereview.controller; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author yz */ public class TestController implements Controller { @Override public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { ModelAndView mv = new ModelAndView(); mv.setViewName("index"); return mv; } }
XML配置具體的bean
<bean name="/test.do" class="org.javaweb.codereview.controller.TestController"/>
7.2 Struts2控制器
Struts2主要的開發模式是基於xml配置,在struts.xml中配置Action地址和對應的處理類。
不過Struts2(2.1.6版本開始)也可以使用struts2-convention-plugin外掛來實現基於註解方式的配置。
需要注意的是Struts2的引數是可以通過get/set方法傳入的,如上圖TestActionAnnotation類的username變數是可以直接在Http請求中的URL傳入的。
7.3 快速找出Http請求請求URL
程式碼審計中我們可以選擇優先從Controller、Servlet和JSP中入手,也可以選擇從漏洞點反向推出Http請求的入口地址,這裡將講解下如何快速找到這些請求入口,因為Struts2和Spring MVC的原理比較接近,所以本節只以Spring MVC為例。
7.3.1 查詢Spring MVC所有的控制器
如果有原始碼的情況下可以使用find命令或者IDEA的全域性搜尋功能即可快速搜尋到所有的控制器,如果只有class檔案的情況下可以使用find命令:
find ~/cms/ -type f -name "*.class" |xargs grep -E "Controller|@RestController|RepositoryRestController"
7.3.2 查詢所有的請求處理URL
查詢請求處理URL的方式同理,使用如下find命令查詢所有class中的請求處理註解:
find ~/cms/ -type f -name "*.class" |xargs grep -E "RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|RepositoryRestResource"
7.4 Spring MVC和Struts2控制器小結
這一小節我們只是簡單的介紹下Spring MVC和Struts2的控制器,在後面的框架服務章節將會詳細介紹。至於如何去快速定位Struts2的action請自行參考Spring MVC的Controller查詢方式這裡不再講解。
五、Java語言的動態性
Java語言動態性一直以來都比較差,並不像PHP那樣靈活。在Java中的動態性往往需要使用一些曲折的方式來實現.這裡簡單列舉了Java十餘種動態性相關技術並總結部分技術實現安全問題。
1.Java反射機制 2.MethodHandle 3.JDK動態代理 4.使用JVM上的動態語言(如:Groovy、JRuby、Jython) 5.表示式庫(如:OGNL、MVEL、SpEL、EL) 6.JSP、JSPX、Quercus(Resin容器提供了PHP5支援) 7.位元組碼庫(如:Asm、Javassist、Cglib、BCEL) 8.ScriptEngineManager(指令碼引擎)。 9.動態編譯(如:JDT、JavaCompiler) 10.ClassLoader、URLClassLoader 11.模版引擎(如:Freemarker、Velocity) 12.序列化、反序列化(包含Java 物件序列化、XML、JSON等) 13.JNI、JNA(Java呼叫C/C++) 14.OSGi(Open Service Gateway Initiative) 15.RMI(Java遠端方法呼叫,基於物件序列化機制實現) 16.WebService 17.JDWP(Java Platform Debugger Architecture Java除錯協議) 18.JMX(Java Management Extensions)
1. Java反射機制特性
Java反射機制可以無視類方法、變數訪問許可權修飾符,可以呼叫任何類的任意方法、訪問並修改成員變數值。也就是說只要發現一處Java反射呼叫漏洞幾乎就可以為所欲為了。當然前提可能需要你能控制反射的類名、方法名和引數。
一行程式碼即可實現反射呼叫Runtime執行本地命令:
Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "whoami")
獲取一個類的物件(如Runtime類)我們一般會採用如下幾種方式:
1.Class.forName(“java.lang.Runtime”)、””.getClass().forName(“java.lang.Runtime”) 2.Runtime.class 3.ClassLoader.getSystemClassLoader().loadClass(“java.lang.Runtime”)
Java反射獲取類方法有兩種方式:
1.getMethod(xxx),getMethods() 2.getDeclaredMethod(xxx)、getDeclaredMethods()。
區別在於getMethod會返回當前類和父類的所有public方法,而getDeclaredMethod返回的是當前的所有方法。
Java反射獲取類成員變數有兩種方式:
1.getField(xxx)、getFields() 2.getDeclaredField(xxx)、getDeclaredFields()
getField和getDeclaredField區別同上,如果想要呼叫private修飾的Field或者Method只需要設定下setAccessible為true就可以了,如:xxxMethod.setAccessible(true)。
Java的大部分框架都是採用了反射機制來實現的(如:Spring MVC、ORM框架等),所以我們不得不掌握Java反射機制來提升我們的程式碼審計能力。
Java反射機制實現無關鍵字執行命令
import java.io.InputStream; import java.lang.reflect.Method; import java.util.Scanner; /** * @author yz */ public class ReflectionTest { public static void exec() { try { System.out.println(Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "curl -i localhost:8000")); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { try { String str = "whoami"; // java.lang.Runtime String runtime = new String(new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101}); // Runtime.class Class<?> c = Class.forName(runtime); // 獲取getRuntime方法,Runtime.getRuntime() Method m1 = c.getMethod(new String(new byte[]{103, 101, 116, 82, 117, 110, 116, 105, 109, 101})); // 獲取Runtime的exec方法,rt.exec(xxx) Method m2 = c.getMethod(new String(new byte[]{101, 120, 101, 99}), String.class); // Runtime.getRuntime().exec(str) Object obj2 = m2.invoke(m1.invoke(null), str); // 獲取命令執行結果Process類的getInputStream()方法 Method m = obj2.getClass().getMethod(new String(new byte[]{103, 101, 116, 73, 110, 112, 117, 116, 83, 116, 114, 101, 97, 109})); m.setAccessible(true); // process.getInputStream() InputStream in = (InputStream) m.invoke(obj2, new Object[]{}); // 輸出InputStream內容到 Scanner scanner = new Scanner(in).useDelimiter("\\A"); System.out.println(scanner.hasNext() ? scanner.next() : ""); } catch (Throwable t) { t.printStackTrace(); } } }
2. JDK7+ MethodHandle
JDK7開始Java提供了MethodHandle可以非常方便的訪問和呼叫類方法,MethodHandle的能力和Java反射機制相似,但效率卻遠高出Java反射機制,但MethodHandle也並不是那麼完美的,缺點是MethodHandle必須要求JDK版本大於等於1.7,MethodHandle也無法像反射那樣呼叫私有方法和變數。
參考:通過程式碼簡單介紹JDK 7的MethodHandle,並與.NET的委託對比。
基於MethodHandle實現的呼叫Runtime執行系統命令
import java.io.InputStream; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.Scanner; /** * @author yz */ public class MethodHandlesTest { public static void main(String[] args) { try { Stringstr= "ping p2j.cn -c 1"; ClassruntimeClass = Runtime.class; MethodHandles.Lookup lookup= MethodHandles.lookup(); // Runtime rt = Runtime.getRuntime() MethodHandle methodHandle = lookup.findStatic( runtimeClass, "getRuntime", MethodType.methodType(runtimeClass) ); // 獲取Runtime的exec方法 MethodHandle execMethod = lookup.findVirtual( runtimeClass, "exec", MethodType.methodType(Process.class, new Class[]{ String.class }) ); // 獲取Process的getInputStream方法 MethodHandle inputStreamMethod = lookup.findVirtual( Process.class, "getInputStream", MethodType.methodType(InputStream.class) ); // 呼叫Runtime.getRuntime().exec(xxx).getInputStream() InputStream in = (InputStream) inputStreamMethod.invoke( execMethod.invoke(methodHandle.invoke(), str) ); // 輸出InputStream內容到 Scanner scanner = new Scanner(in).useDelimiter("\\A"); System.out.println(scanner.hasNext() ? scanner.next() : ""); } catch (Throwable t) { t.printStackTrace(); } } }
六、Java程式碼審計-Checklist
通常我喜歡把程式碼審計的方向分為業務層安全問題、程式碼實現和服務架構安全問題,。
1. 業務層安全常見問題
業務層的安全問題集中在業務邏輯和越權問題上,我們在程式碼審計的過程中儘可能的去理解系統的業務流程以便於發現隱藏在業務中的安全問題。
1.1 業務層中常見的安全問題Checklist
1.使用者登陸、使用者註冊、找回密碼等功能中密碼資訊未採用加密演算法。 2.使用者登陸、使用者註冊、找回密碼等功能中未採用驗證碼或驗證碼未做安全重新整理(未重新整理Session中驗證碼的值)導致的撞庫、密碼爆破漏洞。 3.找回密碼邏輯問題(如:可直接跳過驗證邏輯直接發包修改)。 4.手機、郵箱驗證、找回密碼等涉及到動態驗證碼等功能未限制驗證碼失敗次數、驗證碼有效期、驗證碼長度過短導致的驗證碼爆破問題。 5.充值、付款等功能呼叫了第三方支付系統未正確校驗介面(如:1分錢買IPhone X)。 6.後端採用了ORM框架更新操作時因處理不當導致可以更新使用者表任意欄位(如:使用者註冊、使用者個人資料修改時可以直接建立管理員賬號或其他越權修改操作)。 7.後端採用了ORM框架查詢資料時因處理不當導致可以接收任何引數導致的越權查詢、敏感資訊查詢等安全問題。 8.使用者中心轉賬、修改個人資料、密碼、退出登陸等功能未採用驗證碼或Token機制導致存在CSRF漏洞。 9.後端服務過於信任前端,重要的引數和業務邏輯只做了前端驗證(如:檔案上傳功能的檔案型別只在JS中驗證、後端不從Session中獲取使用者ID、使用者名稱而是直接接收客戶端請求的引數導致的越權問題)。 10.使用者身份資訊認證邏輯問題(如:後臺系統自動登陸時直接讀取Cookie中的使用者名稱、使用者許可權不做驗證)。 11.重要介面採用ID自增、ID可預測並且雲端未驗證引數有效性導致的越權訪問、資訊洩漏問題(如:任意使用者訂單越權訪問)。 12.條件競爭問題,某些關鍵業務(如:使用者轉賬)不支援併發、分散式部署時不支援鎖的操作等。 13.重要介面未限制請求頻率,導致簡訊、郵件、電話、私信等資訊轟炸。 14.敏感資訊未保護,如Cookie中直接儲存使用者密碼等重要資訊。 15.弱加密演算法、弱金鑰,如勿把Base64當成資料加密方式、重要演算法金鑰採用弱口令如123456。 16.後端無異常處理機制、未自定義50X錯誤頁面,伺服器異常導致敏感資訊洩漏(如:資料庫資訊、網站絕對路徑等)。 17.使用DWR框架開發時前後端不分漏洞(如:DWR直接呼叫資料庫資訊把使用者登陸邏輯直接放到了前端來做)。
2. 程式碼實現常見問題
程式碼審計的核心是尋找程式碼中程式實現的安全問題,通常我們會把程式碼審計的重心放在SQL注入、檔案上傳、命令執行、任意檔案讀寫等直接威脅到伺服器安全的漏洞上,因為這一類的漏洞殺傷力極大也是最為致命的。
2.1 程式碼實現中常見的安全問題Checklist
1.任意檔案讀寫(檔案上傳、檔案下載)、檔案遍歷、檔案刪除、檔案重新命名等漏洞 2.SQL注入漏洞 3.XXE(XML實體注入攻擊) 4.表示式執行(SpEL、OGNL、MVEL2、EL等) 5.系統命令執行漏洞(ProcessBuilder) 6.反序列化攻擊(ObjectInputStream、JSON、XML等) 7.Java反射攻擊 8.SSRF攻擊
2.1.1 Java 檔名空位元組截斷漏洞(%00 Null Bytes)
空位元組截斷漏洞漏洞在諸多程式語言中都存在,究其根本是Java在呼叫檔案系統(C實現)讀寫檔案時導致的漏洞,並不是Java本身的安全問題。不過好在高版本的JDK在處理檔案時已經把空位元組檔名進行了安全檢測處理。
2013年9月10日釋出的Java SE 7 Update 40修復了空位元組截斷這個歷史遺留問題。此次更新在java.io.File類中添加了一個isInvalid方法,專門檢測檔名中是否包含了空位元組。
修復的JDK版本所有跟檔名相關的操作都呼叫了isInvalid方法檢測,防止空位元組截斷。
修復前(Java SE 7 Update 25)和修復後(Java SE 7 Update 40)的對比會發現Java SE 7 Update 25中的java.io.File類中並未新增\u0000的檢測。
受空位元組截斷影響的JDK版本範圍:JDK<1.7.40,單是JDK7於2011年07月28日釋出至2013年09月10日發表Java SE 7 Update 40這兩年多期間受影響的就有16個版本,值得注意的是JDK1.6雖然JDK7修復之後釋出了數十個版本,但是並沒有任何一個版本修復過這個問題,而JDK8釋出時間在JDK7修復以後所以並不受此漏洞影響。
參考:
JDK-8014846 : File and other classes in java.io do not handle embedded nulls properly。
維基百科-Java版本歷史
Oracle Java 歷史版本下載
2.1.2 測試Java寫檔案截斷測試
測試類FileNullBytes.java:
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; /** * @author yz */ public class FileNullBytes { public static void main(String[] args) { try { String fileName = "/tmp/null-bytes.txt\u0000.jpg"; FileOutputStream fos = new FileOutputStream(new File(fileName)); fos.write("Test".getBytes()); fos.flush(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } }
使用JDK1.7.0.25測試成功截斷檔名:
使用JDK1.7.0.80測試寫檔案截斷時丟擲java.io.FileNotFoundException: Invalid file path異常:
空位元組截斷利用場景
Java空位元組截斷利用場景最常見的利用場景就是檔案上傳時後端使用了endWith、正則使用如:.(jpg|png|gif)$驗證檔名字尾且檔名最終原樣儲存,同理檔案刪除(delete)、獲取檔案路徑(getCanonicalPath)、建立檔案(createNewFile)、檔案重新命名(renameTo)等方法也可適用。
空位元組截斷修復方案
最簡單直接的方式就是升級JDK,如果擔心升級JDK出現相容性問題可在檔案操作時檢測下檔名中是否包含空位元組,如JDK的修復方式:fileName.indexOf(‘\u0000’)即可。
2.1.2 任意檔案讀取漏洞
任意檔案讀取漏洞即因為沒有驗證請求的資原始檔是否合法導致的,此類漏洞在Java中有著較高的機率出現,任意檔案讀取漏洞看似很簡單,但是在這個問題上翻車的有不乏一些知名的中介軟體:Weblogic、Tomcat、Resin又或者是主流MVC框架:Spring MVC、Struts2。所以在審計檔案讀取功能的時候要非常仔細,或許很容易就會有意想不到的收穫!
任意檔案讀取示例程式碼file-read.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.io.ByteArrayOutputStream" %> <%@ page import="java.io.File" %> <%@ page import="java.io.FileInputStream" %> <% File file = new File(request.getParameter("path")); FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int a = -1; while ((a = fis.read(b)) != -1) { baos.write(b, 0, a); } out.write("<pre>" + new String(baos.toByteArray()) + "</pre>"); fis.close(); %>
訪問file-read.jsp檔案即可讀取任意檔案:http://localhost:8080/file/file-read.jsp?path=/etc/passwd
快速發現這類漏洞得方式其實也是非常簡單的,在IDEA中的專案中重點搜下如下檔案讀取的類。
1.JDK原始的java.io.FileInputStream類 2.JDK原始的java.io.RandomAccessFile類 3.Apache Commons IO提供的org.apache.commons.io.FileUtils類 4.JDK1.7新增的基於NIO非阻塞非同步讀取檔案的java.nio.channels.AsynchronousFileChannel類。 5.JDK1.7新增的基於NIO讀取檔案的java.nio.file.Files類。常用方法如:Files.readAllBytes、Files.readAllLines
如果仍沒有什麼發現可以搜尋一下FileUtil很有可能使用者會封裝檔案操作的工具類。
Java WebSevice
Web Service是一種基於SOAP協議實現的跨語言Web服務呼叫,在Java中Web Service有如下技術實現:Oracle JWS、Apache Axis1、2、XFire、Apache CXF、JBossWS。
Axis1.4 配置
web.xml配置Axis1.4
配置server-config.wsdd檔案註冊Web Service服務類和方法:
FileService類,提供了檔案讀寫介面:
使用IDEA建立Web Service專案預設會建立管理Web Service的API:/servlet/AxisServlet、/services、SOAPMonitor、/servlet/AdminServlet,*.jws以及用監控Web Service的埠5001或5101。
訪問Web Service的FileService服務加上?wsdl引數可以看到FileService提供的服務方法和具體的引數資訊。
使用SOAP-UI呼叫Web Service介面示例:
需要注意的是Web Service也是可以設定授權認證的,如實現了WS-Security的WSS4J。
使用IDEA根據wsdl生成Web Service客戶端程式碼:
設定wsdl地址、包名:
新建FileServiceTest類測試介面呼叫:
package org.javaweb.codereview.axis.client; import java.net.URL; /** * 檔案Web Service服務測試 * * @author yz */ public class FileServiceTest { public static void main(String[] args) { try { FileServiceServicefileService= new FileServiceServiceLocator(); URLwebServiceUrl = new URL("http://localhost:8080/services/FileService"); FileServiceSoapBindingStub soapService= new FileServiceSoapBindingStub(webServiceUrl, fileService); String content = soapService.readFile("/etc/passwd"); System.out.println(content); } catch (Exception e) { e.printStackTrace(); } } }
參考:
Axis1.4框架 實現webservice伺服器和客戶端
使用IDEA根據wsdl生成WebServices客戶端程式碼-Java
axis2 利用小工具cat.aar
*本文作者:安百科技,轉載請註明來自 FreeBuf.COM