SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor

วิธีตรวจสอบคำสั่งที่ผู้ใช้งานรันบนเซิร์ฟเวอร์ Linux

ประวัติ shell ไม่ใช่บันทึกที่เชื่อถือได้ เรียนรู้วิธีใช้ sudo logging, session recording, shell hooks และ auditd execve เพื่อติดตามคำสั่ง พร้อมวิธีส่ง log ออกนอกเครื่อง

สิ่งที่บันทึกคำสั่งที่ผู้ใช้เรียกใช้งานบนเซิร์ฟเวอร์ของคุณจริงๆ

ในการตรวจสอบว่าผู้ใช้เรียกใช้งานคำสั่งใดบ้างบนเซิร์ฟเวอร์ของคุณ คุณจำเป็นต้องมีบันทึกที่ผู้ใช้ไม่สามารถแก้ไขได้ ประวัติการใช้งาน shell (shell history) ไม่ใช่บันทึกประเภทนั้น มันเป็นเพียงไฟล์อำนวยความสะดวกที่เจ้าของบัญชีเป็นผู้เขียนขึ้น และใครก็ตามที่สามารถพิมพ์คำสั่งใน shell นั้นได้ ก็สามารถปิดการทำงานหรือลบประวัติทิ้งได้

มี 4 เลเยอร์ที่สามารถเก็บบันทึกที่แท้จริงได้ โดยแต่ละเลเยอร์มีต้นทุนที่ต้องแลก sudo จะเขียนบันทึกหนึ่งบรรทัดต่อหนึ่งคำสั่งลงใน syslog ส่วน sudo I/O logging จะบันทึกทั้งเซสชันสำหรับบัญชีผู้ใช้หนึ่งบัญชี Shell hook เช่น PROMPT_COMMAND จะบันทึกสิ่งที่ผู้ใช้ bash แบบโต้ตอบได้พิมพ์ลงไป ระบบย่อย audit ของ kernel จะบันทึกการเรียกใช้ execve syscall โดยตรง ซึ่งเป็นเหตุผลว่าทำไมมันจึงเป็นเลเยอร์เดียวที่มองเห็นทุกกระบวนการ (process) คู่มือนี้จะอธิบายไล่ระดับไปตามเลเยอร์เหล่านี้ ระบุขอบเขตการทำงานของแต่ละเลเยอร์ และจบลงด้วยส่วนที่ตัดสินว่าบันทึกเหล่านี้จะมีค่าหรือไม่ นั่นคือการนำบันทึกออกจากเครื่องก่อนที่บุคคลที่คุณกำลังตรวจสอบจะเข้าถึงมันได้

คำเตือนก่อนเริ่มใช้งาน: ระบบย่อย audit เป็นการทำงานในระดับ kernel ดังนั้นจึงไม่สามารถทดสอบภายใน container ที่ใช้ kernel ร่วมกับโฮสต์ได้ โปรดรันคำสั่งเหล่านี้บน KVM VPS ที่คุณมีสิทธิ์ควบคุม kernel ของตนเอง

เหตุใด shell history จึงไม่ใช่บันทึกการตรวจสอบ (audit trail)

~/.bash_history ไม่สามารถใช้เป็นหลักฐานได้ด้วยเหตุผลทั่วไป 4 ประการ และไม่มีข้อใดที่ต้องอาศัยผู้โจมตีที่มีความเชี่ยวชาญเป็นพิเศษ

เป็นกรรมสิทธิ์ของผู้ใช้ ไฟล์นี้มีโหมด 600 และมีบัญชีผู้ใช้นั้นเป็นเจ้าของ ดังนั้น rm ~/.bash_history จึงไม่จำเป็นต้องใช้สิทธิ์พิเศษใดๆ ทั้งสิ้น เช่นเดียวกับการเปิดไฟล์ด้วยโปรแกรมแก้ไขข้อความแล้วลบ 20 บรรทัดที่เป็นปัญหาออกไป

จะถูกเขียนลงไฟล์เมื่อ shell ปิดตัวลง เซสชันที่สิ้นสุดด้วย kill -9 $$ หรือการเชื่อมต่อที่หลุดไปจะไม่บันทึกข้อมูลใดๆ การใช้ history -c ก่อน exit จะให้ผลลัพธ์เดียวกันและดูเหมือนไม่มีเหตุการณ์ใดเกิดขึ้น

สามารถปิดการทำงานได้ด้วยคำสั่งเดียว unset HISTFILE จะหยุดการเขียนไฟล์สำหรับเซสชันนั้นๆ set +o history จะหยุดการบันทึกทันที HISTCONTROL=ignorespace จะซ่อนทุกคำสั่งที่พิมพ์โดยเว้นวรรคไว้ข้างหน้า ทั้งหมดนี้อยู่ใน man bash เนื่องจากถูกออกแบบมาให้อยู่ภายใต้การควบคุมของผู้ใช้

บันทึกสิ่งที่พิมพ์ ไม่ใช่สิ่งที่ทำงานจริง การใช้ alias หรือ shell function หมายความว่าข้อความในไฟล์ไม่ใช่โปรแกรมที่ kernel เรียกใช้งานจริง

นอกจากนี้ยังไม่มีการประทับเวลา (timestamp) เว้นแต่จะมีการตั้งค่า HISTTIMEFORMAT ไว้ในขณะที่บันทึกรายการนั้น เนื่องจาก bash จะเขียนบรรทัดเครื่องหมาย #1755043200 ก็ต่อเมื่อมีการตั้งค่าตัวแปรดังกล่าวเท่านั้น

สำหรับการล็อกอินแบบใช้ร่วมกัน ระบบไม่สามารถระบุตัวตนผู้ใช้งานได้ หากมีคน 3 คนใช้บัญชี deploy เดียวกัน จะเกิดไฟล์ที่ปะปนกันภายใต้ uid เดียวกัน ไม่มีเลเยอร์การบันทึกใดที่สามารถระบุการกระทำของมนุษย์ได้เมื่อมีคนสองคนใช้ uid เดียวกัน ซึ่งเป็นเหตุผลเชิงปฏิบัติสำหรับ การใช้บัญชีที่ไม่มีสิทธิ์พิเศษแยกรายบุคคลแทนการใช้บัญชีร่วมกัน

Shell history ทำหน้าที่ได้ดีในงานหลักของมัน คือการช่วยให้คุณพิมพ์คำสั่งเดิมของเมื่อวานซ้ำได้ง่ายขึ้น ให้ใช้มันเป็นเพียงเบาะแสเท่านั้น แต่อย่านำมาใช้เป็นหลักฐานยืนยันเด็ดขาด

สิ่งที่ sudo บันทึกและจุดที่การบันทึกสิ้นสุดลง

sudo จะส่งข้อมูลหนึ่งบรรทัดสำหรับทุกคำสั่งที่ทำงานไปยัง facility authpriv ของ syslog

sudo grep 'sudo:' /var/log/auth.log | tail -5
journalctl -t sudo -n 5

แต่ละบรรทัดจะระบุชื่อผู้ใช้, เทอร์มินัล, ไดเรกทอรีที่ทำงานอยู่, ผู้ใช้เป้าหมาย และคำสั่งที่ใช้:

sudo:    alice : TTY=pts/0 ; PWD=/home/alice ; USER=root ; COMMAND=/usr/bin/apt update

หาก /var/log/auth.log ไม่มีอยู่จริง แสดงว่าไม่ได้ติดตั้ง rsyslog ไว้ในอิมเมจนั้น และบันทึกข้อมูลจะอยู่ใน journal เท่านั้น โปรดตรวจสอบว่า journal ไม่ได้เป็นแบบ volatile ก่อนที่จะพึ่งพาข้อมูลจากส่วนนี้:

journalctl --list-boots

การที่แสดงรายการเฉพาะการบูตปัจจุบันหมายความว่า /var/log/journal ไม่มีอยู่จริง ดังนั้น journal จึงอยู่ใน /run และทุกบรรทัดจะหายไปเมื่อรีบูตเครื่องครั้งถัดไป ให้ตั้งค่าเป็นแบบถาวรดังนี้:

sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald

มาถึงข้อจำกัด sudo จะบันทึกเฉพาะคำสั่งที่ได้รับคำสั่งให้ทำงานเท่านั้น แต่ไม่ได้บันทึกสิ่งที่คำสั่งนั้นทำต่อหลังจากนั้น ดังนั้นข้อมูลจะสิ้นสุดลงที่บรรทัดเดียว:

sudo -i

log จะได้รับบันทึกเพียงรายการเดียวสำหรับ shell นั้น ทุกคำสั่งที่พิมพ์ภายใน root shell จะไม่ถูกบันทึกโดย sudo เนื่องจาก sudo ไม่ได้อยู่ในเส้นทางการทำงานอีกต่อไป sudo su -, sudo bash และ sudo vim /etc/shadow ตามด้วย :!bash ทั้งหมดมีรูปแบบเดียวกัน กฎ sudoers ที่อนุญาตให้โปรแกรมใดๆ ที่มี shell escape เช่น vim หรือ find คือกฎที่มอบสิทธิ์ root โดยไม่มีการบันทึกกิจกรรม โปรดตรวจสอบว่าบัญชีผู้ใช้นั้นสามารถเข้าถึงอะไรได้บ้างก่อนที่คุณจะเชื่อถือบรรทัด log ของบัญชีนั้น:

sudo -l -U alice

การบันทึกเซสชันทั้งหมดสำหรับหนึ่งบัญชีผู้ใช้

ขั้นแรกให้ตรวจสอบว่าคุณใช้ sudo เวอร์ชันใด เนื่องจากฟีเจอร์นี้ไม่มีในเวอร์ชันที่เขียนด้วยภาษา Rust:

sudo --version | head -1

หากผลลัพธ์แสดงเป็น sudo-rs ให้ข้ามส่วนนี้ไปและใช้ระบบ audit แทน เอกสารประกอบของ Ubuntu สำหรับรุ่น 25.10 และ 26.04 ระบุว่าการบันทึก I/O และ sudoreplay ไม่ได้รับการรองรับ ซึ่งยังคงเป็นความจริง ณ เดือนสิงหาคม 2026 ประเด็นนี้มีความสำคัญเนื่องจาก sudo-rs เป็น sudo ค่าเริ่มต้นในรุ่นเหล่านั้น ดังนั้นการอัปเกรดอาจทำให้การควบคุมที่คุณเคยมีถูกยกเลิกไป รายการการเปลี่ยนแปลงพฤติกรรมของ sudo-rs ทั้งหมด เป็นสิ่งที่ควรศึกษาให้เข้าใจก่อนวางแผนการบันทึก log ผ่าน sudo

สำหรับ sudo เวอร์ชันดั้งเดิมที่ยังคงมาพร้อมกับ Ubuntu 24.04 LTS ให้เปิดใช้งานการบันทึก I/O สำหรับหนึ่งบัญชีผู้ใช้ดังนี้:

sudo visudo -f /etc/sudoers.d/iolog
Defaults:deploy log_output
Defaults!/usr/bin/sudoreplay !log_output

ให้ใช้ visudo แทนการใช้โปรแกรมแก้ไขข้อความทั่วไป เนื่องจากเครื่องมือนี้จะปฏิเสธการบันทึกไฟล์หากรูปแบบไวยากรณ์ไม่ถูกต้อง ไฟล์ sudoers ที่เสียหายจะทำให้ทุกคนไม่สามารถใช้งาน sudo ได้ จากนั้นให้เล่นเซสชันที่บันทึกไว้ซ้ำ:

sudo sudoreplay -l user=deploy
sudo sudoreplay 000001

sudoreplay -l จะแสดงรายการเซสชันพร้อม ID และจะไม่แสดงผลใดๆ หาก log_output ไม่เคยถูกนำมาใช้กับผู้ใช้นั้น สิ่งที่ต้องแลกคือ ทุกไบต์ที่ผ่านเทอร์มินัลจะถูกจัดเก็บไว้ภายใต้ /var/log/sudo-io ดังนั้นเซสชันที่มีการแสดงผลจำนวนมากจะมีขนาดไฟล์ใหญ่ บรรทัดที่สองในไฟล์ sudoers มีไว้เพื่อป้องกันไม่ให้การเล่นเซสชันซ้ำถูกบันทึกซ้อนเข้าไปอีก ต้นทุนที่แท้จริงคือเรื่องความลับ เนื่องจาก log ของ I/O จะเก็บทุกสิ่งที่พิมพ์และแสดงผล รวมถึงรหัสผ่านที่พิมพ์ลงใน prompt ภายในเซสชันนั้น ดังนั้นจึงจำเป็นต้องมีการป้องกันในระดับเดียวกับที่เก็บรหัสผ่าน นอกจากนี้ขอบเขตการทำงานยังจำกัด เพราะระบบจะมองเห็นเฉพาะคำสั่งที่รันผ่าน sudo เท่านั้น ผู้ที่ล็อกอินและทำงานในฐานะตนเองทั้งหมดจะไม่ถูกบันทึกไว้เลย

Shell hooks และวิธีการหลีกเลี่ยง

สูตรสำเร็จที่นิยมใช้สำหรับการ "บันทึกทุกคำสั่ง" คือการใส่ hook แบบ PROMPT_COMMAND ลงใน /etc/profile.d/:

# /etc/profile.d/00-cmdlog.sh
PROMPT_COMMAND='logger -p local6.info -t cmdlog "$(whoami) $$ $(history 1 | sed "s/^ *[0-9]* *//")"'

bash จะรัน PROMPT_COMMAND ก่อนที่จะแสดง prompt แต่ละครั้ง ดังนั้นบรรทัดคำสั่งจะถูกส่งไปยัง syslog ทันทีที่พิมพ์ แทนที่จะรอจนจบการทำงาน และ logger จะเขียนข้อมูลผ่าน system log daemon ทำให้สิทธิ์การเข้าถึงไฟล์ของผู้ใช้ไม่มีผลต่อการทำงานนี้ ให้เปิด login shell ใหม่แล้วตรวจสอบด้วย sudo tail -f /var/log/syslog หรือ journalctl -t cmdlog -f ในกรณีที่เป็น image ที่ไม่มี rsyslog

อย่างไรก็ตาม วิธีนี้สามารถหยุดทำงานได้ด้วย 5 วิธีที่คุณสามารถทดสอบได้ภายในหนึ่งนาที:

  • Non-interactive shells จะไม่แสดง prompt ดังนั้น ssh you@server 'id' จะรันคำสั่งและจบการทำงานโดยไม่มีการบันทึกใดๆ เนื่องจาก PROMPT_COMMAND ไม่เคยถูกประมวลผล
  • มันเป็นเพียงตัวแปรตัวหนึ่ง การใช้ unset PROMPT_COMMAND จะปิดการทำงานของมันไปตลอด session ที่เหลือโดยไม่จำเป็นต้องใช้สิทธิ์พิเศษ
  • ไฟล์นี้จะถูกอ่านเฉพาะตอน login shell เท่านั้น ดังนั้น bash --noprofile --norc จะไม่โหลด /etc/profile.d/ เลย
  • วิธีนี้ใช้ได้เฉพาะกับ bash เท่านั้น ส่วน zsh, sh, python3 -c 'import os; os.system("id")' และ :!id ภายใน vim ต่างก็รันโปรแกรมที่ shell hook ของ bash มองไม่เห็น
  • วิธีนี้บันทึกบรรทัดตามที่พิมพ์ ดังนั้น alias หรือ function ยังคงสามารถซ่อนคำสั่งที่ถูกรันจริงได้

ควรใช้ shell hook เพื่อความสะดวกเท่านั้น มันช่วยตอบคำถามว่า "ฉันรันคำสั่งอะไรไปเมื่อวันอังคารที่แล้ว" สำหรับผู้ใช้ที่ให้ความร่วมมือ แต่อย่าใช้มันเป็นมาตรการควบคุมในรายการตรวจสอบความปลอดภัย

ระบบย่อย audit ของ kernel ตรวจพบทุกคำสั่ง execve

ระบบย่อย audit ของ Linux ซึ่งขับเคลื่อนโดย daemon auditd เป็นชั้นเดียวในที่นี้ที่ผู้ใช้ไม่สามารถหลีกเลี่ยงได้ เนื่องจากบันทึกจะถูกสร้างขึ้นภายใน kernel ในขณะที่ syscall ทำงาน หากกระบวนการใดสั่งรันโปรแกรม จะเกิดเหตุการณ์ขึ้นทันที ไม่ว่าจะเป็น shell, ภาษาที่ใช้ หรือการมีอยู่ของ terminal ก็ไม่มีผลใดๆ

sudo apt update && sudo apt install -y auditd audispd-plugins
sudo systemctl enable --now auditd
sudo auditctl -s

auditctl -s จะแสดงสถานะของ daemon หาก enabled 1 มีค่าไม่เป็นศูนย์ใน pid หมายความว่า daemon กำลังทำงานอยู่ และ lost 0 หมายความว่ายังไม่มีบันทึกใดถูกทิ้งไป โปรดจำตัวนับ lost นี้ไว้ เพราะเราจะกลับมาใช้ในภายหลัง

auid คือฟิลด์ที่ทำให้ระบบ audit มีความสำคัญ PAM จะกำหนด login uid เมื่อเซสชันเริ่มต้นขึ้น และ kernel จะส่งต่อค่านี้ไปยังกระบวนการลูกทุกกระบวนการนับจากนั้นเป็นต้นไป ตรวจสอบค่าของคุณได้ดังนี้:

cat /proc/self/loginuid

เซสชัน SSH แบบโต้ตอบจะแสดง uid ของคุณ เนื่องจาก /etc/pam.d/sshd รวม pam_loginuid.so ไว้ด้วย ค่า 4294967295 หมายความว่า loginuid ไม่เคยถูกกำหนด ซึ่งเป็นเรื่องปกติสำหรับกระบวนการที่เริ่มต้นโดย system daemon ในขณะบูต ส่วนที่สำคัญคือ sudo -i ไม่สามารถเปลี่ยนค่านี้ได้: root shell ที่เปิดโดย alice จะยังคงมี auid เป็น 1000 ดังนั้นทุกคำสั่งที่รันภายในนั้นจึงสามารถระบุตัวตนได้ว่าเป็นของ alice นี่คือช่องโหว่ที่ sudo ทิ้งไว้ การเปลี่ยน loginuid หลังจากกำหนดไปแล้วจำเป็นต้องใช้ CAP_AUDIT_CONTROL ซึ่งผู้ใช้ทั่วไปไม่มีสิทธิ์ดังกล่าว และ sudo auditctl --loginuid-immutable จะปิดการเข้าถึงนี้สำหรับ root จนกว่าจะมีการรีบูตเครื่องครั้งถัดไป

ตรวจสอบให้แน่ใจว่า /etc/pam.d/sshd, /etc/pam.d/login และ /etc/pam.d/cron แต่ละไฟล์มี pam_loginuid.so รวมอยู่ด้วย มิฉะนั้นเหตุการณ์ต่างๆ จะถูกบันทึกโดยไม่มีข้อมูลผู้ใช้แนบมาด้วย ซึ่งเป็นรายการไฟล์เดียวกับที่คุณต้องแก้ไขเมื่อ การเพิ่มความปลอดภัยให้ SSH บน VPS ดังนั้นควรดำเนินการทั้งสองอย่างไปพร้อมกัน

ชุดกฎเริ่มต้นสำหรับ auditd

กฎต่างๆ จะถูกเก็บไว้ใน /etc/audit/rules.d/*.rules โดย augenrules จะทำการรวมไฟล์เหล่านั้นเข้าด้วยกันตามลำดับชื่อไฟล์ ซึ่งลำดับมีความสำคัญต่อการทำงานเนื่องจาก kernel จะหยุดประมวลผลทันทีที่พบกฎแรกที่ตรงกัน โปรดตรวจสอบกฎที่มีอยู่เดิมก่อนเพิ่มกฎใหม่เสมอ เนื่องจาก -D ในไฟล์ลำดับหลังจะล้างกฎทั้งหมดที่โหลดมาก่อนหน้านี้ทิ้ง

ls /etc/audit/rules.d/
cat /etc/audit/rules.d/audit.rules

จากนั้นให้เขียนไฟล์ /etc/audit/rules.d/50-exec.rules:

## Suppressions first: the kernel takes the first matching rule.
-a never,exit -F arch=b64 -S execve -F exe=/usr/bin/dpkg
-a never,exit -F arch=b64 -S execve -F exe=/usr/bin/dpkg-deb
-a never,exit -F arch=b64 -S execve -F exe=/usr/bin/dpkg-query
-a never,exit -F arch=b64 -S execve -F exe=/usr/bin/dpkg-split
-a never,exit -F arch=b64 -S execve -F exe=/usr/bin/dpkg-trigger

## Every program started by a logged-in human.
-a always,exit -F arch=b64 -S execve,execveat -F auid>=1000 -F auid!=unset -k exec
-a always,exit -F arch=b32 -S execve,execveat -F auid>=1000 -F auid!=unset -k exec

## Changes to who may become root.
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity

## Changes to the audit configuration itself.
-w /etc/audit/ -p wa -k auditconfig

โหลดกฎและตรวจสอบความถูกต้อง:

sudo augenrules --load
sudo auditctl -l

การที่ auditctl -l แสดงกฎของคุณกลับมาหมายความว่ากฎเหล่านั้นเริ่มทำงานแล้ว หากขึ้น No rules แสดงว่าการโหลดล้มเหลว และ journalctl -u auditd -n 20 จะระบุชื่อไฟล์และบรรทัดที่ตัวประมวลผลปฏิเสธ ผู้ใช้ audit รุ่นเก่าอาจไม่เข้าใจคีย์เวิร์ด unset หากตัวโหลดแจ้งเตือนเกี่ยวกับฟิลด์นี้ ให้เปลี่ยนไปใช้ -F auid!=4294967295 แทน ซึ่งเป็นค่าเดียวกันแต่เขียนแบบเต็ม

จากนั้นอ่านเหตุการณ์ที่บันทึกไว้:

sudo ausearch -k exec -ts recent -i | tail -40
sudo ausearch -ul 1000 -ts today -i
sudo aureport -k --summary -i

-i จะแปลง uid และหมายเลข syscall ให้เป็นชื่อ ซึ่งในทางปฏิบัติถือเป็นสิ่งที่จำเป็นต้องใช้ -ts recent จะครอบคลุมเหตุการณ์ในช่วง 10 นาทีล่าสุด การทำงานแต่ละครั้งจะมาเป็นกลุ่มของเรคคอร์ด ได้แก่ เรคคอร์ด SYSCALL ที่เก็บ uid, auid, สถานะการ exit และ key, เรคคอร์ด EXECVE ที่มีรายการอาร์กิวเมนต์ทั้งหมด รวมถึงเรคคอร์ด CWD และ PATH สำหรับบริบทเพิ่มเติม

มีข้อจำกัดประการหนึ่งที่ต้องทราบเพราะมักทำให้เกิดความเข้าใจผิด audit จะบันทึกเฉพาะ syscall เท่านั้น และคำสั่ง builtin ของ shell จะไม่มีการเรียก syscall ของตัวเอง ตัวอย่างเช่น cd /root ไม่ได้รันโปรแกรมใดๆ หรือการพิมพ์ echo evil >> /etc/passwd ที่ bash prompt ก็ไม่ได้รันโปรแกรมเช่นกัน เพราะทั้ง echo และการ redirect เกิดขึ้นภายในกระบวนการของ shell ที่กำลังทำงานอยู่ ดังนั้นกฎ execve จะเห็นเฉพาะโปรแกรม และกฎ -w จะเห็นเฉพาะการเขียนข้อมูล ซึ่งทั้งสองอย่างนี้ไม่เพียงพอหากใช้แยกกัน

สุดท้าย ให้ล็อกการตั้งค่า:

## /etc/audit/rules.d/99-finalize.rules
-e 2

-e 2 จะทำให้ชุดกฎไม่สามารถเปลี่ยนแปลงได้จนกว่าจะมีการ reboot ครั้งถัดไป หลังจากโหลดแล้ว auditctl -s จะรายงานค่าเป็น enabled 2 และความพยายามใดๆ ในการเพิ่มหรือลบกฎจะล้มเหลวด้วยข้อผิดพลาด Operation not permitted แม้จะเป็นสิทธิ์ root ก็ตาม ให้เพิ่มไฟล์นี้เป็นลำดับสุดท้าย และเตรียมใจว่าต้อง reboot ทุกครั้งที่ต้องการแก้ไขกฎ การแลกเปลี่ยนนี้คือหัวใจสำคัญ เพราะชุดกฎที่ใครก็สามารถแอบปิดได้นั้นไม่ถือเป็นหลักฐานที่น่าเชื่อถือ

Audit log ที่ไม่มีใครอ่านเป็นเพียงหลักฐานเพื่อการปฏิบัติตามกฎระเบียบเท่านั้น

โหมดความล้มเหลวของ auditd ไม่ใช่การบันทึกข้อมูลตกหล่น แต่เป็นการบันทึกข้อมูลมากเกินไปจนไม่มีใครตรวจสอบ ทำให้ log นั้นมีไว้เพียงเพื่อตอบโจทย์รายการตรวจสอบ (checklist) แทนที่จะใช้ตอบคำถามที่สำคัญ

จงคำนวณปริมาณข้อมูลบนเซิร์ฟเวอร์ของคุณก่อนที่จะปรับแต่งค่าใดๆ:

sudo aureport -k --summary -i
sudo du -sh /var/log/audit

sudo apt upgrade เพียงตัวเดียวอาจรัน process ระยะสั้นหลายพันรายการ และทุกรายการจะพ่วง auid ของคุณไปด้วย ดังนั้นการอัปเดตแพ็กเกจเพียงครั้งเดียวอาจสร้าง log มากกว่าการพิมพ์งานของมนุษย์ตลอดทั้งสัปดาห์ นี่คือเหตุผลที่การตั้งค่า suppress ด้านบนระบุชื่อ dpkg และโปรแกรมสนับสนุนของมัน การทำ suppress ควรทำตามชื่อ executable เท่านั้น ห้ามทำตามชื่อผู้ใช้ เพราะการยกเว้นสำหรับ /usr/bin/dpkg คือช่องโหว่ที่คุณสามารถอธิบายได้ในประโยคเดียว ในขณะที่การยกเว้นสำหรับบัญชีผู้ใช้คือช่องโหว่ที่มีรูปร่างตรงกับสิ่งที่คุณพยายามจะตรวจจับพอดี

คีย์ -k ในแต่ละกฎคือสิ่งที่ทำให้ log สามารถสืบค้นได้ในอีกหนึ่งเดือนถัดมา ausearch -k sudoers คือคำถามที่มีคำตอบ ส่วน ausearch ที่ไม่มีการกรองข้อมูลจะเป็นเพียงกำแพงข้อความที่ฝึกให้คุณเลิกอ่านมันไปเอง หากระบบรวบรวม log ของคุณต้องการรูปแบบ JSON แทนรูปแบบดั้งเดิม laurel คือปลั๊กอินของ auditd ที่จะเขียนเหตุการณ์แต่ละรายการใหม่ให้เป็น JSON object โดยมีการถอดรหัสอาร์กิวเมนต์ให้เรียบร้อย มันจะลงทะเบียนใน /etc/audit/plugins.d/ เหมือนปลั๊กอินอื่นๆ และ auditd จะรับการเปลี่ยนแปลงของปลั๊กอินเมื่อมีการสั่ง sudo pkill -HUP auditd

ต้นทุนที่แท้จริงของ auditd

ทุก syscall ที่ตรงตามเงื่อนไขจะถูกสร้างเป็นบันทึกข้อมูล ซึ่ง kernel จะจัดรูปแบบและส่งต่อไปยัง userspace ต้นทุนที่เกิดขึ้นจะอยู่ในสองส่วนหลัก ซึ่งคุณสามารถวัดผลได้จากภาระงานจริงของตนเอง แทนที่จะคาดเดาจากตัวเลขที่ผู้อื่นเผยแพร่

  • CPU และ latency: เครื่องที่มีการ fork กระบวนการอยู่ตลอดเวลา เช่น build host หรือ CI runner จะสร้างบันทึกข้อมูลต่อการ exec หนึ่งครั้ง เมื่อ backlog ของ kernel เต็ม --backlog_wait_time จะทำให้ kernel สั่งหยุดกระบวนการที่สร้างเหตุการณ์นั้นไว้ชั่วคราว จนกว่าจะมีพื้นที่ว่าง ดังนั้น audit จึงมักปรากฏในรูปแบบของ build ที่ช้าลงมากกว่าจะเป็นการใช้ CPU ที่สูงขึ้น ให้เฝ้าสังเกต backlog และ lost ใน sudo auditctl -s ภายใต้ภาระงานจริง หากค่า lost เพิ่มสูงขึ้น หมายความว่ามีการทิ้งบันทึกข้อมูลไป และ log ที่มีช่องโหว่โดยไม่มีการแจ้งเตือนนั้นแย่ยิ่งกว่าการไม่มี log เลย เพราะคุณจะยังคงเชื่อถือข้อมูลที่ผิดพลาดนั้นอยู่
  • Disk: โปรดอ่าน /etc/audit/auditd.conf และตัดสินใจอย่างรอบคอบว่าจะให้เกิดอะไรขึ้นเมื่อ disk เต็ม เนื่องจากค่าเริ่มต้นที่มากับซอฟต์แวร์เป็นเพียงความเห็นของผู้พัฒนาเท่านั้น max_log_file, num_logs และ max_log_file_action ใช้สำหรับควบคุมการหมุนเวียนไฟล์ log (rotation) ส่วน space_left_action, admin_space_left_action และ disk_full_action ใช้ควบคุมสถานการณ์ฉุกเฉิน โดยการกระทำบางอย่างที่มีให้เลือก เช่น halt และ single จะทำให้เครื่องหยุดทำงานทันทีเพื่อป้องกันไม่ให้บันทึกข้อมูลสูญหาย

บรรทัด -f ใน /etc/audit/rules.d/audit.rules คือการตัดสินใจในระดับ kernel เช่นเดียวกัน โดย -f 1 จะรายงานความล้มเหลวของ audit ไปยัง syslog ส่วน -f 2 จะทำให้ kernel เกิดอาการ panic เลือกใช้ 2 ก็ต่อเมื่อคุณยอมรับได้จริงๆ ที่จะสูญเสียเซิร์ฟเวอร์ไปดีกว่าการสูญเสียบันทึกข้อมูล สำหรับ VPS ที่รันบริการซึ่งมีผู้ใช้งานพึ่งพาอยู่ ควรเลือกใช้วิธีการหมุนเวียนไฟล์ log และย้ายปัญหาด้านพื้นที่จัดเก็บข้อมูลออกไปจากตัวเครื่องจะเหมาะสมกว่า

ส่ง log ออกจากเครื่องแบบเกือบเรียลไทม์

นี่คือส่วนที่รายงานเหตุการณ์ความปลอดภัยมักพิสูจน์ให้เห็นเสมอ log ที่ค้างอยู่บนโฮสต์ที่ถูกบุกรุกสามารถถูกแก้ไขโดยผู้ที่บุกรุกเข้ามาได้ ผู้ใช้ root สามารถเขียนทับ /var/log/auth.log, ลบ /var/log/audit/audit.log และหยุดการทำงานของ daemon ได้ ส่วน -e 2 ป้องกันไม่ให้กฎถูกยกเลิกการโหลด แต่ไม่ได้ช่วยอะไรกับ rm ทุกเลเยอร์ที่อยู่เหนือกว่าจะสร้างหลักฐานได้ก็ต่อเมื่อสำเนาถูกส่งออกจากเครื่องไปก่อนเท่านั้น

การขนส่งข้อมูลของ audit ใช้ปลั๊กอิน audisp-remote จาก audispd-plugins ให้เปิดใช้งานใน /etc/audit/plugins.d/au-remote.conf:

active = yes
direction = out
path = /usr/sbin/audisp-remote
type = always
format = string

ตรวจสอบ path เทียบกับ command -v audisp-remote ก่อนที่คุณจะ reload เพราะ path ที่ผิดจะไม่สร้างผลลัพธ์ใดๆ นอกจากบรรทัดเดียวใน journal ตั้งค่า remote_server และ port ใน /etc/audit/audisp-remote.conf และบนฝั่ง collector ให้ตั้งค่า tcp_listen_port = 60 ใน auditd.conf ของมันเอง จากนั้น reload ด้วย sudo pkill -HUP auditd ในหลาย image การใช้ systemctl restart auditd จะถูกปฏิเสธเนื่องจาก unit file ตั้งค่า RefuseManualStop=yes ไว้ ดังนั้นการส่ง signal จึงเป็นวิธีที่เชื่อถือได้มากกว่า

อีกทางเลือกหนึ่งคือการนำ audit เข้าสู่กระแส syslog ที่คุณส่งต่ออยู่แล้ว /etc/audit/plugins.d/syslog.conf มาพร้อมกับ active = no ให้ตั้งค่าเป็น yes แล้ว reload จากนั้นเหตุการณ์ของ audit จะไปรวมกับบรรทัดของ sudo และรายการอื่นๆ จากนั้นให้ส่งต่อข้อมูลทั้งหมดด้วย rsyslog ผ่าน TLS (transport layer security) ซึ่งต้องใช้แพ็กเกจ rsyslog-gnutls:

# /etc/rsyslog.d/60-forward.conf
*.* action(type="omfwd"
    target="logs.example.net" port="6514" protocol="tcp"
    StreamDriver="gtls" StreamDriverMode="1"
    StreamDriverAuthMode="x509/name"
    StreamDriverPermittedPeers="logs.example.net"
    action.resumeRetryCount="-1"
    queue.type="linkedList" queue.filename="fwd" queue.saveOnShutdown="on")

การตั้งค่าคิวเป็นส่วนที่น่าสนใจ action.resumeRetryCount="-1" จะพยายามส่งซ้ำตลอดไป และคิวที่ช่วยด้วยดิสก์อย่าง queue.saveOnShutdown="on" จะเก็บระเบียนไว้ในขณะที่ collector ไม่สามารถเข้าถึงได้ แล้วจึงส่งข้อมูลเหล่านั้นเมื่อ collector กลับมาออนไลน์ หากไม่มีสองส่วนนี้ การรีบูต collector จะทิ้งช่องโหว่ไว้ในหลักฐานของคุณโดยไม่มีอะไรแจ้งเตือนว่ามีช่องโหว่นั้นอยู่ ให้ใช้การตั้งค่าด้วย sudo systemctl restart rsyslog จากนั้นยืนยันว่าระเบียนมาถึง collector จริงก่อนที่คุณจะเชื่อถือข้อมูลทั้งหมด

เหลืออีกหนึ่งวงจรที่ต้องปิด: collector ต้องเป็นเครื่องที่ผู้ถูกตรวจสอบไม่สามารถล็อกอินเข้าไปได้ หากกลุ่มผู้ดูแลระบบเดียวกันถือสิทธิ์ root บนเซิร์ฟเวอร์ log คุณก็แค่คัดลอกไฟล์ ไม่ใช่การปกป้องไฟล์นั้น ต้องใช้ credential แยกกัน, กุญแจแยกกัน และควรเป็นบัญชีผู้ให้บริการที่แยกจากกัน นี่คือเหตุผลเดียวกับที่ทำให้ วิธีจัดการเซิร์ฟเวอร์ Linux จำนวนมากจากศูนย์กลาง คุ้มค่าที่จะสร้างก่อนที่คุณจะจำเป็นต้องใช้ และเป็นความแตกต่างระหว่างชั่วโมงแรกที่มีประโยชน์กับชั่วโมงแรกที่ไร้ค่าเมื่อ คุณกำลังจัดการกับ VPS ที่ถูกบุกรุก

ตรวจสอบว่าผู้ใช้ทั่วไปไม่สามารถแก้ไขบันทึกได้

ให้ทดสอบข้ออ้างดังกล่าวแทนการคาดเดา โดยใช้บัญชีผู้ใช้ทั่วไปที่ไม่มีสิทธิ์ sudo:

echo test >> /var/log/auth.log
cat /var/log/audit/audit.log
auditctl -D
id -nG

ผลลัพธ์ที่คาดหวังตามลำดับคือ: Permission denied เนื่องจาก auth.log มีเจ้าของเป็น syslog โดยมีกลุ่มคือ adm และโหมด 640; Permission denied อีกครั้ง เนื่องจาก audit log มีโหมด 600 และมีเจ้าของเป็น root; ข้อผิดพลาดที่ปฏิเสธการทำงานเนื่องจากการเปลี่ยนกฎ audit จำเป็นต้องใช้ CAP_AUDIT_CONTROL; และรายการกลุ่มที่ไม่มีทั้ง adm หรือ systemd-journal

การตรวจสอบรายการสุดท้ายคือจุดที่หลายคนมักพลาด การเป็นสมาชิกของ adm จะให้สิทธิ์อ่าน /var/log/auth.log และการเป็นสมาชิกของ systemd-journal จะให้สิทธิ์อ่าน journal ทั้งหมด แต่ทั้งสองสิทธิ์นี้ไม่ให้สิทธิ์ในการเขียน จึงไม่สามารถแก้ไขข้อมูลได้ ทั้งนี้ทั้งสองสิทธิ์ช่วยให้บุคคลสามารถอ่านบรรทัดการตรวจสอบสิทธิ์ทั้งหมดบนเครื่องได้ ซึ่งเป็นการตัดสินใจที่ควรทำอย่างตั้งใจ แทนที่จะคัดลอกบรรทัด usermod -aG มาจากคำตอบในฟอรัม

สุดท้าย ให้ยืนยันสองสิ่งที่ต้องคงอยู่หลังจากการรีบูต:

sudo auditctl -s
systemctl is-enabled auditd

enabled 2 หมายความว่าชุดกฎถูกล็อกไว้จนกว่าจะมีการบูตครั้งถัดไป enabled จากคำสั่งที่สองหมายความว่า auditd จะเริ่มทำงานอีกครั้งหลังจากการบูตนั้น ชุดกฎที่คงอยู่ได้เพียงจนกว่าจะมีการอัปเดต kernel ครั้งถัดไปนั้นไม่ถือว่าเป็น audit trail ที่สมบูรณ์

FAQ

ฉันจะดูคำสั่งทั้งหมดที่ผู้ใช้รายหนึ่งเรียกใช้งานได้อย่างไร?

ค้นหา uid ของผู้ใช้ด้วย id -u alice จากนั้นค้นหาใน audit log ตาม login uid ด้วย sudo ausearch -ul 1000 -ts today -i เพิ่ม -k exec เพื่อจำกัดผลลัพธ์เฉพาะกฎ execve ค่า login uid จะถูกกำหนดไว้ตั้งแต่ตอนล็อกอินและคงอยู่แม้จะมีการใช้ su และ sudo -i ดังนั้นวิธีนี้จึงสามารถตรวจจับคำสั่งที่รันภายใน root shell ที่บัญชีนั้นเปิดขึ้นมาได้ วิธีนี้ใช้ได้เฉพาะกับคำสั่งที่รันหลังจากโหลดกฎเข้าไปแล้วเท่านั้น เนื่องจาก audit ไม่ได้เก็บประวัติของเหตุการณ์ที่ไม่ได้ตั้งค่าให้บันทึกไว้ sudo aureport -k --summary -i จะแสดงจำนวนเหตุการณ์แยกตามกฎหากคุณต้องการดูภาพรวมของข้อมูลก่อน

ผู้ใช้สามารถลบ bash history เพื่อซ่อนสิ่งที่รันไปแล้วได้หรือไม่?

ได้ และไม่จำเป็นต้องใช้สิทธิ์พิเศษใดๆ ~/.bash_history เป็นไฟล์ที่ผู้ใช้รายนั้นเป็นเจ้าของด้วยโหมด 600 ดังนั้นผู้ใช้จึงสามารถแก้ไข ตัดทอน หรือลบไฟล์ทิ้งได้ นอกจากนี้ยังสามารถหยุดการเขียนไฟล์ด้วย unset HISTFILE, หยุดการบันทึกระหว่างเซสชันด้วย set +o history หรือซ่อนคำสั่งเฉพาะรายการโดยการพิมพ์เว้นวรรคไว้ข้างหน้าเมื่อตั้งค่า HISTCONTROL=ignorespace ไว้ Bash จะเขียนไฟล์เมื่อ shell ปิดตัวลง ดังนั้นเซสชันที่ถูกสั่ง kill ด้วย kill -9 $$ จะไม่บันทึกข้อมูลใดๆ ให้ถือว่า shell history เป็นเพียงเบาะแส ไม่ใช่หลักฐาน

sudo บันทึกสิ่งที่เกิดขึ้นภายใน sudo -i หรือไม่?

ไม่ sudo จะบันทึกเฉพาะคำสั่งที่ถูกสั่งให้รันเท่านั้น ดังนั้น sudo -i จะสร้างบรรทัดบันทึกสำหรับ shell เพียงบรรทัดเดียวและไม่มีอะไรหลังจากนั้น ทุกคำสั่งที่พิมพ์ใน root shell นั้น sudo จะมองไม่เห็นเพราะ sudo ไม่ได้เกี่ยวข้องอีกต่อไป sudo su -, sudo bash และโปรแกรมใดๆ ที่อนุญาตให้ใช้ shell escape ก็มีพฤติกรรมเช่นเดียวกัน วิธีปิดช่องโหว่นี้มีสองทางคือ การใช้กฎ audit บน execve ซึ่งจะบันทึกทุกโปรแกรมโดยแนบ login uid ต้นทางไว้ และการตั้งค่า sudoers ที่ไม่อนุญาตให้เข้าถึง shell ตั้งแต่แรก

auditd จะทำให้เซิร์ฟเวอร์ทำงานช้าลงหรือไม่?

ขึ้นอยู่กับจำนวน process ที่ workload ของคุณเรียกใช้งานเป็นหลัก ดังนั้นควรวัดผลจริงแทนการเชื่อตัวเลขใดๆ เซิร์ฟเวอร์ที่ทำหน้าที่ตอบสนองคำขอเป็นส่วนใหญ่จะมีการเรียก exec น้อยมากและจะไม่ได้รับผลกระทบ แต่สำหรับ build host หรือ CI runner ที่มีการเรียก exec ตลอดเวลาอาจสังเกตเห็นความแตกต่างได้ เพราะเมื่อ audit backlog ของ kernel เต็ม process ที่สร้างเหตุการณ์นั้นจะถูกหยุดพักจนกว่าจะมีพื้นที่ว่าง ให้รัน sudo auditctl -s ในขณะที่มี load จริงและคอยสังเกต backlog และ lost หากค่า lost สูงกว่าศูนย์ หมายความว่ามีการทิ้งบันทึก ซึ่งเป็นผลลัพธ์ที่แย่ที่สุดเนื่องจาก log จะมีช่องโหว่ที่มองไม่เห็น

ควรจัดเก็บ audit logs ไว้ที่ไหน?

ควรเก็บไว้บนเครื่องอื่นโดยมีความหน่วงเวลาเพียงไม่กี่วินาที ใครก็ตามที่เข้าถึงสิทธิ์ root บนโฮสต์ที่ถูกตรวจสอบสามารถลบ /var/log/audit/audit.log และเขียนทับ /var/log/auth.log ได้ ดังนั้นสำเนาในเครื่องจะตอบคำถามได้เฉพาะเหตุการณ์ที่ไม่มีใครพยายามซ่อนไว้เท่านั้น ให้ส่งต่อข้อมูลด้วยปลั๊กอิน audisp-remote ไปยัง auditd ส่วนกลาง หรือเปิดใช้งานปลั๊กอิน audit syslog และส่งต่อ syslog stream ทั้งหมดด้วย rsyslog ผ่าน TLS กำหนด credential เฉพาะสำหรับตัวรวบรวมข้อมูล และตรวจสอบให้แน่ใจว่าบัญชีที่ถูกตรวจสอบไม่มีสิทธิ์เข้าถึงเครื่องดังกล่าว