#!/usr/bin/env bash

# Linux Privilege Escalation Vulnerability Auditor
#
# Defensive/local audit only.
#
# Checks:
#   - OS and kernel
#   - pending security updates
#   - sudo / polkit / pkexec
#   - SUID / SGID binaries
#   - dangerous Linux capabilities
#   - writable executables
#   - sudoers configuration
#   - PATH hijacking possibilities
#   - dangerous systemd configurations
#   - containers / virtualization
#   - common privilege-escalation indicators
#
# Exit codes:
#   0 = no obvious critical findings
#   1 = findings detected
#   2 = script error
#
# Run normally:
#   ./linux-privesc-audit.sh
#
# For a more complete filesystem scan:
#   sudo ./linux-privesc-audit.sh

set -u
set -o pipefail

VERSION="1.0"

RED='\033[0;31m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
RESET='\033[0m'

FINDINGS=0
WARNINGS=0

###############################################################################
# Helpers
###############################################################################

section() {
    echo
    echo -e "${BLUE}============================================================${RESET}"
    echo -e "${BLUE}$1${RESET}"
    echo -e "${BLUE}============================================================${RESET}"
}

info() {
    echo -e "${CYAN}[INFO]${RESET} $1"
}

ok() {
    echo -e "${GREEN}[ OK ]${RESET} $1"
}

warn() {
    echo -e "${YELLOW}[WARN]${RESET} $1"
    WARNINGS=$((WARNINGS + 1))
}

crit() {
    echo -e "${RED}[CRIT]${RESET} $1"
    FINDINGS=$((FINDINGS + 1))
}

have() {
    command -v "$1" >/dev/null 2>&1
}

run_root() {
    if [[ $EUID -eq 0 ]]; then
        "$@"
    elif have sudo && sudo -n true >/dev/null 2>&1; then
        sudo "$@"
    else
        return 1
    fi
}

###############################################################################
# Header
###############################################################################

clear 2>/dev/null || true

echo
echo "Linux Privilege Escalation Vulnerability Auditor"
echo "Version: $VERSION"
echo "Host: $(hostname)"
echo "Date: $(date)"
echo

if [[ $EUID -eq 0 ]]; then
    info "Running as root"
else
    info "Running as user: $(id -un)"
    warn "Some checks require root privileges"
fi

###############################################################################
# OS information
###############################################################################

section "Operating system"

if [[ -f /etc/os-release ]]; then
    # shellcheck disable=SC1091
    . /etc/os-release

    echo "Distribution : ${PRETTY_NAME:-unknown}"
    echo "ID           : ${ID:-unknown}"
    echo "Version      : ${VERSION_ID:-unknown}"
else
    warn "/etc/os-release not found"
fi

echo "Kernel       : $(uname -r)"
echo "Architecture : $(uname -m)"

###############################################################################
# Kernel
###############################################################################

section "Kernel security"

KERNEL="$(uname -r)"

echo "Running kernel:"
echo "  $KERNEL"

if [[ -d /proc/sys/kernel ]]; then

    check_sysctl() {
        local name="$1"
        local value

        value="$(sysctl -n "$name" 2>/dev/null || echo unavailable)"

        printf "  %-40s %s\n" "$name" "$value"
    }

    echo
    echo "Important kernel settings:"

    check_sysctl kernel.unprivileged_userns_clone
    check_sysctl kernel.kptr_restrict
    check_sysctl kernel.dmesg_restrict
    check_sysctl fs.protected_symlinks
    check_sysctl fs.protected_hardlinks
fi

# Check reboot requirement
if [[ -f /var/run/reboot-required ]]; then
    crit "System requires a reboot after updates"
fi

###############################################################################
# Debian / Ubuntu package updates
###############################################################################

section "Security updates"

if have apt; then

    info "Checking APT security updates..."

    APT_UPDATES="$(apt list --upgradable 2>/dev/null | tail -n +2 || true)"

    if [[ -n "$APT_UPDATES" ]]; then
        echo "$APT_UPDATES"

        SECURITY_COUNT="$(
            printf '%s\n' "$APT_UPDATES" |
            grep -Ei 'security|ubuntu.*updates|debian-security' |
            wc -l
        )"

        if [[ "$SECURITY_COUNT" -gt 0 ]]; then
            crit "$SECURITY_COUNT security-related package update(s) appear to be available"
        else
            warn "Package updates are available"
        fi
    else
        ok "No APT package updates reported"
    fi

    if have ubuntu-security-status; then
        echo
        info "Ubuntu security status:"
        ubuntu-security-status 2>/dev/null || true
    fi
fi

###############################################################################
# RedHat / Fedora
###############################################################################

if have dnf; then

    info "Checking DNF security updates..."

    SECURITY_UPDATES="$(dnf updateinfo list security 2>/dev/null || true)"

    if [[ -n "$SECURITY_UPDATES" ]]; then
        echo "$SECURITY_UPDATES"
        crit "Security updates are available through DNF"
    else
        ok "No DNF security updates reported"
    fi

elif have yum; then

    info "Checking YUM security updates..."

    SECURITY_UPDATES="$(yum updateinfo list security 2>/dev/null || true)"

    if [[ -n "$SECURITY_UPDATES" ]]; then
        echo "$SECURITY_UPDATES"
        crit "Security updates are available through YUM"
    else
        ok "No YUM security updates reported"
    fi
fi

###############################################################################
# sudo
###############################################################################

section "sudo"

if have sudo; then

    SUDO_VERSION="$(sudo -V 2>/dev/null | head -n 1)"
    echo "$SUDO_VERSION"

    # Extract version
    SUDO_VER="$(
        sudo -V 2>/dev/null |
        head -n 1 |
        grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)+' |
        head -n 1
    )"

    if [[ -n "$SUDO_VER" ]]; then
        echo "Detected sudo version: $SUDO_VER"
    fi

    echo
    info "Checking sudo configuration..."

    if sudo -n -l >/tmp/privesc-sudo.$$ 2>/dev/null; then

        cat /tmp/privesc-sudo.$$ 

        if grep -Eq 'NOPASSWD:|ALL=\(ALL(:ALL)?\)[[:space:]]*ALL|ALL[[:space:]]*=' \
            /tmp/privesc-sudo.$$; then

            crit "Current user appears to have broad sudo privileges"
        fi

    else
        info "Unable to query sudo privileges without authentication"
    fi

    rm -f /tmp/privesc-sudo.$$
else
    warn "sudo is not installed"
fi

###############################################################################
# sudoers
###############################################################################

section "sudoers configuration"

SUDO_FILES=(
    "/etc/sudoers"
    "/etc/sudoers.d"
)

for f in "${SUDO_FILES[@]}"; do
    [[ -e "$f" ]] || continue

    echo
    echo "--- $f ---"

    if [[ -f "$f" ]]; then
        grep -Ev '^[[:space:]]*#|^[[:space:]]*$' "$f" 2>/dev/null || true
    else
        find "$f" -maxdepth 1 -type f -print 2>/dev/null || true
    fi
done

if [[ -d /etc/sudoers.d ]]; then

    while IFS= read -r file; do

        if [[ -w "$file" ]]; then
            crit "Writable sudoers file: $file"
        fi

    done < <(find /etc/sudoers.d -type f 2>/dev/null)

fi

###############################################################################
# polkit
###############################################################################

section "polkit / pkexec"

if have pkexec; then

    PKEXEC_PATH="$(command -v pkexec)"
    PKEXEC_VERSION="$(pkexec --version 2>/dev/null || true)"

    echo "pkexec: $PKEXEC_PATH"
    echo "$PKEXEC_VERSION"

    PKEXEC_REAL="$(readlink -f "$PKEXEC_PATH" 2>/dev/null || echo "$PKEXEC_PATH")"

    if [[ -u "$PKEXEC_REAL" ]]; then
        echo "SUID: yes"
    else
        warn "pkexec is not SUID"
    fi

else
    ok "pkexec not installed"
fi

if have pkaction; then
    echo
    echo "polkit version:"
    pkaction --version 2>/dev/null || true
fi

###############################################################################
# SUID / SGID
###############################################################################

section "SUID / SGID binaries"

if [[ $EUID -eq 0 ]]; then

    SUID_LIST="$(
        find / \
            -xdev \
            -type f \
            \( -perm -4000 -o -perm -2000 \) \
            -printf '%m %u %g %p\n' \
            2>/dev/null |
        sort
    )"

else

    SUID_LIST="$(
        find / \
            -xdev \
            -type f \
            \( -perm -4000 -o -perm -2000 \) \
            -printf '%m %u %g %p\n' \
            2>/dev/null |
        sort
    )"
fi

echo "$SUID_LIST"

echo
info "Checking unusual SUID/SGID binaries..."

while read -r perms owner group path; do
    [[ -n "$path" ]] || continue

    case "$path" in
        /usr/bin/passwd|\
        /usr/bin/chsh|\
        /usr/bin/chfn|\
        /usr/bin/gpasswd|\
        /usr/bin/su|\
        /usr/bin/mount|\
        /usr/bin/umount|\
        /usr/bin/newgrp|\
        /usr/bin/sudo|\
        /usr/bin/pkexec|\
        /usr/bin/ssh-keysign|\
        /usr/lib/dbus-1.0/dbus-daemon-launch-helper)
            ;;
        *)
            warn "Unusual SUID/SGID binary: $path"
            ;;
    esac

done <<< "$SUID_LIST"

###############################################################################
# Linux capabilities
###############################################################################

section "Linux file capabilities"

if have getcap; then

    CAPS="$(getcap -r / 2>/dev/null || true)"

    if [[ -n "$CAPS" ]]; then
        echo "$CAPS"

        while IFS= read -r line; do

            if echo "$line" | grep -Eq \
                'cap_(setuid|setgid|dac_override|dac_read_search|sys_admin|sys_ptrace|sys_module|chown|fowner|fsetid)'; then

                crit "Potentially dangerous file capability: $line"
            fi

        done <<< "$CAPS"

    else
        ok "No file capabilities detected"
    fi

else
    warn "getcap not installed"
fi

###############################################################################
# Writable executables
###############################################################################

section "Writable privileged executables"

SEARCH_PATHS=(
    /usr/bin
    /usr/sbin
    /bin
    /sbin
    /usr/local/bin
    /usr/local/sbin
)

for dir in "${SEARCH_PATHS[@]}"; do

    [[ -d "$dir" ]] || continue

    find "$dir" -xdev -type f -perm -0002 2>/dev/null |
    while IFS= read -r file; do
        warn "World-writable executable: $file"
    done

done

###############################################################################
# Dangerous writable directories
###############################################################################

section "Privileged PATH / directory permissions"

IMPORTANT_DIRS=(
    /usr/bin
    /usr/sbin
    /bin
    /sbin
    /etc
    /etc/sudoers.d
)

for dir in "${IMPORTANT_DIRS[@]}"; do

    [[ -d "$dir" ]] || continue

    MODE="$(stat -c '%a %U:%G' "$dir" 2>/dev/null || true)"

    echo "$dir -> $MODE"

    if [[ -w "$dir" ]]; then
        crit "Current user can write privileged directory: $dir"
    fi

done

###############################################################################
# PATH hijacking
###############################################################################

section "PATH hijacking checks"

echo "PATH:"
echo "$PATH"

IFS=':' read -ra PATH_ENTRIES <<< "$PATH"

for p in "${PATH_ENTRIES[@]}"; do

    [[ -z "$p" ]] && continue

    if [[ "$p" == "." ]]; then
        crit "Current PATH contains '.'"
    fi

    if [[ -d "$p" && -w "$p" ]]; then
        crit "Current user can write PATH directory: $p"
    fi

done

###############################################################################
# Cron
###############################################################################

section "Cron / scheduled jobs"

CRON_DIRS=(
    /etc/cron.d
    /etc/cron.daily
    /etc/cron.hourly
    /etc/cron.weekly
    /etc/cron.monthly
)

for dir in "${CRON_DIRS[@]}"; do

    [[ -d "$dir" ]] || continue

    while IFS= read -r file; do

        if [[ -w "$file" ]]; then
            crit "Writable cron file: $file"
        fi

        echo "$file"

    done < <(find "$dir" -type f 2>/dev/null)

done

if [[ -f /etc/crontab ]]; then

    if [[ -w /etc/crontab ]]; then
        crit "/etc/crontab is writable"
    fi

    echo
    cat /etc/crontab
fi

###############################################################################
# Systemd
###############################################################################

section "systemd privilege escalation checks"

if have systemctl; then

    SYSTEMD_UNITS="$(systemctl list-unit-files --type=service --no-pager --no-legend 2>/dev/null || true)"

    echo "$SYSTEMD_UNITS" | head -n 100

    echo
    info "Checking writable unit files..."

    while read -r unit state rest; do

        [[ -n "$unit" ]] || continue

        UNIT_FILE="$(systemctl show "$unit" -p FragmentPath --value 2>/dev/null || true)"

        [[ -n "$UNIT_FILE" ]] || continue
        [[ -f "$UNIT_FILE" ]] || continue

        if [[ -w "$UNIT_FILE" ]]; then
            crit "Writable systemd unit: $UNIT_FILE"
        fi

    done <<< "$SYSTEMD_UNITS"

fi

###############################################################################
# Docker / containers
###############################################################################

section "Container / virtualization checks"

if have docker; then
    if groups 2>/dev/null | grep -qw docker; then
        crit "Current user is in the docker group"
    fi
fi

if have podman; then
    echo "Podman detected"
fi

if [[ -f /.dockerenv ]]; then
    warn "Running inside a Docker container"
fi

if [[ -f /run/.containerenv ]]; then
    warn "Running inside a container"
fi

if have systemd-detect-virt; then
    echo "Virtualization: $(systemd-detect-virt 2>/dev/null || echo none)"
fi

###############################################################################
# User information
###############################################################################

section "User / group privileges"

echo "Current user:"
id

echo
echo "Groups:"
groups

echo
echo "UID:"
id -u

if [[ "$(id -u)" -eq 0 ]]; then
    crit "Current shell already has UID 0"
fi

###############################################################################
# Dangerous groups
###############################################################################

section "Potentially dangerous group memberships"

DANGEROUS_GROUPS=(
    docker
    lxd
    disk
    adm
    shadow
    sudo
    wheel
    root
    video
)

CURRENT_GROUPS="$(id -nG 2>/dev/null || true)"

for group in "${DANGEROUS_GROUPS[@]}"; do

    if echo "$CURRENT_GROUPS" | tr ' ' '\n' | grep -qx "$group"; then
        case "$group" in
            docker|lxd|disk|shadow)
                crit "Current user belongs to privileged group: $group"
                ;;
            *)
                warn "Current user belongs to privileged group: $group"
                ;;
        esac
    fi

done

###############################################################################
# Interesting environment variables
###############################################################################

section "Environment checks"

for var in LD_PRELOAD LD_LIBRARY_PATH PYTHONPATH PERL5OPT RUBYOPT NODE_OPTIONS; do

    if [[ -n "${!var:-}" ]]; then
        warn "$var is set: ${!var}"
    fi

done

###############################################################################
# NFS / shared filesystems
###############################################################################

section "Filesystem checks"

if have findmnt; then

    findmnt -t nfs,nfs4,cifs,fuse.sshfs 2>/dev/null || true

fi

echo
info "World-writable directories near system paths:"

find /etc /usr /var \
    -xdev \
    -type d \
    -perm -0002 \
    2>/dev/null |
head -n 100

###############################################################################
# Kernel modules
###############################################################################

section "Loaded kernel modules"

if have lsmod; then
    lsmod | head -n 80
fi

###############################################################################
# Security tools
###############################################################################

section "Available security auditing tools"

TOOLS=(
    lynis
    debsecan
    ubuntu-security-status
    needrestart
    rkhunter
    chkrootkit
    auditctl
    ausearch
)

for tool in "${TOOLS[@]}"; do

    if have "$tool"; then
        echo "FOUND: $tool -> $(command -v "$tool")"
    else
        echo "      $tool -> not installed"
    fi

done

###############################################################################
# Recent security updates timestamp
###############################################################################

section "Recent package activity"

if [[ -d /var/log/apt ]]; then

    echo "Recent APT history:"
    find /var/log/apt -type f -maxdepth 1 2>/dev/null |
    sort -r |
    head -n 5

fi

if [[ -d /var/log ]]; then

    find /var/log \
        -maxdepth 2 \
        -type f \
        \( -name '*security*' -o -name '*audit*' \) \
        2>/dev/null |
    head -n 50

fi

###############################################################################
# Ubuntu specific information
###############################################################################

if [[ "${ID:-}" == "ubuntu" ]]; then

    section "Ubuntu-specific security information"

    if have ubuntu-security-status; then
        ubuntu-security-status 2>/dev/null || true
    fi

    echo
    echo "Ubuntu security notices:"
    echo "https://ubuntu.com/security/notices"

fi

###############################################################################
# Summary
###############################################################################

section "Audit summary"

echo
echo "Critical findings : $FINDINGS"
echo "Warnings          : $WARNINGS"

echo

if [[ "$FINDINGS" -gt 0 ]]; then

    echo -e "${RED}Potential privilege-escalation issues were detected.${RESET}"
    echo
    echo "Prioritize:"
    echo "  1. Missing security updates"
    echo "  2. Outdated kernel"
    echo "  3. sudo / polkit vulnerabilities"
    echo "  4. Writable SUID/SGID executables"
    echo "  5. Dangerous file capabilities"
    echo "  6. Writable systemd / cron configuration"
    echo "  7. docker/lxd/disk/shadow group membership"
    echo
    exit 1

elif [[ "$WARNINGS" -gt 0 ]]; then

    echo -e "${YELLOW}No obvious critical issue was detected, but warnings require review.${RESET}"
    exit 0

else

    echo -e "${GREEN}No obvious privilege-escalation issues detected.${RESET}"
    exit 0

fi
