21 Oct 2026 Two critical RCE bugs in PHP core (CVE-2026-109618, CVE-2026-109619). Fixed in 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12. Is my PHP patched? →
Checklamp
Integrity · sha256sum baselines

File checksums: proving nothing changed, and finding what did

Published · updated

After a break-in — or as a precaution before one — the question is always the same: which files are not what they were? A checksum baseline answers it in minutes, and it costs nothing to keep.

Passive. Public responses only, nothing is attacked. No account. The result is on the page. Honest: if a value is hidden, we say so instead of guessing.
Reading public responses — usually 5–20 seconds

Why a checksum, not a timestamp

A modification time is a claim the file system makes and an attacker can reset with touch. A SHA-256 hash is derived from the bytes: if one character changes, the hash changes and there is no way to make the old value come back without putting the old bytes back.

That is the whole trick behind integrity checks in security scanners. It is also why a hash is only as good as the reference: hashing a compromised file and storing the result on the same compromised server proves that the file did not change afterwards — nothing more. The baseline has to live somewhere the attacker cannot reach.

Build a baseline

# one hash per file, relative paths, sorted — the classic format
cd /var/www/site
find . -type f -not -path './cache/*' -not -path './uploads/*' -print0 \
  | sort -z | xargs -0 sha256sum > /root/site.sha256

# count and date it
wc -l /root/site.sha256
date -u +%FT%TZ >> /root/site.sha256

Exclude what legitimately churns: cache directories, session files, uploads, logs. Include everything else — including .htaccess, index.php, and the first two levels of any plugin or module directory, because that is where injected code lives.

Then copy the manifest somewhere else: your laptop, a private bucket, a git repository. A baseline on the same disk as the files it protects is a baseline that disappears with the server.

Compare later, and read the output

cd /var/www/site
sha256sum -c /root/site.sha256 2>&1 | grep -v ': OK$'

The output has three shapes and each means something different:

  • FAILED — the bytes changed since the baseline. Expected after a deploy or an update; suspicious otherwise.
  • No such file or directory — deleted, or added to the exclusions by mistake.
  • Nothing at all — but silence is the dangerous case. A new file that the baseline never saw produces no line, because sha256sum -c only checks what is listed. New files are what webshells are.

So the second half of the check lists files that were not in the manifest:

# files present now but absent from the baseline
cd /var/www/site
find . -type f -not -path './cache/*' -not -path './uploads/*' | sort > /tmp/now.txt
awk '{ $1=""; sub(/^ /,""); print }' /root/site.sha256 | sort > /tmp/base.txt
comm -23 /tmp/now.txt /tmp/base.txt

Three other sources of truth

  1. The package manager. rpm -Va on RPM systems and debsums -c on Debian list files whose content no longer matches the package. This is how you find a modified system binary without keeping your own baseline.
  2. Git, for anything you deploy. git status on the production checkout is an integrity check with a diff attached: git diff shows the injected line. Sites deployed by rsync or FTP lose this, which is one good reason to move to a repository-driven deploy.
  3. Modification times as a filter, not as proof. find /var/www -type f -newermt '2026-10-16' -ls narrows the list to files touched recently; the hash decides whether the change was legitimate.

What an outside check adds

Checksums need the server. From outside, the useful question is different: are you publishing the files that make an attack easy? An exposed .git/ directory hands over the whole source including history; a site archive or database dump in the web root hands over the code and the passwords. Our checker looks for exactly those, because a downloaded backup is the cheapest way into a site — no exploit required.

If you found one exposed, treat it as a disclosure: remove the file, then assume the credentials inside are known and rotate them. Removing a dump after it has been indexed by a crawler is clean-up, not containment.

Questions

Which hash should I use?

SHA-256 is the default answer: fast enough on any modern CPU, no known collisions. MD5 and SHA-1 are fine for detecting accidental corruption and not for anything an attacker might influence.

How often should I re-baseline?

After every deliberate deploy, and never by simply overwriting the old manifest: keep the dated file. Then a change between two baselines is explainable, and an unexplained one stands out.

Is checking checksums useful if the attacker already has root?

Once someone has root, nothing on the host is trustworthy — including the hashing tools. That is why the baseline is stored off-host and the comparison can be run from a rescue system or a fresh copy.

Do I need a commercial file-integrity product?

Not to start. A manifest plus a cron job covers the common case. Commercial tools add real-time watching, central reporting and tamper-resistant storage — worth it when compliance asks, not before.