Skip to content

Commit

Permalink
Release v0.0.2
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoles committed Aug 6, 2021
1 parent f29dcdd commit 622210a
Showing 1 changed file with 160 additions and 41 deletions.
201 changes: 160 additions & 41 deletions so-check.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
#!/bin/bash
# so-check v0.0.1
# Checks for search order privilege escalation vectors
# in system shared objects and executables in $PATH
# so-check v0.0.2
#
# Related reading:
# https://www.win.tue.nl/~aeb/linux/hh/hh-8.html
# https://www.contextis.com/en/blog/linux-privilege-escalation-via-dynamically-linked-shared-object-library
# https://www.contextis.com/media/images/content/Linux_Privilege_Escalation_via_Dynamically_Linked_Shared_Object_Library02.png
# https://www.exploit-db.com/papers/37606
# Checks for search order privilege escalation vectors in system
# environment, system shared objects and executable files in $PATH.
#
# ~ bcoles 2019
# Related reading:
# - https://www.win.tue.nl/~aeb/linux/hh/hh-8.html
# - https://www.contextis.com/en/blog/linux-privilege-escalation-via-dynamically-linked-shared-object-library
# - https://www.contextis.com/media/images/content/Linux_Privilege_Escalation_via_Dynamically_Linked_Shared_Object_Library02.png
# - https://www.exploit-db.com/papers/37606
# - https://blog.pentesteracademy.com/abusing-missing-library-for-privilege-escalation-3-minute-read-296dcf81bec2
# ---
# https://github.com/bcoles/so-check
# v0.0.1 - 2019-04-25
# v0.0.2 - 2021-08-06
# ~ bcoles

IFS=$'\n\t'
VERBOSE="false"

__script_params=("$@")

readonly _version="0.0.1"
readonly _version="0.0.2"

function info() { echo -e "\\033[1;34m[*]\\033[0m $*"; }
function warn() { echo -e "\\033[1;33m[WARNING]\\033[0m $*"; }
Expand All @@ -28,16 +34,20 @@ function __main__() {
echo

if [ "$(id -u)" -eq 0 ]; then
warn "Running this tool as root does not make much sense (unless you are in a root user namespace) as root user has write permissions for all files."
echo
echo "Running this tool as root does not make sense."
echo
id
exit 1
fi

setup
echo

info "System info:"
echo
echo "User: $(id)"
echo "Kernel: $(uname -a)"
echo "Working directory: $(pwd)"
echo

info "Environment info:"
echo

Expand All @@ -50,23 +60,110 @@ function __main__() {
echo "Library search paths: ${search_paths}"
echo

ld_preload_config="/etc/ld.so.preload"

info "Checking ${ld_preload_config}..."
echo

if [ -w "${ld_preload_config}" ]; then
issue "${ld_preload_config} is writable!"
fi

if [ -r "${ld_preload_config}" ]; then
while read -r line; do
p="${line}"

if [ -w "${p}" ]; then
issue "${p} in ${ld_preload_config} is writable!"
fi
done <<< "$(cat "${ld_preload_config}")"
fi

# TODO: extract and iterate through all "include" directories
# rather than using hard coded /etc/ld.so.conf.d/ directory
ld_so_config="/etc/ld.so.conf"

info "Checking ${ld_so_config}..."
echo

if [ -w "${ld_so_config}" ]; then
issue "${ld_so_config} is writable!"
fi

if [ -r "${ld_so_config}" ]; then
while read -r line; do
p="${line}"

if [[ ! "${p}" =~ ^/ ]]; then
continue
fi

if [ -d "${p}" ] && [ -w "${p}" ]; then
issue "${p} in ${ld_so_config} is writable!"
fi
done <<< "$(cat "${ld_so_config}")"
fi

ld_so_config_dir="/etc/ld.so.conf.d/"

info "Checking ${ld_so_config_dir}*.conf..."
echo

if [ -w "${ld_so_config_dir}" ]; then
issue "${ld_so_config_dir} is writable!"
fi

array=()
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done < <(find "${ld_so_config_dir}" -maxdepth 1 -name "*.conf" -print0 2>/dev/null)

for ((i=0; i<${#array[@]}; i++)); do
f="$(realpath "${array[$i]}")"

verbose "${f}"

if [ -w "${f}" ]; then
issue "${f} is writable!"
fi

while read -r line; do
p="${line}"

if [[ "${p}" =~ ^# ]]; then
continue
fi

if [ -d "${p}" ] && [ -w "${p}" ]; then
issue "${p} directory in ${f} is writable!"
fi
done <<< "$(cat "${f}")"
done

info "Checking library paths..."
echo

if [ ! -z "${LD_LIBRARY_PATH}" ] && [ -d "${LD_LIBRARY_PATH}" ] && [ -w "${LD_LIBRARY_PATH}" ]; then
issue "LD_LIBRARY_PATH $LD_LIBRARY_PATH is writable!"
if [ -n "${LD_LIBRARY_PATH}" ] && [ -d "${LD_LIBRARY_PATH}" ] && [ -w "${LD_LIBRARY_PATH}" ]; then
issue "\$LD_LIBRARY_PATH $LD_LIBRARY_PATH is writable!"
fi

if [ ! -z "${LD_RUN_PATH}" ] && [ -d "${LD_RUN_PATH}" ] && [ -w "${LD_RUN_PATH}" ]; then
issue "LD_RUN_PATH $LD_RUN_PATH is writable!"
if [ -n "${LD_RUN_PATH}" ] && [ -d "${LD_RUN_PATH}" ] && [ -w "${LD_RUN_PATH}" ]; then
issue "\$LD_RUN_PATH $LD_RUN_PATH is writable!"
fi

while read -r line; do
p="${line}"

if [ -z "${p}" ]; then
issue "Library search path contains empty path (working directory)"
continue
fi

if [ -d "${p}" ] && [ -w "${p}" ]; then
issue "${p} directory in library search path is writable!"
fi

analyze_libraries_in_directory "${p}"
done <<< "${search_paths//:/$'\n'}"

info "Checking executable paths..."
Expand All @@ -76,12 +173,12 @@ function __main__() {
p="${line}"

if [ -z "${p}" ]; then
issue "\$PATH contains empty path"
issue "\$PATH contains empty path (working directory)"
continue
fi

if [ "${p}" = "." ]; then
issue "\$PATH contains working directory '.'"
issue "\$PATH contains '.' (working directory)"
continue
fi

Expand All @@ -93,8 +190,7 @@ function __main__() {
info "Searching executables in ${p} ..."
echo

search_path "${p}"
echo
analyze_executables_in_directory "${p}"
done <<< "${PATH//:/$'\n'}"

info "Complete"
Expand All @@ -118,8 +214,7 @@ function setup() {
done

# Optional
#IFS=' ' read -r -a array <<< "objdump ldd readelf"
IFS=' ' read -r -a array <<< "objdump ldd"
IFS=' ' read -r -a array <<< "objdump ldd readelf"
for bin in "${array[@]}"
do
if ! command_exists "${bin}"; then
Expand All @@ -132,7 +227,7 @@ function objdump_rpath() {
path="${1}"

rpath=$(objdump -x "${path}" 2>/dev/null | grep RPATH | sed 's/RPATH\s*//g' | sed -e 's/^[[:space:]]*//')
if [ ! -z "${rpath}" ]; then
if [ -n "${rpath}" ]; then
verbose "${path} - RPATH: ${rpath}"

while read -r line; do
Expand All @@ -143,12 +238,12 @@ function objdump_rpath() {
fi

if [ -z "${p}" ]; then
issue "${path} RPATH contains empty path"
issue "${path} RPATH contains empty path (working directory)"
continue
fi

if [ "${p}" = "." ]; then
issue "${path} RPATH contains working directory '.'"
issue "${path} RPATH contains '.' (working directory)"
continue
fi

Expand All @@ -163,7 +258,7 @@ function objdump_runpath() {
path="${1}"

runpath=$(objdump -x "${path}" 2>/dev/null | grep RUNPATH | sed 's/RUNPATH\s*//g' | sed -e 's/^[[:space:]]*//')
if [ ! -z "${runpath}" ]; then
if [ -n "${runpath}" ]; then
verbose "${path} - RUNPATH: ${runpath}"

while read -r line; do
Expand All @@ -174,12 +269,12 @@ function objdump_runpath() {
fi

if [ -z "${p}" ]; then
issue "${path} RUNPATH contains empty path"
issue "${path} RUNPATH contains empty path (working directory)"
continue
fi

if [ "${p}" = "." ]; then
issue "${path} RUNPATH contains working directory '.'"
issue "${path} RUNPATH contains '.' (working directory)"
continue
fi

Expand All @@ -194,7 +289,7 @@ function ldd_notfound() {
path="${1}"

notfound=$(ldd "${path}" 2>/dev/null | grep "not found")
if [ ! -z "${notfound}" ]; then
if [ -n "${notfound}" ]; then
issue "${path} missing: ${notfound}"
fi
}
Expand All @@ -203,10 +298,11 @@ function ldd_libasan() {
path="${1}"

libasan=$(ldd "${path}" 2>/dev/null | grep "libasan.so")
if [ ! -z "${libasan}" ]; then
verbose "${path} uses libasan.so"
if [ -u "${f}" ]; then
if [ -n "${libasan}" ]; then
if [ -u "${path}" ]; then
issue "${path} is setuid and uses libasan.so !"
else
verbose "${path} uses libasan.so"
fi
fi
}
Expand All @@ -215,12 +311,35 @@ function readelf_interp() {
path="${1}"

interp=$(readelf -l "${path}" 2>/dev/null | grep "interpreter:" | cut -d':' -f2- | sed -e 's/\]$//g' | sed -e 's/^[[:space:]]*//')
if [ ! -z "${interp}" ] && [ -w "${interp}" ]; then
issue "${path} interpreter ${interp} is writable!"
if [ -n "${interp}" ] && [ -w "${interp}" ]; then
if [ -u "${path}" ]; then
issue "${path} is setuid and interpreter ${interp} is writable!"
else
issue "${path} interpreter ${interp} is writable!"
fi
fi
}

function search_path() {
function analyze_libraries_in_directory() {
path="${1}"

array=()
while IFS= read -r -d $'\0'; do
array+=("$REPLY")
done < <(find "${path}" -maxdepth 1 -print0 2>/dev/null)

for ((i=0; i<${#array[@]}; i++)); do
f="$(realpath "${array[$i]}")"

verbose "${f}"

if [ -w "${f}" ]; then
issue "${f} in library search path is writable!"
fi
done
}

function analyze_executables_in_directory() {
path="${1}"

array=()
Expand All @@ -247,10 +366,10 @@ function search_path() {
ldd_libasan "${f}"
fi

#if command_exists readelf; then
# readelf_interp "${f}"
#fi
done <<< "${search_paths//:/$'\n'}"
if command_exists readelf; then
readelf_interp "${f}"
fi
done
}

if [[ "${BASH_SOURCE[0]}" = "$0" ]]; then
Expand Down

0 comments on commit 622210a

Please sign in to comment.