I recently showed an example of a file integrity check script that calculates SHA-256 hashes. However, if the files are large and 100 gigabytes or more, this will naturally put a significant load on the disk system. Therefore, if this is critical and slows down other processes, I will give an example of a simple script that works much faster. It simply checks the file size and mtime (modification time). However, it is worth considering that, unlike the SHA-256 hash, an attacker can specify the mtime of a new, substituted, infected file similar to the old one.
Installing python3 on Ubuntu Server:
python3 --version
apt install python3 python3-pip
Script contents:
#!/usr/bin/env python3
import os
import json
import time
import subprocess
WATCH_DIR = "/var/www"
DB_FILE = "/root/db_size_mtime.json"
LOG_FILE = "/root/file_monitor.log"
EMAIL_TO = "root"
EMAIL_SUBJECT = "FILE MODIFIED (size+mtime)"
EXCLUDE_EXT = [".log", ".tmp"]
EXCLUDE_NAMES = ["hgrc"]
EXCLUDE_DIRS = [
"/var/www/logs",
"/var/www/tmp",
]
# ============================================
def is_excluded(path):
name = os.path.basename(path)
if name in EXCLUDE_NAMES:
return True
for d in EXCLUDE_DIRS:
if path.startswith(d):
return True
ext = os.path.splitext(name)[1]
return ext in EXCLUDE_EXT
def fingerprint(path):
st = os.stat(path)
return {
"size": st.st_size,
"mtime": int(st.st_mtime)
}
def load_db():
if os.path.exists(DB_FILE):
with open(DB_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return {}
def save_db(db):
os.makedirs(os.path.dirname(DB_FILE), exist_ok=True)
with open(DB_FILE, "w", encoding="utf-8") as f:
json.dump(db, f, indent=2)
def scan():
data = {}
for root, dirs, files in os.walk(WATCH_DIR):
if any(root.startswith(d) for d in EXCLUDE_DIRS):
continue
for name in files:
path = os.path.join(root, name)
if is_excluded(path):
continue
try:
data[path] = fingerprint(path)
except Exception:
continue
return data
def log_changes(changes):
if not changes:
return
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
with open(LOG_FILE, "a", encoding="utf-8", errors="replace") as f:
f.write(f"\n[{time.strftime('%Y-%m-%d %H:%M:%S')}]\n")
for c in changes:
f.write(c + "\n")
def send_email(changes):
if not changes:
return
body = "\n".join(changes)
body = body.encode("utf-8", errors="replace").decode("utf-8")
msg = f"Subject: {EMAIL_SUBJECT}\n\n{body}"
try:
p = subprocess.Popen(
["/usr/sbin/sendmail", EMAIL_TO],
stdin=subprocess.PIPE
)
p.communicate(msg.encode("utf-8"))
except Exception as e:
print("[EMAIL ERROR]", e)
def main():
old = load_db()
new = scan()
changes = []
# Изменённые
for path, meta in new.items():
if path in old and old[path] != meta:
changes.append(f"MODIFIED: {path}")
# Удалённые
for path in old:
if path not in new:
changes.append(f"DELETED: {path}")
# Новые
for path in new:
if path not in old:
changes.append(f"NEW: {path}")
if changes:
log_changes(changes)
send_email(changes)
save_db(new)
if __name__ == "__main__":
main()
Let’s make the file executable:
chmod +x ixnfo.com.py
./ixnfo.com.py
Let’s add it to cron:
02 7 * * * root /dir/ixnfo.com.py > /dev/null 2>&1
See also my articles:
Using and configuring CRON
File integrity check script (SHA-256 hashes)
