auditd for Responders: Rules, ausearch & aureport
- Explain what the Linux audit subsystem records that journald and syslog cannot, and why it is tamper-evident
- List the running rules with auditctl and add both a watch and a syscall rule, persisting them in /etc/audit/rules.d
- Search the audit log by rule key, record type, user, and time window with ausearch
- Read a multi-line SYSCALL/EXECVE/PATH record and use aureport to summarise authentication and execution activity
- Use audit records to prove that a specific file was accessed or a specific binary was executed
Why auditd exists
Frame the audit subsystem as the kernel's tamper-evident record of who did what.
Ordinary logs are written by the programs being logged.
sshddecides what to put in the auth log; a shell writes nothing about the commands it runs unless you bolt something on. That is fine for operations but weak for investigation: a program that has been subverted can simply lie, or write nothing at all.The Linux audit subsystem answers a different question — did this actually happen? — by recording events inside the kernel, at the moment a syscall returns, before any user-space program gets a chance to spin the story. The kernel emits audit events to a dedicated daemon, `auditd`, which writes them to `/var/log/audit/audit.log`. It can capture things no ordinary log sees: every
execve(every command run, by whom, with which arguments), every read or write of a watched file, every change to a watched directory, and every login.Think of
auditdas the courtroom stenographer. An ordinary log is a casual diary the suspect keeps themselves — they choose what to write and can tear out a page. The stenographer sits in the corner of the kernel and transcribes every official action verbatim, in order, as it occurs. When you need to prove something in an incident, you reach for the transcript, not the diary.Is audit even running? auditctl -s
Confirm the daemon is active and read its operational state before trusting any rule.
Before you write rules or trust a search, confirm the subsystem is actually collecting.
auditctl -s(status) prints the kernel-side state: whether auditing is enabled, the PID of theauditdprocess receiving events, how many events were lost (a backlog overflow means you have gaps), and the failure mode.sudo auditctl -senabled 1 failure 1 pid 842 rate_limit 0 backlog_limit 8192 lost 0 backlog 0 backlog_wait_time 60000
- -s
- Report the subsystem status (enabled flag, receiving PID, loss counters, limits).
enabled 1means the kernel is auditing;enabled 2would mean locked (rules immutable until reboot).pid 842is the liveauditd— apid 0means no daemon is collecting, so events are being dropped.failure 1is the action on error (1 = log to printk). The number to watch in an incident is `lost`: anything above 0 means the kernel overran its backlog and some events never reached the log — a gap you must account for.Check yourself
In
auditctl -soutput, which field tells you the kernel dropped events because its buffer overflowed?Reading and writing rules: watches vs syscall rules
List the active rules and understand the two kinds of rule you can add.
Audit only records what its rules tell it to.
auditctl -llists the rules currently loaded in the kernel. There are two kinds, and knowing which to reach for is the core skill:- Watches answer "who touched this path?" —
-w <path> -p <perms> -k <key>. Perms arer(read),w(write),x(execute),a(attribute/metadata change). A watch is really shorthand the kernel expands into syscall rules on that path. - Syscall rules answer "who called this syscall?" —
-a always,exit -F arch=b64 -S <syscall> -k <key>. These fire on any invocation of a syscall (optionally filtered with-Fon uid, success, paths, etc.), regardless of which file is involved.
The `-k` key is a label you attach to a rule so you can later pull exactly its events with
ausearch -k. Choose it carefully — it is how your future self finds these records in a million-line log.sudo auditctl -l-w /etc/passwd -p wa -k passwd_changes -w /etc/shadow -p wa -k shadow_changes -a always,exit -F arch=b64 -S execve -k exec -a always,exit -F arch=b64 -F euid=0 -S execve -k root_exec
- -l
- List all audit rules currently loaded in the kernel.
Read top to bottom: two watches flag any write ( w) or attribute change (a) to/etc/passwdand/etc/shadowunder keyspasswd_changes/shadow_changes; two syscall rules record everyexecve(keyexec) and, more narrowly, everyexecverun as root (-F euid=0, keyroot_exec).arch=b64scopes the rule to 64-bit syscalls — you typically pair it with ab32copy so a 32-bit binary can't slip past.sudo auditctl -w /etc/passwd -p wa -k passwd_changes- -w
- Set a watch on this file or directory path.
- -p wa
- Trigger on write (w) and attribute/metadata changes (a). Add r/x for read/execute.
- -k
- Tag matching events with this searchable key.
This adds a watch right now, in the running kernel. Any process that writes to or changes the ownership/permissions of /etc/passwdwill now generate an audit record carrying the keypasswd_changes. Useful for an ad-hoc 'watch this during the next hour' — but read the mistake below before you rely on it. Silent on success — confirm it registered withauditctl -l, which lists the active watch.sudo tee /etc/audit/rules.d/50-responder.rules > /dev/null <<'EOF' -w /etc/passwd -p wa -k passwd_changes -a always,exit -F arch=b64 -S execve -k exec EOF sudo augenrules --load sudo auditctl -l | tail -2/usr/sbin/augenrules: No change -w /etc/passwd -p wa -k passwd_changes -a always,exit -F arch=b64 -S execve -k exec
Persisting is two moves: write the rule text into a *.rulesfile underrules.d, then run `augenrules --load`, which compiles every file in that directory into one ruleset and loads it. Now the rules survive reboots (they reload fromrules.dat boot).auditctl -lconfirms they are live. (No changehere just means the running set already matched.)Check yourself
You want to record every time anyone READS /etc/shadow. Is that a watch or a syscall rule, and which perm letter?
- Watches answer "who touched this path?" —
Finding events: ausearch
Pull exactly the records you want by key, type, user, and time.
/var/log/audit/audit.logis enormous and the records are dense, so you almost nevercatit. `ausearch` is the query tool: it filters the log and, crucially, reassembles the multi-line records that belong to a single event and renders timestamps as human-readable dates.The filters you will reach for constantly:
-k <key>— events tagged with a rule key (the payoff for tagging your rules)-m <type>— by record type, e.g.USER_LOGIN,EXECVE,SYSCALL,USER_AUTH-ui <uid>/-ua <auid>— by user id (or audit login uid, which survivessu)-ts <when>/-te <when>— time start/end, e.g.-ts today,-ts recent
Example — not runnable on your lab box
sudo ausearch -k exec -ts todaytime->Mon Jun 23 14:02:10 2026 type=PROCTITLE msg=audit(1750687330.501:8842): proctitle=2F62696E2F6E63002D6C2D70... type=PATH msg=audit(1750687330.501:8842): item=0 name="/usr/bin/nc" inode=2097 mode=0100755 ouid=0 ogid=0 type=CWD msg=audit(1750687330.501:8842): cwd="/tmp" type=EXECVE msg=audit(1750687330.501:8842): argc=4 a0="nc" a1="-l" a2="-p" a3="4444" type=SYSCALL msg=audit(1750687330.501:8842): arch=c000003e syscall=59 success=yes exit=0 ppid=1402 pid=2207 auid=1000 uid=1000 gid=1000 comm="nc" exe="/usr/bin/nc" key="exec"
- -k exec
- Only events tagged with the rule key 'exec'.
- -ts today
- Only events from the start of today (also accepts 'recent', 'this-week', or a date/time).
One event is several record lines sharing the same id ( ...:8842). Read it bottom-up: the SYSCALL line is the spine —syscall=59isexecve,success=yes, run byauid=1000(the login user, even if they later became root),exe=/usr/bin/nc. The EXECVE line decodes the arguments:nc -l -p 4444— a netcat listener on port 4444, a textbook backdoor. PATH shows the binary's inode/mode, CWD the working directory (/tmp). You have just proven who ran what, with which arguments, from where.Example — not runnable on your lab box
sudo ausearch -m USER_LOGIN -ts today | tail -6time->Mon Jun 23 09:14:55 2026 type=USER_LOGIN msg=audit(1750662895.310:512): pid=1402 uid=0 auid=1000 ses=42 msg='op=login id=1000 exe="/usr/sbin/sshd" hostname=203.0.113.9 addr=203.0.113.9 terminal=ssh res=success' time->Mon Jun 23 09:15:02 2026 type=USER_LOGIN msg=audit(1750662902.778:518): pid=1455 uid=0 auid=1000 ses=43 msg='op=login id=1000 exe="/usr/sbin/sshd" hostname=203.0.113.9 addr=203.0.113.9 terminal=ssh res=failed'
- -m USER_LOGIN
- Filter to records of type USER_LOGIN (one of dozens — USER_AUTH, ADD_USER, CRED_ACQ, etc.).
-mfilters by record type rather than your own rule key — handy for the built-in events the kernel and PAM emit without any rule from you. Each line carriesaddr=(source IP),terminal=ssh, andres=success/res=failed. Here loginauid=1000from203.0.113.9succeeded at 09:14 then failed at 09:15 — a pattern worth tying to the auth log.Check yourself
Which ausearch flag pulls every event recorded by a specific rule, using the label you set with -k when you wrote the rule?
Summarising at a glance: aureport
Turn the raw log into high-level summaries to spot where to dig.
ausearchanswers a specific question; `aureport` gives you the overview — counts and tables across the whole log so you can see where to pointausearch. It is your triage pass: "how many failed logins, by whom?", "what executed today?", "which users were most active?"Example — not runnable on your lab box
sudo aureport --auth -ts today | tailAuthentication Report ============================================ # date time acct host term exe success event ============================================ 1. 06/23/2026 09:14:55 ec2-user 203.0.113.9 ssh /usr/sbin/sshd yes 512 2. 06/23/2026 09:15:02 root 203.0.113.9 ssh /usr/sbin/sshd no 518 3. 06/23/2026 09:15:04 root 203.0.113.9 ssh /usr/sbin/sshd no 520 4. 06/23/2026 09:15:06 root 203.0.113.9 ssh /usr/sbin/sshd no 522
- --auth
- Summarise authentication attempts (login/auth events).
- -ts today
- Restrict the report to today (same time syntax as ausearch).
One row per auth attempt with successyes/no and the trailing event number. Here a flurry ofnoresults forrootfrom a single host — a brute-force signature. The event number (520, 522…) is the handle to pivot:sudo ausearch -a 520pulls the full record for that exact event to see the detail behind the summary line.Example — not runnable on your lab box
sudo aureport -x --summary -ts todayExecutable Summary Report ================================= total file ================================= 1843 /usr/bin/bash 402 /usr/bin/sshd 77 /usr/bin/sudo 5 /usr/bin/nc 2 /tmp/.cache/kworkerd
- -x
- Report on executables (which binaries ran).
- --summary
- Aggregate into totals per item instead of one line per event.
A ranked tally of every binary that ran today. The eye goes straight to the bottom: /usr/bin/ncrunning at all is suspicious on a server, and/tmp/.cache/kworkerd— a binary executing from a hidden temp path posing as a kernel thread — is a glaring anomaly. Now you pivot back:sudo ausearch -x /tmp/.cache/kworkerdreads exactly who launched it and with what arguments.Check yourself
You want a ranked count of every binary that executed today, not a line per event. Which tool and flags?
Proving it happened: anatomy of a record
Read a full multi-line record well enough to stand behind it as evidence.
An investigator's job often comes down to a yes/no a manager or a court can rely on: did this file get read? did this binary run, and by whom? The audit record is the artifact that settles it — but only if you can read every field. One event is several record lines sharing the same `audit(timestamp:serial)` id;
ausearchgroups them for you.Example — not runnable on your lab box
sudo ausearch -k shadow_read -ts recenttime->Mon Jun 23 14:40:11 2026 type=PATH msg=audit(1750689611.220:9051): item=0 name="/etc/shadow" inode=1180 mode=0100000 ouid=0 ogid=0 nametype=NORMAL type=CWD msg=audit(1750689611.220:9051): cwd="/home/webadmin" type=SYSCALL msg=audit(1750689611.220:9051): arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=7ffd2c1b a2=0 a3=0 items=1 ppid=1403 pid=3120 auid=1000 uid=1000 gid=1000 euid=0 comm="cat" exe="/usr/bin/cat" key="shadow_read"
- -k shadow_read
- Events from the watch on /etc/shadow's read permission.
This is the proof. PATH names the exact file touched — /etc/shadow. SYSCALL carries the verdict:syscall=257isopenat, `success=yes` withexit=3(it got file descriptor 3 — the open succeeded). The actor is pinned down:comm="cat",exe=/usr/bin/cat,pid=3120, andauid=1000— the login user webadmin — who was running witheuid=0(they had escalated to root). You can now state, with timestamp and process lineage, that webadmin read the shadow file at 14:40:11. That is courtroom-grade.Check yourself
In a SYSCALL record, which field proves the operation actually worked rather than merely being attempted?
Try it in your live terminal
Inspect the real audit subsystem yourself — robustly, even where it isn't running.
You have a real root shell to your right. The audit subsystem often isn't active in a container, so each command below degrades gracefully — it shows you the real output where audit is running and a clear message where it isn't. Run them in order; the goal is to make the inspection workflow reflex, not to depend on a particular box's state.
command -v auditctl || echo "auditd not installed here"First, is the tooling even present? command -vprints the path toauditctlif installed, otherwise the fallback message fires. No tooling means this box was built without the audit userspace — note it and move on.sudo auditctl -s 2>/dev/null || echo "audit not active"Read the subsystem status: look for enabled 1and a non-zeropid. If the kernel has no audit support or the daemon isn't attached, the command fails quietly and you getaudit not active.sudo auditctl -l 2>/dev/nullList the loaded rules. Empty output usually means No rules(a clean box with nothing being watched) — which is itself worth knowing before you trust any search.sudo ausearch -m USER_LOGIN -ts today 2>/dev/null | tail || echo "no audit log"Pull today's logins by record type. Where an audit log exists you'll see the USER_LOGIN records with source addresses and success/failure; otherwise the fallback prints no audit log.sudo aureport --auth 2>/dev/null | tail || trueFinally the summary view: the authentication report condensed to its last rows. || truekeeps the sequence from erroring out on a box with no log — the point is to practise the aureport-then-ausearch reflex, not to find a guaranteed hit.
- auditd records events in the kernel — every execve, watched-file access, and login — into /var/log/audit/audit.log: a tamper-evident transcript that ordinary, program-written logs can't match. It is not in the journal.
- Rules come in two kinds: watches (
-w path -p perms -k key, for 'who touched this file') and syscall rules (-a always,exit -F arch=b64 -S syscall -k key, for 'who called this syscall'). Always give a -k key. - auditctl rules are runtime-only and vanish on reboot — persist them in /etc/audit/rules.d/*.rules and load with augenrules --load.
- Investigate with ausearch (-k key, -m type, -ua loginuid, -ts time) and triage with aureport (--auth, -x --summary); the event number/path from a report feeds straight back into ausearch -a / -x.
- Read a record bottom-up: SYSCALL is the spine (success/exit, auid, exe), EXECVE decodes the command line, PATH names the file. success=yes is what turns 'attempted' into 'proven'.
| Goal | Command | Notes |
|---|---|---|
| Is auditing on? | auditctl -s | Check enabled, pid, and the lost counter |
| List active rules | auditctl -l | Watches and syscall rules currently in the kernel |
| Watch a file (who touched it) | auditctl -w /etc/passwd -p wa -k passwd_changes | Runtime only — persist in rules.d |
| Record a syscall (e.g. exec) | -a always,exit -F arch=b64 -S execve -k exec | Pair with a b32 rule for 32-bit binaries |
| Make rules permanent | edit /etc/audit/rules.d/*.rules then augenrules --load | Survives reboot |
| Search by rule key | ausearch -k exec | The payoff for tagging rules with -k |
| Search by record type | ausearch -m USER_LOGIN | Built-in events without your own rules |
| Search by user / time | ausearch -ua 1000 -ts today | -ua loginuid follows the user through su/sudo |
| Summarise auth attempts | aureport --auth | Spot brute-force; pivot with ausearch -a <event> |
| Summarise executions | aureport -x --summary | Spot binaries running from /tmp or unexpected tools |
