Skip to content

Instantly share code, notes, and snippets.

@krushik
Last active April 16, 2026 22:51
Show Gist options
  • Select an option

  • Save krushik/a237053c237d921ecf194065ad546852 to your computer and use it in GitHub Desktop.

Select an option

Save krushik/a237053c237d921ecf194065ad546852 to your computer and use it in GitHub Desktop.
Bash script to verify the integrity of all installed Debian packages. It compares the metadata stored in the dpkg database with the actual files. In practice, this means checking `/var/lib/dpkg/info/<package>.md5sums` (from `DEBIAN/md5sums` inside the .deb) against the live filesystem via `dpkg --verify`. Mismatches and errors are logged to syslog
#!/bin/bash
# integrity check script for Debian packages using dpkg metadata vs live files.
# License: MIT
# Copyright (c) 2026 krushik
set -o pipefail
renice -n 19 "$$" >/dev/null || true
ionice -c 2 -n 7 -p "$$" >/dev/null || true
LOG_TAG="dpkg-verify-check"
_errfile=$(mktemp -t dpkg-verify-err.XXXXXX)
trap 'rm -f "$_errfile"' EXIT
output=$(dpkg --verify --verify-format rpm 2>"$_errfile"); rc=$?
(( rc )) && logger -t "$LOG_TAG" -p local6.err "dpkg --verify failed with exit code $rc: $(<"$_errfile")"
set -o noglob # to preserve * in patterns
ignore_patterns=(
/usr/lib/example2ignore/*
/var/ossec/{etc,ruleset/sca,queue}/*
)
set +o noglob
while IFS= read -r line; do
# verify-format "rpm": ??5?????? [c] pathname
status="${line:0:9}"
attribute="${line:10:1}"
file="${line:12}"
[[ "${status:2:1}" != "5" ]] && continue # skip if no checksum mismatch
[[ "$attribute" == "c" ]] && continue # skip conffiles
for p in "${ignore_patterns[@]}"; do
# shellcheck disable=SC2053
[[ "$file" == $p ]] && continue 2
done
package=$(dpkg-query -S -- "$file" 2>/dev/null | head -1)
package=${package%%:*}
current_md5=$(md5sum "$file" 2>/dev/null | awk '{print $1}')
[[ -z "$current_md5" ]] && current_md5="missing"
expected_md5=
if [[ -n "$package" && -f "/var/lib/dpkg/info/${package}.md5sums" ]]; then
expected_md5=$(awk -v f="${file#/}" '$2 == f {print $1; exit}' \
"/var/lib/dpkg/info/${package}.md5sums")
[[ -z "$expected_md5" ]] && expected_md5="unknown"
fi
logger -t "$LOG_TAG" -p local6.notice "pkg=$package file=$file expected_md5=$expected_md5 current_md5=$current_md5"
done <<< "$output"
exit "$rc"
<decoder name="dpkg-verify">
<program_name>dpkg-verify-check</program_name>
</decoder>
<decoder name="dpkg-verify-mismatch">
<parent>dpkg-verify</parent>
<regex>pkg=(\S+) file=(\S+) expected_md5=(\S+) current_md5=(\S+)</regex>
<order>package,file,expected_md5,current_md5</order>
</decoder>
<decoder name="dpkg-verify-error">
<parent>dpkg-verify</parent>
<regex>failed with exit code (\d+): (.*)</regex>
<order>exit_code,error_message</order>
</decoder>
<group name="dpkg,integrity">
<rule id="255829" level="10">
<decoded_as>dpkg-verify</decoded_as>
<description>dpkg checksum mismatch detected</description>
</rule>
<!-- dpkg tampering -->
<rule id="255830" level="10">
<if_sid>255829</if_sid>
<field name="package">\S+</field>
<description>dpkg tampering: $(package) $(file) expected=$(expected_md5) got=$(current_md5)</description>
</rule>
<!-- missing files -->
<rule id="255831" level="10">
<if_sid>255830</if_sid>
<field name="current_md5">missing</field>
<description>dpkg file missing: $(file) from $(package)</description>
</rule>
<!-- whitelist -->
<rule id="255832" level="0">
<if_sid>255830</if_sid>
<field name="file">^/usr/share/doc/</field>
<description>Ignore documentation</description>
</rule>
<!-- dpkg verify failed -->
<rule id="255833" level="10">
<if_sid>255829</if_sid>
<field name="exit_code">\d+</field>
<description>dpkg verify failed with exit code $(exit_code): $(error_message)</description>
</rule>
</group>
@krushik

krushik commented Apr 16, 2026

Copy link
Copy Markdown
Author

also added decoders and rules for wazuh/ossec

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment