From aaecb3e72811d36e4405b88cd901b02d0e11c133 Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Sat, 30 Aug 2025 18:41:38 +0000 Subject: [PATCH] Add linpeas privilege escalation checks from: HTB Eureka: From Actuator HeapDump to SSH, credential capture via Gateway, and r --- linPEAS/README.md | 4 + .../15_Bash_arithmetic_untrusted_logs.sh | 215 ++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 linPEAS/builder/linpeas_parts/4_procs_crons_timers_srvcs_sockets/15_Bash_arithmetic_untrusted_logs.sh diff --git a/linPEAS/README.md b/linPEAS/README.md index 620608d..ad5f94c 100755 --- a/linPEAS/README.md +++ b/linPEAS/README.md @@ -22,6 +22,10 @@ Check how to **select the checks you want to build [in your own linpeas followin Note that by default, in the releases pages of this repository, you will find a **linpeas with all the checks**. +### New in Aug 2025 + +- Added heuristic detection for Bash arithmetic injection in root-run periodic parsers (cron/timers). LinPEAS now inspects root cron entries and systemd timers to find shell scripts that parse logs and use arithmetic contexts like (( ... )), let, or declare -i with untrusted variables. This may reveal dangerous patterns where attacker-controlled log lines can trigger command substitution inside arithmetic evaluation. + ## Differences between `linpeas_fat.sh`, `linpeas.sh` and `linpeas_small.sh`: - **linpeas_fat.sh**: Contains all checks, even third party applications in base64 embedded. diff --git a/linPEAS/builder/linpeas_parts/4_procs_crons_timers_srvcs_sockets/15_Bash_arithmetic_untrusted_logs.sh b/linPEAS/builder/linpeas_parts/4_procs_crons_timers_srvcs_sockets/15_Bash_arithmetic_untrusted_logs.sh new file mode 100644 index 0000000..4009f79 --- /dev/null +++ b/linPEAS/builder/linpeas_parts/4_procs_crons_timers_srvcs_sockets/15_Bash_arithmetic_untrusted_logs.sh @@ -0,0 +1,215 @@ +# Title: Processes & Cron & Services & Timers - Bash arithmetic on untrusted logs +# ID: PR_Bash_arithmetic_untrusted_logs +# Author: HT Bot +# Last Update: 2025-08-30 +# Description: Heuristic detection of root-run periodic scripts (cron/timers) that perform Bash arithmetic on variables sourced from logs or user-controlled files. Flags common patterns that enable command substitution inside arithmetic ((...)), let, or declare -i when parsing log lines/arguments. +# License: GNU GPL +# Version: 1.0 +# Functions Used: print_2title, print_3title, print_info +# Global Variables: $SEARCH_IN_FOLDER +# Initial Functions: +# Generated Global Variables: $cron_file, $line, $tok, $script, $args, $arg, $timer_unit, $service_unit, $exec_line, $user_field, $cand, $match_lines, $severity, $msg, $file, $sev_and_lines, $candidates_tmp +# Fat linpeas: 0 +# Small linpeas: 1 + +if ! [ "$SEARCH_IN_FOLDER" ]; then + print_2title "Potential Bash arithmetic injection in root-run parsers (cron/timers)" + print_info "https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#scheduledcron-jobs" + + # Small helpers ----------------------------------------------------------- + uniq_print_cand() { + # de-duplicate candidate scripts + # usage: uniq_print_cand + cand="$1" + [ -z "$cand" ] && return + [ ! -f "$cand" ] && return + # only consider text-ish files to keep it fast + if grep -Iq . "$cand" 2>/dev/null; then + printf '%s\n' "$cand" + fi + } + + is_shell_script() { + # returns 0 if file looks like a shell script (shebang or .sh), else 1 + [ ! -f "$1" ] && return 1 + head -n1 "$1" 2>/dev/null | grep -Eq '(/bash|/sh|/dash|/zsh|/ksh)' && return 0 + printf '%s' "$1" | grep -qE '\\.sh(\\.|$)' && return 0 + return 1 + } + + find_arith_patterns() { + # echo severity and matched lines for arithmetic patterns + # severity: STRONG if command substitution inside arithmetic, WEAK if variable arithmetic + file="$1" + # Strong: command substitution inside arithmetic context + if grep -nE '\\(\\(.*\\$\\(.*\\).*\\)\\)|\\blet\\b[^#]*\\$\\(.*\\)|declare[[:space:]]+-i[[:space:]]+[A-Za-z_][A-Za-z0-9_]*=[^#]*\\$\\(.*\\)' "$file" 2>/dev/null | head -n 3 | sed 's/^/ /' ; then + severity="STRONG" + match_lines=$(grep -nE '\\(\\(.*\\$\\(.*\\).*\\)\\)|\\blet\\b[^#]*\\$\\(.*\\)|declare[[:space:]]+-i[[:space:]]+[A-Za-z_][A-Za-z0-9_]*=[^#]*\\$\\(.*\\)' "$file" 2>/dev/null | head -n 3) + printf 'STRONG\n'; printf '%s\n' "$match_lines" + return 0 + fi + # Weak: arithmetic with unquoted variables that could be attacker-controlled + if grep -nE '\\(\\([^#]*\\$[A-Za-z_][A-Za-z0-9_]*[^#]*\\)\\)|\\blet\\b[^#]*\\$[A-Za-z_][A-Za-z0-9_]*|declare[[:space:]]+-i[[:space:]]+[A-Za-z_][A-Za-z0-9_]*=[^#]*\\$[A-Za-z_][A-Za-z0-9_]*' "$file" 2>/dev/null | head -n 3 | sed 's/^/ /' ; then + severity="WEAK" + match_lines=$(grep -nE '\\(\\([^#]*\\$[A-Za-z_][A-Za-z0-9_]*[^#]*\\)\\)|\\blet\\b[^#]*\\$[A-Za-z_][A-Za-z0-9_]*|declare[[:space:]]+-i[[:space:]]+[A-Za-z_][A-Za-z0-9_]*=[^#]*\\$[A-Za-z_][A-Za-z0-9_]*' "$file" 2>/dev/null | head -n 3) + printf 'WEAK\n'; printf '%s\n' "$match_lines" + return 0 + fi + return 1 + } + + looks_like_log_parsing() { + # heuristics: script references logs/WWW logs/tmp or uses variables LOG/FILE in pipelines/process substitution + file="$1" + # explicit log-like paths + if grep -qE '/var/log|/var/www/.*/log|/tmp/' "$file" 2>/dev/null; then + return 0 + fi + # log-ish variables used in pipelines or redirections + if grep -qE '(LOG_FILE|LOG|FILE)=' "$file" 2>/dev/null && \ + grep -qE '(grep|awk|sed|cut|tail|head)[^\n]*\\$[A-Za-z_][A-Za-z0-9_]*' "$file" 2>/dev/null; then + return 0 + fi + # process substitution with grep/cat reading from variable + grep -qE '(<\\(|<\\s*[^<])' "$file" 2>/dev/null && \ + grep -qE 'grep[^\n]*\\$[A-Za-z_][A-Za-z0-9_]*' "$file" 2>/dev/null && return 0 + return 1 + } + + print_writable_arg_info() { + # Given an argument path, print if current user can write the file or its parent dir + arg="$1" + [ -z "$arg" ] && return + case "$arg" in + /*) + if [ -e "$arg" ]; then + if [ -w "$arg" ]; then + echo " Writable argument file: $arg"; ls -l "$arg" 2>/dev/null | sed 's/^/ /' + fi + else + d=$(dirname -- "$arg") + if [ -d "$d" ] && [ -w "$d" ]; then + echo " Parent dir writable: $d (arg $arg does not exist)" + ls -ld "$d" 2>/dev/null | sed 's/^/ /' + fi + fi + ;; + esac + } + + # Collect candidate scripts from cron ------------------------------------------------ + candidates_tmp=$(mktemp 2>/dev/null || echo "/tmp/.lp.cand.$$") + : > "$candidates_tmp" + + # Root crontabs and system cron files + for cron_file in /etc/crontab /var/spool/cron/crontabs/root /etc/cron.d/*; do + [ -r "$cron_file" ] || continue + # Iterate non-comment lines + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + \#*|"") continue ;; + esac + # Extract absolute paths from the line + # First token that is an absolute path is likely the script/binary + script="" + args="" + # Get all absolute-like tokens + for tok in $(printf '%s\n' "$line" | grep -oE '/[^[:space:]]+'); do + if [ -z "$script" ]; then + script="$tok" + else + args="$args $tok" + fi + done + if [ -n "$script" ]; then + uniq_print_cand "$script" >> "$candidates_tmp" + # If script is run by root (likely in these files), show writable argument hints now + if [ -n "$args" ]; then + echo "Root cron entry: $script$args" + for arg in $args; do + print_writable_arg_info "$arg" + done + fi + fi + done < "$cron_file" + done + + # run-parts style cron directories (executed as root by system crond/anacron) + for d in /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly; do + [ -d "$d" ] || continue + for f in "$d"/*; do + [ -f "$f" ] || continue + uniq_print_cand "$f" >> "$candidates_tmp" + done + done + + # Collect candidate scripts from systemd timers -------------------------------------- + if command -v systemctl >/dev/null 2>&1; then + systemctl list-timers --all 2>/dev/null | awk 'NR>1 {print $1}' | grep -E '\\.timer$' | while read -r timer_unit; do + [ -z "$timer_unit" ] && continue + service_unit=$(systemctl show "$timer_unit" -p Unit 2>/dev/null | cut -d= -f2) + [ -z "$service_unit" ] && continue + user_field=$(systemctl show "$service_unit" -p User 2>/dev/null | cut -d= -f2) + # Default user for services without User= is root; include both empty and root + if [ -z "$user_field" ] || [ "$user_field" = "root" ]; then + exec_line=$(systemctl show "$service_unit" -p ExecStart 2>/dev/null | cut -d= -f2) + # Extract first absolute path as executable/script and any absolute path args + script="" + args="" + for tok in $(printf '%s\n' "$exec_line" | grep -oE '/[^[:space:]]+'); do + if [ -z "$script" ]; then + script="$tok" + else + args="$args $tok" + fi + done + if [ -n "$script" ]; then + uniq_print_cand "$script" >> "$candidates_tmp" + if [ -n "$args" ]; then + echo "Root timer entry: $script$args" + for arg in $args; do + print_writable_arg_info "$arg" + done + fi + fi + fi + done + fi + + # Evaluate candidates --------------------------------------------------------------- + if [ -s "$candidates_tmp" ]; then + print_3title "Reviewing root-run scripts for arithmetic on untrusted input" + sort -u "$candidates_tmp" | while read -r script; do + [ -z "$script" ] && continue + [ -r "$script" ] || continue + is_shell_script "$script" || continue + + sev_and_lines=$(find_arith_patterns "$script") + if [ $? -eq 0 ]; then + severity=$(printf '%s' "$sev_and_lines" | head -n1) + match_lines=$(printf '%s' "$sev_and_lines" | tail -n +2) + if looks_like_log_parsing "$script"; then + echo "[!] $severity risk: arithmetic evaluation with variables in $script" + printf '%s\n' "$match_lines" | sed 's/^/ /' + # Bonus: try to spot obvious log file variables + if grep -qE 'LOG_FILE|LOG_PATH|LOG' "$script" 2>/dev/null; then + msg=$(grep -nE 'LOG_FILE|LOG_PATH|LOG' "$script" 2>/dev/null | head -n 2) + printf '%s\n' "$msg" | sed 's/^/ hint: /' + fi + fi + fi + done + fi + + rm -f "$candidates_tmp" 2>/dev/null + echo "" +else + # Folder analysis mode: just list potential log-parsing shell scripts under the target folder + print_2title "Potential Bash arithmetic/log-parsing scripts in folder" + find "$SEARCH_IN_FOLDER" -type f -name "*.sh" -maxdepth 6 2>/dev/null \ + -exec sh -c 'head -n1 "$1" 2>/dev/null | grep -Eq "(/bash|/sh|/dash|/zsh|/ksh)" || exit 1' _ {} \; \ + -exec grep -Ilq . {} \; \ + -exec grep -qE "\\(\\(|\\blet\\b|declare[[:space:]]+-i" {} \; \ + -print + echo "" +fi