Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions configs/linux/allowlist-paths.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# One path fragment per line.
# Blank lines and comments are ignored.
#
# Examples:
# /opt/company-approved-tool
# /var/tmp/approved-maintenance
6 changes: 6 additions & 0 deletions configs/linux/allowlist-services.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# One service/unit name or path fragment per line.
# Blank lines and comments are ignored.
#
# Examples:
# sshd.service
# /etc/sv/agetty-tty1
6 changes: 6 additions & 0 deletions configs/linux/allowlist-suid.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# One SUID/SGID path fragment per line.
# Blank lines and comments are ignored.
#
# Examples:
# /usr/bin/sudo
# /usr/bin/passwd
6 changes: 6 additions & 0 deletions configs/linux/allowlist-users.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# One username or user fragment per line.
# Blank lines and comments are ignored.
#
# Examples:
# deploy
# backup
34 changes: 34 additions & 0 deletions lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,37 @@ safe_run() {
command_exists() {
command -v "$1" >/dev/null 2>&1
}

opsforge_allowlist_file() {
local name="$1"
printf '%s/configs/linux/allowlist-%s.conf\n' "$(opsforge_repo_root)" "$name"
}

opsforge_is_allowlisted() {
local name="$1"
local value="$2"
local file entry
file="$(opsforge_allowlist_file "$name")"
[ -r "$file" ] || return 1

while IFS= read -r entry || [ -n "$entry" ]; do
case "$entry" in
''|'#'*) continue ;;
esac
case "$value" in
*"$entry"*) return 0 ;;
esac
done < "$file"

return 1
}

opsforge_reduced_severity() {
case "$1" in
critical) printf 'high\n' ;;
high) printf 'medium\n' ;;
medium) printf 'low\n' ;;
low) printf 'info\n' ;;
*) printf 'info\n' ;;
esac
}
60 changes: 50 additions & 10 deletions scripts/linux/endpoint/linux-triage-collector.sh
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,16 @@ while IFS= read -r line; do
[ -n "$line" ] || continue
severity="medium"
case "$line" in *"/dev/shm/"*) severity="high" ;; esac
title="Process executable runs from temporary path"
recommendation="Validate process lineage, binary hash, and whether execution from temporary paths is expected."
if opsforge_is_allowlisted paths "$line"; then
severity="$(opsforge_reduced_severity "$severity")"
title="$title (allowlisted)"
recommendation="$recommendation This matched allowlist-paths.conf; verify the allowlist entry is still wanted."
fi
write_finding_json "$TMP_FINDINGS" "LINUX-TRIAGE-PROC-$(printf '%s' "$line" | cksum | awk '{print $1}')" \
"Process executable runs from temporary path" "$severity" "$HOST" "endpoint" "$line" \
"Validate process lineage, binary hash, and whether execution from temporary paths is expected."
"$title" "$severity" "$HOST" "endpoint" "$line" \
"$recommendation"
done < "$OUT_DIR/normalized/suspicious-process-paths.txt"

if [ -s "$OUT_DIR/raw/deleted-running-binaries.txt" ]; then
Expand All @@ -243,17 +250,33 @@ fi
} | sort -u > "$OUT_DIR/normalized/world-writable-sensitive-files.txt"
while IFS= read -r file; do
[ -n "$file" ] || continue
severity="high"
title="World-writable sensitive file"
recommendation="Remove world-write permissions and investigate recent modification history."
if opsforge_is_allowlisted paths "$file"; then
severity="$(opsforge_reduced_severity "$severity")"
title="$title (allowlisted)"
recommendation="$recommendation This matched allowlist-paths.conf; verify the allowlist entry is still wanted."
fi
write_finding_json "$TMP_FINDINGS" "LINUX-TRIAGE-WW-$(printf '%s' "$file" | cksum | awk '{print $1}')" \
"World-writable sensitive file" "high" "$HOST" "hardening" "$file" \
"Remove world-write permissions and investigate recent modification history."
"$title" "$severity" "$HOST" "hardening" "$file" \
"$recommendation"
done < "$OUT_DIR/normalized/world-writable-sensitive-files.txt"

bounded_find / -xdev \( -perm -4000 -o -perm -2000 \) -type f -mtime -14 -print 2>/dev/null > "$OUT_DIR/normalized/recent-suid-sgid.txt" || true
while IFS= read -r file; do
[ -n "$file" ] || continue
severity="medium"
title="Recent SUID or SGID file"
recommendation="Validate package ownership, timestamp, hash, and whether privileged mode is expected."
if opsforge_is_allowlisted suid "$file" || opsforge_is_allowlisted paths "$file"; then
severity="$(opsforge_reduced_severity "$severity")"
title="$title (allowlisted)"
recommendation="$recommendation This matched an allowlist; verify the entry is still wanted."
fi
write_finding_json "$TMP_FINDINGS" "LINUX-TRIAGE-SUID-$(printf '%s' "$file" | cksum | awk '{print $1}')" \
"Recent SUID or SGID file" "medium" "$HOST" "hardening" "$file" \
"Validate package ownership, timestamp, hash, and whether privileged mode is expected."
"$title" "$severity" "$HOST" "hardening" "$file" \
"$recommendation"
done < "$OUT_DIR/normalized/recent-suid-sgid.txt"

if command -v systemctl >/dev/null 2>&1; then
Expand All @@ -262,9 +285,18 @@ if command -v systemctl >/dev/null 2>&1; then
execs="$(systemctl show "$unit" -p ExecStart --value 2>/dev/null || true)"
case "$fragment $execs" in
*"/tmp/"*|*"/dev/shm/"*|*"/var/tmp/"*)
severity="high"
title="Systemd service references temporary path"
evidence="$unit $fragment $execs"
recommendation="Disable unauthorized service units after preserving the unit file and referenced binary."
if opsforge_is_allowlisted services "$evidence" || opsforge_is_allowlisted paths "$evidence"; then
severity="$(opsforge_reduced_severity "$severity")"
title="$title (allowlisted)"
recommendation="$recommendation This matched an allowlist; verify the entry is still wanted."
fi
write_finding_json "$TMP_FINDINGS" "LINUX-TRIAGE-SYSTEMD-$(printf '%s' "$unit" | cksum | awk '{print $1}')" \
"Systemd service references temporary path" "high" "$HOST" "persistence" "$unit $fragment $execs" \
"Disable unauthorized service units after preserving the unit file and referenced binary."
"$title" "$severity" "$HOST" "persistence" "$evidence" \
"$recommendation"
;;
esac
done
Expand All @@ -279,9 +311,17 @@ done > "$INIT_TEMP_MATCHES"

while IFS= read -r line; do
[ -n "$line" ] || continue
severity="high"
title="Init service references temporary path"
recommendation="Validate the service file and referenced binary before changing service state."
if opsforge_is_allowlisted services "$line" || opsforge_is_allowlisted paths "$line"; then
severity="$(opsforge_reduced_severity "$severity")"
title="$title (allowlisted)"
recommendation="$recommendation This matched an allowlist; verify the entry is still wanted."
fi
write_finding_json "$TMP_FINDINGS" "LINUX-TRIAGE-INIT-$(printf '%s' "$line" | cksum | awk '{print $1}')" \
"Init service references temporary path" "high" "$HOST" "persistence" "$line" \
"Validate the service file and referenced binary before changing service state."
"$title" "$severity" "$HOST" "persistence" "$line" \
"$recommendation"
done < "$INIT_TEMP_MATCHES"

finalize_findings_json "$TMP_FINDINGS" "$OUT_DIR/findings.json"
Expand Down
30 changes: 27 additions & 3 deletions scripts/linux/hardening/linux-privilege-surface-audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,41 @@ safe_run "$OUT_DIR/raw/home-permissions.txt" sh -c 'find /home -maxdepth 1 -type
grep -Rni 'NOPASSWD' /etc/sudoers /etc/sudoers.d 2>/dev/null > "$OUT_DIR/normalized/nopasswd-rules.txt" || true
while IFS= read -r line; do
[ -n "$line" ] || continue
write_finding_json "$TMP_FINDINGS" "LINUX-PRIV-NOPASSWD-$(printf '%s' "$line" | cksum | awk '{print $1}')" "sudo NOPASSWD rule present" "medium" "$HOST" "hardening" "$line" "Validate whether passwordless sudo is required and restrict command scope."
severity="medium"
title="sudo NOPASSWD rule present"
recommendation="Validate whether passwordless sudo is required and restrict command scope."
if opsforge_is_allowlisted users "$line"; then
severity="$(opsforge_reduced_severity "$severity")"
title="$title (allowlisted)"
recommendation="$recommendation This matched allowlist-users.conf; verify the entry is still wanted."
fi
write_finding_json "$TMP_FINDINGS" "LINUX-PRIV-NOPASSWD-$(printf '%s' "$line" | cksum | awk '{print $1}')" "$title" "$severity" "$HOST" "hardening" "$line" "$recommendation"
done < "$OUT_DIR/normalized/nopasswd-rules.txt"

awk -F: '$1=="docker" || $1=="lxd" {print}' "$OUT_DIR/raw/groups.txt" | while IFS= read -r line; do
members="${line##*:}"
[ -n "$members" ] || continue
write_finding_json "$TMP_FINDINGS" "LINUX-PRIV-GROUP-$(printf '%s' "$line" | cksum | awk '{print $1}')" "Privileged container group has users" "high" "$HOST" "hardening" "$line" "Review Docker/LXD group membership as root-equivalent access."
severity="high"
title="Privileged container group has users"
recommendation="Review Docker/LXD group membership as root-equivalent access."
if opsforge_is_allowlisted users "$line"; then
severity="$(opsforge_reduced_severity "$severity")"
title="$title (allowlisted)"
recommendation="$recommendation This matched allowlist-users.conf; verify the entry is still wanted."
fi
write_finding_json "$TMP_FINDINGS" "LINUX-PRIV-GROUP-$(printf '%s' "$line" | cksum | awk '{print $1}')" "$title" "$severity" "$HOST" "hardening" "$line" "$recommendation"
done

awk '$1 ~ /^..w|^.....w|^........w/ {print}' "$OUT_DIR/raw/path-permissions.txt" | while IFS= read -r line; do
write_finding_json "$TMP_FINDINGS" "LINUX-PRIV-PATH-$(printf '%s' "$line" | cksum | awk '{print $1}')" "Writable PATH directory" "high" "$HOST" "hardening" "$line" "Remove write access from PATH directories or remove them from privileged execution paths."
severity="high"
title="Writable PATH directory"
recommendation="Remove write access from PATH directories or remove them from privileged execution paths."
if opsforge_is_allowlisted paths "$line"; then
severity="$(opsforge_reduced_severity "$severity")"
title="$title (allowlisted)"
recommendation="$recommendation This matched allowlist-paths.conf; verify the entry is still wanted."
fi
write_finding_json "$TMP_FINDINGS" "LINUX-PRIV-PATH-$(printf '%s' "$line" | cksum | awk '{print $1}')" "$title" "$severity" "$HOST" "hardening" "$line" "$recommendation"
done

finalize_findings_json "$TMP_FINDINGS" "$OUT_DIR/findings.json"
Expand Down
23 changes: 19 additions & 4 deletions scripts/linux/hardening/suid-drift-monitor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,31 @@ else
[ -n "$path" ] || continue
severity="medium"
case "$path" in /bin/*|/sbin/*|/usr/bin/*|/usr/sbin/*) severity="low" ;; *) severity="high" ;; esac
title="New SUID/SGID file detected"
recommendation="Validate package ownership, hash, and change approval."
if opsforge_is_allowlisted suid "$path" || opsforge_is_allowlisted paths "$path"; then
severity="$(opsforge_reduced_severity "$severity")"
title="$title (allowlisted)"
recommendation="$recommendation This matched an allowlist; verify the entry is still wanted."
fi
write_finding_json "$TMP_FINDINGS" "LINUX-SUID-NEW-$(printf '%s' "$path" | cksum | awk '{print $1}')" \
"New SUID/SGID file detected" "$severity" "$HOST" "hardening" "$path" \
"Validate package ownership, hash, and change approval."
"$title" "$severity" "$HOST" "hardening" "$path" \
"$recommendation"
done < "$OUT_DIR/raw/new-privileged-files.txt"
awk -F '\t' '$1 ~ /7..|.7.|..7/ {print}' "$CURRENT" > "$OUT_DIR/raw/world-writable-privileged-files.txt"
while IFS= read -r line; do
[ -n "$line" ] || continue
severity="critical"
title="World-writable privileged file"
recommendation="Remove write permissions immediately and investigate file provenance."
if opsforge_is_allowlisted suid "$line" || opsforge_is_allowlisted paths "$line"; then
severity="$(opsforge_reduced_severity "$severity")"
title="$title (allowlisted)"
recommendation="$recommendation This matched an allowlist; verify the entry is still wanted."
fi
write_finding_json "$TMP_FINDINGS" "LINUX-SUID-WW-$(printf '%s' "$line" | cksum | awk '{print $1}')" \
"World-writable privileged file" "critical" "$HOST" "hardening" "$line" \
"Remove write permissions immediately and investigate file provenance."
"$title" "$severity" "$HOST" "hardening" "$line" \
"$recommendation"
done < "$OUT_DIR/raw/world-writable-privileged-files.txt"
fi

Expand Down
14 changes: 11 additions & 3 deletions scripts/linux/persistence/linux-persistence-hunter.sh
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,18 @@ if [ -s "$MATCHES" ]; then
case "$text" in
*"/dev/shm"*|*"/tmp/"*|*"nc -e"*|*"socat exec"*|*"chattr +i"*|*"chmod +s"*) severity="high" ;;
esac
title="Suspicious persistence content"
evidence="$file:$lineno $text"
recommendation="Review the referenced persistence location, validate owner and change history, and remove unauthorized entries."
if opsforge_is_allowlisted paths "$evidence" || opsforge_is_allowlisted services "$evidence"; then
severity="$(opsforge_reduced_severity "$severity")"
title="$title (allowlisted)"
recommendation="$recommendation This matched an allowlist; verify the entry is still wanted."
fi
write_finding_json "$TMP_FINDINGS" "LINUX-PERSISTENCE-$(printf '%s' "$file:$lineno" | cksum | awk '{print $1}')" \
"Suspicious persistence content" "$severity" "$HOST" "persistence" \
"$file:$lineno $text" \
"Review the referenced persistence location, validate owner and change history, and remove unauthorized entries."
"$title" "$severity" "$HOST" "persistence" \
"$evidence" \
"$recommendation"
done < "$MATCHES"
fi

Expand Down
Loading