JSONP 是解決跨網域 JavaScript 呼叫的古老方法,簡單有效又不挑瀏覽器,至今仍是我常用的兵器之一。最近在想一個問題,JSONP 呼叫時由客戶端指定 Callback 函式名稱,是一個可以注入惡意程式碼的管道,有否存在 XSS 攻擊的風險?需不需要積極防護?

經過嘗試,發現要透過 JSONP 發動攻擊是可能的,但前題是開發者犯了某些低級錯誤。

使用以下網頁示範:

<%@ Page Language="C#" %>
<script runat="server">
    void Page_Load(object sender, EventArgs e) {
        if (Request["mode"] == "jsonp") {
            string callback = Request["callback"];
            Response.Write(string.Format("{0}({1})",
                callback, 
                Newtonsoft.Json.JsonConvert.SerializeObject(
                    "Param=>" + Request["param"]
                )));
            Response.End();
        }
    }
</script>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>JSONP XSS Issue</title>
    <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
</head>
<body>
    <input type="text" id="txtParam" value="Darkthread" />
    <button>Test JSONP</button> 
    <script>
$("button").click(function() {
    $.ajax({
        url: "http://127.0.0.1/JSONP/JsonpDemo.aspx?mode=jsonp&param=" + $("#txtParam").val(),
        dataType: "jsonp"
    }).done(function(data) {
        alert(data);
    });
});
    </script>
</body>
</html>

範例為求單純,客戶端與伺服器端寫在同一支程式,但我將它掛在 IIS localhost/JSONP/ 下,呼叫時則用 127.0.0.1/JSONP,瀏覽器將其視為兩個網站,GET/POST AJAX 呼叫將被阻擋,要透過 dataType: "jsonp" 才能溝通

執行時客戶端透過 jQuery.ajax 發出 JSONP 呼叫並帶入param 參數,jQuery 會附上隨機產生的函式名稱當成 callback 參數;而伺服器端傳回"callback函式名稱(傳回資料的JSON結果內容)"送交客戶端執行,傳回資料會經由 JSON 還原成 done(function(data) { … })中的 data。

程式裡故意留了一個低級錯誤,url: "httq://127.0.0.1/JSONP/JsonpDemo.aspx?mode=jsonp&param=" + $("#txtParam").val() 直接串接使用者輸入內容,跟 SQL Injection 一樣會導致被注入惡意程式的風險,請看示範:(註:依我個人看法,此攻擊管道雖然存在,但要藉由它攻擊第三者還需其他條件配合,例如:惡意指令寫入DB、程式由DB讀取後當成呼叫 JSONP 的 URL 參數,並在第三者看得到的網頁執行)

使用者輸入&callback=後就可以串接任意 JavaScript 程式碼(跟 SQL Injection 的單引號起手式如出一轍),如同 SQL Injection 要靠 Parameter 杜絕,在 QueryString 串接參數內容時要用 encodeURIcomponent 編碼也是基本常識(補充),而既然是用 jQuery,透過 data: { param: $("#txtParam").val() } 交給 jQuery 更省事:

$("button").click(function() {
    $.ajax({
        url: "httq://127.0.0.1/JSONP/JsonpDemo.aspx",
        data: { mode: "jsonp", param: $("#txtParam").val() },
        dataType: "jsonp"
    }).done(function(data) {
        alert(data);
    });
});

param 參數經過編碼後就永遠只是字串值,無法再夾帶攻擊指令。

進一步想,那麼伺服器端是否也能加上防護,阻止客戶端犯下愚惷的錯誤呢?不難,只要妥善過濾 callback 傳入內容即可,既然 callback 是個函式名稱,我們可限定它只能包含英數字及底線符號,即可社絕被夾帶 JavaScript 程式,例如:

    void Page_Load(object sender, EventArgs e) {
        if (Request["mode"] == "jsonp") {
            string callback = Request["callback"];
            if (!System.Text.RegularExpressions.Regex.IsMatch(
                callback, "^[0-9A-Za-z_]{1,64}$")) 
            {
                Response.Write("alert('Invalid callback parameter');");
            }
            else 
            {
                Response.Write(string.Format("{0}({1})",
                callback, 
                Newtonsoft.Json.JsonConvert.SerializeObject(
                    "Param=>" + Request["param"]
                )));
            }
            Response.End();
        }
    }

程式檢查 callback 參數是否由純粹英數字或底線組成,並限定長度最多 64 字元,遇到被穿插 JavaScript 程式片段時改傳警告:

【結論】

當開發者犯下 URL 直接串接使用者輸入內容的低級錯誤,就可能導致 JSONP 遭受 XSS 攻擊,請務必使用 encodeURIcomponent() 編碼;至於伺服器端,嚴格限定 callback 參數只可包含單純文數字,可進一步防呆。

補記:發現 jQuery 1.x $.ajax 執行 JSONP 出錯時不會觸發 error 事件或 fail(),需升級至 2.x 以上版本或考慮改用第三方外掛如 jquery-jsonp


Comments

Be the first to post a comment

Post a comment