您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何分析Java Web安全中的代碼審計,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
通俗的說Java代碼審計就是通過審計Java代碼來發現Java應用程序自身中存在的安全問題,由于Java本身是編譯型語言,所以即便只有class文件的情況下我們依然可以對Java代碼進行審計。對于未編譯的Java源代碼文件我們可以直接閱讀其源碼,而對于已編譯的class或者jar文件我們就需要進行反編譯了。
Java代碼審計其本身并無多大難度,只要熟練掌握審計流程和常見的漏洞審計技巧就可比較輕松的完成代碼審計工作了。但是Java代碼審計的方式絕不僅僅是使用某款審計工具掃描一下整個Java項目代碼就可以完事了,一些業務邏輯和程序架構復雜的系統代碼審計就非常需要審計者掌握一定的Java基礎并具有具有一定的審計經驗、技巧甚至是對Java架構有較深入的理解和實踐才能更加深入的發現安全問題。
本文將分為多章節來講述Java代碼審計需要掌握的前置知識以及Java代碼審計的流程、技巧。
在開始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源碼和字節碼。
簡單的說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加密后的類方式而已,這種加密方式曾在實戰中也有遇到。
通常我們在某些特殊的場景下拿到的只是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反編譯了。
IntelliJ IDEA是Jetbrains出品的一款非常強大的Java IDE,IDEA提供了強大的代碼搜索、近乎完美的反編譯、動態調試等功能可以最大程度的輔助我們代碼審計。
不可以否認,與IDEA相比雖然Eclipse和Netbeans也有與之類似的功能,但是在真正的實戰體驗中個人更傾向于使用IDEA,雖然曾經的我也是一個重度Eclipse開發者。
IDEA的搜索快捷鍵是:??F,使用IDEA提供的搜索功能可以非常快速的定位漏洞點信息。
IDEA可以通過自定義搜索范圍來精確查找我們需要審計的代碼。默認搜索的是所有的位置,不過我們可以點擊紅色箭頭指向的...按鈕來細化我們的搜索范圍。
自定義搜索范圍示例:
自定義搜索范圍后就可以在搜索時使用自定義的配置進行范圍搜索了,有助于我們在挖漏洞的時候縮小代碼定位范圍。
搜索快捷鍵: ?O,標記搜索支持類名、方法名搜索(包括class或jar文件中的方法也支持搜索)。
當我們審計代碼的時候發現某個方法或類有漏洞時我們需要定位到漏洞的請求地址(觸發點),復雜業務系統往往會讓我們很難定位到漏洞的觸發點。借助IDEA的方法調用鏈搜索功能就可以很輕松的找出方法的調用鏈和觸發點。
選擇類或者方法名-->右鍵-->Find Useages或者使用快捷鍵?F7
為了更好的管理項目我們通常會采用分層架構的方式來開發Java Web項目,分層設計的好處在于可以非常方便的分清楚包之間的業務邏輯關系。
常見的JavaWeb項目分層:
視圖層(View 視圖)
控制層(Controller、Action 控制層)
服務層(Service)
業務邏輯層BO(business object)
實體層(entity 實體對象、VO(value object) 值對象 、模型層(bean)。
持久層(dao- Data Access Object 數據訪問層、PO(persistant object) 持久對象)
基于Java分層架構的示例項目:
如今的較為大型的Java Web項目通常都采用了模塊化方式開發,借助于Maven、Gradle依賴管理工具,Java可以非常輕松的完成模塊化開發。除此之外使用OSGi(Open Service Gateway Initiative 可實現模塊熱部署)技術開發來Java動態模塊化系統也是較為常見的。
采用模塊化開發也會給我們做代碼審計帶來一定的難度,因為需要在更多的依賴庫中去尋找需要我們審計的代碼。
使用Maven開發的JavaWeb項目示例:
Servlet是在Java Web容器上運行的小程序,通常我們用Servlet來處理一些較為復雜的服務器端的業務邏輯。值得注意的是在Servlet3.0之后(Tomcat7+)可以使用注解方式配置Servlet了。
基于注解的Servlet
Servlet3.0之前的版本都需要在web.xml中配置,Servlet是兩對標簽,由<servlet>和<servlet-mapping>組成,Spring MVC框架就是基于Servlet技術實現的。
基于配置實現的Servlet
HttpServlet類
實現一個Servlet很簡單,只需要繼承javax.servlet.http.HttpServlet類并重寫doXXX方法或者service方法就可以了,其中需要注意的是重寫HttpServlet類的service方法可以獲取到上述七種Http請求方法的請求。
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方法調用。
Filter是JavaWeb中的過濾器,用于過濾URL請求。通過Filter我們可以實現URL請求資源權限驗證、用戶登陸檢測等功能。Filter是一個接口,實現一個Filter只需要重寫init、doFilter、destroy方法即可,其中過濾邏輯都在doFilter方法中實現。
Filter和Servlet一樣是Java Web中最為核心的部分,使用Servlet和Filter可以實現后端接口開發和權限控制,當然使用Filter機制也可以實現MVC框架,Struts2實現機制就是使用的Filter。
Filter的配置類似于Servlet,由<filter>和<filter-mapping>兩組標簽組成,如果Servlet版本大于3.0同樣可以使用注解的方式配置Filter。
對于基于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更多的是用來處理后端業務請求上
傳統的開發存在結構混亂易用性差耦合度高可維護性差等多種問題,為了解決這些毛病分層思想和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語言動態性一直以來都比較差,并不像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)
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(); } } }
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 { String str = "ping p2j.cn -c 1"; Class runtimeClass = 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(); } } }
通常我喜歡把代碼審計的方向分為業務層安全問題、代碼實現和服務架構安全問題,。
業務層的安全問題集中在業務邏輯和越權問題上,我們在代碼審計的過程中盡可能的去理解系統的業務流程以便于發現隱藏在業務中的安全問題。
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直接調用數據庫信息把用戶登陸邏輯直接放到了前端來做)。
代碼審計的核心是尋找代碼中程序實現的安全問題,通常我們會把代碼審計的重心放在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 { FileServiceService fileService = new FileServiceServiceLocator(); URL webServiceUrl = 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(); } } }
關于如何分析Java Web安全中的代碼審計就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。