Giao diện
Thực hành: Security Audit Challenge
🎯 Mục tiêu
🎯 Sau bài thực hành này, bạn sẽ:
- Nhận diện các lỗ hổng bảo mật phổ biến trong Python code
- Sửa lỗi SQL injection, path traversal, unsafe deserialization
- Áp dụng security best practices vào dự án thực tế
Mô tả bài tập
Bạn được giao nhiệm vụ audit một ứng dụng web Python có nhiều lỗ hổng bảo mật. Nhiệm vụ là tìm ra tất cả vulnerabilities và viết lại code an toàn. Đây là kỹ năng thiết yếu cho mọi developer chuyên nghiệp.
Yêu cầu
Bài 1: SQL Injection
Tìm và sửa lỗ hổng SQL injection trong đoạn code sau.
python
import sqlite3
def get_user_unsafe(username: str) -> dict:
"""⚠️ CODE CÓ LỖ HỔNG — TÌM VÀ SỬA"""
conn = sqlite3.connect("app.db")
cursor = conn.cursor()
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)
row = cursor.fetchone()
conn.close()
return {"id": row[0], "username": row[1]} if row else None
# Attacker có thể truyền: username = "' OR '1'='1"
# TODO: Viết lại hàm get_user_safe sử dụng parameterized queriesBài 2: Path Traversal
Sửa lỗi cho phép truy cập file ngoài thư mục cho phép.
python
import os
def read_file_unsafe(filename: str) -> str:
"""⚠️ CODE CÓ LỖ HỔNG — TÌM VÀ SỬA"""
file_path = os.path.join("/app/uploads", filename)
with open(file_path, "r") as f:
return f.read()
# Attacker có thể truyền: filename = "../../etc/passwd"
# TODO: Viết lại với path validation để ngăn directory traversalBài 3: Unsafe Deserialization
Tìm lỗ hổng trong việc sử dụng pickle để deserialize dữ liệu.
python
import pickle
import base64
def load_session_unsafe(session_data: str) -> dict:
"""⚠️ CODE CÓ LỖ HỔNG — TÌM VÀ SỬA"""
decoded = base64.b64decode(session_data)
return pickle.loads(decoded)
# Attacker có thể craft pickle payload thực thi code tùy ý
# TODO: Thay thế bằng phương pháp an toàn (json, hmac signing)Bài 4: Hardcoded Secrets
Tìm và sửa các vấn đề về quản lý secrets.
python
class AppConfig:
"""⚠️ CODE CÓ LỖ HỔNG — TÌM VÀ SỬA"""
DB_PASSWORD = "super_secret_123"
API_KEY = "sk-abc123def456"
JWT_SECRET = "my-jwt-secret"
@staticmethod
def get_db_url():
return f"postgresql://admin:{AppConfig.DB_PASSWORD}@localhost/mydb"
# TODO: Refactor để lấy secrets từ environment variables
# Sử dụng os.environ hoặc python-dotenvGợi ý
Gợi ý Bài 1
- Dùng parameterized query:
cursor.execute("SELECT * FROM users WHERE username = ?", (username,)) - Placeholder
?(SQLite) hoặc%s(PostgreSQL) ngăn SQL injection - Không bao giờ dùng f-string hoặc
.format()cho SQL queries
Gợi ý Bài 2
- Dùng
Path.resolve()để chuẩn hóa path, loại bỏ.. - Kiểm tra
resolved_path.is_relative_to(base_dir)(Python 3.9+) - Hoặc so sánh
os.path.commonpath([base_dir, resolved])vớibase_dir
Gợi ý Bài 3
pickle.loads()có thể thực thi arbitrary code — không an toàn với untrusted data- Thay bằng
json.loads()cho dữ liệu đơn giản - Nếu cần serialize phức tạp, dùng HMAC để verify integrity trước khi load
Gợi ý Bài 4
os.environ.get("DB_PASSWORD")lấy từ environment variablepython-dotenvload từ file.env(không commit vào git)- Raise error rõ ràng nếu secret không được cấu hình
Lời giải tham khảo
Xem lời giải (Bài 1 & 2 — Bài 3, 4 tự hoàn thành từ gợi ý)
python
import sqlite3
from pathlib import Path
# Bài 1: Parameterized query
def get_user_safe(username: str) -> dict | None:
conn = sqlite3.connect("app.db")
cursor = conn.cursor()
cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
row = cursor.fetchone()
conn.close()
return {"id": row[0], "username": row[1]} if row else None
# Bài 2: Path traversal prevention
UPLOAD_DIR = Path("/app/uploads").resolve()
def read_file_safe(filename: str) -> str:
file_path = (UPLOAD_DIR / filename).resolve()
if not file_path.is_relative_to(UPLOAD_DIR):
raise PermissionError("Truy cập file ngoài thư mục không được phép")
with open(file_path, "r") as f:
return f.read()