Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
+476
@@ -0,0 +1,476 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#########################################################################################
|
||||
#### user-audit.sh — Audit local user accounts, sudo access, SSH keys, and passwords ####
|
||||
#### Shows last login, password age, group memberships, and security warnings ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### License: MIT ####
|
||||
#### Version 1.00 ####
|
||||
#### ####
|
||||
#### Usage: ####
|
||||
#### ./user-audit.sh ####
|
||||
#### ./user-audit.sh --system ####
|
||||
#### ./user-audit.sh --section sudo,ssh ####
|
||||
#### ####
|
||||
#### See --help for all options. ####
|
||||
#########################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ──────────────────────────────────────────────────────────
|
||||
SECTIONS="${SECTIONS:-all}"
|
||||
VERBOSE="${VERBOSE:-false}"
|
||||
COLOR="${COLOR:-auto}"
|
||||
FILTER="${FILTER:-human}"
|
||||
WARN_DAYS="${WARN_DAYS:-14}"
|
||||
|
||||
# ── State ─────────────────────────────────────────────────────────────
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
readonly SCRIPT_NAME
|
||||
TOTAL_USERS=0
|
||||
SUDO_USERS=0
|
||||
SSH_KEY_USERS=0
|
||||
PASSWORD_WARNINGS=0
|
||||
|
||||
# ── Colors ────────────────────────────────────────────────────────────
|
||||
setup_colors() {
|
||||
if [[ "$COLOR" == "never" ]]; then
|
||||
RED="" GREEN="" YELLOW="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
return
|
||||
fi
|
||||
if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
DIM='\033[2m'
|
||||
RESET='\033[0m'
|
||||
else
|
||||
RED="" GREEN="" YELLOW="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Logging ───────────────────────────────────────────────────────────
|
||||
log() { echo -e "${CYAN}[INFO]${RESET} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
|
||||
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────
|
||||
section_header() {
|
||||
echo ""
|
||||
echo -e " ${BOLD}${CYAN}── $1 ──${RESET}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
field() {
|
||||
printf " ${BOLD}%-22s${RESET} %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
field_color() {
|
||||
printf " ${BOLD}%-22s${RESET} %b\n" "$1" "$2"
|
||||
}
|
||||
|
||||
should_show() {
|
||||
[[ "$SECTIONS" == "all" ]] || [[ ",$SECTIONS," == *",$1,"* ]]
|
||||
}
|
||||
|
||||
is_sudo_user() {
|
||||
local user="$1"
|
||||
local sudo_groups="sudo wheel adm"
|
||||
for grp in $sudo_groups; do
|
||||
if getent group "$grp" 2>/dev/null | grep -qw "$user"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
get_ssh_key_count() {
|
||||
local user="$1"
|
||||
local home_dir="$2"
|
||||
local auth_file="${home_dir}/.ssh/authorized_keys"
|
||||
if [[ -r "$auth_file" ]]; then
|
||||
grep -cE "^(ssh-|ecdsa-|sk-)" "$auth_file" 2>/dev/null || echo "0"
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
get_last_login() {
|
||||
local user="$1"
|
||||
local last_info
|
||||
last_info=$(lastlog -u "$user" 2>/dev/null | tail -1)
|
||||
if echo "$last_info" | grep -q "Never logged in"; then
|
||||
echo "Never"
|
||||
else
|
||||
echo "$last_info" | awk '{print $4, $5, $6, $7, $9}' 2>/dev/null || echo "Unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
get_password_age() {
|
||||
local user="$1"
|
||||
if [[ ! -r /etc/shadow ]]; then
|
||||
echo "no-access"
|
||||
return
|
||||
fi
|
||||
local shadow_entry
|
||||
shadow_entry=$(grep "^${user}:" /etc/shadow 2>/dev/null || true)
|
||||
if [[ -z "$shadow_entry" ]]; then
|
||||
echo "no-entry"
|
||||
return
|
||||
fi
|
||||
local last_change
|
||||
last_change=$(echo "$shadow_entry" | cut -d: -f3)
|
||||
if [[ -z "$last_change" || "$last_change" == "0" ]]; then
|
||||
echo "must-change"
|
||||
return
|
||||
fi
|
||||
local today
|
||||
today=$(( $(date +%s) / 86400 ))
|
||||
local age=$(( today - last_change ))
|
||||
echo "$age"
|
||||
}
|
||||
|
||||
get_password_max_days() {
|
||||
local user="$1"
|
||||
if [[ ! -r /etc/shadow ]]; then
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
local shadow_entry
|
||||
shadow_entry=$(grep "^${user}:" /etc/shadow 2>/dev/null || true)
|
||||
if [[ -z "$shadow_entry" ]]; then
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
local max_days
|
||||
max_days=$(echo "$shadow_entry" | cut -d: -f5)
|
||||
if [[ -z "$max_days" || "$max_days" == "99999" ]]; then
|
||||
echo ""
|
||||
else
|
||||
echo "$max_days"
|
||||
fi
|
||||
}
|
||||
|
||||
password_status_color() {
|
||||
local age="$1"
|
||||
local max_days="$2"
|
||||
|
||||
if [[ "$age" == "no-access" ]]; then
|
||||
echo "${DIM}(shadow unreadable)${RESET}"
|
||||
return
|
||||
fi
|
||||
if [[ "$age" == "no-entry" ]]; then
|
||||
echo "${DIM}N/A${RESET}"
|
||||
return
|
||||
fi
|
||||
if [[ "$age" == "must-change" ]]; then
|
||||
echo "${YELLOW}must change${RESET}"
|
||||
PASSWORD_WARNINGS=$((PASSWORD_WARNINGS + 1))
|
||||
return
|
||||
fi
|
||||
|
||||
local status="${age} days"
|
||||
if [[ -n "$max_days" ]]; then
|
||||
local remaining=$(( max_days - age ))
|
||||
if [[ "$remaining" -le 0 ]]; then
|
||||
status="${RED}EXPIRED (${age}d / ${max_days}d max)${RESET}"
|
||||
PASSWORD_WARNINGS=$((PASSWORD_WARNINGS + 1))
|
||||
elif [[ "$remaining" -le "$WARN_DAYS" ]]; then
|
||||
status="${YELLOW}${age}d (expires in ${remaining}d)${RESET}"
|
||||
PASSWORD_WARNINGS=$((PASSWORD_WARNINGS + 1))
|
||||
else
|
||||
status="${GREEN}${age}d (${remaining}d remaining)${RESET}"
|
||||
fi
|
||||
else
|
||||
status="${GREEN}${age}d (no expiry)${RESET}"
|
||||
fi
|
||||
echo "$status"
|
||||
}
|
||||
|
||||
shell_color() {
|
||||
local shell="$1"
|
||||
case "$shell" in
|
||||
*/nologin|*/false)
|
||||
echo "${RED}${shell}${RESET}" ;;
|
||||
*)
|
||||
echo "${GREEN}${shell}${RESET}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# USER LISTING
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
show_users() {
|
||||
section_header "User Accounts"
|
||||
|
||||
printf " ${BOLD}%-16s %6s %-18s %-14s %s${RESET}\n" "USERNAME" "UID" "SHELL" "LAST LOGIN" "PASSWORD AGE"
|
||||
printf " %s\n" "$(printf '%.0s─' {1..78})"
|
||||
|
||||
while IFS=: read -r username _ uid _ _ home shell; do
|
||||
if [[ "$FILTER" == "human" && "$uid" -lt 1000 && "$username" != "root" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
TOTAL_USERS=$((TOTAL_USERS + 1))
|
||||
|
||||
local last_login
|
||||
last_login=$(get_last_login "$username")
|
||||
if [[ ${#last_login} -gt 14 ]]; then
|
||||
last_login="${last_login:0:14}"
|
||||
fi
|
||||
|
||||
local pw_age pw_max pw_status
|
||||
pw_age=$(get_password_age "$username")
|
||||
pw_max=$(get_password_max_days "$username")
|
||||
pw_status=$(password_status_color "$pw_age" "$pw_max")
|
||||
|
||||
local shell_display
|
||||
shell_display=$(shell_color "$shell")
|
||||
|
||||
printf " %-16s %6s %b %-14s %b\n" \
|
||||
"$username" "$uid" \
|
||||
"$(printf '%-18b' "$shell_display")" \
|
||||
"$last_login" "$pw_status"
|
||||
|
||||
verbose " ${username}: home=${home} shell=${shell}"
|
||||
done < /etc/passwd
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# SUDO ACCESS
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
show_sudo() {
|
||||
section_header "Sudo / Wheel Membership"
|
||||
|
||||
local found=0
|
||||
while IFS=: read -r username _ uid _ _ _ _; do
|
||||
if [[ "$FILTER" == "human" && "$uid" -lt 1000 && "$username" != "root" ]]; then
|
||||
continue
|
||||
fi
|
||||
if is_sudo_user "$username"; then
|
||||
local groups_list
|
||||
groups_list=$(groups "$username" 2>/dev/null | cut -d: -f2 | xargs)
|
||||
printf " ${GREEN}✓${RESET} %-20s %s\n" "$username" "$groups_list"
|
||||
SUDO_USERS=$((SUDO_USERS + 1))
|
||||
found=1
|
||||
fi
|
||||
done < /etc/passwd
|
||||
|
||||
if [[ "$found" -eq 0 ]]; then
|
||||
echo " No sudo/wheel users found"
|
||||
fi
|
||||
|
||||
# Check sudoers.d
|
||||
if [[ -d /etc/sudoers.d ]] && [[ -r /etc/sudoers.d ]]; then
|
||||
local sudoers_files
|
||||
sudoers_files=$(find /etc/sudoers.d -type f ! -name '.*' 2>/dev/null | wc -l)
|
||||
if [[ "$sudoers_files" -gt 0 ]]; then
|
||||
echo ""
|
||||
field "sudoers.d files:" "$sudoers_files"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# SSH KEYS
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
show_ssh() {
|
||||
section_header "SSH Authorized Keys"
|
||||
|
||||
printf " ${BOLD}%-20s %s${RESET}\n" "USERNAME" "KEY COUNT"
|
||||
printf " %s\n" "$(printf '%.0s─' {1..35})"
|
||||
|
||||
local found=0
|
||||
while IFS=: read -r username _ uid _ _ home _; do
|
||||
if [[ "$FILTER" == "human" && "$uid" -lt 1000 && "$username" != "root" ]]; then
|
||||
continue
|
||||
fi
|
||||
local key_count
|
||||
key_count=$(get_ssh_key_count "$username" "$home")
|
||||
if [[ "$key_count" -gt 0 ]]; then
|
||||
printf " %-20s ${GREEN}%s${RESET}\n" "$username" "$key_count"
|
||||
SSH_KEY_USERS=$((SSH_KEY_USERS + 1))
|
||||
found=1
|
||||
fi
|
||||
done < /etc/passwd
|
||||
|
||||
if [[ "$found" -eq 0 ]]; then
|
||||
echo " No SSH authorized keys found"
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# PASSWORD STATUS
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
show_passwords() {
|
||||
section_header "Password Status"
|
||||
|
||||
if [[ ! -r /etc/shadow ]]; then
|
||||
warn "Cannot read /etc/shadow — run as root for password details"
|
||||
return
|
||||
fi
|
||||
|
||||
printf " ${BOLD}%-16s %-12s %-12s %s${RESET}\n" "USERNAME" "LAST CHANGE" "MAX DAYS" "STATUS"
|
||||
printf " %s\n" "$(printf '%.0s─' {1..62})"
|
||||
|
||||
while IFS=: read -r username _ uid _ _ _ _; do
|
||||
if [[ "$FILTER" == "human" && "$uid" -lt 1000 && "$username" != "root" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local shadow_entry
|
||||
shadow_entry=$(grep "^${username}:" /etc/shadow 2>/dev/null || true)
|
||||
if [[ -z "$shadow_entry" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local pw_field last_change max_days
|
||||
pw_field=$(echo "$shadow_entry" | cut -d: -f2)
|
||||
last_change=$(echo "$shadow_entry" | cut -d: -f3)
|
||||
max_days=$(echo "$shadow_entry" | cut -d: -f5)
|
||||
|
||||
# Skip locked/disabled accounts
|
||||
if [[ "$pw_field" == "!" || "$pw_field" == "!!" || "$pw_field" == "*" ]]; then
|
||||
local lock_status="${DIM}locked${RESET}"
|
||||
printf " %-16s %-12s %-12s %b\n" "$username" "-" "-" "$lock_status"
|
||||
continue
|
||||
fi
|
||||
|
||||
local change_date="-"
|
||||
if [[ -n "$last_change" && "$last_change" != "0" ]]; then
|
||||
change_date=$(date -d "1970-01-01 + ${last_change} days" +%Y-%m-%d 2>/dev/null || echo "$last_change")
|
||||
fi
|
||||
|
||||
local max_display="-"
|
||||
if [[ -n "$max_days" && "$max_days" != "99999" ]]; then
|
||||
max_display="${max_days}d"
|
||||
fi
|
||||
|
||||
local pw_age pw_status
|
||||
pw_age=$(get_password_age "$username")
|
||||
pw_status=$(password_status_color "$pw_age" "$(get_password_max_days "$username")")
|
||||
|
||||
printf " %-16s %-12s %-12s %b\n" "$username" "$change_date" "$max_display" "$pw_status"
|
||||
done < /etc/passwd
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# SUMMARY
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print_summary() {
|
||||
echo ""
|
||||
echo -e " ${BOLD}══════════════════════════════════════════${RESET}"
|
||||
echo -e " ${BOLD}User Audit Summary${RESET}"
|
||||
echo -e " ${BOLD}══════════════════════════════════════════${RESET}"
|
||||
|
||||
field "Total users shown:" "$TOTAL_USERS"
|
||||
field_color "Sudo/wheel users:" "${GREEN}${SUDO_USERS}${RESET}"
|
||||
field "Users with SSH keys:" "$SSH_KEY_USERS"
|
||||
|
||||
if [[ "$PASSWORD_WARNINGS" -gt 0 ]]; then
|
||||
field_color "Password warnings:" "${YELLOW}${PASSWORD_WARNINGS}${RESET}"
|
||||
else
|
||||
field_color "Password warnings:" "${GREEN}0${RESET}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# USAGE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
${SCRIPT_NAME} — Audit local user accounts, sudo access, SSH keys, and passwords
|
||||
|
||||
USAGE:
|
||||
${SCRIPT_NAME} [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--human Show only human users (UID >= 1000 + root, default)
|
||||
--system Include system users (all UIDs)
|
||||
--section SECTIONS Comma-separated sections to show (default: all)
|
||||
Available: users, sudo, ssh, passwords
|
||||
--warn-days DAYS Password expiry warning threshold (default: ${WARN_DAYS})
|
||||
--verbose Enable debug output
|
||||
--no-color Disable colored output
|
||||
--help Show this help
|
||||
|
||||
EXAMPLES:
|
||||
# Audit human users (default)
|
||||
./user-audit.sh
|
||||
|
||||
# Include system accounts
|
||||
./user-audit.sh --system
|
||||
|
||||
# Show only sudo and SSH key info
|
||||
./user-audit.sh --section sudo,ssh
|
||||
|
||||
# Full audit with password details (needs root)
|
||||
sudo ./user-audit.sh --section passwords
|
||||
EOF
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# ARGUMENT PARSING
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--human)
|
||||
FILTER="human"; shift ;;
|
||||
--system)
|
||||
FILTER="system"; shift ;;
|
||||
--section)
|
||||
SECTIONS="$2"; shift 2 ;;
|
||||
--warn-days)
|
||||
WARN_DAYS="$2"; shift 2 ;;
|
||||
--verbose)
|
||||
VERBOSE="true"; shift ;;
|
||||
--no-color)
|
||||
COLOR="never"; shift ;;
|
||||
--help|-h)
|
||||
setup_colors
|
||||
usage
|
||||
exit 0 ;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
echo "Run ${SCRIPT_NAME} --help for usage" >&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# MAIN
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
main() {
|
||||
parse_args "$@"
|
||||
setup_colors
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}User Audit — $(hostname -f 2>/dev/null || hostname)${RESET}"
|
||||
echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S %Z')${RESET}"
|
||||
echo -e "Filter: ${FILTER}"
|
||||
|
||||
should_show "users" && show_users
|
||||
should_show "sudo" && show_sudo
|
||||
should_show "ssh" && show_ssh
|
||||
should_show "passwords" && show_passwords
|
||||
|
||||
print_summary
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user