《這是知識點》之上傳POST可繞過Java過濾器
技術標籤:Java的小小世界工作中的點點滴滴這是知識點javaspringxss安全
專案場景:
提示:公司有個專案做安全測評時發現了一個注入問題。系統設定了全域性的XSS過濾器,在其他功能點上生效了,但在一個釋出功能沒有被過濾,後續排查發現安全測評走的是上傳包注入方式,故記錄此次問題
問題描述:
問題功能模組的作用是一個新增相關引數並上傳附件的點,問題出在新增的引數未被全域性XSS過濾器生效原因分析:
基於Tomcat和Spring MVC的底層程式碼分析問題原因
常用的請求分文三種,GET型請求,普通POST型請求和上傳POST型請求。本文的普通型POST請求指的是除上傳POST型請求之外的POST請求,而上傳POST型請求就是我們上傳包對應的請求。
- Spring MVC如何獲取到HTTP請求引數值?
前端提交的請求會先到達Tomcat伺服器,其解析請求引數主要在Request.parseParameters()中進行。
//org.apache.catalina.connector.Request
protected void parseParameters() {
this.parametersParsed = true;
Parameters parameters = this.coyoteRequest.getParameters();
boolean success = false;
try {
parameters.setLimit(this.getConnector().getMaxParameterCount());
...
parameters.handleQueryParameters();
if (this.usingInputStream || this.usingReader) {
success = true;
} else if (!this.getConnector().isParseBodyMethod(this.getMethod())) {
success = true;
} else {
// 獲取請求包ContentType頭
String contentType = this.getContentType();
...
// 如果請求ContentType為multipart/form-data,也就是上傳POST
if ("multipart/form-data".equals(contentType)) {
//對上傳包進行解析
this.parseParts();
success = true;
return;
} else if (!"application/x-www-form-urlencoded".equals(contentType)) {
success = true;
return;
} else {
int len = this.getContentLength();
...
parameters.processParameters(formData, 0, len);
...
success = true;
}
}
}
}
Tomcat會根據ContentType是否為multipart/form-data判斷是否問上傳POST型請求,若是則會呼叫parseParts()來解析,我們繼續跟進。由於allowCasualMultipartParsing配置項預設為false,parseParts()直接就返回了,也就是說Tomcat預設不會解析上傳POST請求。
private void parseParts() {
if (this.parts == null && this.partsParseException == null) {
MultipartConfigElement mce = this.getWrapper().getMultipartConfigElement();
if (mce == null) {
/*
Tomcat7.0+ 已經內建了multipart支援,但是必須顯示啟用,預設關閉。在全域性tomcat配置檔案context.xml,或者為war的本地context.xml新增<Context allowCasualMultipartParsing="true">開啟。
*/
if (!this.getContext().getAllowCasualMultipartParsing()) {
this.parts = Collections.emptyList();
return;
}
...
}
...
}
}
對針對GET行請求和普通POST,Tomcat會呼叫parameters.processParameters()方法來解析,接下來Spring MVC會收到Tomcat傳來的HttpServletRequest,此時若請求為上傳POST型,Spring MVC會繼續呼叫commons-fileuplad.jar對Tomcat傳來的原生Servlet請求類HttpServletRequest的例項進行解析處理。
Spring MVC將原生的HttpServletRequest物件傳入CommonsMultipartResolver類的parseRequest()方法進行解析處理。
CommonsMultipartResolver.parseRequest()方法主要分兩步對上傳請求進行解析。
第一步,呼叫commons-fileupload.jar中的ServletFileUpload類的parseRequest()方法來解析出儲存有上傳表單各個元素的FileItem列表。
第二步,呼叫CommonsFileUploadSupport.parseFileItem()方法解析FileItem列表為儲存有表單欄位名,欄位值等資訊MultipartParsingResult型別的Map。
最後將上傳表單解析的所有元素(multipartFiles,multipartParameters,multipartParameterContentTypes)封裝為一個MultipartParsingResult並返回。至此上傳POST型請求的解析工作完成。
下面我們來看下這兩步的執行細節。首先第一步最終的處理方法為FileUploadBase.parseRequest(),FileUploadBase.parseRequest()解析完會返回一個FileItem例項列表。FileItem就是儲存著上傳表單的各種元素(欄位名,ContentType,是否是簡單表單欄位,檔名。),接著來到第二步,呼叫CommonsFileUploadSupport.parseFileItem()對commons-fileupload.jar處理的結果—FileItem列表,進行處理。最後將上傳表單解析的所有元素(multipartFiles,multipartParameters,multipartParameterContentTypes)封裝為一個MultipartParsingResult並返回。至此上傳POST型請求的解析工作完成。
最後Spring MVC,會使用HandlerMethodInvoker.resolveRequestParam()方法,將解析好的請求引數的值,繫結到不同的物件上,方便Controller層獲取。
- 上傳包無法被過濾的原理
經過跟蹤發現,Spring MVC對各型別請求引數的解析並實現自動繫結,主要在HandlerMethodInvoker.resolveRequestParam()方法。
通過調式發現,這裡如果是GET型和普通POST型請求的話,getRequest()獲取到的物件是我們編寫的過濾類XssHttpServletRequestWrapper的例項,故呼叫該物件getParameterValues()來獲取值,自然是被過濾了!
若是上傳POST行請求的話,getRequest()獲取到的是CommonsMultipartResolver類的物件。但實際上呼叫該物件的getParamterValues()方法,會執行到DefaultMultipartHttpServletRequest類的getParamterValues()類獲取值。上傳包中的引數值沒有被過濾,是因為Spring MVC在解析上傳包獲取其引數值時,沒有使用我們編寫的過濾類XssHttpServletRequestWrapper中的getParamterValues()方法,而是使用了DefaultMultipartHttpServletRequest類getParamterValuses()。
最後特別說明一點,其實上傳POST請求資料是流經過過濾器的。沒有被過濾,是由於獲取引數值的時候,沒有呼叫過濾器Wrapper物件的方法。所以最終我們看到了上傳包可以“繞過”過濾器檢查的現象。
另外,實際開發中,檔案上傳的檔案包上傳最好對檔案頭進行分析,判斷檔案型別,不單單的對檔案字尾做判斷,常見的檔案型別的檔案頭是可預見的。
關注Github:1/2極客
關注部落格:御前提筆小書童
關注網站:開發者的花花世界
關注公眾號:開發者的花花世界

