とある事情でファイル改竄検知や root によるコマンド実行の検知のために auditd を使う必要があったのでそのメモ。
試した環境は次の通りです。
- centos-release-7-9.2009.0.el7.centos.x86_64
- audit-2.8.5-4.el7.x86_64
- kernel-3.10.0-1160.83.1.el7.x86_64
参考
リスト
auditd は「リスト」にルールを定義します。リストには以下のものがあります。
- task
- exit
- user
- filesystem
- exclude
システムで監査イベントが発生すると最初に exclude を除くいずれかのリストのルールが順番に適用され、そこで除外されなければ最後に exclude リストのルールが適用され、そこでも除外されなければログなどに出力されます。
リストへのルールの追加は次のコマンドで行います。exit がリスト名、always はアクションです。
auditctl -a always,exit -F perm=x -F euid=0
アクションは always と never のいずれかを指定します。いわゆる許可と拒否です。なお、リストとアクションは逆に指定しても OK です(e.g. exit,always
)。
-F
で監査イベントのフィールドで条件を指定できます。リストごとに指定可能なフィールドは異なります。例えば exclude は msgtype や SELinux 関係の一部のフィールドしか指定できません。他にも -S
でシステムコール名、-w
でファイル名やディレクトリ名、-p
でファイルやディレクトリに対する操作、などが指定できます。
task リスト
task リストは fork や clone で新しくプロセスが作成されたときに適用されます。このリストで除外されなければ新しく生まれたプロセスで監査コンテキストが初期化されます。要するに、それ以後そのプロセスではシステムコールがフックされて監査イベントが発生し、exit リストのフィルタが適用されるようになります。
task リストで除外された場合、そのプロセスではシステムコールがフックされないため、exit リストがどうであれシステムコールの監査イベントは発生しなくなります。
auditctl -a never,task
システムコール監査が不要なとき、exit リストを空のままにしておけば何も記録はされませんが、task リストはデフォルトで always なので、ルールが未定義でもすべてのプロセスでシステムコールがフックされます。ので、システムコール監査が不要なら明示的に task リストで除外しておいた方がパフォーマンスは良いです。そのため、大抵のディストリでは配布物に含まれるデフォルトのルールで -a never,task
となっているようです。
また、設定例では次のように SELinux のラベルで crond から実行されたときはシステムコールの監査を無効にしたりしていました。
auditctl -A never,task -F subj_type=system_cronjob_t
auditctl -A never,task -F subj_type=crond_t
プロセス単位で監査を除外したいときは exit リストよりも task リストが良いでしょう。
exit リスト
システムコールの終了時に適用されるリストです。
auditctl -a always,exit -F perm=x -F euid=0
システムコールの監査では1回のシステムコールで複数行のログが出力されます。例えば下記はカレントディレクトリ /etc/ssh
で cat ssh_config
したログです。
type=SYSCALL msg=audit(1686548854.861:788133): arch=c000003e syscall=59 success=yes exit=0 a0=af0470 a1=aed550 a2=a172a0 a3=7fffb20ca420 items=2 ppid=26926 pid=26969 auid=1003 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=144027 comm="cat" exe="/usr/bin/cat" key="exec"
type=EXECVE msg=audit(1686548854.861:788133): argc=2 a0="cat" a1="ssh_config"
type=CWD msg=audit(1686548854.861:788133): cwd="/etc/ssh"
type=PATH msg=audit(1686548854.861:788133): item=0 name="/bin/cat" inode=8410545 dev=ca:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 objtype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
type=PATH msg=audit(1686548854.861:788133): item=1 name="/lib64/ld-linux-x86-64.so.2" inode=12583884 dev=ca:01 mode=0100755 ouid=0 ogid=0 rdev=00:00 objtype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
type=PROCTITLE msg=audit(1686548854.861:788133): proctitle=636174007373685F636F6E666967
type=SYSCALL が実際にシステムコールの内容です。このログでは引数(a0~a3)に文字列が渡されていてもポインタ値しか記録されないためにその内容は全然わかりません。
type=EXECVE はシステムコールが execve だったときに記録されます。このログでは引数のうち文字列部分が何であったかも表示されています。
type=CWD はシステムコールの実行時のカレントディレクトリです。execve の引数に相対パスがある場合、カレントディレクトリによって実際のファイルパスが変わるため、カレントディレクトリも監査ログに記録されています。
type=PATH はシステムコールの引数で指定されたパスの絶対パスが記録されます。引数に相対パスが指定されていてもこのログには絶対パスが記録されます。
type=PROCTITLE は所謂 proctitle で、システムコールの元のプロセスのコマンドラインが記録されます。この例の値を16進デコードすると cat\nssh_config
です。
user リスト
ユーザー空間で発生した監査イベントをフィルタリングするためのリストです。ユーザー空間で発生する監査イベントには次のようなものがあります。
CRYPTO_*
CRED_*
USER_*
USER
SERVICE_START
SERVICE_STOP
SOFTWARE_UPDATE
- yum や dnf によるソフトウェアのインストール・アップデート
- その他たくさん
要するにシステムコール関係の監査イベント以外はすべてこちらに属します(後述の filesystem リストなどもあるので正確ではない)。
man によると CAP_AUDIT_WRITE
ケーパビリティを持つプロセスのユーザー空間のイベントは何もしなくても記録されます。ので、ルールを追加していない状態でもユーザー空間の監査イベントはログには記録されます。never でルールを追加すれば特定のイベントを記録しないようにできます。また、プロセスから CAP_AUDIT_WRITE
を落としてやるとなにも記録されなくなります。
capsh --drop='cap_audit_write' -- -c 'auditctl -m "this is test"'
ユーザー空間の監査イベントがすべて不要な場合、never,user だけを条件無しで指定してもダメでした。
auditctl -a never,user
なにかしら条件を指定する必要があります。
auditctl -a never,user -F uid=0
auditctl -a never,user -F uid!=0
filesystem リスト
man によると tracefs や debugfs などのファイルシステム全体のイベントを除外するためのものらしいのですが・・どちらもよくわかりません。
いわゆるファイルシステムの監査 -w
とは無関係です。xfs とかの普通のファイルシステムを指定するものでもありません。
auditctl -a never,filesystem -F fstype=tracefs
auditctl -a never,filesystem -F fstype=debugfs
auditctl -a never,filesystem -F fstype=xfs
auditctl -a never,filesystem
auditctl -w /root -p wa
exclude リスト
他のリストのルールが適用された後、除外されなかった監査イベントに対して exclude リストが適用されます。
このリストではイベントを除外することだけ可能なのでアクションの指定は無視され、追加されたルールは常に never となります。
auditctl -a always,exclude -F msgtype=USER
perm オプション
exit リストで指定する perm はパーミッションのことですがいわゆるファイルのパーミッションとはあまり関係無く、相当するシステムコールと一致する条件として扱われます。
例えば perm=x なら実行なので execve とかです。ただ、システムコールで指定する場合は CPU アーキも指定する必要があるため。
auditctl -a always,exit -F euid=0 -F perm=x
auditctl -a always,exit -F euid=0 -F arch=b64 -S execve
他にも perm=r なら読込なので openat が読込可能なフラグ付きで呼ばれたとき、などです。詳細はよくわからないけれども次のコードでイメージが掴めるでしょうか。
LOGIN イベント
ssh やシリアルコンソールなどからのログイン時には user リストが適用される USER_*** イベントがたくさん発生するのですが、同じようにログイン成功のタイミングで発生する LOGIN イベントはなぜか user リストの never では除外できませんでした。
exclude リストでなら除外できます。理由は不明。
auditctl -a never,user -F msgtype=LOGIN
auditctl -a never,exclude -F msgtype=LOGIN
ファイルやディレクトリの監査
-w
でファイルやディレクトリに対する操作の監査が可能です。-p
で操作内容も指定できます。
auditctl -w /root -p w
これは実際のところシステムコールの監査なので、仕組み的には exit リストで -F path
や -F dir
などを指定するのと変わりありません。
なお、ルール定義時点の i-node 番号が追跡されるため、i-node 番号が変わると監査されなくなります。
mkdir /test
auditctl -w /test -p w
touch /test/a
rm -fr /test
mkdir /test
touch /test/a
auid
リストのフィルタで指定可能なフィールドに auid というものがあります。これは Audit User ID のことで、ログイン時にユーザーに割り当てられた後、su や sudo でユーザーが切り換えられた後も継承されます。
例えば次のように一般ユーザーでログイン後に su や sudo などで特権ユーザーでのコマンド実行のみを記録することができます。
auditctl -a always,exit -F perm=x -F euid=0 -F 'auid>0' -F 'auid!=unset'
なお、CAP_AUDIT_CONTROL
ケーパビリティがあれば auid は改竄可能らしいです。root に su した後に改竄されると意味が無いので、次の設定で最初に auid が割り当てられた後不変にできます。ただし、これを設定すると特定の種類のコンテナ(certain kinds of containers)で問題が発生する可能性があるとのことです。
auditctl --loginuid-immutable
とことで何故か task リストだと auid=0 での除外設定が効きませんでした。auid でのフィルタが効かないわけでは内容なのですが・・理由は不明
auditctl -a never,task -F auid=0
さいごに
1監査イベント=1ログ、ではないので取り扱いしづらいです。