HackBlue

Tools · Python and Bash for security automation · 04 / 20

Writing a file integrity baseline script in Python with SHA-256

Hash every file in a directory tree into a JSON baseline, compare later runs to report added, removed, and changed files, and run it on a schedule.

10 min read · practice only with authorization

The idea

File integrity monitoring compares the current state of important files with a known-good baseline. Dedicated tools such as AIDE or Wazuh do this at scale, but a small script teaches the logic and is handy for one-off checks, such as verifying a web root after a deployment.

The script

Hash files in chunks so large files do not fill memory, skip symlinks, and store size and modification time alongside the hash for context.

Snippet
#!/usr/bin/env python3
"""Create or compare a SHA-256 baseline of a directory."""
import argparse, hashlib, json, os, sys

def sha256(path):
    h = hashlib.sha256()
    with open(path, 'rb') as f:
        for chunk in iter(lambda: f.read(1 << 20), b''):
            h.update(chunk)
    return h.hexdigest()

def scan(root):
    out = {}
    for d, _, files in os.walk(root):
        for name in files:
            p = os.path.join(d, name)
            if os.path.islink(p):
                continue
            st = os.stat(p)
            out[os.path.relpath(p, root)] = {'sha256': sha256(p), 'size': st.st_size, 'mtime': int(st.st_mtime)}
    return out

p = argparse.ArgumentParser(description=__doc__)
p.add_argument('root'); p.add_argument('baseline'); p.add_argument('--create', action='store_true')
a = p.parse_args()
now = scan(a.root)
if a.create:
    json.dump(now, open(a.baseline, 'w'), indent=1); print(f'{len(now)} files recorded'); sys.exit(0)
old = json.load(open(a.baseline))
added = sorted(set(now) - set(old)); removed = sorted(set(old) - set(now))
changed = sorted(k for k in set(now) & set(old) if now[k]['sha256'] != old[k]['sha256'])
for label, items in [('ADDED', added), ('REMOVED', removed), ('CHANGED', changed)]:
    for k in items:
        print(label, k)
sys.exit(1 if added or removed or changed else 0)

Lab: detect a change

Create a baseline of a test web root, then add a file, edit one, and delete one. Run the compare and confirm all three are reported and the exit code is 1. Then schedule it with a systemd timer or cron to run nightly and email or log the output. Store the baseline somewhere the monitored host cannot modify, or an attacker could update it.

Snippet
python3 fim.py /var/www/html baseline.json --create
echo test > /var/www/html/new.php; echo '<!-- edit -->' >> /var/www/html/index.html; rm /var/www/html/old.txt
python3 fim.py /var/www/html baseline.json; echo exit=$?

Limits

A script running on the monitored host can be tampered with by an attacker who has root. Production file integrity monitoring signs or ships baselines off-host and reports to a central system. Exclude directories that change constantly, such as caches and upload folders, or the output becomes noise.

Diagram

Baseline on host versus off host

Where you keep the baseline matters.

Baseline on the monitored host

Easy to set up. Yes

Attacker with root can. Edit baseline and script

Use for. Quick checks, labs

Baseline off host

Easy to set up. Some work

Attacker with root can. Change files, but not the reference

Use for. Real monitoring with central alerts

Check yourself

1. Why hash files in chunks?

2. Which categories does the compare report?

3. Why store the baseline off the monitored host?