WEB安全新玩法 [8] 阻止訂單重複提交

天存資訊發表於2021-07-28

交易訂單的重複提交雖然通常不會直接影響現金流和商品流,但依然會給網站運營方帶來損害,如消耗系統資源、影響正常使用者訂單生成、製造惡意使用者發起糾紛的機會等。倘若訂單物件是虛擬商品,也有可能造成實際損失。訂單重複提交的檢查工作本應該由網站自身實現,而 iFlow 業務安全加固平臺則可以為未實現這項功能的網站提供防護。


以某開源購物網站為例,攻擊者能夠輕鬆實現訂單的重複提交。我們看看如何在不修改網站原始碼的前提下,使用 iFlow 通過透明加入一次性令牌來阻止訂單的重複提交攻擊。

一、不檢查訂單重複提交的原始網站

原始網站系統沒有檢查訂單的重複提交,攻擊者可以簡單地重複提交訂單。

1.1 正常使用者訪問

已登入使用者在選擇購買一件商品後,進入到確認訂單頁面:

pic1

使用者點選提交訂單按鈕後,網站回覆訂單已生成:

pic2

可以在我的訂單列表中看到剛才的訂單:

pic3

訂單生成的互動過程反映在 HTTP 協議層面如下:

sequenceDiagram participant 正常使用者 participant 瀏覽器 participant Web伺服器 正常使用者->>瀏覽器: 選擇購買商品 瀏覽器->>Web伺服器: 請求:/index.php?s=/member/paymentorder Web伺服器->>瀏覽器: 返回:確認訂單頁面 瀏覽器->>Web伺服器: 請求:/js/payment_orders/payment_orders.js Web伺服器->>瀏覽器: 返回:payment_orders.js 瀏覽器->>正常使用者: 顯示:確認訂單頁面 正常使用者->>瀏覽器: 點選提交訂單按鈕 瀏覽器->>Web伺服器: js傳送AJAX:提交訂單資訊 Note over Web伺服器: 系統生成正常訂單 Web伺服器->>瀏覽器: 重定向頁面:訂單已生成 瀏覽器->>正常使用者: 顯示:訂單已生成

1.2 攻擊者訪問

攻擊者使用 Burpsuite 工具作為瀏覽器和 Web 伺服器之間的代理。

攻擊者象正常使用者一樣選擇商品和確認提交後,能夠在 Burpsuite 中的 HTTP history 中找到這個提交訂單資訊的請求。攻擊者右鍵點選 Send to Repeater 後進入 Repeater 標籤頁。

pic4

攻擊者通過多次點選 Send 按鈕來重複發出請求報文從而重複產生訂單,並可以在我的訂單中看到多個重複生成的訂單,如下圖所示:

pic5

HTTP 協議層面互動如下:

sequenceDiagram participant 攻擊者 participant 瀏覽器 participant 攻擊工具 participant Web伺服器 攻擊者->>瀏覽器: 選擇購買商品 瀏覽器->>Web伺服器: 請求:/index.php?s=/member/paymentorder Web伺服器->>瀏覽器: 返回:確認訂單頁面 瀏覽器->>Web伺服器: 請求:/js/payment_orders/payment_orders.js Web伺服器->>瀏覽器: 返回:payment_orders.js 瀏覽器->>攻擊者: 顯示:確認訂單頁面 攻擊者->>瀏覽器: 點選提交訂單按鈕 瀏覽器->>攻擊工具: js傳送AJAX:提交訂單資訊 rect rgb(250, 128, 128) Note over 攻擊工具: 記錄請求報文 end 攻擊工具->>Web伺服器: 傳送AJAX:提交訂單資訊 Note over Web伺服器: 系統生成正常訂單 Web伺服器->>瀏覽器: 重定向頁面:訂單已生成 瀏覽器->>攻擊者: 顯示:訂單已生成 Loop 重複 rect rgb(250, 128, 128) 攻擊者->>攻擊工具: 傳送已記錄的請求報文 end 攻擊工具->>Web伺服器: 提交訂單資訊 rect rgb(250, 128, 128) Note over Web伺服器: 系統生成重複訂單 end Web伺服器->>攻擊工具: 返回響應報文 攻擊工具->>攻擊者: 忽略響應報文 end

二、iFlow虛擬補丁後的網站

我們在 Web 伺服器前部署 iFlow 業務安全加固平臺,它有能力攔截、計算和修改雙向 HTTP 報文並具備儲存能力,成為 Web 應用的虛擬補丁。在本例中,iFlow 在載入訂單支付程式碼時生成並加入一次性隨機令牌,在提交訂單時檢查這個令牌的存在。

2.1 正常使用者訪問

使用者在訪問確認訂單頁面時,瀏覽器自動載入處理訂單支付的 JS 程式碼 (payment_orders.js)。iFlow 截獲這段程式碼的響應返回,生成一個隨機令牌儲存在本地儲存中,並修改 JS 程式碼將隨機令牌加入到 AJAX 傳送列表中。使用者在點選提交訂單按鈕時,JS 程式碼發出 AJAX 請求將隨機令牌隨同訂單資訊一起發出,iFlow 截獲請求,檢查引數中的令牌是否與儲存的令牌一致,並清除本地儲存中儲存的令牌。對於一個正常使用者來說,它們一定是相同的,於是 iFlow 去掉令牌引數,將僅包含訂單資訊的請求發往 Web 伺服器處理。

正常使用者的 HTTP 協議互動過程如下:

sequenceDiagram participant 正常使用者 participant 瀏覽器 participant iFlow participant Web伺服器 正常使用者->>瀏覽器: 選擇購買商品 瀏覽器->>Web伺服器: 請求:/index.php?s=/member/paymentorder Web伺服器->>瀏覽器: 返回:確認訂單頁面 瀏覽器->>Web伺服器: 請求:/js/payment_orders/payment_orders.js Web伺服器->>iFlow: 返回:payment_orders.js Note over iFlow: 生成隨機令牌並儲存 rect rgb(160, 250, 160) iFlow->>瀏覽器: 修改:AJAX傳送列表中加入令牌 end 瀏覽器->>正常使用者: 顯示:確認訂單頁面 正常使用者->>瀏覽器: 點選提交訂單按鈕 瀏覽器->>iFlow: js傳送AJAX:訂單資訊及令牌 rect rgb(160, 250, 160) Note over iFlow: 與儲存的令牌比對:通過 Note over iFlow: 清除儲存的令牌 end iFlow->>Web伺服器: 提交訂單資訊 Note over Web伺服器: 系統生成正常訂單 Web伺服器->>瀏覽器: 重定向頁面:訂單已生成 瀏覽器->>正常使用者: 顯示:訂單已生成

2.2 攻擊者訪問

如前所示,攻擊者記錄下正常操作時的提交訂單的請求報文,然後用工具重放這段報文。由於在第一次正常提交後,iFlow 已經清除了本地儲存中儲存的令牌,因此後續的重複提交被 iFlow 拒絕。

攻擊者的 HTTP 協議互動過程如下:

sequenceDiagram participant 攻擊者 participant 瀏覽器 participant 攻擊工具 participant iFlow participant Web伺服器 攻擊者->>瀏覽器: 選擇購買商品 瀏覽器->>Web伺服器: 請求:/index.php?s=/member/paymentorder Web伺服器->>瀏覽器: 返回:確認訂單頁面 Web伺服器->>iFlow: 返回:payment_orders.js Note over iFlow: 生成隨機令牌並儲存 rect rgb(160, 250, 160) iFlow->>瀏覽器: 修改:AJAX傳送列表中加入令牌 end Web伺服器->>瀏覽器: 返回:payment_orders.js 瀏覽器->>攻擊者: 顯示:確認訂單頁面 攻擊者->>瀏覽器: 點選提交訂單按鈕 瀏覽器->>攻擊工具: js傳送AJAX:訂單資訊及令牌 rect rgb(250, 128, 128) Note over 攻擊工具: 記錄請求報文 end 攻擊工具->>iFlow: 傳送AJAX:訂單資訊及令牌 rect rgb(160, 250, 160) Note over iFlow: 與儲存的令牌比對:通過 Note over iFlow: 清除儲存的令牌 end iFlow->>Web伺服器: 提交訂單資訊 Note over Web伺服器: 系統生成正常訂單 Web伺服器->>瀏覽器: 重定向頁面:訂單已生成 瀏覽器->>攻擊者: 顯示:訂單已生成 Loop 重複 rect rgb(250, 128, 128) 攻擊者->>攻擊工具: 傳送已記錄的請求報文 end 攻擊工具->>iFlow: 提交訂單資訊+令牌 rect rgb(250, 128, 128) Note over iFlow: 檢查儲存的令牌:無 end iFlow->>攻擊工具: 中斷連線 end

2.3 程式碼

iFlow 內建的 W2 語言是一種專門用於實現 Web 應用安全加固的類程式語言。它介於配置和通用語言之間,具備程式設計的基本要素和針對 HTTP 協議的特有擴充套件,能為業務系統編寫涉及複雜判斷和動態修改的邏輯。

考慮到安全產品的使用者通常為非程式設計師,他們習慣面對配置檔案而非一段程式碼。因此,W2 語言雖包含語言要素,仍以規則檔案方式呈現,並採用可以體現層次結構和方便詞法校驗的 JSON 格式。

用 W2 語言實現上述虛擬補丁的程式碼如下:

[
    {
        "if": "REQUEST_FILENAME == '/js/payment_orders/payment_orders.js'",
        "then": {
            "execution": [
                "TX.raw_token = md5(random())",
                "SESSION.order_token@300 = TX.raw_token",
                "TX.js_part = '\"' .. TX.raw_token .. '\"'",
                {
                    "directive": "alterResponseBody",
                    "op": "string",
                    "target": "'leavemessage' : leavemessage,", 
                    "substitute": "'leavemessage' : leavemessage, 'order_token' : ${TX.js_part},"
                }
            ]
        }
    },
    {
        "if": [
            "REQUEST_METHOD == 'POST'",
            "REQUEST_FILENAME == '/index.php'",
            "@ARGS.s == '/order/ordercreate'"
        ],
        "then": {
            "if": "SESSION.order_token",
            "then": {
                "if": "@ARGS.order_token != SESSION.order_token",
                "then": {
                    "verdict": {
                        "action": "drop",
                        "log": "${@ARGS.order_token} is not equal to ${SESSION.order_token}!"
                    }
                },
                "else": [
                    "SESSIOIN.order_token = null",
                    {
                        "directive": "alterArgPost",
                        "op": "unset",
                        "name": "order_token"
                    }
                ]
            },
            "else": {
                "verdict": {
                    "action": "drop",
                    "log": "${SESSION.order_token} is not exist!"
                }
            }
        }
    }
]

示例程式碼中有兩條規則,分別作用如下:

第一條規則

當瀏覽器請求 payment_orders.js 時,iFlow 攔截響應報文。它首先生成一個隨機令牌 raw_token 並將其存放在會話 (SESSION) 儲存變數 order_token 中,然後修改處理使用者提交訂單的 AJAX 操作,將隨機令牌加入到 POST 的傳送引數列表中。

第二條規則

當使用者執行提交訂單時,JS 發出一個 AJAX 的 POST 請求,iFlow 攔截此請求。它檢查會話 (SESSION) 儲存變數 order_token 和引數中的 order_token,如果前者不存在或者兩者不相等,即判定為非法請求。否則,將儲存變數 order_token 清除,將請求引數 order_token 消除 (以免影響後端應用),然後發給後端 Web 伺服器。

注意:上述會話中的 order_token 標誌是儲存在伺服器端的 iFlow 儲存中的,在瀏覽器端是看不到資料更無法進行偽造的。

三、總結

iFlow 使用兩條規則在不修改伺服器端程式碼的前提下,透明地實現了隨機令牌的一次性發放和使用,避免了簡單的重複提交。

當然,如果攻擊者完全模擬使用者正常操作,重複發起包含前後 2 次會話的攻擊行為,則本文中的規則無法阻擋這種重複提交。但顯然這種行為需要更復雜的攻擊技巧而不只依靠簡單地重放實現,何況,使用 iFlow 還能構建出更復雜的防護策略。(張戈 | 天存資訊)

相關文章