auditd는 리눅스 커널의 감사 서브시스템(kaudit)과 사용자 공간 데몬을 연결해 시스템콜, 파일 접근, 사용자 인증 이벤트를 실시간으로 기록하는 표준 감사 프레임워크다. 침투 테스트를 몇 년 하다 보니 이런 패턴이 반복되는데, 대부분의 서버가 auditd를 설치만 해두고 규칙은 기본값 그대로 방치한다. 이 상태로는 공격자의 흔적이 로그에 거의 남지 않는다. 이 글에서는 실제 공격 시나리오를 하나씩 재현하고, 각 단계에 대응하는 auditd 규칙을 붙여 탐지 가능한 시스템을 만드는 방법을 다룬다.
auditd는 커널 kaudit에서 발생한 이벤트를 /var/log/audit/audit.log에 기록하며, syslog와 달리 시스템콜 단위의 결정론적 감사를 제공한다.
실전 규칙은 /etc/audit/rules.d/*.rules에 저장하고 augenrules --load로 커널에 로드하며, -e 2 플래그로 규칙을 재부팅 전까지 잠글 수 있다.
SSH 키 도용, sudoers 변조, cron 지속화, LD_PRELOAD 우회, 커널 모듈 로드는 각각 1~2줄의 규칙으로 100% 탐지 가능하다.
공격자는 auditctl -e 0이나 규칙 삭제로 감사 자체를 무력화하려 하므로, immutable 플래그와 별도 로그 서버 전송이 필수다.
auditd 아키텍처와 kaudit의 관계
auditd를 제대로 다루려면 데몬이 아닌 커널 감사 서브시스템 kaudit부터 이해해야 한다. 시스템콜이 실행되면 커널은 감사 컨텍스트를 채우고, 로드된 규칙과 매칭되면 netlink 소켓(NETLINK_AUDIT)을 통해 사용자 공간으로 이벤트를 밀어 넣는다. 사용자 공간에서는 auditd 데몬이 이 소켓을 읽어 /var/log/audit/audit.log에 기록하고, 필요하면 audispd(2.6 이전) 또는 auditd 내장 dispatcher(2.6+)를 통해 rsyslog, remote logger, Wazuh 등으로 전달한다.
이 구조가 syslog와 결정적으로 다른 점은 두 가지다. 첫째, 감사 이벤트는 시스템콜이 반환되기 전 커널 컨텍스트에서 채워지므로 프로세스 이름·PID·auid(원본 로그인 UID)를 위조할 수 없다. 둘째, 규칙 매칭이 커널에서 이루어지므로 사용자 공간 필터링과 달리 이벤트 손실이 거의 없다. 반대로 오탐지 규칙 하나가 초당 수만 건의 이벤트를 만들어 시스템 부하를 일으킬 수 있다는 점은 뒤에서 다룰 튜닝 이슈로 이어진다.
감사 시스템의 상태는 auditctl -s로 확인할 수 있다. 주요 필드는 enabled(0=비활성, 1=활성, 2=immutable), backlog_limit(커널 백로그 큐 크기), lost(폐기된 이벤트 수)다. 필자는 침투 테스트 첫 단계에서 항상 이 셋을 먼저 확인한다. lost>0이면 실제 공격 로그가 이미 유실되고 있다는 뜻이니까.
설치와 첫 규칙 로드
대부분의 배포판은 감사 시스템을 기본 포함하지만 데몬만 활성화하면 되는 경우가 많다. Ubuntu 24.04·RHEL 9·Debian 12 기준 설치는 다음과 같다.
규칙 파일은 /etc/audit/rules.d/ 아래에 여러 개로 나누어 두고, augenrules --load가 이들을 병합해 /etc/audit/audit.rules를 만든 뒤 커널에 로드한다. 필자는 규칙을 다음 세 파일로 분리해 관리한다 (사실은 네 개지만).
/etc/audit/rules.d/
├── 10-base.rules # 백로그, 실패 정책
├── 30-compliance.rules # CIS/STIG 규칙 세트
├── 50-attack.rules # 공격 탐지용 사용자 정의 규칙
└── 99-finalize.rules # -e 2 (immutable) 마지막에
파일명 접두어 숫자는 로드 순서에 영향을 주므로 immutable 플래그를 반드시 마지막(99-)에 두어야 한다. 그렇지 않으면 immutable 이후 나열된 규칙은 로드되지 않고, 재부팅해야 반영된다. 이 순서 문제로 하루를 날린 적이 있으니 진지하게 말한다.
audit.rules 문법 완벽 정리
audit 규칙은 크게 제어 규칙, 파일 워치, 시스템콜 규칙 세 종류다. 문법이 좀 예민해서 처음 접하면 헷갈리는데, 몇 가지 패턴만 익히면 전체 규칙 세트를 자유롭게 작성할 수 있다.
제어 규칙
-D # 기존 규칙 모두 삭제 (파일 첫 줄)
-b 65536 # 커널 백로그 큐 크기
-f 1 # failure 모드: 0=무시, 1=printk, 2=panic
-i # 로드 중 오류 무시
--backlog_wait_time 60000
-f 2는 감사 실패 시 커널 패닉을 유발한다. 규제 산업에서는 필수지만 잘못 쓰면 서비스가 멈추므로, 대부분 -f 1로 시작한다.
파일 워치
-w /etc/passwd -p wa -k identity
# 파일 경로 -p 권한 -k 태그
# w=write, a=attribute, r=read, x=execute
파일 워치는 내부적으로 inotify 유사 메커니즘이 아니라 커널 감사 filter가 처리한다. 심볼릭 링크의 대상까지 자동 추적하지는 않으므로 /etc/passwd가 실제로 다른 위치에 있으면 워치가 놓친다.
시스템콜 규칙
-a always,exit -F arch=b64 -S execve -F auid>=1000 -F auid!=unset -k user_exec
# -a action,filter (always,exit / never,exit / always,exclude 등)
# -F 필드 조건 (arch=b64는 64비트 syscall 테이블)
# -S 시스템콜 이름 (여러 개 나열 가능)
# -k 키 (ausearch 시 검색용)
가장 자주 혼동하는 부분이 auid다. auid는 사용자가 로그인할 때 login·sshd가 설정하는 원본 UID로, su나 sudo로 신원을 바꿔도 유지된다. 즉 공격자가 root로 권한 상승해도 auid는 처음 로그인한 계정을 가리키므로 감사의 핵심이다. -F auid!=unset은 부팅 초기 auid=-1(4294967295) 상태를 걸러낸다.
공격 시나리오별 탐지 규칙 설계
필자가 침투 테스트에서 반복해서 쓰는 공격 5가지를, 재현 명령과 탐지 규칙 한 쌍으로 정리했다. 각 규칙은 오탐 최소화를 위해 auid와 성공 여부까지 걸어 실제 위협만 필터링한다. 런타임 계층의 eBPF 기반 Falco/Tetragon 탐지와 조합하면 syscall 단위 커버리지를 이중화할 수 있다.
1) SSH 인증 파일 도용
공격자는 로컬 셸을 얻으면 곧바로 cat /root/.ssh/authorized_keys이나 ~/.ssh/id_rsa를 훔쳐 지속화한다. 실제로 이 시나리오를 최근 프로젝트에서 그대로 마주쳤다.
# 공격
cat /root/.ssh/authorized_keys
# 탐지 규칙
-w /root/.ssh -p wa -k ssh_keys_root
-w /home -p wa -k ssh_keys_users
-a always,exit -F arch=b64 -S openat -F path=/root/.ssh/id_rsa -F success=1 -k ssh_key_read
2) sudoers 변조 지속화
흔한 리버스 셸 후 명령이 echo 'user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers다.
-w /etc/sudoers -p wa -k sudoers_change
-w /etc/sudoers.d/ -p wa -k sudoers_change
3) Cron·systemd 타이머 지속화
-w /etc/crontab -p wa -k cron_change
-w /etc/cron.d/ -p wa -k cron_change
-w /var/spool/cron/ -p wa -k cron_change
-w /etc/systemd/system/ -p wa -k systemd_unit
# 시간 변경
-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time_change
-w /etc/localtime -p wa -k time_change
# 사용자·그룹 정보 변경
-w /etc/group -p wa -k identity
-w /etc/passwd -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/security/opasswd -p wa -k identity
# 시스템 로케일 변경
-w /etc/issue -p wa -k system-locale
-w /etc/issue.net -p wa -k system-locale
-w /etc/hosts -p wa -k system-locale
-w /etc/hostname -p wa -k system-locale
# MAC 정책 변경 (SELinux/AppArmor)
-w /etc/selinux/ -p wa -k MAC-policy
-w /etc/apparmor/ -p wa -k MAC-policy
-w /etc/apparmor.d/ -p wa -k MAC-policy
# 세션 시작
-w /var/run/utmp -p wa -k session
-w /var/log/wtmp -p wa -k logins
-w /var/log/btmp -p wa -k logins
# DAC 권한 변경
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=unset -k perm_mod
-a always,exit -F arch=b64 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=unset -k perm_mod
# 실패한 파일 접근
-a always,exit -F arch=b64 -S open,openat -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access
-a always,exit -F arch=b64 -S open,openat -F exit=-EPERM -F auid>=1000 -F auid!=unset -k access
# 권한 상승 명령 사용
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=unset -k privileged
참고로 CIS 규칙 세트에는 -F arch=b32도 함께 포함하는 것이 원칙이지만, 최근 배포판은 32비트 시스템콜을 거의 쓰지 않으므로 프로덕션에서는 b64만 유지해도 실제 커버리지 손실은 미미하다. 감사 대비 성능 트레이드오프가 크다면 이 최적화를 고려하자.
immutable 플래그와 변조 방지
필자가 침투 테스트 리포트에 가장 많이 쓰는 지적이 바로 이 부분이다. 공격자는 root를 얻자마자 auditctl -D로 규칙을 지우거나 auditctl -e 0으로 감사 자체를 끈다. 이를 막는 유일한 표준 방법이 immutable 플래그다.
# /etc/audit/rules.d/99-finalize.rules
-e 2
-e 2가 로드되면 다음 재부팅까지 규칙 추가·삭제와 enabled 값 변경이 모두 커널 수준에서 거부된다. root라도 예외 없다. 단, immutable 상태에서 규칙을 갱신하려면 재부팅이 필요하므로 자동 배포 파이프라인에서는 needs-restart 플래그로 다음 유지보수 창에 반영하도록 설계해야 한다.
변조 방지의 두 번째 계층은 로그 자체를 원격지로 전송해 로컬 감사 로그가 삭제되어도 흔적이 남게 하는 것이다. 이는 다음 절에서 다룬다.
ausearch와 aureport로 로그 분석하기
audit.log는 사람이 읽기 어려운 key=value 스트림이지만, 두 도구로 대부분의 분석이 끝난다.
# 최근 24시간, sudoers 변경 이벤트
sudo ausearch -k sudoers_change -ts recent -i
# 특정 UID의 실행 이벤트
sudo ausearch -ua 1000 -sc execve -i --format text
# 로그인 실패 요약
sudo aureport --failed --summary
# 사용자별 명령 실행 top 10
sudo aureport -x --summary | head
-i 플래그는 UID·GID·syscall 번호를 사람이 읽는 이름으로 변환한다. --format text는 자연어 문장으로 출력해 인시던트 리포트에 그대로 붙일 수 있어서 편하다. linux-audit/audit-userspace 저장소의 examples/ 디렉터리에는 실전용 aureport 쿼리 스크립트가 다수 포함되어 있으니 참고할 만하다.
SIEM 전송과 실시간 알림
솔직히 로컬 로그만으로는 공격자가 rm -rf /var/log/audit/를 실행하면 그걸로 끝이다. audispd 플러그인을 통해 rsyslog로 넘긴 뒤 원격 SIEM으로 전송하는 것이 표준 구성이다.
# /etc/audit/plugins.d/syslog.conf
active = yes
direction = out
path = /sbin/audisp-syslog
type = always
args = LOG_LOCAL6
format = string
rsyslog 측에서는 local6.*를 별도 파일이나 TCP 리모트로 라우팅한다. Wazuh를 쓰는 경우 에이전트가 /var/log/audit/audit.log를 직접 tail하므로 syslog 우회 없이 바로 매니저로 전송된다. Splunk Universal Forwarder도 동일하게 audit.log를 소스로 지정하면 된다.
# /etc/rsyslog.d/60-audit.conf
if $syslogfacility-text == 'local6' then {
action(type="omfwd" target="siem.internal.corp" port="6514"
protocol="tcp" streamdriver="gtls"
streamdriverMode="1" streamdriverAuthMode="x509/name")
stop
}
TLS 전송은 반드시 켜야 한다. auditd 이벤트에는 명령줄 인자와 파일 경로가 포함되어 있어 평문 전송 시 크리덴셜이 노출될 수 있다. Red Hat 감사 시스템 공식 문서도 원격 감사 전송을 필수 컨트롤로 언급한다.
성능 튜닝과 흔한 함정
auditd를 처음 프로덕션에 넣는 팀이 겪는 문제는 대체로 세 가지다.
백로그 오버플로우
auditctl -s의 lost가 0이 아니면 커널 백로그가 넘쳐 이벤트가 폐기되고 있다. 대응은 -b 값을 증가시키는 것. 웹 서버는 65536, DB 서버는 131072를 시작점으로 잡고 lost가 안정될 때까지 두 배씩 늘린다.
디스크 I/O 폭주
기본 write_logs = yes와 flush = incremental_async는 성능에 안전한 조합이다. 규제 요구로 flush = sync를 켜면 fsync가 매 이벤트마다 발생해 IOPS가 급증한다. 이때는 감사 로그를 별도 NVMe 디바이스로 분리하거나, 원격 전송만 사용하고 로컬 파일 flush를 낮춘다.
과도한 execve 감사
-S execve -F auid>=1000는 개발 서버에서는 안전하지만 CI/CD 러너처럼 초당 수백 개의 프로세스가 생성되는 노드에서는 CPU가 15%까지 튄다. 이런 노드에는 -F auid!=<runner_uid>로 러너 UID를 제외하거나, syscall 감사 대신 eBPF 기반 런타임 보안 도구로 대체하는 것이 낫다.
자주 묻는 질문
auditd와 syslog의 차이는 무엇인가요?
syslog는 애플리케이션이 syslog(3)로 자발적으로 남긴 메시지를 사용자 공간에서 수집합니다. auditd는 커널의 감사 서브시스템이 시스템콜 반환 직전 결정론적으로 이벤트를 생성하므로 프로세스가 로그를 남기지 않아도 감사할 수 있고 위조가 불가능합니다.
auditd 규칙을 immutable로 만드는 방법은 무엇인가요?
규칙 파일 마지막에 -e 2를 추가하고 augenrules --load로 반영하면 다음 재부팅까지 규칙 변경과 감사 비활성화가 커널 수준에서 차단됩니다. root라도 예외 없이 거부됩니다.
auditd 로그가 너무 커지면 어떻게 관리하나요?
/etc/audit/auditd.conf의 max_log_file과 num_logs로 자동 로테이션을 설정합니다. 규제 준수가 필요하면 max_log_file_action = keep_logs로 삭제 대신 유지하고, 원격 SIEM으로 전송해 로컬 디스크 압박을 없애는 것이 표준입니다.
auditd 규칙이 CIS Benchmark를 완전히 만족하나요?
본문의 30-compliance.rules 세트는 CIS Distribution Independent Linux v3.0.0의 섹션 4.1을 대부분 충족합니다. 다만 CIS는 arch=b32 규칙과 특정 컨트롤 파일 워치(/etc/sudoers.d 등)를 명시적으로 요구하므로 최종 배포 전 lynis audit system이나 oscap 스캐너로 검증하는 것을 권장합니다.
컨테이너 환경에서도 auditd를 쓸 수 있나요?
auditd는 커널 전역 감사 프레임워크라 컨테이너 네임스페이스별로 분리되지 않으므로 호스트에서만 실행해야 합니다. 쿠버네티스 노드에서는 auditd로 호스트 계층을, 컨테이너 내부는 Falco나 Tetragon 같은 eBPF 도구로 커버하는 이중 계층 구성이 실무 표준입니다.
eBPF 기반 리눅스 런타임 보안 모니터링을 Falco와 Tetragon으로 구축하는 실전 가이드입니다. 커널 수준 위협 탐지부터 TracingPolicy를 통한 정책 집행까지, 프로덕션에서 바로 쓸 수 있는 설치법과 커스텀 룰, 성능 최적화 전략을 코드 예제와 함께 다룹니다.