1. 程式人生 > >HTTP協議安全頭部X-Content-Type-Options引入的問題

HTTP協議安全頭部X-Content-Type-Options引入的問題

原文地址:http://www.jackieathome.net/archives/369.html?utm_source=tuicool&utm_medium=referral

前段時間測試MM反饋了一個問題,在富文字編輯器裡上傳的圖片無法正常呈現。因為Jackie在本機的環境上沒有觀察類似的現象,而恰好那天測試環境的某個重要配項被改錯了,於是Jackie想當然的歸類為配置項錯誤引入的問題。但修改完測試環境的配置項後,測試反饋富文字編輯器內圖片無法呈現的現象依然存在。

這下就有點麻煩了,問題是在版本上線的前一天晚上發現的,如果問題不能儘快處理,勢必對第二天的版本上線產生影響。雖然逢上線必加班至深夜,但也不能讓問題掛在Jackie手裡。

時間緊急,問題詭異,Jackie馬上放下手頭的工作,全力投入分析工作中。

排查過程

  • 如前所述,配置項修改正確後,問題現象仍然存在,因此首先排除了配置項配置引入問題的可能。
  • 請測試MM幫忙復現問題,仔細觀察之後,發現使用不同瀏覽器訪問問題頁面時可以觀察到不同的現象:
    • 使用Chrome訪問頁面時,富文字編輯器內的圖片可以正常呈現。
    • 使用IE9、IE11訪問頁面時,富文字編輯器內的圖片無法呈現。從瀏覽器的呈現效果看,貌似是圖片載入失敗;但在偵錯程式的網路面板檢視頁面資源的載入情況,可以觀察到瀏覽器發起了圖片的獲取請求,而Web應用確實返回了圖片資訊;但就是沒有呈現出來。
    • 直接把富文字編輯器的內圖片的URL複製出來,貼到瀏覽器的位址列裡訪問,圖片可以正常呈現。
  • 在生產環境和體驗環境上做對比驗證,使用IE9、IE11訪問這兩個環境,檢視富文字編輯器內的圖片,沒有觀察到前述的問題,因此可以判定測試MM在測試環境上發現的問題是最近引入的,時間範圍不超過2個星期,因為那段時間專案組早已切換為兩週一迭代的節奏。
  • 檢查富文字編輯器相關程式碼的提交記錄,發現最近的提交記錄遠在幾個月前,因此基本可以排除富文字編輯器自身程式碼的問題。
  • 腫麼搞呢?似乎進入了死衚衕。看來常規的方法都不見效,只能逐一排查程式碼提交記錄了。

幸運的是提交記錄不多,10分鐘之內被Jackie掃描了一遍;大部分提交記錄都是清白了,唯一可疑的提交記錄來自於Jackie本人,果然這個問題只能由Jackie來定位和分析。為了解決AppScan報告中提到的“HTTP響應缺少安全頭部”的警告,Jackie在發現問題的那天早上修改了Tomcat的配置,增加了安全頭部相關的過濾器,而晚上留在辦公室加班,目的就是要確認前述的警告是否已消除。

為了確認前述問題和HTTP安全頭部的相關性,Jackie手工修改測試環境上Tomcat的配置,去掉了增加安全頭部的過濾器,重啟Tomcat後嘗試重現問題,驚喜的發現,富文字編輯器內的圖片恢復正常呈現,這說明HTTP響應增加安全頭部之後,對基本功能產生了影響。

當前啟用了HTTP協議的安全頭部的如下幾個:

  • Strict-Transport-Security
  • X-Frame-Options
  • X-Content-Type-Options
  • X-XSS-Protection

範圍比較小,逐個排查之後,發現前述問題現象和X-Content-Type-Options相關,因此決定仍然啟用HTTP安全頭部的輸出,但禁用X-Content-Type-Options,富文字編輯器內的圖片可以正常呈現,同時不會對安全性造成很大的影響。

本來覺得修改Tomcat的配置和業務不相關,不會有什麼問題,也沒有過基本功能,結果偏偏天不遂人願,還真讓測試MM發現個詭異問題。看來僥倖心理不能有,該做的工作不能省,否則就得加班、加倍的補回來。

下面使用最少的樣例程式碼來說明問題是如何發生的。

問題是如何發生的

準備復現環境

jsp頁面

<%@ page session="false" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title><%=request.getServletContext().getServerInfo() %></title>
    </head>
        <div>
            <img alt="FileDownload" src="<%=request.getContextPath()%>/GetPicture">
        </div>
    <body>

    </body>
</html>

GetPicture的實現

如下這段程式碼的實現存在問題,使用流方式返回資料時,沒有顯式指定Content-Type

package com.struts2.servlets;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;

public class GetPictureServlet extends HttpServlet {

    private static final long serialVersionUID = -5935833295545479697L;
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {          
        String path = req.getServletContext().getRealPath("");

        byte[] buffer = new byte[4096];
        int count = 0;
        InputStream is = null;
        OutputStream os = null;

        try
        {
            is = new FileInputStream(new File(path, "tomcat.png"));
            os = resp.getOutputStream();

            while ((count = is.read(buffer)) > 0) {
                os.write(buffer, 0, count);
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(os);
        }
    }
}

web.xml

<servlet>
    <servlet-name>GetPictureServlet</servlet-name>
    <servlet-class>com.struts2.servlets.GetPictureServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>GetPictureServlet</servlet-name>
    <url-pattern>/GetPicture</url-pattern>
</servlet-mapping>    

使用瀏覽器觀察

重溫一些安全相關的HTTP響應頭中對X-Content-Type-Options的介紹。

網際網路上的資源有各種型別,通常瀏覽器會根據響應頭的Content-Type欄位來分辨它們的型別。例如:”text/html”代表html文件,”image/png”是PNG圖片,”text/css”是CSS樣式文件。然而,有些資源的Content-Type是錯的或者未定義。這時,某些瀏覽器會啟用MIME-sniffing來猜測該資源的型別,解析內容並執行。

使用瀏覽器除錯面板來觀察HTTP響應的頭部,對上文做驗證。

  1. 未啟用HttpHeaderSecurityFilter時,HTTP響應的頭部如下
    Content-Length:5103
    Date:Mon, 02 May 2016 05:07:22 GMT
    Server:Apache-Coyote/1.1
    
  2. 修改$CATALINA_BASE/conf/web.xml,啟用HttpHeaderSecurityFilter
    <filter>
        <filter-name>httpHeaderSecurity</filter-name>
        <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
        <async-supported>true</async-supported>        
    </filter>
    

    使用瀏覽器的除錯面板觀察Tomcat響應瀏覽器請求時的HTTP頭部

    Cache-Control:private
    Content-Length:5103
    Date:Mon, 02 May 2016 05:42:00 GMT
    Expires:Thu, 01 Jan 1970 08:00:00 CST
    Server:Apache-Coyote/1.1
    Strict-Transport-Security:max-age=30;includeSubDomains
    X-Content-Type-Options:nosniff
    X-Frame-Options:SAMEORIGIN
    X-XSS-Protection:1; mode=block
    

    此時,使用瀏覽器訪問頁面時,圖片不能正常呈現。

  3. 修改$CATALINA_BASE/conf/web.xml,禁用X-Content-Type-Options特性
    <filter>
        <filter-name>httpHeaderSecurity</filter-name>
        <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
        <async-supported>true</async-supported>           
        <init-param>
            <param-name>blockContentTypeSniffingEnabled</param-name>
            <param-value>false</param-value>
        </init-param>
    </filter>
    

    使用瀏覽器的除錯面板觀察Tomcat響應瀏覽器請求時的HTTP頭部

    Cache-Control:private
    Content-Length:5103
    Date:Mon, 02 May 2016 05:50:39 GMT
    Expires:Thu, 01 Jan 1970 08:00:00 CST
    Server:Apache-Coyote/1.1
    Strict-Transport-Security:max-age=30;includeSubDomains
    X-Frame-Options:SAMEORIGIN
    X-XSS-Protection:1; mode=block
    

    此時,使用瀏覽器訪問頁面時,圖片可以正常呈現。

  4. 修改$CATALINA_BASE/conf/web.xml,恢復X-Content-Type-Options特性
    <filter>
        <filter-name>httpHeaderSecurity</filter-name>
        <filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
        <async-supported>true</async-supported>           
    </filter>
    

    修改GetPictureServlet類的實現,寫入圖片流前先設定HTTP響應頭部Content-Type,取值為image/png

    resp.setHeader("Content-Type", "image/png");
    

    使用瀏覽器的除錯面板觀察Tomcat響應瀏覽器請求時的HTTP頭部

    Cache-Control:private
    Content-Length:5103
    Content-Type:image/png
    Date:Thu, 05 May 2016 15:38:12 GMT
    Expires:Thu, 01 Jan 1970 08:00:00 CST
    Server:Apache-Coyote/1.1
    Strict-Transport-Security:max-age=30;includeSubDomains
    X-Content-Type-Options:nosniff
    X-Frame-Options:SAMEORIGIN
    X-XSS-Protection:1; mode=block
    

    此時,使用瀏覽器訪問頁面時,圖片可以正常呈現。

結論

按照減少 MIME 型別的安全風險的介紹,IE的行為受X-Content-Type-Options的影響,如果Web應用沒有返回Content-Type,那麼IE9、IE11將拒絕載入相關資源。

如果伺服器傳送響應頭 “X-Content-Type-Options: nosniff”,則 script 和 styleSheet 元素會拒絕包含錯誤的 MIME 型別的響應。這是一種安全功能,有助於防止基於 MIME 型別混淆的攻擊。

從前述的測試結果看,的確證實了X-Content-Type-Options對IE9、IE11的影響。

但仍有不明之事,文章減少 MIME 型別的安全風險只提到了scriptstylesheet標籤,沒有提到img標籤,不瞭解為什麼X-Content-Type-Options對圖片的載入也會產生影響。後來使用Windows 10的Edge做驗證,發現Edge也存在相同的問題,只要啟用了X-Content-Type-Options,那麼圖片必定呈現不出來。Jackie猜,這也許是文件太舊了,或者是Jackie沒有找到最新的文件。

另外按照Jerry Qu的文章一些安全相關的HTTP響應頭的描述,X-Content-Type-Options應該會影響Chrome的行為,但從測試結果看,使用img標籤載入圖片時,如果Web應用沒有返回Content-Type,無論是否啟用X-Content-Type-Options,Chrome都可以正常呈現圖片,這一點比較奇怪。

參考資料