SSD Nodes Learn 🎉 VPS $5.50/월부터
가이드 Matt Connor작성자 Matt Connor

리눅스 서버 사용자 명령어 감사 및 기록 방법

셸 히스토리는 감사 추적이 될 수 없습니다. sudo 로그, 세션 레코딩, 셸 훅, auditd execve 규칙의 차이점을 비교하고, 공격자가 로그를 삭제하기 전에 외부 서버로 안전하게 전송하는 실무적인 구축 가이드를 확인해 보시기 바랍니다.

서버에서 사용자가 실행한 명령을 실제로 기록하는 방법

서버에서 사용자가 실행한 명령을 감사하려면 사용자가 수정할 수 없는 기록이 필요합니다. 셸 히스토리는 그러한 기록이 아닙니다. 이는 기록을 작성한 계정이 소유한 편의용 파일일 뿐이며, 해당 셸에 입력할 수 있는 사용자라면 누구나 기능을 끄거나 파일을 삭제할 수 있습니다.

실제 기록을 유지하는 계층은 네 가지가 있으며, 각 계층마다 비용이 발생합니다. sudo는 명령당 한 줄씩 syslog에 기록합니다. sudo I/O 로깅은 특정 계정의 전체 세션을 캡처합니다. PROMPT_COMMAND과 같은 셸 훅은 대화형 bash 사용자가 입력한 내용을 기록합니다. 커널 감사 하위 시스템은 execve 시스템 호출 자체를 기록하며, 이것이 모든 프로세스를 볼 수 있는 유일한 계층인 이유입니다. 이 가이드는 해당 계층들을 단계별로 설명하고, 각 계층의 한계를 짚어본 뒤, 마지막으로 가장 중요한 부분인 '감사 대상자가 기록에 접근하기 전에 해당 기록을 외부로 전송하는 방법'을 다룹니다.

시작하기 전에 한 가지 주의할 점이 있습니다. 감사 하위 시스템은 커널 수준에서 작동하므로, 호스트 커널을 공유하는 컨테이너 내부에서는 테스트할 수 없습니다. 이 명령들은 커널을 직접 제어할 수 있는 KVM VPS 환경에서 실행하십시오.

쉘 히스토리가 감사 추적이 될 수 없는 이유

~/.bash_history는 네 가지 일반적인 이유로 증거로서의 가치가 없으며, 공격자가 영리할 필요조차 없습니다.

사용자의 소유입니다. 파일 모드는 600이며 해당 계정이 소유하므로, rm ~/.bash_history은 어떠한 권한도 필요하지 않습니다. 편집기로 파일을 열어 중요한 20줄을 삭제하는 것 역시 아무런 제약이 없습니다.

쉘이 종료될 때 기록됩니다. kill -9 $$로 세션을 종료하거나 연결이 끊기면 아무것도 기록되지 않습니다. exit 이전에 history -c을 실행해도 동일한 결과가 나타나며, 마치 아무 일도 없었던 것처럼 보입니다.

한 단어로 기록을 끌 수 있습니다. unset HISTFILE은 해당 세션에서 파일 기록을 중단합니다. set +o history은 즉시 기록을 멈춥니다. HISTCONTROL=ignorespace는 앞에 공백을 두고 입력한 모든 명령을 숨깁니다. 이 모든 기능은 man bash에 포함되어 있으며, 이는 사용자가 직접 제어하도록 설계되었기 때문입니다.

실행된 내용이 아니라 입력된 내용을 기록합니다. 별칭(alias)이나 쉘 함수를 사용하면 파일에 기록된 텍스트와 커널이 실제로 실행한 프로그램이 다를 수 있습니다.

또한 HISTTIMEFORMAT가 설정된 상태에서 항목이 기록되지 않는 한 타임스탬프도 존재하지 않습니다. bash는 해당 변수가 설정된 경우에만 #1755043200 마커 라인을 기록하기 때문입니다.

공유 계정으로 로그인하는 경우 누가 작업했는지 알 수 없습니다. 세 사람이 하나의 deploy 계정을 사용하면 하나의 uid 아래에 뒤섞인 파일 하나만 생성됩니다. 두 사람이 하나의 uid를 공유하는 상황에서는 어떤 로깅 계층도 특정 작업을 개인에게 귀속시킬 수 없으며, 이것이 공유 계정 대신 개인별로 권한 없는 계정을 사용하는 것에 대한 실질적인 근거입니다.

쉘 히스토리는 본연의 목적인 '어제 입력한 명령을 다시 입력하도록 돕는 기능'에는 충실합니다. 이를 힌트로 활용하십시오. 절대로 증거로 제시해서는 안 됩니다.

sudo가 기록하는 내용과 기록이 중단되는 지점

sudo는 실행하는 모든 명령에 대한 한 줄의 기록을 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을 신뢰하기 전에 휘발성인지 확인하십시오.

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

로그에는 셸에 대한 단일 기록만 남습니다. root 셸 내부에서 입력된 모든 명령은 sudo가 더 이상 경로상에 존재하지 않기 때문에 sudo가 확인할 수 없습니다. sudo su -, sudo bash, sudo vim /etc/shadow 뒤에 :!bash를 사용하는 경우 모두 동일한 형태를 띱니다. vimfind과 같이 셸 이스케이프가 가능한 모든 프로그램을 허용하는 sudoers 규칙은 기록되지 않는 root 권한을 부여하는 규칙과 같습니다. 로그 기록을 신뢰하기 전에 해당 계정이 실제로 접근할 수 있는 범위를 먼저 확인하십시오.

sudo -l -U alice

특정 계정의 전체 세션 기록하기

먼저 사용 중인 sudo 버전을 확인하십시오. Rust로 재작성된 버전에는 이 기능이 포함되어 있지 않습니다:

sudo --version | head -1

출력 결과에 sudo-rs가 표시된다면 이 섹션을 건너뛰고 audit 하위 시스템을 사용하십시오. Ubuntu 25.10 및 26.04 릴리스에 대한 공식 문서에는 I/O 로깅과 sudoreplay이 지원되지 않는다고 명시되어 있으며, 이는 2026년 8월 기준으로도 유효합니다. 해당 릴리스에서는 sudo-rs가 기본 sudo이므로, 업그레이드 시 기존에 사용하던 제어 기능이 제거될 수 있다는 점을 유의해야 합니다. sudo 기반 로깅을 계획하기 전에 sudo-rs 동작 변경 사항 전체 목록을 읽어보는 것이 좋습니다.

Ubuntu 24.04 LTS에서 여전히 제공하는 원본 sudo를 사용하는 경우, 특정 계정에 대해 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 줄은 재생 세션 자체가 다시 기록되는 것을 방지합니다. 실제 위험 요소는 보안 정보입니다. I/O 로그에는 세션 내 프롬프트에 입력한 비밀번호를 포함하여 입력하고 출력된 모든 내용이 저장되므로, 비밀번호 저장소와 동일한 수준의 보호가 필요합니다. 또한 적용 범위가 제한적입니다. sudo를 통해 실행된 명령어만 기록됩니다. 로그인 후 본인 계정으로만 작업하는 사용자는 전혀 기록되지 않습니다.

셸 훅과 이를 우회하는 정확한 방법

"모든 명령어를 기록"하기 위해 널리 알려진 방식은 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를 실행합니다. 따라서 명령어가 종료될 때가 아니라 입력되는 즉시 syslog로 전송됩니다. 또한 logger은 시스템 로그 데몬을 통해 기록되므로 사용자의 파일 권한 설정과는 무관하게 작동합니다. 새로운 로그인 셸을 열고 sudo tail -f /var/log/syslog로 확인하거나, rsyslog가 없는 이미지라면 journalctl -t cmdlog -f로 확인하십시오.

하지만 이 방식은 다음 5가지 방법으로 쉽게 우회할 수 있으며, 각 방법은 1분 내로 재현 가능합니다.

  • 비대화형(non-interactive) 셸은 프롬프트를 출력하지 않습니다. ssh you@server 'id'는 명령어를 실행하고 즉시 반환하며, PROMPT_COMMAND이 평가되지 않으므로 아무것도 기록되지 않습니다.
  • 이는 변수입니다. unset PROMPT_COMMAND을 실행하면 해당 세션이 유지되는 동안 기록 기능이 비활성화되며, 관리자 권한도 필요하지 않습니다.
  • 해당 파일은 로그인 셸에서만 읽힙니다. bash --noprofile --norc/etc/profile.d/을 전혀 소싱(source)하지 않습니다.
  • 이는 bash 전용입니다. vim 내의 zsh, sh, python3 -c 'import os; os.system("id")', :!id은 모두 bash 프롬프트 훅이 감지할 수 없는 프로그램을 실행합니다.
  • 입력된 라인 그대로 기록되므로, 별칭(alias)이나 함수를 사용하면 실제로 실행된 명령어를 숨길 수 있습니다.

셸 훅은 편의를 위한 도구로만 사용하십시오. 협조적인 사용자가 "지난 화요일에 내가 무엇을 실행했는가"를 확인하는 용도로는 적합합니다. 체크리스트에서 이를 보안 통제 수단으로 간주하게 해서는 안 됩니다.

커널 audit 하위 시스템은 모든 execve를 감시합니다

auditd 데몬이 구동하는 Linux audit 하위 시스템은 사용자가 우회할 수 없는 유일한 계층입니다. 시스템 호출이 실행되는 순간 커널 내부에서 기록이 생성되기 때문입니다. 프로세스가 프로그램을 실행하면 이벤트가 발생합니다. 셸, 프로그래밍 언어, 터미널 존재 여부는 영향을 주지 않습니다.

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

auditctl -s은 데몬 상태를 출력합니다. pid가 0이 아닌 enabled 1은 데몬이 실행 중임을 의미하며, lost 0은 아직 누락된 기록이 없음을 나타냅니다. lost 카운터를 기억하십시오. 나중에 다시 다룹니다.

auid는 audit을 사용할 가치를 부여하는 필드입니다. PAM은 세션이 시작될 때 로그인 uid를 설정하며, 커널은 이후 생성되는 모든 자식 프로세스에 이 값을 전달합니다. 자신의 값을 확인하십시오:

cat /proc/self/loginuid

대화형 SSH 세션은 /etc/pam.d/sshdpam_loginuid.so이 포함되어 있으므로 자신의 uid를 출력합니다. 4294967295 값은 loginuid가 설정되지 않았음을 의미하며, 이는 부팅 시 시스템 데몬에 의해 시작된 프로세스에서는 정상입니다. 중요한 점은 sudo -i이 이를 변경하지 않는다는 것입니다. alice가 연 root 셸도 여전히 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가 포함되어 있는지 확인하십시오. 그렇지 않으면 이벤트에 사용자 정보가 연결되지 않은 채로 수신됩니다. 이는 VPS의 SSH 접근 강화 시 수정하는 파일 목록과 동일하므로, 두 작업을 함께 수행하십시오.

auditd를 위한 초기 규칙 집합

규칙은 /etc/audit/rules.d/*.rules에 위치합니다. augenrules은 파일 이름 순서대로 규칙을 하나로 합치며, 커널은 첫 번째로 일치하는 규칙에서 처리를 멈추므로 규칙의 순서가 동작을 결정합니다. 나중에 추가하는 -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와 시스템 콜 번호를 이름으로 변환하므로 실무에서 필수적입니다. -ts recent는 최근 10분간의 기록을 조회합니다. 각 실행은 일련의 레코드 그룹으로 도착합니다. uid, auid, 종료 상태, 키를 포함하는 SYSCALL 레코드, 전체 인수 목록이 담긴 EXECVE 레코드, 그리고 문맥을 위한 CWDPATH 레코드가 포함됩니다.

주의해야 할 중요한 제한 사항이 하나 있습니다. audit은 시스템 콜을 기록하는데, 셸 내장 명령어는 자체적인 시스템 콜을 발생시키지 않습니다. cd /root은 프로그램을 실행하지 않습니다. bash 프롬프트에서 입력한 echo evil >> /etc/passwd 역시 프로그램을 실행하지 않는데, 이는 echo와 리다이렉션이 이미 실행 중인 셸 프로세스 내부에서 처리되기 때문입니다. 따라서 execve 규칙은 프로그램을 감시하고, -w 규칙은 쓰기 작업을 감시합니다. 어느 하나만으로는 충분하지 않습니다.

마지막으로 설정을 잠급니다:

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

-e 2는 다음 재부팅 전까지 규칙 집합을 변경 불가능한 상태로 만듭니다. 로드 후 auditctl -senabled 2을 보고하며, root 사용자를 포함하여 누구든 규칙을 추가하거나 삭제하려는 시도는 Operation not permitted 오류와 함께 실패합니다. 이 파일은 마지막에 추가하십시오. 규칙을 변경하려면 매번 재부팅해야 합니다. 누구나 몰래 끌 수 있는 규칙 집합은 증거로서 가치가 없으므로, 이러한 불편함은 보안을 위한 필수적인 선택입니다.

아무도 읽지 않는 감사 로그는 규정 준수를 위한 산물일 뿐입니다

auditd의 실패 모드는 이벤트를 누락하는 것이 아닙니다. 너무 많은 내용을 기록하여 아무도 들여다보지 않게 되고, 결국 로그가 질문에 답하기 위한 도구가 아니라 체크리스트를 만족하기 위한 존재로 전락하는 것입니다.

설정을 조정하기 전에 자신의 서버에서 직접 계산을 수행하십시오:

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

단일 sudo apt upgrade는 수천 개의 단기 프로세스를 실행하며, 각 프로세스는 사용자의 auid를 포함합니다. 따라서 패키지 업데이트 한 번이 일주일 동안 사람이 입력한 로그보다 더 많은 양을 생성할 수 있습니다. 위에서 dpkg과 그 보조 도구들을 제외(suppression) 처리한 이유가 바로 이것입니다. 제외는 항상 실행 파일 단위로 수행하십시오. 사용자 단위로 제외해서는 안 됩니다. /usr/bin/dpkg에 대한 예외는 한 문장으로 설명 가능한 보안 구멍이지만, 계정에 대한 예외는 당신이 포착하려던 바로 그 행위와 똑같은 형태의 보안 구멍이 됩니다.

각 규칙에 포함된 -k 키는 한 달 뒤에도 로그를 검색할 수 있게 만드는 핵심 요소입니다. ausearch -k sudoers은 질문에 대한 답변입니다. 필터가 없는 ausearch는 읽기를 포기하게 만드는 텍스트의 벽일 뿐입니다. 만약 로그 수집기가 네이티브 형식 대신 JSON을 요구한다면, laurel을 사용하십시오. 이는 각 이벤트를 인자가 디코딩된 하나의 JSON 객체로 다시 작성하는 auditd 플러그인입니다. 다른 플러그인과 마찬가지로 /etc/audit/plugins.d/에 등록하며, auditd는 sudo pkill -HUP auditd 시점에 플러그인 변경 사항을 반영합니다.

auditd의 실제 비용

일치하는 모든 syscall은 커널이 포맷하여 유저스페이스로 전달하는 레코드가 됩니다. 이 비용은 두 곳에서 발생하며, 타인이 공개한 수치를 추측하기보다 본인의 워크로드에서 직접 측정할 수 있습니다.

  • CPU 및 지연 시간. 지속적으로 fork를 수행하는 빌드 호스트나 CI 러너는 exec마다 레코드를 생성합니다. 커널 백로그가 가득 차면 --backlog_wait_time은 공간이 확보될 때까지 이벤트를 생성한 프로세스를 일시 중지시키므로, audit은 CPU 점유율이 아닌 빌드 속도 저하로 나타납니다. 실제 부하 상태에서 sudo auditctl -s 내의 backloglost를 모니터링하십시오. lost 수치가 상승한다는 것은 레코드가 유실되었음을 의미하며, 기록이 누락된 로그는 아예 없는 로그보다 위험합니다. 기록을 신뢰하게 되기 때문입니다.
  • 디스크. /etc/audit/auditd.conf를 읽고 디스크가 가득 찼을 때 어떻게 대응할지 신중하게 결정하십시오. 기본값은 하나의 의견일 뿐입니다. max_log_file, num_logs, max_log_file_action는 로그 로테이션을 제어합니다. space_left_action, admin_space_left_action, disk_full_action은 비상 상황을 제어하며, haltsingle을 포함한 일부 가용 동작은 레코드 유실을 막기 위해 시스템을 중단시킵니다.

/etc/audit/rules.d/audit.rules-f 라인은 커널 수준에서 동일한 결정을 내리는 설정입니다. -f 1은 audit 실패를 syslog에 보고하며, -f 2는 커널 패닉을 유발합니다. 레코드 유실보다 서버 중단이 낫다고 판단되는 경우에만 2를 선택하십시오. 사용자가 의존하는 서비스를 운영하는 VPS라면, 로테이션을 설정하고 저장소 문제를 서버 외부로 분리하는 것이 좋습니다.

로그를 실시간에 가깝게 외부로 전송하기

사고 보고서들이 증명하듯, 침해된 호스트에 남겨진 로그는 공격자에 의해 수정될 수 있습니다. root 권한을 가진 공격자는 /var/log/auth.log을 다시 쓰고, /var/log/audit/audit.log을 삭제하며, 데몬을 중지할 수 있습니다. -e 2은 규칙이 제거되는 것을 방지하지만, rm에 대해서는 아무런 조치를 할 수 없습니다. 상위의 모든 계층은 로그 사본이 먼저 해당 장비에서 외부로 나갈 때만 증거로서의 가치를 가집니다.

Audit 자체의 전송 기능은 audispd-pluginsaudisp-remote 플러그인을 사용합니다. /etc/audit/plugins.d/au-remote.conf에서 이 기능을 활성화하십시오:

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

설정을 다시 불러오기 전에 command -v audisp-remote를 기준으로 path을 확인하십시오. 경로가 잘못되면 저널에 한 줄의 메시지만 남을 뿐 아무런 동작도 하지 않기 때문입니다. /etc/audit/audisp-remote.conf에서 remote_serverport을 설정하고, 수집기(collector) 측의 auditd.conf에서는 tcp_listen_port = 60을 설정하십시오. sudo pkill -HUP auditd으로 설정을 다시 불러옵니다. 많은 이미지에서 systemctl restart auditd은 거부되는데, 이는 unit 파일이 RefuseManualStop=yes를 설정하기 때문입니다. 따라서 신호를 보내는 것이 가장 확실한 방법입니다.

다른 방법은 Audit 로그를 이미 전송 중인 syslog 스트림에 포함하는 것입니다. active = no/etc/audit/plugins.d/syslog.conf과 함께 제공됩니다. 이를 yes로 설정하고 다시 불러오면, Audit 이벤트가 sudo 로그 및 기타 모든 로그와 합쳐집니다. 그 후 rsyslog-gnutls 패키지가 필요한 rsyslog의 TLS(transport layer security)를 사용하여 전체 로그를 전송하십시오:

# /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"을 사용한 디스크 보조 큐는 수집기에 연결할 수 없는 동안 레코드를 보관했다가 연결이 복구되면 전송합니다. 이 두 설정이 없으면 수집기가 재부팅될 때 증거에 공백이 생기며, 그 공백이 발생했다는 사실조차 알 수 없습니다. sudo systemctl restart rsyslog로 설정을 적용하고, 신뢰하기 전에 레코드가 수집기에 실제로 도착하는지 확인하십시오.

마지막으로 해결해야 할 과제는 수집기를 감사 대상자가 로그인할 수 없는 장비로 구성하는 것입니다. 동일한 관리자 그룹이 로그 서버의 root 권한까지 가지고 있다면, 파일을 복사했을 뿐 보호한 것은 아닙니다. 자격 증명과 키를 분리하고, 이상적으로는 별도의 제공자 계정을 사용하는 것이 좋습니다. 이는 다수의 Linux 서버를 중앙에서 관리하는 방법을 미리 구축해야 하는 이유와 같으며, 침해된 VPS를 처리할 때 첫 한 시간을 유용하게 보낼 수 있을지 아니면 무용지물로 만들지를 결정짓는 차이입니다.

일반 사용자가 레코드를 수정할 수 없는지 확인

가정하지 말고 직접 테스트합니다. sudo 권한이 없는 일반 계정에서 다음을 실행합니다.

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

순서대로 다음 결과를 예상하십시오. Permission denied이 출력되어야 합니다. auth.logsyslog가 소유하고 그룹은 adm이며 모드는 640이기 때문입니다. 다시 Permission denied가 출력되어야 합니다. 감사 로그는 모드가 600이고 root가 소유하기 때문입니다. 실행 거부 오류가 발생해야 합니다. 감사 규칙을 변경하려면 CAP_AUDIT_CONTROL 권한이 필요하기 때문입니다. 또한 그룹 목록에는 adm이나 systemd-journal이 포함되어 있지 않아야 합니다.

마지막 확인 항목은 사람들이 흔히 놓치는 부분입니다. adm 그룹에 속하면 /var/log/auth.log에 대한 읽기 권한이 부여되며, systemd-journal 그룹에 속하면 저널 전체에 대한 읽기 권한이 부여됩니다. 두 그룹 모두 쓰기 권한은 부여하지 않으므로 데이터 변조는 불가능합니다. 다만 두 그룹 모두 시스템의 모든 인증 로그를 읽을 수 있게 하므로, 포럼 답변에서 usermod -aG 줄을 단순히 복사하기보다는 의도적으로 결정해야 할 사항입니다.

마지막으로 재부팅 후에도 유지되어야 할 두 가지 사항을 확인합니다.

sudo auditctl -s
systemctl is-enabled auditd

enabled 2는 다음 부팅 전까지 규칙 세트가 잠겨 있음을 의미합니다. 두 번째 명령의 enabled은 부팅 후에도 auditd가 다시 시작됨을 의미합니다. 다음 커널 업데이트까지만 유지되는 규칙 세트는 감사 추적(audit trail)으로서의 가치가 없습니다.

FAQ

특정 사용자가 실행한 모든 명령을 어떻게 확인합니까?

id -u alice 명령으로 사용자의 uid를 찾은 다음, 로그인 uid를 기준으로 감사 로그를 검색합니다: sudo ausearch -ul 1000 -ts today -i. -k exec을 추가하여 execve 규칙으로 범위를 제한하십시오. 로그인 uid는 로그인 시점에 설정되며 susudo -i 이후에도 유지되므로, 해당 계정이 연 root 셸 내부에서 실행된 명령도 포착할 수 있습니다. 이 방법은 규칙이 로드된 이후에 실행된 명령에 대해서만 작동합니다. 감사 시스템은 기록하도록 설정되지 않은 이벤트의 기록을 보관하지 않기 때문입니다. 데이터의 형태를 먼저 확인하려면 sudo aureport -k --summary -i를 사용하여 규칙별 카운트를 확인하십시오.

사용자가 bash 기록을 삭제하여 실행한 명령을 숨길 수 있습니까?

네, 별도의 권한이 없어도 가능합니다. ~/.bash_history은 해당 사용자가 소유하며 모드는 600이므로, 사용자가 직접 편집하거나 내용을 비우거나 삭제할 수 있습니다. 또한 unset HISTFILE을 설정하여 기록을 중단하거나, set +o history로 세션 도중 기록을 멈추거나, HISTCONTROL=ignorespace이 설정된 상태에서 명령 앞에 공백을 넣어 기록에서 제외할 수도 있습니다. Bash는 셸이 종료될 때 파일을 기록하므로, kill -9 $$로 강제 종료된 세션은 아무것도 기록되지 않습니다. 셸 기록은 참고용으로만 활용하고 증거로 삼아서는 안 됩니다.

sudo는 sudo -i 내부에서 일어나는 일을 기록합니까?

아니요. sudo는 실행을 요청받은 명령만 기록하므로, sudo -i은 셸에 대한 한 줄의 기록만 남길 뿐 그 이후의 작업은 기록하지 않습니다. 해당 root 셸에서 입력된 모든 명령은 sudo가 더 이상 관여하지 않으므로 sudo 로그에 나타나지 않습니다. sudo su -, sudo bash 및 셸 이스케이프가 가능한 모든 허용된 프로그램도 동일하게 동작합니다. 이 간극을 메우는 방법은 두 가지입니다. 첫째, 원래의 로그인 uid가 첨부된 모든 프로그램을 기록하는 execve에 대한 감사 규칙을 설정하는 것이고, 둘째, 애초에 셸을 제공하지 않도록 sudoers 규칙을 구성하는 것입니다.

auditd가 서버 속도를 저하시킵니까?

이는 전적으로 워크로드가 시작하는 프로세스 수에 달려 있으므로, 수치를 맹신하기보다 직접 측정해야 합니다. 요청에 응답하는 것이 주된 작업인 서버는 exec 호출이 거의 없어 성능 저하를 체감하지 못합니다. 반면 빌드 호스트나 CI 러너는 지속적으로 exec를 호출하므로 성능 저하가 크게 나타날 수 있습니다. 커널 감사 백로그가 가득 차면 이벤트를 생성한 프로세스가 공간이 확보될 때까지 일시 중지되기 때문입니다. 실제 부하 상태에서 sudo auditctl -s을 실행하여 backloglost를 확인하십시오. lost이 0보다 크면 기록이 누락되었다는 의미이며, 이는 로그에 보이지 않는 공백이 생기는 최악의 결과입니다.

감사 로그는 어디에 저장해야 합니까?

수 초 정도의 지연 시간을 두고 다른 머신에 저장해야 합니다. 감사 대상 호스트에서 root 권한을 획득한 사람은 누구나 /var/log/audit/audit.log를 삭제하고 /var/log/auth.log를 다시 작성할 수 있으므로, 로컬 복사본은 아무도 숨기려 하지 않은 사건에 대해서만 답을 줄 수 있습니다. audisp-remote 플러그인을 사용하여 중앙 auditd로 전달하거나, 감사 syslog 플러그인을 활성화하여 rsyslog를 통해 TLS로 전체 syslog 스트림을 전달하십시오. 수집기에는 별도의 자격 증명을 부여하고, 감사를 받는 계정이 수집기에 접근할 수 없도록 설정하십시오.