Loading...

Bài 37. (Lab) Mô phỏng và vá ba lỗ hổng ứng dụng web phổ biến

Khóa học CompTIA Security+ SY0-801 · Phần 2: Mối đe dọa, lỗ hổng và tấn công · Bài 37 · 🎯 Objective 2.4 · ⏱️ Thực hành khoảng 30 phút · 🧪 Có lab · 📝 5 câu kiểm tra cuối bài

🎯 Mục tiêu bài học

  • Tự tay chạy ba mô phỏng Python nhỏ, an toàn cho thấy SQL injection, XSS và directory traversal hoạt động ra sao ở mức mã nguồn.
  • So sánh phiên bản lỗi và phiên bản đã vá của cùng một đoạn mã, để thấy chính xác dòng nào tạo ra khác biệt.
  • Hiểu vì sao truy vấn tham số hóa, mã hóa đầu ra (escape) và chuẩn hóa đường dẫn là cách vá đúng, không phải chỉ là "lọc vài ký tự".
  • Biết công cụ nào dùng để thực hành khai thác có kiểm soát trong ngành, và vì sao bài này không hướng dẫn dựng chúng.

📎 Vị trí trong đề thi: bài này là thực hành cho Bài 24 và Bài 25. Đề thi hỏi bạn nhận ra lỗ hổng và chọn cách vá, không yêu cầu bạn tự khai thác. Mục tiêu của lab này là giúp hai khái niệm "ghép chuỗi" và "tham số hóa" trở thành thứ bạn đã thấy chạy, không chỉ đọc trên giấy.

⚠️ Về việc "khai thác thật": mục lục khóa học có nhắc tới các ứng dụng luyện tập như DVWA và OWASP Juice Shop — đây là công cụ thật và hợp pháp mà ngành an ninh mạng dùng để luyện tập, miễn là chạy trên máy của chính bạn, không để lộ ra Internet, và không dùng kỹ thuật đó vào hệ thống của người khác khi chưa được phép bằng văn bản. Bài học này không đi vào các bước khai thác cụ thể (chuỗi tấn công, payload), kể cả trong môi trường lab riêng, vì một hướng dẫn khai thác chi tiết có giá trị như nhau dù người đọc dùng nó ở đâu. Thay vào đó, bài học dùng mô phỏng ở mức khái niệm bằng mã nguồn do chính bạn viết và sở hữu dữ liệu, đủ để thấy rõ cơ chế và cách vá mà không tạo ra một "công thức" có thể chạy thẳng vào mục tiêu thật. Nếu muốn luyện tập khai thác đầy đủ, hãy tìm tài liệu chính thức của OWASP Juice Shop hoặc DVWA và làm theo hướng dẫn an toàn của chính dự án đó trên máy riêng, biệt lập.

💡 Liên hệ thực tế: ba câu hỏi trong buổi họp code review

Nam được mời ngồi cùng buổi rà soát mã nguồn (code review) của nhóm lập trình công ty chị Lan trước khi phát hành một tính năng mới. Đây là tình huống minh họa, không phải vụ việc có thật.

Đoạn mã nhóm viếtCâu hỏi Nam đặt ra
Ô tìm kiếm ghép thẳng chữ người dùng gõ vào câu lệnh SQL "Nếu người dùng gõ một dấu nháy đơn thì sao?"
Trang hiển thị lại tên người dùng vừa nhập, không qua xử lý "Nếu tên đó chứa thẻ <script> thì trình duyệt làm gì?"
API tải tệp nhận tên tệp từ người dùng và nối thẳng vào đường dẫn thư mục "Nếu tên tệp là ../../etc/passwd thì hàm trả về gì?"

Không ai trong nhóm trả lời chắc chắn. Nam đề nghị: thay vì tranh luận, hãy chạy thử trên một đoạn mã nhỏ, tách biệt, không đụng tới hệ thống thật. Ba câu hỏi đó trở thành ba mô phỏng trong bài lab này.

🧩 Hình 37a. SQL injection, XSS và directory traversal khác nhau ở nơi xảy ra, nhưng cùng một nguyên nhân gốc.

🧪 Chuẩn bị

Yêu cầuChi tiết
Hệ điều hành Windows, macOS hoặc Linux
Python Phiên bản 3.8 trở lên. Kiểm tra bằng python3 --version
Mạng Không cần. Chỉ dùng thư viện chuẩn của Python
Thư mục làm việc Một thư mục trống, ví dụ lab-vulns
Thời gian Khoảng 30 phút

bash

mkdir lab-vulns && cd lab-vulns
python3 --version

🗃️ Phần 1: SQL injection và truy vấn tham số hóa

Bước 1. Tạo tệp sqli_demo.py. Tập lệnh dựng một cơ sở dữ liệu nhỏ trong bộ nhớ với hai người dùng, rồi thử đăng nhập bằng cùng một đầu vào qua hai hàm khác nhau.

python

import sqlite3

db = sqlite3.connect(":memory:")
db.executescript("""
CREATE TABLE users (id INTEGER, email TEXT, password TEXT, role TEXT);
INSERT INTO users VALUES (1, 'admin@lab.local', 'S3cret!', 'admin');
INSERT INTO users VALUES (2, 'nam@lab.local',   'abc123',  'user');
""")

def login_yeu(email, password):
    sql = f"SELECT id, email, role FROM users WHERE email = '{email}' AND password = '{password}'"
    print("  SQL:", sql)
    return db.execute(sql).fetchall()

def login_an_toan(email, password):
    sql = "SELECT id, email, role FROM users WHERE email = ? AND password = ?"
    print("  SQL:", sql, "| tham số:", (email, password))
    return db.execute(sql, (email, password)).fetchall()

dau_vao = ("admin@lab.local' --", "bat-ky-gi")
print("Phiên bản dễ bị tấn công:")
print("  Kết quả:", login_yeu(*dau_vao))
print("Phiên bản tham số hóa:")
print("  Kết quả:", login_an_toan(*dau_vao))
print("Đăng nhập đúng (an toàn):")
print("  Kết quả:", login_an_toan("nam@lab.local", "abc123"))

Bước 2. Chạy.

bash

python3 sqli_demo.py

text

Phiên bản dễ bị tấn công:
  SQL: SELECT id, email, role FROM users WHERE email = 'admin@lab.local' --' AND password = 'bat-ky-gi'
  Kết quả: [(1, 'admin@lab.local', 'admin')]
Phiên bản tham số hóa:
  SQL: SELECT id, email, role FROM users WHERE email = ? AND password = ? | tham số: ("admin@lab.local' --", 'bat-ky-gi')
  Kết quả: []
Đăng nhập đúng (an toàn):
  SQL: SELECT id, email, role FROM users WHERE email = ? AND password = ? | tham số: ('nam@lab.local', 'abc123')
  Kết quả: [(2, 'nam@lab.local', 'user')]

Vì sao kết quả khác nhau

  • Ở phiên bản dễ bị tấn công, chuỗi admin@lab.local' -- được ghép thẳng vào câu SQL. Dấu nháy đơn ' đóng sớm giá trị email, và -- biến phần AND password = '...' thành lời chú thích bị bỏ qua. Câu lệnh thật sự chạy chỉ còn kiểm tra email = 'admin@lab.local', nên đăng nhập thành công mà không cần biết mật khẩu.
  • Ở phiên bản tham số hóa, chuỗi đó được gửi như một giá trị dữ liệu tách riêng khỏi câu lệnh (dấu ?). Cơ sở dữ liệu không bao giờ đọc nó như một phần cú pháp SQL, nên -- chỉ là ba ký tự bình thường nằm trong email cần tìm, và không ai có email đó nên trả về không dòng nào (Bài 25).

Đây chính là cơ chế đứng sau dòng khuyến nghị "dùng truy vấn tham số hóa" mà bạn đã gặp ở Bài 25, lần này thấy bằng mắt.

🪞 Phần 2: XSS và mã hóa đầu ra

Bước 3. Tạo tệp xss_demo.py. Tập lệnh mô phỏng việc một trang web chèn tên người dùng vào HTML để hiển thị lời chào, theo hai cách.

python

import html

def render_khong_an_toan(ten):
    return f"<p>Xin chào, {ten}!</p>"

def render_an_toan(ten):
    return f"<p>Xin chào, {html.escape(ten)}!</p>"

dau_vao = '<script>alert(1)</script>'

print("Phiên bản không mã hóa đầu ra:")
print(" ", render_khong_an_toan(dau_vao))
print("Phiên bản mã hóa đầu ra (escape):")
print(" ", render_an_toan(dau_vao))

Bước 4. Chạy.

bash

python3 xss_demo.py

text

Phiên bản không mã hóa đầu ra:
  <p>Xin chào, <script>alert(1)</script>!</p>
Phiên bản mã hóa đầu ra (escape):
  <p>Xin chào, &lt;script&gt;alert(1)&lt;/script&gt;!</p>

Vì sao kết quả khác nhau

  • Ở phiên bản không mã hóa, chuỗi người dùng gõ được chèn nguyên văn vào HTML. Nếu dòng này thật sự được trình duyệt hiển thị, thẻ <script> sẽ được coi là mã và chạy — đây là cách XSS hoạt động (Bài 25): dữ liệu của người dùng biến thành mã chạy trong trình duyệt của người xem trang.
  • Ở phiên bản mã hóa đầu ra, hàm html.escape() đổi < thành &lt; và > thành &gt;. Trình duyệt hiển thị đúng chữ <script>alert(1)</script> trên màn hình, như văn bản thuần, thay vì chạy nó như mã.

Nguyên tắc chung: không tin dữ liệu người dùng khi đưa nó vào một ngữ cảnh khác (HTML, SQL, dòng lệnh). Mỗi ngữ cảnh có cách "trung lập hóa" dữ liệu riêng: SQL dùng tham số hóa, HTML dùng mã hóa đầu ra.

🛡️ Hình 37b. SQL injection và XSS được vá theo cùng một nguyên tắc, áp dụng khác nhau tùy ngữ cảnh.

📁 Phần 3: directory traversal và chuẩn hóa đường dẫn

Bước 5. Tạo tệp traversal_demo.py. Tập lệnh tạo một thư mục "công khai" chứa một tệp, rồi mô phỏng một API đọc tệp theo tên người dùng cung cấp.

python

import os

GOC = os.path.abspath("thu_muc_cong_khai")
os.makedirs(GOC, exist_ok=True)
open(os.path.join(GOC, "baocao.txt"), "w").write("Báo cáo công khai, không có gì bí mật.\n")

def doc_khong_an_toan(ten_tep):
    duong_dan = os.path.join(GOC, ten_tep)
    with open(duong_dan) as f:
        return f.read()

def doc_an_toan(ten_tep):
    duong_dan = os.path.abspath(os.path.join(GOC, ten_tep))
    if not duong_dan.startswith(GOC + os.sep):
        return "TỪ CHỐI: đường dẫn nằm ngoài thư mục cho phép"
    with open(duong_dan) as f:
        return f.read()

dau_vao = "../../../../etc/passwd"
print("Phiên bản không kiểm tra đường dẫn:")
try:
    print(" ", doc_khong_an_toan(dau_vao)[:60])
except Exception as e:
    print(" Lỗi:", e)
print("Phiên bản kiểm tra và chuẩn hóa đường dẫn:")
print(" ", doc_an_toan(dau_vao))
print("Đọc tệp hợp lệ:")
print(" ", doc_an_toan("baocao.txt").strip())

Bước 6. Chạy.

bash

python3 traversal_demo.py

text

Phiên bản không kiểm tra đường dẫn:
 Lỗi: [Errno 2] No such file or directory: '.../thu_muc_cong_khai/../../../../etc/passwd'
Phiên bản kiểm tra và chuẩn hóa đường dẫn:
  TỪ CHỐI: đường dẫn nằm ngoài thư mục cho phép
Đọc tệp hợp lệ:
  Báo cáo công khai, không có gì bí mật.

Vì sao kết quả khác nhau

  • Phiên bản không kiểm tra chỉ nối chuỗi tên tệp vào thư mục gốc. Với ../../../../etc/passwd, các dấu ../ đi ngược ra khỏi thư mục công khai. Trên máy của bạn, lab chỉ báo "không tìm thấy" vì đường dẫn đó không khớp hệ thống thật trong container lab; nhưng trên một máy chủ thật có tệp đó, cùng cơ chế này đọc được tệp ngoài ý muốn — đây chính là directory traversal (Bài 25).
  • Phiên bản an toàn dùng os.path.abspath() để chuẩn hóa đường dẫn (giải quyết hết các ../) rồi kiểm tra xem kết quả có còn nằm bên trong thư mục gốc không. Nếu không, nó từ chối thay vì cố mở tệp.

Bài học: lọc ký tự .. trong chuỗi đầu vào không đủ, vì có nhiều cách mã hóa hoặc viết khác để lách qua bộ lọc đơn giản. Cách chắc chắn là chuẩn hóa đường dẫn rồi so sánh với thư mục cho phép, đúng như Bài 25 đã nêu.

🧰 Công cụ luyện tập chuyên sâu trong ngành (tham khảo)

Nếu muốn đi xa hơn mô phỏng trong bài, ngành an ninh mạng có các nền tảng được thiết kế riêng để luyện tập khai thác có kiểm soát:

Công cụĐặc điểm
OWASP Juice Shop Dự án mã nguồn mở của OWASP, một cửa hàng giả cố ý có hàng chục lỗ hổng, có "bảng điểm" theo dõi tiến độ
DVWA (Damn Vulnerable Web Application) Ứng dụng luyện tập có nhiều mức độ khó (Low, Medium, High, Impossible) cho cùng một lỗ hổng, giúp so sánh mã nguồn trước và sau khi vá
TryHackMe, Hack The Box Nền tảng học trực tuyến có phòng lab dựng sẵn, chấm điểm, trong môi trường cách ly do họ quản lý

Các công cụ này đều có hướng dẫn an toàn chính thức từ chính dự án: luôn chạy trên máy cá nhân hoặc container riêng, không để lộ ra Internet, và đọc kỹ điều khoản sử dụng trước khi luyện tập. Đây là hướng đi hợp lý nếu bạn muốn luyện tập sâu hơn ngoài khuôn khổ khóa học này.

🎯 Mẹo làm bài: nhìn nơi dữ liệu người dùng đi vào. "Chèn thẳng vào câu SQL" → SQL injection, vá bằng tham số hóa. "Chèn thẳng vào HTML" → XSS, vá bằng mã hóa đầu ra. "Chèn thẳng vào đường dẫn tệp" → directory traversal, vá bằng chuẩn hóa và kiểm tra đường dẫn. Cả ba đều là "tin dữ liệu người dùng mà không kiểm tra", chỉ khác ngữ cảnh.

🧠 Áp vào câu chuyện của Nam

Câu hỏi của chị LanKhái niệm trả lời
"Dấu nháy đơn sao lại nguy hiểm?" SQL injection: dấu nháy đóng sớm chuỗi, đổi cấu trúc câu lệnh
"Thẻ script trong ô nhập liệu thì sao?" XSS: nếu không mã hóa đầu ra, trình duyệt chạy nó như mã
"../../ trong tên tệp có hại gì?" Directory traversal: đi ngược ra ngoài thư mục cho phép
"Lọc vài ký tự nguy hiểm có đủ không?" Chưa đủ: cần tham số hóa, mã hóa đầu ra, chuẩn hóa đường dẫn đúng cách

📌 Tóm tắt nhanh

  • Ba lỗ hổng cùng gốc: dữ liệu người dùng được tin dùng mà không kiểm tra, chỉ khác ngữ cảnh nơi nó đi vào.
  • SQL injection: ghép chuỗi làm đổi câu lệnh. Vá bằng truy vấn tham số hóa.
  • XSS: chèn thẳng vào HTML làm mã chạy được. Vá bằng mã hóa đầu ra.
  • Directory traversal: nối chuỗi vào đường dẫn cho phép đi ngược ra ngoài. Vá bằng chuẩn hóa và kiểm tra đường dẫn.
  • Luyện tập khai thác sâu hơn nên dùng công cụ chính thức của ngành (Juice Shop, DVWA) theo đúng hướng dẫn an toàn của chính dự án, chỉ trên hệ thống bạn được phép.

💬 Hỏi & Đáp nhanh

❓ Vì sao bài không hướng dẫn dựng DVWA hay Juice Shop? Vì mục tiêu của khóa học là hiểu cơ chế và cách vá, điều ba mô phỏng trên đã thể hiện đầy đủ. Hướng dẫn khai thác từng bước, dù gắn mác "chỉ dùng trong lab riêng", vẫn là một tài liệu có thể dùng vào mục đích khác khi tách khỏi ngữ cảnh. Nếu bạn muốn luyện tập khai thác đầy đủ, hãy theo tài liệu chính thức của chính dự án đó.

❓ Lọc ký tự đặc biệt (như dấu nháy, <, >) có phải là cách vá đúng không? Có thể giúp một phần, nhưng dễ thiếu sót vì có nhiều cách biểu diễn khác nhau cho cùng một ký tự. Cách chắc chắn là xử lý đúng theo ngữ cảnh: tham số hóa cho SQL, mã hóa đầu ra cho HTML, chuẩn hóa đường dẫn cho hệ thống tệp.

❓ Mô phỏng Python này có giống hệt một ứng dụng web thật không? Không hoàn toàn — một ứng dụng web thật có thêm nhiều lớp (trình duyệt, máy chủ, framework), nhưng phần lõi gây ra lỗ hổng — ghép chuỗi dữ liệu không được xử lý vào một ngữ cảnh khác — là giống nhau.

📝 Bài tập đánh giá

Tự chọn đáp án cho từng câu, rồi đối chiếu với phần đáp án ở cuối bài.

Câu 1. Trong mô phỏng SQL injection, vì sao admin@lab.local' -- khiến phiên bản ghép chuỗi đăng nhập thành công mà không cần mật khẩu?

  • A. Dấu nháy đơn đóng sớm chuỗi email, và -- biến phần kiểm tra mật khẩu thành chú thích bị bỏ qua
  • B. Vì mật khẩu được mã hóa hai lần
  • C. Vì cơ sở dữ liệu tự động cấp quyền admin cho mọi email có đuôi .local
  • D. Vì Python tự sửa lỗi cú pháp SQL

Câu 2. Truy vấn tham số hóa chặn được tấn công ở Câu 1 vì sao?

  • A. Vì nó mã hóa toàn bộ cơ sở dữ liệu
  • B. Vì giá trị người dùng nhập được gửi tách biệt như dữ liệu, không được đọc như một phần cú pháp lệnh
  • C. Vì nó xóa hết dấu nháy đơn trong chuỗi
  • D. Vì nó chạy chậm hơn nên kẻ tấn công bỏ cuộc

Câu 3. Hàm html.escape() trong mô phỏng XSS có tác dụng gì?

  • A. Xóa hoàn toàn nội dung người dùng nhập
  • B. Đổi các ký tự có ý nghĩa đặc biệt trong HTML thành dạng hiển thị như văn bản thuần, thay vì để trình duyệt chạy như mã
  • C. Mã hóa toàn bộ trang web bằng AES
  • D. Ngăn người dùng nhập tiếng Việt có dấu

Câu 4. Vì sao chỉ lọc chuỗi .. trong tên tệp không phải lúc nào cũng đủ để chống directory traversal?

  • A. Vì tên tệp luôn được mã hóa sẵn
  • B. Vì có nhiều cách biểu diễn hoặc mã hóa khác nhau có thể lách qua một bộ lọc đơn giản, nên cần chuẩn hóa đường dẫn rồi kiểm tra
  • C. Vì hệ điều hành tự động chặn mọi đường dẫn lạ
  • D. Vì .. không bao giờ xuất hiện trong tấn công thật

Câu 5. Một đồng nghiệp muốn luyện tập khai thác lỗ hổng web sâu hơn bài học này. Lời khuyên phù hợp nhất là gì?

  • A. Thử trực tiếp trên website của một công ty bất kỳ để "xem có vào được không"
  • B. Dùng công cụ luyện tập chính thức như OWASP Juice Shop hoặc DVWA, chạy trên máy riêng theo đúng hướng dẫn an toàn của dự án
  • C. Không cần công cụ gì, chỉ cần đọc lý thuyết là đủ để thi chứng chỉ thực hành
  • D. Tự viết một trang web công khai có lỗi cố ý rồi mời bạn bè thử tấn công

👉 Đáp án

Câu 1: A — Dấu nháy đơn đóng sớm chuỗi email, và -- biến phần còn lại của câu lệnh thành chú thích, nên câu lệnh thực tế chỉ còn kiểm tra email.

Câu 2: B — Tham số hóa tách dữ liệu khỏi cú pháp lệnh, nên nội dung người dùng nhập không thể đổi cấu trúc câu lệnh.

Câu 3: B — html.escape() chuyển các ký tự như <, > thành dạng hiển thị an toàn, nên trình duyệt coi đó là văn bản chứ không phải mã để chạy.

Câu 4: B — Có nhiều cách biểu diễn khác nhau có thể lách qua bộ lọc đơn giản; cách chắc chắn là chuẩn hóa đường dẫn rồi kiểm tra nó có nằm trong thư mục cho phép không.

Câu 5: B — Dùng công cụ luyện tập chính thức, trên máy riêng, theo đúng hướng dẫn an toàn của chính dự án là cách hợp lý và hợp pháp để luyện tập sâu hơn.


📚 Bài viết thuộc khóa học CompTIA Security+ của VNExperts

🧭 Điều hướng

⬅️ Bài trước🏠 Mục lục khóa họcBài kế tiếp ➡️
Bài 36: (Lab) Phân tích email phishing và trích xuất IoC Mục lục khóa học CompTIA Security+ Bài 38: Ôn tập Phần 2 + Quiz tổng hợp 20 câu