隨著網站架構和應用程式越來越依賴 NoSQL 資料庫(例如 MongoDB、CouchDB、Redis 等),NoSQL injection 攻擊變得愈發普及。
NoSQL 資料庫與傳統的 SQL 資料庫相比,儲存結構更加靈活且支援非結構化資料。但這種靈活性也伴隨著風險,尤其是在資料庫查詢未經適當處理的情況下,攻擊者可以注入惡意查詢來竊取資料或執行未經授權的操作。
NoSQL(Not Only SQL)資料庫是一類非關聯型資料庫,與傳統的 SQL 資料庫(如 MySQL、PostgreSQL)不同,NoSQL 資料庫不依賴固定的表格和欄位架構。這使得 NoSQL 資料庫能夠更靈活地儲存和處理非結構化和半結構化的資料。
NoSQL 資料庫有多種類型,常見的有以下幾類:
在現代 Web 應用程式中,NoSQL 資料庫通常被用來處理海量資料,尤其是在高流量和高擴展需求的環境下,例如社交媒體平台、電商網站、即時消息系統等。NoSQL 的非結構化資料儲存方式使得它在處理多樣化的資料時極具優勢,但這也引入了新的安全挑戰,特別是在應對使用者輸入時。
這些特性使得 NoSQL 資料庫在資安領域成為攻擊者的潛在目標,特別是當未經驗證的使用者輸入直接進行查詢時,容易引發 NoSQL injection 攻擊。
接下來,我們將探討 NoSQL injection 攻擊的原理與風險。
NoSQL injection 是指攻擊者透過操控未經妥善處理的查詢語法來改變應用程式的行為。這類攻擊通常發生在開發者未正確驗證或清理使用者輸入時。舉例來說,在 MongoDB 中,查詢語法可能如下所示:
db.users.find({ username: userInput.username, password: userInput.password });
假設 userInput
是從表單接收的使用者輸入,攻擊者可以透過提交特殊格式的輸入來操控查詢,例如:
{ "$ne": null }
這會使條件無效,允許攻擊者繞過認證,取得所有使用者資料。
在這個 lab 中,將學習如何利用 NoSQL injection 攻擊漏洞,將使用 MongoDB 和 Node.js 構建一個簡單的商品搜尋系統,並展示 NoSQL injection 的危害。
https://github.com/fei3363/ithelp_web_security_2024/commit/bb7ea18f8b7462e1c888a5651135d91002720efd
docker-compose.yml
我們將使用 Docker 和 MongoDB 一起執行。這裡的 docker-compose.yml
文件將同時啟動 MongoDB 和 Node.js 應用程式,並確保 MongoDB 在啟動時自動初始化測試資料。
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:
在 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 }
]);
我們將實作一個簡單的商品搜尋功能,這個功能存在 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
// 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;
假設我們想要搜尋商品名稱中包含 "筆記型電腦" 的商品,我們可以發送如下請求:
curl "http://nodelab.feifei.tw/api/product/search?q=筆記型電腦"
這將回傳符合條件的商品。
我們可以嘗試發送以下惡意請求,試圖透過 MongoDB 的查詢運算符進行注入:
curl "http://nodelab.feifei.tw/api/product/search?q=%27%20||%201==1//"
查詢語句:
curl "http://nodelab.feifei.tw/api/product/search?q=%27%20||%201==1//"
這個查詢包含了惡意的 NoSQL 查詢條件。具體解釋如下:
%27
是 URL 編碼,代表單引號('),這是查詢字串的開頭。||
是邏輯運算符,表示 "或"。1==1
是邏輯運算,因為這個條件永遠為真,因此無論輸入什麼,條件都會成立。//
用來註解掉後續程式碼,這樣可以忽略掉後面的部分。結果解釋:
在 MongoDB 中,當 find()
查詢條件不安全地使用未經處理的輸入時,這樣的查詢條件會被直接應用到查詢中。由於 1==1
永遠為真,這意味著無論實際條件是什麼,查詢都會回傳所有商品,而不僅僅是符合特定名稱的商品。
攻擊效果:
該攻擊成功繞過了應用程式的邏輯,將所有資料庫中的商品回傳,而不是僅僅回傳名稱匹配的結果。這表明應用程式未能正確地處理使用者的輸入,從而暴露出潛在的 NoSQL injection 漏洞。
圖像說明:
圖像中展示了攻擊的結果,攻擊者成功回傳了所有的商品資料,而不是僅僅基於名稱搜尋的結果,這是由於應用程式中的查詢邏輯被注入攻擊操控。
這種 NoSQL injection 攻擊會導致敏感資料暴露,攻擊者能夠取得系統中所有的資料,這在實際應用中是非常危險的。如果這是使用者資料或其他機密資料,攻擊者可以通過這類漏洞取得並利用資料,造成資料洩露甚至更嚴重的後果。
為了防止這樣的攻擊,我們可以採取以下防禦措施:
修改 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,以下幾個開發方向可以幫助開發者加強應用程式的安全性:
npm audit
等工具來檢測可能存在的安全漏洞,並針對 NoSQL 查詢語法進行靜態分析。在本篇文章中,我們深入探討了 NoSQL 資料庫的基本概念及其應用,並透過實作展示了 NoSQL injection 的潛在風險。我們還分析了如何利用 NoSQL injection 漏洞進行攻擊,並詳細說明了如何防止這些攻擊。隨著 NoSQL 資料庫在現代 Web 應用中越來越廣泛的使用,理解和防範 NoSQL injection 攻擊已成為開發者的必備技能之一。
透過這個實作 lab,我們學習了如何:
哪一個 NoSQL 資料庫類型最適合處理大量的非結構化資料?
答案:B. 文件型資料庫
解析: 文件型資料庫(如 MongoDB)使用 JSON 或 BSON 格式來儲存資料,非常適合處理非結構化或半結構化資料。
以下哪個查詢語句最容易受到 NoSQL injection 攻擊?
db.users.find({ username: userInput.username })
db.users.find({ username: { $eq: userInput.username } })
db.users.find({ password: { $regex: userInput.password } })
db.users.find({ username: { $eq: userInput.username, password: userInput.password } })
答案:C. db.users.find({ password: { $regex: userInput.password } })
解析: 使用正則表達式($regex
)且未經處理的使用者輸入時,會使應用程式容易受到 injection 攻擊。
哪個防禦措施對防止 NoSQL injection 攻擊最有效?
答案:C. 使用參數化查詢
解析: 參數化查詢將使用者輸入與查詢邏輯分離,是防止注入攻擊最有效的方法。
什麼情況下會發生 NoSQL injection 攻擊?
答案:B. 當使用者的輸入沒有經過驗證和清理時
解析: NoSQL injection 攻擊主要發生在未經妥善處理的使用者輸入被直接插入到查詢語句中。
下列哪項是預防 NoSQL injection 攻擊的關鍵措施之一?
答案:B. 對使用者輸入進行過濾和驗證
解析: 驗證與清理使用者的輸入是預防 NoSQL injection 攻擊的關鍵措施之一,防止不合法的查詢進入系統。
實作清單
環境設置
docker-compose.yml
文件來啟動 MongoDB 和 Node.js 應用程式應用實作
防禦措施實作
測試與驗證