Java Web安全-程式碼審計(二)
點選上方“ 凌天實驗室 ”可訂閱哦!
文接上回,穿越捷徑: Java Web安全-程式碼審計(一)
4
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>
和 <servlet-mapping>
組成, Spring MVC
框架就是 基於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,由 <filter>
和 <filter-mapping>
兩組標籤組成,如果Servlet版本大於3.0同樣可以使用註解的方式配置Filter。
6. Filter和Servlet的總結
對於基於 Filter
和 Servlet
實現的簡單架構專案,程式碼審計的重心集中於找出所有的 Filter
分析其過濾規則,找出是否有做全域性的安全過濾、敏感的URL地址是否有做許可權校驗並嘗試繞過 Filter
過濾。第二點則是找出所有的 Servlet
,分析 Servlet
的業務是否存在安全問題,如果存在安全問題是否可以利用?是否有許可權訪問?利用時是否被Filter過濾等問題,切勿看到 Servlet
、 JSP
中的漏洞點就妄下定論,不要忘了 Servlet
前面很有可能存在一個全域性安全過濾的 Filter
。
Filter
和 Servlet
都是 Java Web
提供的API,簡單的總結了下有如下共同點。
-
Filter
和Servlet
都需要在web.xml
或註解
(@WebFilter
、@WebServlet
)中配置,而且配置方式是非常的相似的。 -
Filter
和Servlet
都可以處理來自Http請求的請求,兩者都有request
、response
物件。 -
Filter
和Servlet
基礎概念不一樣,Servlet
定義是容器端小程式,用於直接處理後端業務邏輯,而Filter
的思想則是實現對Java Web請求資源的攔截過濾。 -
Filter
和Servlet
雖然概念上不太一樣,但都可以處理Http請求,都可以用來實現MVC控制器(Struts2
和Spring
框架分別基於Filter
和Servlet
技術實現的)。 -
一般來說
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查詢方式這裡不再講解。
5
Java語言的動態性
Java語言動態性一直以來都比較差,並不像PHP那樣靈活。在Java中的動態性往往需要使用一些曲折的方式來實現.這裡簡單列舉了Java十餘種動態性相關技術並總結部分技術實現安全問題。
-
Java反射機制
-
MethodHandle
-
JDK動態代理
-
使用JVM上的動態語言(如:
Groovy
、JRuby
、Jython
) -
表示式庫(如:
OGNL
、MVEL
、SpEL
、EL
) -
JSP
、JSPX
、Quercus
(Resin容器提供了PHP5支援) -
位元組碼庫(如:
Asm
、Javassist
、Cglib
、BCEL
) -
ScriptEngineManager(指令碼引擎)。
-
動態編譯(如:JDT、JavaCompiler)
-
ClassLoader
、URLClassLoader
-
模版引擎(如:
Freemarker
、Velocity
) -
序列化、反序列化(包含
Java 物件序列化
、XML
、JSON
等) -
JNI
、JNA
(Java呼叫C/C++) -
OSGi
(Open Service Gateway Initiative
) -
RMI(Java遠端方法呼叫,基於物件序列化機制實現)
-
WebService
-
JDWP
(Java Platform Debugger Architecture
Java除錯協議) -
JMX(Java Management Extensions)
1. Java反射機制特性
Java反射機制可以無視類方法、變數訪問許可權修飾符,可以 呼叫任何類的任意方法、訪問並修改成員變數值
。也就是說只要發現一處Java反射呼叫漏洞幾乎就可以為所欲為了。當然前提可能需要你能 控制反射的類名、方法名和引數
。
一行程式碼即可實現反射呼叫Runtime執行本地命令:
Runtime.class.getMethod("exec", String.class).invoke(Runtime.class.getMethod("getRuntime").invoke(null), "whoami")
獲取一個類的物件(如Runtime類)我們一般會採用如下幾種方式:
Class.forName("java.lang.Runtime")、"".getClass().forName("java.lang.Runtime") Runtime.class ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime")
Java反射獲取類方法有兩種方式:
-
getMethod(xxx)
,getMethods()
-
getDeclaredMethod(xxx)
、getDeclaredMethods()
。
區別在於 getMethod會返回當前類和父類的所有public方法
,而 getDeclaredMethod返回的是當前的所有方法
。
Java反射獲取類成員變數有兩種方式:
-
getField(xxx)
、getFields()
-
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
PS:還有最後一講哦,敬請期待~
凌天
實驗室
安全實驗室,是安百科技旗下針對應用安全領域進行攻防研究的專業技術團隊,其核心成員來自原烏雲創始團隊及社群知名白帽子,團隊專業性強、技術層次高且富有實戰經驗。實驗室成立於2016年,發展至今團隊成員已達35人,在應用安全領域深耕不輟,向網路安全行業頂尖水平攻防技術團隊的方向夯實邁進。