Check Whether Databases Listen Publicly
The fastest database security check is the listening address.
ss -ltnp | awk '$4 ~ /:(5432|3306)$/ {print}'
problem area
Defensive commands for checking exposure, logs, permissions, and suspicious activity.
33 checked fixes
The fastest database security check is the listening address.
ss -ltnp | awk '$4 ~ /:(5432|3306)$/ {print}'
Audit environment labels without printing secret values.
grep -RhoE 'ENVIRONMENT|NODE_ENV|APP_ENV|RAILS_ENV' config deploy | sort -u
Check what environment variables exist without printing their secret values.
docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' api | sed 's/=.*$/=/'
Docker keeps a recent event trail for starts, stops, pulls, and health changes.
docker events --since 30m --until 0s
The firewall was active, but the defaults mattered more than the rule list.
ufw status verbose
Numbered rules make firewall review less ambiguous.
ufw status numbered
The packet path was hiding below UFW.
nft list ruleset | sed -n '/chain input/,/}/p'
Legacy firewall state can still explain live exposure.
iptables -S INPUT
Firewall rules matter after you know what is listening.
ss -ltnp
Localhost services are different from public listeners.
ss -ltnp | awk 'NR==1 || $4 ~ /^(0[.]0[.]0[.]0|[[]::[]]|[*]):/'
An open firewall rule can outlive the service it was created for.
comm -23 <(ufw status numbered | awk '/ALLOW/ {print}' | grep -Eo '[0-9]+/(tcp|udp)' | cut -d/ -f1 | sort -u) <(ss -ltnp | awk '/LISTEN/ {n=split($4,a,":"); print a[n]}' | sort -u)
The process was public, but the firewall did not mention it.
comm -13 <(ufw status numbered | awk '/ALLOW/ {print}' | grep -Eo '[0-9]+/(tcp|udp)' | cut -d/ -f1 | sort -u) <(ss -ltnp | awk '$4 ~ /^(0[.]0[.]0[.]0|[[]::[]]|[*]):/ {n=split($4,a,":"); print a[n]}' | sort -u)
SSH can be locked down by source and still bind publicly.
ss -ltnp | awk '$4 ~ /:22$/ && $4 !~ /^127[.]/ {print}'
The database was listening, but only on localhost.
ss -ltnp | awk '$4 ~ /^127[.]0[.]0[.]1:(5432|3306|6379)$/ {print}'
Before blaming the firewall, check whether anything is actually listening.
ss -ltnp
Unexpected connections are easier to reason about when you can see them directly.
ss -tan state established
Failed SSH attempts are noisy; grouping users makes the pattern readable.
sed -n 's/.*Failed password for \(invalid user \)\?\([^ ]*\) from .*/\2/p' logs/auth.log | sort | uniq -c | sort -nr
The loudest SSH source is usually visible with one count.
sed -n 's/.*Failed password .* from \([0-9.]*\) port.*/\1/p' logs/auth.log | sort | uniq -c | sort -nr
During first response, successful logins matter more than background noise.
grep 'Accepted publickey' logs/auth.log
Privilege use is one of the fastest first-response signals.
grep 'sudo:' logs/auth.log | tail -n 10
Unexpected network listeners are first-response evidence.
ss -ltnp
Not every local account should be able to log in.
awk -F: '$7 ~ /sh$/ {print $1, $7}' etc/passwd
SSH policy should be visible before you change it.
grep -nE '^(PasswordAuthentication|PermitRootLogin|PubkeyAuthentication|AllowUsers)' etc/ssh/sshd_config
World-writable web paths deserve immediate review.
find srv/www -type d -perm -0002 -print
SSH private keys should not be readable like ordinary files.
find home -type f -name 'id_*' -printf '%m %p\n' | awk '$1 > 600'
Authorized keys are the server's practical access list.
find home -path '*/.ssh/authorized_keys' -printf '%m %p\n'
One address can turn a normal access log into a wall of failed requests.
awk '$9 ~ /^4/ {count[$1]++} END {for (ip in count) print count[ip], ip}' ./fixtures/nginx/access.log | sort -nr | head
Most site traffic is boring. The weird methods are worth a look.
awk '$6 !~ /^"(GET|POST|HEAD|OPTIONS)$/ {print $1, $6, $7, $9}' ./fixtures/nginx/access.log | sort | uniq -c | sort -nr
A strange traffic spike often has a strange user agent.
awk -F'"' '{print $6}' ./fixtures/nginx/access.log | sort | uniq -c | sort -nr | head
A site does not need WordPress to receive WordPress-looking probes.
awk '$7 ~ /(admin|login|wp-|phpmyadmin)/ {print $1, $7, $9}' ./fixtures/nginx/access.log | sort | uniq -c | sort -nr | head
One missing URL is normal. A repeated missing URL is a signal.
awk '$9==404 {count[$7]++} END {for (path in count) if (count[path] >= 3) print count[path], path}' ./fixtures/nginx/access.log | sort -nr | head
Traffic spikes are easier to read when you bucket them by time.
awk '{minute=substr($4,2,17); count[minute]++} END {for (m in count) print count[m], m}' ./fixtures/nginx/access.log | sort -nr | head
The suspicious pattern is sometimes one client hammering one URL.
awk '{key=$1 " " $7; count[key]++} END {for (k in count) if (count[k] >= 5) print count[k], k}' ./fixtures/nginx/access.log | sort -nr | head