iT邦幫忙

2024 iThome 鐵人賽

DAY 13
0
Security

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

資安這條路:Day 13 探索 NoSQL injection 以及防範措施

  • 分享至 

  • xImage
  •  

前言

隨著網站架構和應用程式越來越依賴 NoSQL 資料庫(例如 MongoDB、CouchDB、Redis 等),NoSQL injection 攻擊變得愈發普及。

NoSQL 資料庫與傳統的 SQL 資料庫相比,儲存結構更加靈活且支援非結構化資料。但這種靈活性也伴隨著風險,尤其是在資料庫查詢未經適當處理的情況下,攻擊者可以注入惡意查詢來竊取資料或執行未經授權的操作。

目錄

  1. NoSQL 基礎
  2. NoSQL injection 基礎
  3. 實作 lab
  4. 攻擊演練
  5. 總結
  6. 小試身手
  7. 實作整理

NoSQL 基礎

NoSQL(Not Only SQL)資料庫是一類非關聯型資料庫,與傳統的 SQL 資料庫(如 MySQL、PostgreSQL)不同,NoSQL 資料庫不依賴固定的表格和欄位架構。這使得 NoSQL 資料庫能夠更靈活地儲存和處理非結構化和半結構化的資料。

NoSQL 資料庫類型

NoSQL 資料庫有多種類型,常見的有以下幾類:

  1. 文件型資料庫(Document Store): 使用 JSON 或 BSON 格式儲存文件,典型代表為 MongoDB。文件資料庫的結構靈活,每個文件可以包含不同的欄位。
  2. 鍵值型資料庫(Key-Value Store): 使用簡單的鍵值對來儲存資料。Redis 和 DynamoDB 是此類型的代表。這種資料庫可以非常高效地進行快速查詢。
  3. 圖形資料庫(Graph Database): 主要用於處理需要儲存關聯資料的應用,例如社交網絡。典型代表為 Neo4j。它能夠有效地查找和分析節點與節點之間的關係。
  4. 資料行資料庫(Column Store): 使用資料行來儲存資料,適合處理大規模讀取和寫入的應用。Apache Cassandra 和 HBase 是此類型的代表。

NoSQL 的優勢

  1. 靈活性: NoSQL 資料庫允許動態擴展資料模型,不需要預先定義的表結構,因此可以快速適應變更。
  2. 水平擴展: NoSQL 資料庫可以輕鬆實現水平擴展,透過增加更多的伺服器來處理大量的資料和請求,而不是僅依賴一個高性能的伺服器。
  3. 高可用性和分佈式架構: 大多數 NoSQL 資料庫設計為分佈式系統,可以提供高可用性和容錯性,甚至在某些節點失效的情況下仍能保持系統運行。

NoSQL 的局限

  1. 缺少標準化: 與 SQL 資料庫不同,NoSQL 資料庫缺乏統一的查詢語言,每個資料庫都有自己特定的查詢方式。
  2. 一致性問題: 由於大多數 NoSQL 資料庫以可用性和分區容錯性為優先(CAP 理論),它們可能在強一致性上有所妥協,這在某些應用場景中可能引發問題。
  3. 學習曲線: 由於 NoSQL 資料庫的類型和查詢方式多樣,學習和使用 NoSQL 可能比 SQL 更具挑戰性。

NoSQL 在 Web 安全中的應用

在現代 Web 應用程式中,NoSQL 資料庫通常被用來處理海量資料,尤其是在高流量和高擴展需求的環境下,例如社交媒體平台、電商網站、即時消息系統等。NoSQL 的非結構化資料儲存方式使得它在處理多樣化的資料時極具優勢,但這也引入了新的安全挑戰,特別是在應對使用者輸入時。

這些特性使得 NoSQL 資料庫在資安領域成為攻擊者的潛在目標,特別是當未經驗證的使用者輸入直接進行查詢時,容易引發 NoSQL injection 攻擊。

接下來,我們將探討 NoSQL injection 攻擊的原理與風險。

NoSQL injection 基礎

NoSQL injection 是指攻擊者透過操控未經妥善處理的查詢語法來改變應用程式的行為。這類攻擊通常發生在開發者未正確驗證或清理使用者輸入時。舉例來說,在 MongoDB 中,查詢語法可能如下所示:

db.users.find({ username: userInput.username, password: userInput.password });

假設 userInput 是從表單接收的使用者輸入,攻擊者可以透過提交特殊格式的輸入來操控查詢,例如:

{ "$ne": null }

這會使條件無效,允許攻擊者繞過認證,取得所有使用者資料。

實作 lab:建立 NoSQL Injection 攻擊測試環境

目標

在這個 lab 中,將學習如何利用 NoSQL injection 攻擊漏洞,將使用 MongoDB 和 Node.js 構建一個簡單的商品搜尋系統,並展示 NoSQL injection 的危害。

本次程式碼連結

https://github.com/fei3363/ithelp_web_security_2024/commit/bb7ea18f8b7462e1c888a5651135d91002720efd

1. 設定 docker-compose.yml

我們將使用 Docker 和 MongoDB 一起執行。這裡的 docker-compose.yml 文件將同時啟動 MongoDB 和 Node.js 應用程式,並確保 MongoDB 在啟動時自動初始化測試資料。

  • 新增 mongo 中 volumes 的 init-mongo.js
version: '3'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      - mongo
    environment:
      MONGODB_URI: mongodb://mongo:27017/myapp
    volumes:
      - .:/usr/src/app
    command: npm start

  mongo:
    image: mongo:4.2
    ports:
      - "27017:27017"
    volumes:
      - mongodb_data:/data/db
      - ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js

volumes:
  mongodb_data:

2. 初始化 MongoDB 資料

init-mongo.js 中,我們將設定一個測試資料庫 myapp 並插入一些商品資料,這樣我們的應用程式可以直接讀取這些資料並執行查詢:

db = db.getSiblingDB('myapp');

db.createCollection('products');

// 插入測試商品資料
db.products.insertMany([
  { name: "筆記型電腦", category: "電子產品", price: 1200, stock: 50 },
  { name: "智慧型手機", category: "電子產品", price: 800, stock: 100 },
  { name: "平板電腦", category: "電子產品", price: 600, stock: 75 },
  { name: "耳機", category: "配件", price: 150, stock: 200 },
  { name: "鍵盤", category: "配件", price: 100, stock: 150 },
  { name: "咖啡杯", category: "家居", price: 20, stock: 300 }
]);

3. 商品搜尋系統

我們將實作一個簡單的商品搜尋功能,這個功能存在 NoSQL injection 的漏洞,使用者可以透過未經處理的輸入進行惡意查詢。

  • db/mongoose.js
    • 資料庫連線資訊與測試資訊
// db/mongoose.js

const mongoose = require('mongoose');

// 連接 MongoDB
const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true
    });
    console.log('Connected to MongoDB');
  } catch (err) {
    console.error('Failed to connect to MongoDB', err);
    process.exit(1);
  }
};

module.exports = connectDB;
  • models/product.model.js
    • 資料表架構
// models/product.model.js

const mongoose = require('mongoose');

const productSchema = new mongoose.Schema({
  name: String,
  category: String,
  price: Number,
  stock: Number
});

const Product = mongoose.model('Product', productSchema);

module.exports = Product;
  • controllers/productController.js
    • API 資訊
// controllers/productController.js

const Product = require('../models/product.model');

// 列出所有商品
exports.getAllProducts = async (req, res) => {
  try {
    const products = await Product.find();
    res.json(products);
  } catch (error) {
    res.status(500).send("伺服器錯誤");
  }
};

// 商品搜尋 API,存在 NoSQL injection 漏洞
exports.searchProducts = async (req, res) => {
  const query = req.query.q;
  console.log('搜尋條件:', query);
  
  try {
    // 使用未經驗證的查詢字串,容易被注入惡意查詢
    const products = await Product.find({ name: new RegExp(query, 'i') });
    console.log('搜尋結果:', products);
    res.json(products);
  } catch (error) {
    res.status(500).send("伺服器錯誤");
  }
};
  • web/routes/productRoutes.js
// routes/productRoutes.js
const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');
// 路由設置
router.get('/list', productController.getAllProducts);
router.get('/search', productController.searchProducts);
module.exports = router;

NoSQL Injection 攻擊演練

1. 正常搜尋範例

假設我們想要搜尋商品名稱中包含 "筆記型電腦" 的商品,我們可以發送如下請求:

curl "http://nodelab.feifei.tw/api/product/search?q=筆記型電腦"

image

這將回傳符合條件的商品。

2. 利用 NoSQL injection 攻擊

我們可以嘗試發送以下惡意請求,試圖透過 MongoDB 的查詢運算符進行注入:

curl "http://nodelab.feifei.tw/api/product/search?q=%27%20||%201==1//"

image

  1. 查詢語句:

    curl "http://nodelab.feifei.tw/api/product/search?q=%27%20||%201==1//"
    

    這個查詢包含了惡意的 NoSQL 查詢條件。具體解釋如下:

    • %27 是 URL 編碼,代表單引號('),這是查詢字串的開頭。
    • || 是邏輯運算符,表示 "或"。
    • 1==1 是邏輯運算,因為這個條件永遠為真,因此無論輸入什麼,條件都會成立。
    • // 用來註解掉後續程式碼,這樣可以忽略掉後面的部分。
  2. 結果解釋:
    在 MongoDB 中,當 find() 查詢條件不安全地使用未經處理的輸入時,這樣的查詢條件會被直接應用到查詢中。由於 1==1 永遠為真,這意味著無論實際條件是什麼,查詢都會回傳所有商品,而不僅僅是符合特定名稱的商品。

  3. 攻擊效果:
    該攻擊成功繞過了應用程式的邏輯,將所有資料庫中的商品回傳,而不是僅僅回傳名稱匹配的結果。這表明應用程式未能正確地處理使用者的輸入,從而暴露出潛在的 NoSQL injection 漏洞。

  4. 圖像說明:
    圖像中展示了攻擊的結果,攻擊者成功回傳了所有的商品資料,而不是僅僅基於名稱搜尋的結果,這是由於應用程式中的查詢邏輯被注入攻擊操控。

攻擊帶來的風險:

這種 NoSQL injection 攻擊會導致敏感資料暴露,攻擊者能夠取得系統中所有的資料,這在實際應用中是非常危險的。如果這是使用者資料或其他機密資料,攻擊者可以通過這類漏洞取得並利用資料,造成資料洩露甚至更嚴重的後果。

防禦措施

為了防止這樣的攻擊,我們可以採取以下防禦措施:

  1. 輸入驗證與清理: 檢查和驗證所有的使用者輸入,防止惡意查詢條件被注入。
  2. 參數化查詢: 不直接將使用者輸入嵌入到查詢中,應使用參數化的查詢。

修改 productController.js 中的 searchProducts 函數以防禦 NoSQL injection:

exports.searchProducts = async (req, res) => {
  const query = req.query.q;
  
  // 檢查輸入是否為空或無效
  if (!query || typeof query !== 'string') {
    return res.status(400).send("無效的搜尋條件");
  }
  
  console.log('安全搜尋條件:', query);
  
  try {
    // 使用安全查詢,避免注入
    const products = await Product.find({ name: { $regex: new RegExp(query, 'i') } });
    console.log('搜尋結果:', products);
    res.json(products);
  } catch (error) {
    res.status(500).send("伺服器錯誤");
  }
};

這樣的修改可以防止惡意的 NoSQL injection,因為查詢條件不再直接接受使用者的 JSON 輸入,而是透過正則運算式進行安全的字串匹配。

小結

在這個 lab 中,我們透過一個簡單的商品搜尋系統,展示了 NoSQL injection 的風險,並展示了如何防止這類攻擊。開發者在處理 NoSQL 資料庫時,應特別注意輸入的驗證與處理,以防止資料庫查詢被惡意操控。

防範措施

為了防範 NoSQL injection,以下幾個開發方向可以幫助開發者加強應用程式的安全性:

  1. 輸入驗證與清理:無論是 SQL 還是 NoSQL 資料庫,未經清理的使用者輸入都是系統面臨的最大威脅。務必使用可靠的驗證機制來確保輸入符合預期格式。例如,對於使用者名與密碼,應該強制執行長度限制與格式要求。
  2. 使用參數化查詢:透過參數化查詢,開發者可以將使用者輸入與查詢邏輯分離,避免直接將使用者輸入嵌入查詢語句中。例如,MongoDB 支援將查詢條件分開處理,減少潛在的注入風險。
  3. 最小權限原則:確保資料庫帳號僅具有必要的讀寫權限,並且每個功能的權限設定應該嚴格遵守最小化原則,這樣即使發生注入攻擊,也能將影響範圍縮至最小。
  4. 安全性檢查與審計:定期進行程式碼審查與安全性測試,使用像 npm audit 等工具來檢測可能存在的安全漏洞,並針對 NoSQL 查詢語法進行靜態分析。

總結

在本篇文章中,我們深入探討了 NoSQL 資料庫的基本概念及其應用,並透過實作展示了 NoSQL injection 的潛在風險。我們還分析了如何利用 NoSQL injection 漏洞進行攻擊,並詳細說明了如何防止這些攻擊。隨著 NoSQL 資料庫在現代 Web 應用中越來越廣泛的使用,理解和防範 NoSQL injection 攻擊已成為開發者的必備技能之一。

透過這個實作 lab,我們學習了如何:

  1. 利用 MongoDB 和 Node.js 建立商品搜尋系統。
  2. 演示 NoSQL injection 攻擊的運行方式。
  3. 介紹防止 NoSQL injection 攻擊的最佳實踐,包括輸入驗證和參數化查詢。

小試身手

  1. 哪一個 NoSQL 資料庫類型最適合處理大量的非結構化資料?

    • A. 鍵值型資料庫
    • B. 文件型資料庫
    • C. 圖形資料庫
    • D. 資料行資料庫

    答案:B. 文件型資料庫
    解析: 文件型資料庫(如 MongoDB)使用 JSON 或 BSON 格式來儲存資料,非常適合處理非結構化或半結構化資料。

  2. 以下哪個查詢語句最容易受到 NoSQL injection 攻擊?

    • A. db.users.find({ username: userInput.username })
    • B. db.users.find({ username: { $eq: userInput.username } })
    • C. db.users.find({ password: { $regex: userInput.password } })
    • D. db.users.find({ username: { $eq: userInput.username, password: userInput.password } })

    答案:C. db.users.find({ password: { $regex: userInput.password } })
    解析: 使用正則表達式($regex)且未經處理的使用者輸入時,會使應用程式容易受到 injection 攻擊。

  3. 哪個防禦措施對防止 NoSQL injection 攻擊最有效?

    • A. 限制資料庫的寫入權限
    • B. 檢查使用者輸入的格式和長度
    • C. 使用參數化查詢
    • D. 定期更新資料庫版本

    答案:C. 使用參數化查詢
    解析: 參數化查詢將使用者輸入與查詢邏輯分離,是防止注入攻擊最有效的方法。

  4. 什麼情況下會發生 NoSQL injection 攻擊?

    • A. 當開發者使用參數化查詢時
    • B. 當使用者的輸入沒有經過驗證和清理時
    • C. 當資料庫使用強一致性模型時
    • D. 當應用程式使用關聯型資料庫時

    答案:B. 當使用者的輸入沒有經過驗證和清理時
    解析: NoSQL injection 攻擊主要發生在未經妥善處理的使用者輸入被直接插入到查詢語句中。

  5. 下列哪項是預防 NoSQL injection 攻擊的關鍵措施之一?

    • A. 限制資料庫的讀取權限
    • B. 對使用者輸入進行過濾和驗證
    • C. 使用 NoSQL 資料庫的預設設定
    • D. 在伺服器上啟用防火牆

    答案:B. 對使用者輸入進行過濾和驗證
    解析: 驗證與清理使用者的輸入是預防 NoSQL injection 攻擊的關鍵措施之一,防止不合法的查詢進入系統。

實作清單

  1. 環境設置

    • 安裝 Docker 和 Docker Compose
    • 準備 docker-compose.yml 文件來啟動 MongoDB 和 Node.js 應用程式
    • 初始化 MongoDB 資料,插入測試資料
  2. 應用實作

    • 建立商品搜尋系統,包含 MongoDB 資料庫連接、模型定義和查詢功能
    • 設計 API 來實現商品搜尋功能並展示 NoSQL injection 的攻擊場景
    • 使用 Curl 測試正常查詢和惡意查詢
  3. 防禦措施實作

    • 改進搜尋 API,增加輸入驗證和清理功能
    • 確保所有查詢都透過參數化查詢執行,避免使用者輸入直接操作查詢邏輯
  4. 測試與驗證

    • 測試防禦措施是否有效,嘗試注入攻擊並驗證應用程式是否能正確防禦
    • 測試正常查詢的功能,確保應用程式功能在防禦措施啟用後仍能正常工作

上一篇
資安這條路:Day 12 探索 ORM 機制與網站檔案上傳漏洞
系列文
資安這條路:系統化學習網站安全與網站滲透測試13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言