1. 程式人生 >實用技巧 >其它 >《這是知識點》之上傳POST可繞過Java過濾器

《這是知識點》之上傳POST可繞過Java過濾器


技術標籤:Java的小小世界工作中的點點滴滴這是知識點javaspringxss安全

專案場景:

提示:公司有個專案做安全測評時發現了一個注入問題。系統設定了全域性的XSS過濾器,在其他功能點上生效了,但在一個釋出功能沒有被過濾,後續排查發現安全測評走的是上傳包注入方式,故記錄此次問題


問題描述:

問題功能模組的作用是一個新增相關引數並上傳附件的點,問題出在新增的引數未被全域性XSS過濾器生效

原因分析:

基於Tomcat和Spring MVC的底層程式碼分析問題原因

常用的請求分文三種,GET型請求,普通POST型請求和上傳POST型請求。本文的普通型POST請求指的是除上傳POST型請求之外的POST請求,而上傳POST型請求就是我們上傳包對應的請求。

  1. 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層獲取。

  1. 上傳包無法被過濾的原理
    經過跟蹤發現,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極客

關注部落格:御前提筆小書童

關注網站:開發者的花花世界

關注公眾號:開發者的花花世界

《這是知識點》之上傳POST可繞過Java過濾器