記錄最近維護古蹟遇到的問題一枚。

接到通報,老系統某個使用 JSONP 呼叫第三方網頁執行指定作業並接收回覆,不知何時開始失效了。

客戶端程式類似這樣:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JSONP Test</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
    <div>
        <button id="btnTest">JSONP Test</button>
        <textarea id="txtResult" rows="4" cols="50" style="display:block; margin-top:6px;">
        </textarea>
    </div>
    <script>
        function handleResponse(data) {
            console.log(data);
        }
        $('#btnTest').click(function() {
            $.ajax({
                url: 'http://172.23.160.1/aspnet/jsonp/api.aspx',
                data: { userId: 'Jeffrey' },
                dataType: 'jsonp',
                success: function(data) {
                    $('#txtResult').val(JSON.stringify(data));
                }
            });
        });
    </script>
</body>
</html>

奇妙的是,使用 F12 監看 JSONP 呼叫,HTTP 狀態 200,但沒有回應內容:

意思是 api.aspx程式有成功執行 (HTTP 200)也回傳了,但資料長度是 0 Byte?收到一張無聲卡?

困惑了一陣子,同事改用 Chrome 測試才出現轉機。Chrome 執行相同網頁也是無回應,但有提供一則錯誤代碼 ERR_BLOCKED_BY_ORB

ORB 是 Opaque Response Blocking 的縮寫,指瀏覽器可依要求,拒絕處理「不透明的回應」(Opaque Responses),意指沒有明確標示的 MIME 類型,或者 MIME 類型與實際內容不匹配。而有個 Header X-Content-Type-Options: nosniff 可要求瀏覽器嚴格遵守資源的 MIME 類型,禁止進行「MIME 類型嗅探」(MIME Type Sniffing) 自動偵測並設定 MIME Type 以免形成資安漏洞。(延伸閱讀:資安議題 — Http Security Header by LSZ)

舉個例子,若網站允許使用者上傳 .jpg 檔。攻擊者寫了一段內含 JavaScript 的 HTML 假裝 .jpg 上傳,其他使用者瀏覽該圖片時若瀏覽器啟用 MIME Type Sniffing 自動識別校正成網頁呈現,攻擊者的惡意 JavaScript 便會被執行。

回頭看這個重現情境中的 api.aspx,內容像以下這樣。有兩大重點,1) 有回傳 Header X-Content-Type-Options: nosniff 2) 未指定 MIME Type。

<%@Page Language="C#"%>
<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        Response.Headers.Add("X-Content-Type-Options", "nosniff ");
        var callback = Request.QueryString["callback"];
        if (!string.IsNullOrEmpty(callback) && 
            System.Text.RegularExpressions.Regex.IsMatch(callback, "^[a-zA-Z0-9_]+$"))
        {
            Response.Write(callback + "({name:'Jeffrey', score:32767})");
        }
        else
        {
            Response.Write("Invalid callback");
        }
    }   
</script>

搞懂這些,要解決就簡單了,加上 Response.ContentType = "application/javascript" 就好了。

<%@Page Language="C#"%>
<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        Response.Headers.Add("X-Content-Type-Options", "nosniff ");
        var callback = Request.QueryString["callback"];
        if (!string.IsNullOrEmpty(callback) && 
            System.Text.RegularExpressions.Regex.IsMatch(callback, "^[a-zA-Z0-9_]+$"))
        {
            Response.ContentType = "application/javascript";
            Response.Write(callback + "({name:'Jeffrey', score:32767})");
        }
        else
        {
            Response.Write("Invalid callback");
        }
    }   
</script>

搞定收工。

This post describes a JSONP callback issue in an old system. The problem, identified as ERR_BLOCKED_BY_ORB in Chrome, was due to missing MIME type specification. Solution: Adding Response.ContentType = "application/javascript" in the server-side code resolved the issue.


Comments

Be the first to post a comment

Post a comment