2 of 3

Central Logging, AIDE and AnsibleLesson 2 of 3

Detecting changed files with AIDE

Build an AIDE baseline, define what to watch, read a check report and update the database after an approved change.

  • aide
  • aide --init
  • aide --check
  • aide --update
  • aide --compare
  • /etc/aide.conf
  • rpm -V
  • rpm -Va
  • sha256sum
  • systemctl enable --now

45 min3 exercises

After this lesson you can

  • Explain what AIDE records, what it cannot see, and why the database must be trusted.
  • Initialise the database, add a watch rule for a directory, and run a check.
  • Read the summary, the one-line change codes and the detail section, and decide between 'approved change' and 'incident'.

The idea

AIDE (Advanced Intrusion Detection Environment) answers one question: has any file I care about changed since I last said everything was fine? It records attributes of chosen files in a database, and later compares the filesystem to the database and reports the differences.

It does not prevent anything, and it does not tell you who or why. Its value is that unexpected change becomes visible: a modified sshd_config, a replaced binary, a permission bit flipped on /etc/shadow.

What it records

Each rule names attributes to store. The common ones:

LetterAttributeLetterAttribute
ppermissionsssize
u / gowner / groupmmodification time
nnumber of linksa / caccess / change time
iinodesha256, sha512content hash
selinuxSELinux contextxattrs, aclextended attributes, ACLs

Rules group attributes: LAB = p+u+g+s+m+sha256 defines a group named LAB, and /opt/webapp/conf LAB applies it to everything under that path. RHEL’s /etc/aide.conf ships with ready-made groups and watches /boot, /etc, /usr and other system trees; read it before you change it.

The database is the trust anchor

If an attacker can change both the file and the database, AIDE reports nothing. So in production the baseline is copied somewhere the machine cannot write (read-only media, another host), and the check runs from that copy. On the exam the point is simpler: create the baseline while the system is in a known good state, and update it only after you have understood each reported change.

FileRole
/etc/aide.confRules and groups; where the databases live
/var/lib/aide/aide.db.gzThe baseline AIDE compares against (database_in)
/var/lib/aide/aide.db.new.gzWhere --init and --update write the new database (database_out)
/var/log/aide/Log directory, if you configure one

Commands

aide --init       # scan and write a brand-new database (aide.db.new.gz)
aide --check      # compare filesystem with the baseline; read-only
aide --update     # compare AND write a new database that includes today's state
aide --compare    # compare database_in with database_new, no filesystem scan

The exit status of --check is a bit mask (1 = new files, 2 = removed, 4 = changed), so 0 means clean and anything else means differences or an error. That makes it easy to put in a scheduled job.

Try it

Real output from AIDE 0.18 (second lines of long hash values omitted), with a small test configuration that watches a directory called watch with the LAB group above. The commands are the same on RHEL; only the configuration path and database location differ.

aide --init
aide --check | head -2
Start timestamp: 2026-11-04 17:03:06 +0300 (AIDE 0.18.6)
AIDE successfully initialized database.
New AIDE database written to /tmp/ai/db/aide.db.new.gz
Start timestamp: 2026-11-04 17:03:06 +0300 (AIDE 0.18.6)
AIDE found NO differences between database and filesystem. Looks okay!!

Now make three changes: append to a file, change another file’s mode, create a new file.

echo "changed" >> watch/app.conf
chmod 600 watch/data.txt
echo new > watch/extra.txt
aide --check | sed -n 2,40p
AIDE found differences between database and filesystem!!

Summary:
  Total number of entries:	4
  Added entries:		1
  Removed entries:		0
  Changed entries:		3

---------------------------------------------------
Added entries:
---------------------------------------------------

f+++++++++++++++++: /tmp/ai/watch/extra.txt

---------------------------------------------------
Changed entries:
---------------------------------------------------

d = ... m         : /tmp/ai/watch
f > ... m   H     : /tmp/ai/watch/app.conf
f = p.. .   .     : /tmp/ai/watch/data.txt

---------------------------------------------------
Detailed information about changes:
---------------------------------------------------

Directory: /tmp/ai/watch
 Mtime     : 2026-11-04 17:02:59 +0300        | 2026-11-04 17:03:06 +0300

File: /tmp/ai/watch/app.conf
 Size      : 6                                | 14
 Mtime     : 2026-11-04 17:02:59 +0300        | 2026-11-04 17:03:06 +0300
 SHA256    : tqmNnOmi2RSSiPo99C03fD5Cc3r9za9x | WCgELHbZ3cq8+a/GEyqkTTl52MXU6zll

File: /tmp/ai/watch/data.txt
 Perm      : -rw-r--r--                       | -rw-------

Reading the report

The report has three levels, and you read them in order.

  1. Summary: how many added, removed and changed. Zero everywhere means clean.
  2. One-line codes: the first letter is the type (f file, d directory, l link). Then a column per attribute: a letter (p, m, s, H for hash) means that attribute changed, a dot means unchanged, = and > describe the size relationship. f+++++++++++++++++ means a file that is new.
  3. Detailed information: the old value on the left, the new on the right, only for attributes that changed.

The three entries tell three different stories. extra.txt appeared (a new file). app.conf changed in content (size and hash). data.txt changed only in permissions: no content change, but a mode change on a sensitive file is exactly what you want to notice. The directory line is an automatic side effect: adding a file changes the parent’s mtime.

Accepting changes

aide --update | head -3
cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
aide --check | head -2

--update reports the same differences as --check and also writes a database containing the current state. Do it only after reviewing the report. Updating blindly turns AIDE into a tool that approves everything.

AIDE versus rpm -V

rpm -V PACKAGE (or rpm -Va for everything) compares installed files with the package database. It needs no setup and covers every file a package owns, but it cannot see files that belong to no package, and the database is itself on the machine. AIDE covers any path you choose and can be baselined at a moment you control. They answer slightly different questions, so use both: rpm -Va for “does this still match the package?”, AIDE for “has anything changed since the baseline?”

When AIDE misbehaves

SymptomCauseFix
Couldn't open file ... aide.db.gzBaseline missingaide --init, then copy aide.db.new.gz to aide.db.gz
Same differences every runDatabase not replaced after --updateCopy the new database over the old one
Thousands of changes after updatesPackages changed /usr, /bootExpected after dnf update; run --update after patching, and review
Report full of noise from one directoryCache, logs or spool watchedExclude with !/path or watch only attributes that matter
Rule has no effectPath typed wrong, regular expression not anchored, exclusion placed after a broader ruleCheck with aide --check after a small test change
Check is very slowHashing everything under /usrUse fewer or lighter hashes for large trees

Going further

Scheduling. A check is only useful if it runs. A systemd timer calling aide --check and mailing or logging the result is the usual approach; systemd-run --on-calendar is a quick way to test it. Beyond the exam: the baseline should be signed or stored off the machine, and a SIEM should consume the report. AIDE sees changes only when it runs, so an attacker who changes and restores a file between checks leaves no trace.

Practice

0 of 3 done

Do these on your own system. They get harder. Check each result, then tick it off.

  1. Warm-up

    Baseline, change, detect

    Install AIDE and create the baseline with the default configuration. Run a check and confirm it reports no differences. Then create /etc/aide-lab-marker containing one line, run the check again, and confirm the new file is reported. (Use a file in a directory the default rules watch; /etc is.)

    Check your result
    • sudo ls -l /var/lib/aide/aide.db.gz exists.
    • The first sudo aide --check ends with AIDE found NO differences between database and filesystem. Looks okay!! (on a freshly installed machine; see the note on first runs).
    • After the change, the report lists /etc/aide-lab-marker under Added entries and /etc under changed directories.
    Hint

    aide --init writes /var/lib/aide/aide.db.new.gz. AIDE reads aide.db.gz, so you must copy the new file over the old name. aide --check compares; it never changes the database.

    Solution
    sudo dnf install -y aide
    sudo aide --init
    sudo cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
    sudo aide --check | head -3
    echo "marker" | sudo tee /etc/aide-lab-marker
    sudo aide --check | sed -n 1,20p
  2. Practice

    Watch a directory, ignore the noise

    An application keeps its settings in /opt/webapp/conf and writes cache files into /opt/webapp/cache. Add rules so AIDE watches the permissions, owner, size, mtime and SHA-256 of everything under conf, and ignores cache. Rebuild the baseline. Prove it: change conf/app.conf (reported), create a file in cache (not reported), then chmod 600 a second file in conf (reported as a permission change only).

    Check your result
    • grep -n webapp /etc/aide.conf shows a watch line and an exclusion line (starting with !).
    • The check reports app.conf with a changed size/hash and the second file with only p (permissions) in its change code.
    • Nothing under /opt/webapp/cache appears in the report.
    Hint

    A rule is PATH GROUP. A path prefixed with ! is excluded. Define a group with your own attribute list near the top of the file, or reuse one that already exists. Exclusion lines should come first in practice, and paths are regular expressions anchored at the start.

    Solution
    sudo mkdir -p /opt/webapp/conf /opt/webapp/cache
    echo 'port=8080' | sudo tee /opt/webapp/conf/app.conf
    echo 'x' | sudo tee /opt/webapp/conf/other.conf
    sudo tee -a /etc/aide.conf <<'EOT'
    
    # EX342 lab: web application
    WEBAPP = p+u+g+s+m+sha256
    !/opt/webapp/cache
    /opt/webapp/conf WEBAPP
    EOT
    sudo aide --init && sudo cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
    echo 'port=9090' | sudo tee /opt/webapp/conf/app.conf
    echo junk | sudo tee /opt/webapp/cache/tmp1
    sudo chmod 600 /opt/webapp/conf/other.conf
    sudo aide --check | sed -n 1,30p
  3. Challenge

    Approved change or intrusion?

    Setup (with a baseline that covers /etc):

    sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
    sudo useradd -m -s /bin/bash svc-backup2
    sudo chmod 644 /etc/shadow
    sudo touch -d '2020-01-01' /etc/hosts

    Run a check and classify every reported item as expected maintenance (you can explain it) or suspicious (you cannot, or it weakens security). Fix the suspicious ones, then update the baseline so that only the approved changes are accepted. Finish with a clean check.

    Check your result
    • ls -l /etc/shadow shows mode ---------- (000) again.
    • grep -i '^PermitRootLogin' /etc/ssh/sshd_config is not yes.
    • sudo aide --check ends with Looks okay!!.
    • Your notes name the shadow permission change as the clearest sign of tampering.
    Hint

    /etc/passwd, /etc/shadow, /etc/group, /etc/gshadow change when you add a user, which is a normal change. /etc/shadow becoming world-readable is not: the change code shows p (permissions). Compare rpm -V setup for the files that belong to a package. Mtime-only changes on a file are usually harmless but still worth a comment.

    Solution
    sudo aide --check | sed -n 1,40p
    rpm -V setup openssh-server
    sudo chmod 000 /etc/shadow
    sudo sed -i 's/^PermitRootLogin yes/#PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
    sudo systemctl reload sshd
    sudo aide --update | head -3
    sudo cp -p /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
    sudo aide --check | head -2