iT邦幫忙

2024 iThome 鐵人賽

DAY 24
1
Security

資安這條路:系統化學習網站安全與網站滲透測試系列 第 24

資安這條路:Day 24 WebSocket 與商業邏輯漏洞

  • 分享至 

  • xImage
  •  

WebSocket

什麼是 WebSocket

WebSocket 是一種能在單一 TCP 連線上提供全雙工通訊的協定。它讓客戶端和伺服器之間能進行持續性的雙向通訊,特別適合需要即時更新的應用程式,例如線上遊戲、聊天軟體和即時資料展示。

WebSocket 的主要特點包括:

  1. 持久連線:一旦建立連線,就能保持開啟狀態,不需要重複建立。
  2. 全雙工通訊:客戶端和伺服器可以同時傳送和接收資料。
  3. 低延遲:相較於輪詢或長輪詢,WebSocket 可以達成更低的延遲。

規範:

  • 2011 年由 IETF(網際網路工程任務小組)標準化為 RFC 6455
  • 由 RFC 7936 提供補充規範

全雙工通訊

全雙工通訊(Full-duplex Communication)是一種通訊方式,
允許資料在兩個方向上同時傳輸。

特點

  1. 雙向同時:資料可以同時在兩個方向上傳送和接收。
  2. 效率高:不需要等待一方完成傳輸才能回應。
  3. 即時性:適合需要即時互動的應用。

比喻

想像一條雙向車道,車輛可以同時在兩個方向上行駛,
不需要等待對向車道淨空。

應用場景

  • 網路電話(VoIP)
  • 線上視訊會議
  • 即時多人線上遊戲

輪詢(Polling)

輪詢是一種客戶端定期向伺服器請求更新的技術。

特點

  1. 週期性:客戶端按固定間隔發送請求。
  2. 主動性:由客戶端發起請求。
  3. 資源消耗:可能會造成不必要的網路流量和伺服器負擔。

類型

  1. 短輪詢(Short Polling):客戶端頻繁地發送請求。
  2. 長輪詢(Long Polling):伺服器在沒有更新時保持連線開啟,直到有新資料或超時。

比喻

一個學生(客戶端)不斷詢問老師(伺服器)「有新課程(訊息)嗎?」
即使大多數時候答案是「沒有」。

應用場景

  • 舊式聊天系統
  • 簡單的通知系統
  • 不要求極高即時性的資料更新

比較

相較於輪詢,全雙工通訊(如 WebSocket)提供了更有效率和即時的通訊方式。輪詢可能會導致延遲和不必要的資源消耗,而全雙工通訊允許即時、雙向的資料流動,特別適合需要即時更新的應用場景。

WebSocket 可能存在哪些安全漏洞?

WebSocket 如果使用不當,可能會出現以下安全漏洞:

  1. 身分驗證和授權不足
    • 若沒有適當的身分驗證機制
      • 未經授權的使用者可能連上 WebSocket 伺服器
    • 例子
      • 線上遊戲使用 WebSocket 進行即時通訊
      • 但伺服器沒有加入身分驗證
      • 結果讓駭客輕易地連上伺服器,竊聽其他玩家的對話
  2. 跨站 WebSocket 劫持(CSWSH)
    • 類似於 CSRF 攻擊
      • 駭客可能誘使受害者的瀏覽器與惡意伺服器建立 WebSocket 連線
    • 例子
      • 受害者不小心點擊了一個惡意連結
      • 該連結自動在背景與駭客的 WebSocket 伺服器建立連線
      • 導致駭客可以讀取受害者在其他分頁中的敏感資訊
  3. 資料外洩
    • 若敏感資料透過不安全的 WebSocket 連線
      • ws:// 而非 wss:// 傳輸
      • 可能被中間人攻擊者攔截
    • 例子
      • 線上聊天室的開發者使用不安全的 ws:// 協定
      • 受害者在咖啡廳使用公共 Wi-Fi 時
      • 受害者聊天內容被附近的駭客攔截並竊取
  4. 阻斷服務攻擊(DoS)
    • 駭客可能建立大量 WebSocket 連線
      • 耗盡伺服器資源
    • 例子
      • 股票資訊網站使用 WebSocket 提供即時報價
      • 競爭對手利用機器人建立了上萬個 WebSocket 連線
      • 導致伺服器超載當機
      • 其他使用者無法連線
  5. 輸入驗證不足
    • 若伺服器沒有妥善驗證透過 WebSocket 接收的資料
    • 可能導致注入攻擊或其他安全問題
    • 例子
      • 網站允許使用者傳送自訂的 HTML 訊息
      • 駭客利用這點傳送含有惡意 JavaScript 的訊息
      • 導致其他使用者的瀏覽器執行了這段惡意程式碼

為了避免這些漏洞,開發者應該實施嚴格的身分驗證、使用安全的 wss:// 協定、限制連線數量、仔細驗證所有輸入資料,並確保整體系統的安全性。定期的安全稽核和滲透測試也是確保 WebSocket 應用程式安全的重要措施。

商業邏輯漏洞

是什麼

商業邏輯漏洞是指應用程式的設計或實現中存在的缺陷,
這些缺陷允許用戶以意料之外的方式操作系統,
可能導致未授權的行為或對系統造成損害。

這些漏洞通常與應用程式的特定業務流程或功能相關。

常見的商業邏輯漏洞有哪些

商業邏輯漏洞可能會造成系統安全性的重大問題,以下是幾種常見的情況:

  1. 權限提升
    • 使用者能夠存取或修改本來沒有權限的資源
    • 例子
      • 駭客發現某網路書店的漏洞
      • 透過修改網址中的使用者 ID
      • 成功存取其他會員的訂單資料
      • 甚至修改管理員的個人資料
  2. 繞過作業流程
    • 使用者能夠跳過正常的業務流程步驟
    • 例子
      • 資安檢測人員模擬買家在某購物網站下單後
      • 發現可以直接修改訂單狀態為「已出貨」
      • 而不需等待賣家確認和實際出貨
  3. 資源濫用
    • 系統允許使用者以不合理的方式使用資源
    • 例子
      • 攻擊者發現某論壇網站沒有限制註冊次數
      • 於是寫了一個程式
      • 在短時間內註冊了上萬個帳號
      • 導致網站伺服器超載
  4. 資料驗證不足
    • 系統接受不合理或不一致的資料輸入
    • 例子
      • 一位使用者在網路銀行轉帳時
      • 發現可以輸入負數金額
      • 輸入 -1000 元後
      • 銀行系統誤將其視為存款而非扣款
  5. 競爭條件(Race Condition)
    • 在執行緒同時執行中,系統未正確處理時序(事件發生的順序和時間關係)問題
      • 比喻
        • 忙碌餐廳
        • 廚師(處理程式)同時準備多道菜餚
        • 服務生(執行緒)同時接待不同的客人
        • 收銀台(共享資源)同時處理多筆帳單
    • 例子
      • 參與者在某購物網站的限量特賣活動中
      • 發現快速重複點擊「確認購買」按鈕
      • 可以在系統來不及更新庫存的情況下
      • 購買到超過限購數量的商品
  6. 價格操縱
    • 在電子商務系統中,使用者能夠不正當地影響價格或折扣
    • 例子
      • 消費者在某網路商城發現
      • 當購物車中商品總價接近滿額折扣門檻時
      • 加入一個很便宜的商品後再移除
      • 就能觸發折扣但實際消費未達標準
  7. 庫存管理問題
    • 系統允許不合理的庫存操作
    • 例子
      • 駭客在某網路商店發現可以將商品庫存設為負數
      • 因此超賣商品,接受比實際庫存更多的訂單

這些商業邏輯漏洞往往不像技術漏洞那樣容易被自動化工具發現,
需要開發者和測試人員仔細審查系統流程,
確保每個步驟都有適當的檢查和限制。

程式碼實作

程式碼

https://github.com/fei3363/ithelp_web_security_2024/commit/10da3fd3a8d56ee4a56a389601e0ad3f680a6ddb

controllers/inventoryController.js

  • 檔案名稱與路徑:/web/controllers/inventoryController.js
  • 程式碼內容:
const { broadcastInventoryUpdate } = require('../websocket');

let inventory = {
  'item1': 100,
  'item2': 200,
  'item3': 300
};

const inventoryController = {
    getALLInventory(req, res) {
        res.json(inventory);
    },

    updateInventory(req, res) {
        const { item, quantity } = req.body;
        if (item && quantity !== undefined) {
            inventory[item] = quantity;
            broadcastInventoryUpdate({ item, quantity });
            res.json({ message: '庫存已更新', item, quantity });
        } else {
            res.status(400).json({ error: '無效的請求' });
        }
    },
};

module.exports = inventoryController;
  • 目的
    • 實現庫存管理的核心邏輯
    • 提供取得所有庫存和更新庫存的功能
  • 關鍵點
    • 使用 broadcastInventoryUpdate 函數來及時廣播庫存更新
    • 使用簡單的物件結構模擬庫存資料
  • 潛在問題
    • 缺乏輸入驗證,可能導致不合法的庫存值(如負數)
    • 沒有處理並行更新的邏輯,可能導致資料不一致
    • 直接修改全域變數 inventory 物件可能導致意外的副作用

package.json

  • 檔案名稱與路徑:/web/package.json
  • 程式碼內容(部分):
{
  "dependencies": {
    // ...其他相依特建
    "ws": "^8.6.0"
  }
}
  • 目的
    • 新增 WebSocket 庫的相依性
  • 關鍵點
    • 使用 "ws" 來實現 WebSocket 功能
  • 潛在問題
    • 依賴特定版本可能導致未來的兼容性問題

routes/inventoryRoutes.js

  • 檔案名稱與路徑:/web/routes/inventoryRoutes.js
  • 程式碼內容:
const express = require('express');
const router = express.Router();
const inventoryController = require('../controllers/inventoryController');

router.get('/', inventoryController.getALLInventory);
router.post('/update', inventoryController.updateInventory);

module.exports = router;
  • 目的
    • 定義庫存管理相關的 API 路由
  • 關鍵點
    • 使用 Express 路由器組織 API 端點
    • 將請求處理邏輯委託給 inventoryController
  • 潛在問題
    • 缺乏中間 Middleware 來處理身份驗證和授權
    • 沒有處理可能的路由錯誤

server.js

  • 檔案名稱與路徑:/web/server.js
  • 程式碼內容(部分):
const WebSocket = require('ws');
const http = require('http');
const { initWebSocket } = require('./websocket');
const inventoryRoutes = require('./routes/inventoryRoutes');

// ... 其他程式碼 ...

app.use('/api/inventory', inventoryRoutes);

const server = http.createServer(app);
initWebSocket(server);

server.listen(port, () => {
  console.log(`HTTP and WebSocket server running at http://localhost:${port}`);
});
  • 目的
    • 整合 WebSocket 功能到現有的 Express 應用中
    • 新增庫存管理路由
  • 關鍵點
    • 使用 http.createServer 建立可同時支持 HTTP 和 WebSocket 的伺服器
    • 呼叫 initWebSocket 初始化 WebSocket 伺服器
  • 潛在問題:
    • 沒有錯誤處理機制來處理 WebSocket 初始化失敗的情況
    • 所有路由和 Middleware 都在同一個檔案中,可能導致維護困難

websocket.js

  • 檔案名稱與路徑:/web/websocket.js
  • 程式碼內容:
const WebSocket = require('ws');

let wss;
const clients = new Set();

function initWebSocket(server) {
  wss = new WebSocket.Server({ server });

  wss.on('connection', (ws) => {
    clients.add(ws);
    console.log('新的 WebSocket 連接');

    ws.on('close', () => {
      clients.delete(ws);
      console.log('WebSocket 連接關閉');
    });
  });
}

function broadcastInventoryUpdate(update) {
  const message = JSON.stringify(update);
  clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
}

module.exports = { initWebSocket, broadcastInventoryUpdate };
  • 目的
    • 實現 WebSocket 伺服器功能
    • 提供廣播更新的機制
  • 關鍵點
    • 使用 Set 來管理 WebSocket 連接
    • 提供 broadcastInventoryUpdate 函數來向所有連接的客戶端發送更新
  • 潛在問題
    • 沒有實現身份驗證或授權機制
    • broadcastInventoryUpdate 函數沒有錯誤處理,可能因單個客戶端錯誤而中斷整個廣播過程
    • 全域變數 wss 變數可能導致測試困難和潛在的並行問題

測試與實作步驟

步驟 1: 下載和準備 WebSocket 客戶端工具

  • 指令
curl -O -L https://github.com/vi/websocat/releases/download/v1.13.0/websocat.x86_64-unknown-linux-musl
  1. 指令目的:下載 WebSocket 客戶端工具 "websocat",用於測試 WebSocket 連接。
  2. 指令結果:成功下載 websocat 執行檔。
  3. 指令截圖:下載 websocat
  4. 其他
    websocat 是一個 WebSocket 客戶端指令介面工具,適合用於測試和調試 WebSocket 服務。

步驟 2: 檢查下載的檔案並設定執行權限

  • 指令
ls -al websocat.x86_64-unknown-linux-musl
chmod +x websocat.x86_64-unknown-linux-musl
  1. 指令目的:檢查下載的檔案並給予執行權限。
  2. 指令結果:顯示檔案詳細資訊,並成功新增執行權限。
  3. 指令截圖:設定執行權限
  4. 其他:確保檔案具有正確的權限是執行下載的執行檔的必要步驟。

步驟 3: 建立 WebSocket 連接並監聽更新

  • 指令:
./websocat.x86_64-unknown-linux-musl ws://nodelab.feifei.tw
  1. 指令目的:建立與 WebSocket 伺服器的連接,並監聽來自伺服器的訊息。
  2. 指令結果:成功連接並接收到多條庫存更新訊息:
    {"item":"item1","quantity":50}
    {"item":"item1","quantity":0}
    {"item":"<h1>item1</h1>","quantity":0}
    {"item":"item2","quantity":-1000}
    
  3. 指令截圖:連接並監聽更新
  4. 其他
    • 這些訊息代表 WebSocket 連接成功建立,並且伺服器正在廣播庫存更新。
    • 注意到一些潛在的問題:允許將庫存設為 0 或負數,以及接受 HTML 標籤作為商品名稱。

步驟 4: 檢查初始庫存狀態

  • 指令:
curl http://nodelab.feifei.tw/api/inventory && echo
  1. 指令目的:取得目前的庫存狀態。
  2. 指令結果:
    {"item1":100,"item2":200,"item3":300}
    
  3. 指令截圖:image
  4. 其他:這顯示初始的庫存狀態,所有商品都有正常的數量。

步驟 5: 更新商品 item1 的庫存為 50

  • 指令:
curl -X POST http://nodelab.feifei.tw/api/inventory/update -H "Content-Type: application/json" -d '{"item": "item1", "quantity": 50}' && echo
  1. 指令目的:測試正常的庫存更新功能,將 item1 的數量更新為 50。
  2. 指令結果:
    {"message":"庫存已更新","item":"item1","quantity":50}
    
  3. 指令截圖:image
  4. 其他
    • 更新成功,系統接受庫存數量
    • 任何人都可以更新庫存

步驟 6: 將商品 item1 的庫存設定為 0

  • 指令:
curl -X POST http://nodelab.feifei.tw/api/inventory/update -H "Content-Type: application/json" -d '{"item": "item1", "quantity": 0}' && echo
  1. 指令目的:測試將庫存設定為零的情況,這在某些系統中可能被視為無效操作。

  2. 指令結果:

    {"message":"庫存已更新","item":"item1","quantity":0}
    
  3. 指令截圖:image

  4. 其他

    • 系統允許將庫存設定為零
    • 需要考慮是否應該允許零庫存,或是否應該有特殊的處理邏輯(如標記為缺貨)

步驟 7: 新增帶有 HTML 標籤的新商品

指令:

curl -X POST http://nodelab.feifei.tw/api/inventory/update -H "Content-Type: application/json" -d '{"item": "<h1>item1</h1>", "quantity": 0}' && echo
  1. 指令目的:測試系統對特殊字串(如 HTML 標籤)的處理,以及新增新商品的權限控制。
  2. 指令結果:
    {"message":"庫存已更新","item":"<h1>item1</h1>","quantity":0}
    
  3. 指令截圖:image
  4. 其他
    • 系統接受了包含 HTML 標籤的商品名稱,是一個嚴重的安全隱患
    • 可能導致跨站腳本攻擊(XSS)
    • 系統允許新增新的商品,沒有進行權限檢查

步驟 8: 將商品 item2 的庫存設定為負數

  • 指令:
curl -X POST http://nodelab.feifei.tw/api/inventory/update -H "Content-Type: application/json" -d '{"item": "item2", "quantity": -1000}' && echo
  1. 指令目的:測試系統是否允許設定負數庫存,正常可能為無效行為
  2. 指令結果:
    {"message":"庫存已更新","item":"item2","quantity":-1000}
    
  3. 指令截圖:image
  4. 其他
    • 系統允許將庫存設定為負數,這是一個嚴重的邏輯錯誤
    • 負數庫存在大多數實際應用中是沒有意義的,可能導致庫存管理混亂

總結

目前庫存系統實作中的多個安全漏洞和邏輯錯誤,涉及 WebSocket 和商業邏輯兩個層面。

由於 WebSocket 的即時特性,這些問題對所有連線的使用者都是可見的:

  1. WebSocket 安全問題
    • 缺乏身分驗證:任何人都可以建立 WebSocket 連線並接收更新,無需驗證身分
    • 資訊洩漏:所有連線的客戶端都能看到所有的庫存更新,可能洩漏敏感的業務資訊
    • 連線管理不當:沒有限制單一 IP 或使用者可以建立的 WebSocket 連線數量,可能被用於 DoS 攻擊
  2. 缺乏輸入驗證
    • 系統接受任何輸入作為有效的商品名稱和數量
    • 這些不當的輸入會立即透過 WebSocket 廣播給所有客戶端,所有人都能看到
  3. 允許不合理的庫存值
    • 包括零和負數庫存
    • 這些異常值會即時透過 WebSocket 通知所有客戶端,可能導致庫存管理混亂
  4. 潛在 XSS 漏洞
    • 接受 HTML 標籤作為商品名稱
    • 這些標籤會透過 WebSocket 傳送給所有客戶端,可能在所有連線的客戶端上執行惡意腳本
  5. 缺乏授權檢查
    • 允許隨意新增新商品和修改現有商品庫存
    • 這些變更會即時傳播給所有 WebSocket 客戶端,惡意使用者可能操縱庫存資訊
  6. 業務邏輯問題
    • 未對特殊情況(如零庫存或負庫存)進行適當處理
    • 這些異常狀況會透過 WebSocket 通知所有客戶端,可能導致訂單處理錯誤
  7. 競爭條件
    • 在高並行情況下,可能出現庫存不一致的問題
    • 多個客戶端同時更新同一商品庫存時,可能導致數據不一致,所有客戶端都能看到這種不一致
  8. 缺乏資料過濾
    • 所有庫存更新都被廣播給所有連線的客戶端,沒有根據使用者權限進行過濾
    • 競爭對手可能利用這點獲得即時的庫存資訊

小試身手

  1. 在給定的 WebSocket 實作中,以下哪項不是一個安全隱患?
    A. 缺乏身分驗證機制
    B. 使用 Set 來管理 WebSocket 連線
    C. 允許任何客戶端連線並接收所有更新
    D. 沒有限制單一 IP 的連線數量

    答案:B

    解析:使用 Set 來管理 WebSocket 連線是一個好的做法,它能有效地管理連線並避免重複。其他選項都是安全隱患:缺乏身分驗證可能導致未經授權的存取,允許所有客戶端接收所有更新可能導致敏感資訊外洩,而不限制連線數量可能被用於阻斷服務(DoS)攻擊。

  2. 在更新庫存的功能中,哪一項操作可能導致最嚴重的安全問題?
    A. 將商品庫存設定為 0
    B. 更新不存在的商品
    C. 將商品名稱設定為 "<script>alert('XSS')</script>"
    D. 將商品庫存設定為負數

    答案:C

    解析:雖然所有這些操作都可能導致問題,但將商品名稱設定為包含 JavaScript 程式碼的 HTML 標籤是最危險的,因為它可能導致跨網站指令碼攻擊(XSS)。這種攻擊可能影響所有查看庫存資訊的使用者,造成廣泛的安全威脅。其他選項主要是邏輯或資料完整性問題,雖然也需要處理,但不會直接導致程式碼執行的安全漏洞。

  3. 在目前的實作中,為什麼允許將庫存設定為負數是一個問題?
    A. 這會導致系統當機
    B. 這違反了基本的業務邏輯
    C. 這會使 WebSocket 連線中斷
    D. 這會自動觸發訂單取消

    答案:B

    解析:允許負數庫存違反了基本的業務邏輯。在現實世界中,庫存不可能為負數。這種情況可能導致庫存管理混亂、錯誤的銷售預測,以及可能的超賣問題。雖然這不會直接導致系統當機或 WebSocket 連線問題,但它反映了系統在輸入驗證和業務規則執行方面的不足。

  4. 在 WebSocket 實作中,為什麼對所有連線的客戶端廣播所有更新可能是個問題?
    A. 這會增加伺服器的負載
    B. 這可能洩漏敏感的業務資訊
    C. 這會使 WebSocket 連線變慢
    D. 這會導致客戶端記憶體溢位

    答案:B

    解析:雖然廣播所有更新確實可能增加伺服器負載,但最主要的問題是可能洩漏敏感的業務資訊。在一個正式環境中,不同的使用者或客戶端應該只能存取他們有權限看到的資訊。廣播所有更新意味著每個連線的客戶端都能看到系統中的所有庫存變化,這可能包括競爭對手或未經授權的使用者不應該看到的資訊。

  5. 以下哪項不是解決目前系統中的競爭條件(Race Condition)問題的有效方法?
    A. 使用資料庫交易
    B. 實作 Optimistic Locking
    C. 增加伺服器的處理能力
    D. 使用 Atomic Operation

    答案:C

    解析:增加伺服器的處理能力不能有效解決競爭條件問題。競爭條件是由於多個程式同時存取共享資源而導致的邏輯問題,單純增加處理能力並不能解決這個根本問題。相反,使用資料庫交易、實作 Optimistic Locking 或使用 Atomic Operation 都是處理競爭條件的有效方法。這些方法可以確保在高併發情況下,資料的一致性和完整性得到維護。


上一篇
資安這條路:Day 23 利用 Node.js 了解目錄穿越攻擊(Directory Traversal/Path Traversal )
下一篇
資安這條路:Day 25 GraphQL 漏洞實作
系列文
資安這條路:系統化學習網站安全與網站滲透測試30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言