auditd のメモ

とある事情でファイル改竄検知や 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 リスト

システムコールの終了時に適用されるリストです。

# root によるすべてのコマンド実行を記録
auditctl -a always,exit -F perm=x -F euid=0

システムコールの監査では1回のシステムコールで複数行のログが出力されます。例えば下記はカレントディレクトリ /etc/sshcat 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
    • auditctl -m "message"
  • 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
# ok
auditctl -a never,filesystem -F fstype=debugfs
# ok
auditctl -a never,filesystem -F fstype=xfs
# file system type is unknown for field: fstype

auditctl -a never,filesystem
auditctl -w /root -p wa
# filesystem リストと -w の監査は無関係なので /root 以下に書き込むと監査イベントは発生する

exclude リスト

他のリストのルールが適用された後、除外されなかった監査イベントに対して exclude リストが適用されます。 このリストではイベントを除外することだけ可能なのでアクションの指定は無視され、追加されたルールは常に never となります。

auditctl -a always,exclude -F msgtype=USER
# エラーにはならないものの never,exclude と同じです

perm オプション

exit リストで指定する perm はパーミッションのことですがいわゆるファイルのパーミッションとはあまり関係無く、相当するシステムコールと一致する条件として扱われます。

例えば perm=x なら実行なので execve とかです。ただ、システムコールで指定する場合は CPU アーキも指定する必要があるため。

# 以下の2つは概ね同じ意味
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
# LOGIN が記録される

auditctl -a never,exclude -F msgtype=LOGIN
# LOGIN が記録されなくなる

ファイルやディレクトリの監査

-w でファイルやディレクトリに対する操作の監査が可能です。-p で操作内容も指定できます。

# /root 以下に対する書き込み
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ログ、ではないので取り扱いしづらいです。