topic

Cybersecurity Triage

Defensive commands for checking exposure, logs, permissions, and suspicious activity.

69 checked fixes

Commands in this topic

Cybersecurity Triage Dry run

Simulate Security Package Upgrades

Security patch triage starts by seeing what apt would change, without changing it.

apt-get -s upgrade | awk '/^Inst/ && /security/ {print}'
Cybersecurity Triage Read-only

Prove a Package Candidate Is From Security

The package name is not enough; the candidate repository tells the patch story.

apt-cache policy openssl | sed -n '/Installed:/p;/Candidate:/p;/security/p'
Cybersecurity Triage Read-only

Find Held Packages Blocking Patches

A held package can quietly keep a security update out of production.

apt-mark showhold | sed 's/^/held: /'
Cybersecurity Triage Dry run

Dry-Run Unattended Security Upgrades

Unattended upgrades can explain what they would patch before they patch it.

unattended-upgrade --dry-run --debug 2>&1 | sed -n '/Packages that will be upgraded:/,/^$/p'
Cybersecurity Triage Read-only

Build a Recent Apt Patch Timeline

Apt history turns patch claims into timestamps and package names.

awk '/^(Start-Date|Commandline|Upgrade|End-Date)/ {print}' /var/log/apt/history.log
Cybersecurity Triage Can be slow

Find Warnings in Apt Terminal Logs

The package installed, but the terminal log may still contain the warning that matters.

grep -Ei 'warning|error|failed|dpkg' /var/log/apt/term.log
Cybersecurity Triage Dry run

Preview Security Impact of dist-upgrade

Kernel and dependency security fixes may only appear in the broader upgrade plan.

apt-get -s dist-upgrade | awk '/^Inst/ {print}'
Cybersecurity Triage Read-only

Check Whether Patches Require Reboot

Some security fixes are not complete until the host boots the new kernel or libraries.

test -f /var/run/reboot-required && printf 'reboot-required\n' && cat /var/run/reboot-required.pkgs
Cybersecurity Triage Can be slow

Extract Environment Names Only

Audit environment labels without printing secret values.

grep -RhoE 'ENVIRONMENT|NODE_ENV|APP_ENV|RAILS_ENV' config deploy | sort -u
Cybersecurity Triage Read-only

Inspect Container Environment Names

Check what environment variables exist without printing their secret values.

docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' api | sed 's/=.*$/=<redacted>/'
Cybersecurity Triage Read-only

Review Recent Docker Events

Docker keeps a recent event trail for starts, stops, pulls, and health changes.

docker events --since 30m --until 0s
Cybersecurity Triage Sensitive output

Read UFW Policy Verbosely

The firewall was active, but the defaults mattered more than the rule list.

ufw status verbose
Cybersecurity Triage Sensitive output

List Numbered UFW Rules

Numbered rules make firewall review less ambiguous.

ufw status numbered
Cybersecurity Triage Read-only

Show the nftables Input Chain

The packet path was hiding below UFW.

nft list ruleset | sed -n '/chain input/,/}/p'
Cybersecurity Triage Sensitive output

Show iptables INPUT Rules

Legacy firewall state can still explain live exposure.

iptables -S INPUT
Cybersecurity Triage Read-only

Show Publicly Bound Listeners

Localhost services are different from public listeners.

ss -ltnp | awk 'NR==1 || $4 ~ /^(0[.]0[.]0[.]0|[[]::[]]|[*]):/'
Cybersecurity Triage Sensitive output

Find Allowed Ports with No Listener

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)
Cybersecurity Triage Sensitive output

Find Public Listeners Not Allowed by UFW

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)
Cybersecurity Triage Read-only

Check Whether SSH Is Publicly Bound

SSH can be locked down by source and still bind publicly.

ss -ltnp | awk '$4 ~ /:22$/ && $4 !~ /^127[.]/ {print}'
Cybersecurity Triage Read-only

Show Local-Only Database Listeners

The database was listening, but only on localhost.

ss -ltnp | awk '$4 ~ /^127[.]0[.]0[.]1:(5432|3306|6379)$/ {print}'
Cybersecurity Triage Sensitive output

Redact Secret-Looking Log Lines

Incident notes should not copy secrets forward.

grep -RInEi '(password|token|secret|authorization)' /var/log/app /var/log/deploy.log 2>/dev/null | sed -E 's/((password|token|secret)[[:space:]]*[:=])[[:alnum:]_.-]+/\1REDACTED/Ig; s/([Aa]uthorization[[:space:]]*:[[:space:]]*[Bb]earer[[:space:]]+)[[:alnum:]_.-]+/\1REDACTED/g'
Cybersecurity Triage Can be slow

Find Writable Directories Missing the Sticky Bit

A writable log directory is not the same thing as a safe shared directory.

find /srv/www/example -type d -perm -0002 ! -perm -1000 -printf '%m %u:%g %p\n' 2>/dev/null | sort
Cybersecurity Triage Sensitive output

Find World-Readable Secret-Looking Files

The fastest secret audit starts with readable files that look like secrets.

find /srv/www/example -type f -perm -0004 \( -iname '*secret*' -o -iname '*.env' -o -iname '*token*' -o -iname '*key*' \) -printf '%M %u:%g %p\n' 2>/dev/null | sort
Cybersecurity Triage Can be slow

Find Config Files with Execute Bits

Config files do not usually need to be executable.

find /srv/www/example -type f -perm /111 \( -path '*/config/*' -o -name '*.env' -o -name '*.conf' \) -printf '%M %u:%g %p\n' 2>/dev/null | sort
Cybersecurity Triage Can be slow

Find Upload Files Writable Outside the Owner

Uploads are supposed to be writable at the edge, not writable forever by everyone.

find /srv/www/example/shared/uploads -type f -perm /0022 -printf '%M %u:%g %p\n' 2>/dev/null | sort
Cybersecurity Triage Read-only

Find Listening Ports with ss

Before blaming the firewall, check whether anything is actually listening.

ss -ltnp
Cybersecurity Triage Sensitive output

Summarize SSH Auth Outcomes

SSH logs get easier to read once accepted and failed methods are counted.

awk '/sshd/ && /Accepted/ {print "accepted", $7} /sshd/ && /Failed password/ {print "failed", "password"} /sshd/ && /Failed publickey/ {print "failed", "publickey"}' logs/auth.log | sort | uniq -c | sort -nr
Cybersecurity Triage Sensitive output

Find SSH Password Auth Exceptions

A global password-auth setting can be changed later by a Match block.

awk '/^Match /{ctx=$0} /^PasswordAuthentication|^AuthenticationMethods|^[[:space:]]+PasswordAuthentication|^[[:space:]]+AuthenticationMethods/ {print (ctx ? ctx : "global") ": " $0}' etc/ssh/sshd_config
Cybersecurity Triage Sensitive output

List SSH Allow and Deny Rules

SSH access can be shaped by users, groups, and Match blocks.

grep -RhnE '^(AllowUsers|AllowGroups|DenyUsers|DenyGroups|Match )' etc/ssh
Cybersecurity Triage Sensitive output

Inventory SSH authorized_keys

authorized_keys files are the practical list of who can use key-based SSH.

find /home -path '*/.ssh/authorized_keys' -exec awk '{print FILENAME, $1, $NF}' {} + 2>/dev/null
Cybersecurity Triage Sensitive output

List Accepted SSH Login Sources

Successful SSH logins are the access events worth anchoring first.

awk '/Accepted publickey/ {print $1, $2, $3, $9, $11}' logs/auth.log
Cybersecurity Triage Sensitive output

Show Failed SSH Public-Key Users

A failed public-key attempt often points to stale keys or the wrong account.

awk '/Failed publickey/ {print $9, $11}' logs/auth.log | sort | uniq -c | sort -nr
Cybersecurity Triage Sensitive output

Summarize SSH Authorized Key Types

Key inventory gets more useful when old key types stand out.

find /home -path '*/.ssh/authorized_keys' -exec awk '{print $1}' {} + 2>/dev/null | sort | uniq -c | sort -nr
Cybersecurity Triage Sensitive output

Find Loose authorized_keys Modes

SSH key access files should not be looser than intended.

find /home -path '*/.ssh/authorized_keys' -printf '%m %p\n' 2>/dev/null | awk '$1 > 600'
Cybersecurity Triage Sensitive output

Show SSH Auth Policy Order

The order of Include, Match, and authentication directives changes how SSH policy reads.

grep -nE '^(Include|Match |PubkeyAuthentication|PasswordAuthentication|AuthenticationMethods|[[:space:]]+(PasswordAuthentication|AuthenticationMethods))' etc/ssh/sshd_config
Cybersecurity Triage Sensitive output

Extract SSH AllowUsers Accounts

AllowUsers turns SSH access into an explicit account list.

awk '/^AllowUsers/ {for (i = 2; i <= NF; i++) print $i}' etc/ssh/sshd_config
Cybersecurity Triage Read-only

List Accounts with Login Shells

Login shells are the first account inventory to review.

awk -F: '$7 ~ /(bash|sh|zsh)$/ {printf "%s %s\n", $1, $7}' /etc/passwd
Cybersecurity Triage Sensitive output

Find Password-Enabled Accounts

A shell account with an unlocked password hash deserves extra attention.

sudo awk -F: '$2 !~ /^(!|\*)/ {print $1}' /etc/shadow
Cybersecurity Triage Sensitive output

Review sudo Grants

Privilege paths should be visible before you remove or approve access.

awk -F: '$1=="sudo" {print "sudo group: " $4}' /etc/group; sudo grep -RhnE '^[^#].*ALL=' /etc/sudoers /etc/sudoers.d 2>/dev/null
Cybersecurity Triage Sensitive output

Count authorized_keys by User

authorized_keys is the practical SSH access list.

find /home -path '*/.ssh/authorized_keys' -exec sh -c 'for f do user=$(basename "$(dirname "$(dirname "$f")")"); keys=$(grep -vc "^[[:space:]]*#" "$f"); printf "%s %s %s\n" "$user" "$keys" "$f"; done' sh {} + 2>/dev/null | sort
Cybersecurity Triage Sensitive output

Find SSH Keys for nologin Users

A nologin shell does not automatically mean SSH keys are irrelevant.

comm -12 <(awk -F: '$7 !~ /(bash|sh|zsh)$/ {print $1}' /etc/passwd | sort) <(find /home -path '*/.ssh/authorized_keys' -printf '%h\n' 2>/dev/null | awk -F/ '{print $(NF-1)}' | sort)
Cybersecurity Triage Sensitive output

Show Successful Logins and sudo Use

Access reviews need both who logged in and who elevated privileges.

grep -E 'Accepted publickey|sudo:' /var/log/auth.log 2>/dev/null
Cybersecurity Triage Sensitive output

List Privileged Group Members

Group membership can grant more access than the username suggests.

awk -F: '$1 ~ /^(sudo|adm|docker)$/ && $4 != "" {print $1 ": " $4}' /etc/group
Cybersecurity Triage Sensitive output

Summarize sudo Commands by User

Privilege history is easier to review when users and commands are separated.

sed -n 's/.*sudo: *\([^: ]*\).*COMMAND=\(.*\)$/\1 -> \2/p' /var/log/auth.log 2>/dev/null | sort
Cybersecurity Triage Sensitive output

Review a Breakglass Account

Emergency accounts should be easy to find and hard to ignore.

sudo grep -Rhn 'breakglass' /etc /home /var/log/auth.log 2>/dev/null
Cybersecurity Triage Sensitive output

Find SSH Key Users with sudo

The highest-priority access review starts where SSH keys and sudo overlap.

comm -12 <(find /home -path '*/.ssh/authorized_keys' -printf '%h\n' 2>/dev/null | awk -F/ '{print $(NF-1)}' | sort) <(awk -F: '$1=="sudo" {gsub(",","\n",$4); print $4}' /etc/group | sort)
Cybersecurity Triage Sensitive output

Count Failed SSH Login Users

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
Cybersecurity Triage Sensitive output

Count Failed SSH Login IPs

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
Cybersecurity Triage Sensitive output

Show Accepted SSH Logins

During first response, successful logins matter more than background noise.

grep 'Accepted publickey' logs/auth.log
Cybersecurity Triage Sensitive output

Show Recent sudo Commands

Privilege use is one of the fastest first-response signals.

grep 'sudo:' logs/auth.log | tail -n 10
Cybersecurity Triage Read-only

List Users with Login Shells

Not every local account should be able to log in.

awk -F: '$7 ~ /sh$/ {print $1, $7}' etc/passwd
Cybersecurity Triage Sensitive output

Check Key SSH Authentication Settings

SSH policy should be visible before you change it.

grep -nE '^(PasswordAuthentication|PermitRootLogin|PubkeyAuthentication|AllowUsers)' etc/ssh/sshd_config
Cybersecurity Triage Can be slow

Find Loose Private Key Permissions

SSH private keys should not be readable like ordinary files.

find /home -type f -name 'id_*' -printf '%m %p\n' 2>/dev/null | awk '$1 > 600'
Cybersecurity Triage Sensitive output

List authorized_keys Files

Authorized keys are the server's practical access list.

find /home -path '*/.ssh/authorized_keys' -printf '%m %p\n' 2>/dev/null
Cybersecurity Triage Read-only

Find the IPs Creating the Most 4xx Noise

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}' /var/log/nginx/access.log | sort -nr | head
Cybersecurity Triage Read-only

Spot Unusual HTTP Methods in Access Logs

Most site traffic is boring. The weird methods are worth a look.

awk '$6 !~ /^"(GET|POST|HEAD|OPTIONS)$/ {print $1, $6, $7, $9}' /var/log/nginx/access.log | sort | uniq -c | sort -nr
Cybersecurity Triage Read-only

Count the Most Common User Agents

A strange traffic spike often has a strange user agent.

awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
Cybersecurity Triage Read-only

Find Common Admin Probe Paths

A site does not need WordPress to receive WordPress-looking probes.

awk '$7 ~ /(admin|login|wp-|phpmyadmin)/ {print $1, $7, $9}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
Cybersecurity Triage Read-only

Find Paths Repeatedly Returning 404

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}' /var/log/nginx/access.log | sort -nr | head
Cybersecurity Triage Read-only

Spot Request Bursts by Minute

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}' /var/log/nginx/access.log | sort -nr | head
Cybersecurity Triage Read-only

Find Clients Repeating the Same Path

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}' /var/log/nginx/access.log | sort -nr | head