Skip to content

Instantly share code, notes, and snippets.

@jas-
Last active October 8, 2020 03:06
Show Gist options
  • Save jas-/89fa126e02ae0c51b9fe48f472827232 to your computer and use it in GitHub Desktop.
Save jas-/89fa126e02ae0c51b9fe48f472827232 to your computer and use it in GitHub Desktop.
Find potential privilege escalation with defined services. STIG VID's; V-906, V-907, V-910, V-4089, V-4090, V-4091, V-22354, V-22355, V-59827, V-59831, V-59833, V-59835, V-59837, V-59839, V-59841, V-59843
#!/bin/bash
# Handle the following STIG Vulnerability ID's
# Requires bash > v4
# HP-UX: V-906, V-907, V-910, V-4089, V-4090, V-4091, V-22354, V-22355
# OEL: V-906, V-907, V-910, V-4089, V-4090, V-4091, V-22354, V-22355
# RHEL: V-906, V-907, V-910, V-4089, V-4090, V-4091, V-22354, V-22355
# Solaris: V-906, V-907, V-910, V-4089, V-4090, V-4091, V-22354, V-22355, V-59827, V-59831, V-59833, V-59835, V-59837, V-59839, V-59841, V-59843
# Author: Jason Gerfen <jason.gerfen@gmail.com>
# License: MIT
# Define start service(s)
declare -a inits
inits+=("/etc/rc*") # init.d
inits+=("/etc/init*") # xinit.d
inits+=("/lib/svc") # Solaris SMF
inits+=("/usr/lib/systemd/system") # RHL systemd
# Array of allowed owners
declare -a allowed
allowed+=("root")
allowed+=("sys")
allowed+=("bin")
# Minimum octal mode for permissions
perm=00750
# File extraction pattern
pattern="[/\|~\|..][a-z0-9A-Z._-]*"
# Ensure path is robust
PATH=$PATH:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
# Set awk to nawk if Solaris
if [ $(uname -a | grep -c SunOS) -ne 0 ]; then
nawk="$(which nawk 2>/dev/null)"
else
nawk="$(which awk 2>/dev/null)"
fi
# Resolve symlinks to a file
# FIX: Needs to handle recursion for non readlink systems
function get_inode()
{
# Copy ${1} to a local variable
local inode="${1}"
# If ${file} is a link
if [[ -h ${inode} ]] || [[ -L ${inode} ]]; then
# Attempt to follow link where readlink is available
[ $(which readlink | grep -c "^/") -gt 0 ] &&
inode="$(readlink -f ${inode} 2>/dev/null)"
fi
echo "${inode}" && return 0
}
# Pluck *possible* file(s) from a file based on ${pattern}
function extract_filenames()
{
local file="${1}"
local iterations=10 # This will search up to 10 paths deep
local -a results=()
local tpat=
local i=0
# Iterate 0 - ${iterations}
#for i in $(seq 1 ${iterations}); do
while [ ${i} -le ${iterations} ]; do
# Combine ${tpat} with ${pattern} to account for BRE limitations with complex regex's
tpat="${tpat}${pattern}"
# Combine the ${prefix}, ${tpat} & ${suffix} for a complete regex
pat="${prefix}${tpat}${suffix}"
# Extract any patterns matching ${pat} from ${file} while assigning to ${tresults[@]}
results+=($(sed -ne "s|.*\(${pat}\).*|\1|p" ${file} 2> /dev/null))
# Increment ${i}
i=$(( ${i} + 1 ))
done
# Provide the ${results[@]} if > 0
[ ${#results[@]} -gt 0 ] && echo "${results[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '
}
# Strip out dupes from provided array
function remove_duplicates()
{
local -a obj
obj=("${@}")
echo "${obj[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '
}
# Test for actual files & non-binaries
function test_file()
{
# Reassign ${@}
local -a obj="${@}"
local -a results
# Iterate ${obj[@]}
for inode in ${obj[@]}; do
# Filter for valid file(s) & assign to ${results[@]}
[ -f ${inode} ] && results+=(${inode})
done
echo "${results[@]}"
}
# Is ELF or data file?
function is_compiled()
{
echo $(file ${1} | egrep -c 'ELF|data')
}
# Perform search of array
function in_array()
{
local args=("${@}")
local needle="${args[0]}"
local haystack=("${args[@]:1}")
for i in ${haystack[@]}; do
if [[ ${i} == ${needle} ]]; then
echo 0 && return 0
fi
done
echo 1 && return 1
}
# Get current owner
function get_inode_user()
{
local inode="${1}"
echo "$(ls -lad ${inode} | awk '{print $3}')" && return 0
}
# Get current group ownership
function get_inode_group()
{
local inode="${1}"
echo "$(ls -lad ${inode} | awk '{print $4}')" && return 0
}
# Resolve the current permission (non POSIX compat)
function get_octal()
{
local inode="${1}"
# Verify ${inode} exists
if [[ ! -d ${inode} ]] && [[ ! -f ${inode} ]]; then
return 1
fi
echo $(ls -lahd ${inode} | ${nawk} '{k = 0; for (g=2; g>=0; g--) for (p=2; p>=0; p--) {c = substr($1, 10 - (g * 3 + p), 1); if (c ~ /[sS]/) k += g * 02000; else if (c ~ /[tT]/) k += 01000; if (c ~ /[rwxts]/) k += 8^g * 2^p} if (k) printf("%05o", k)}')
return 0
}
# Get list of init scripts to examine
files=( $(find ${inits[@]} -type f -ls | awk '{print $11}') )
# Iterate ${files[@]}, resolve to actual file and pluck possible file(s) for examination
for inode in ${files[@]}; do
# Handle symlinks
inode="$(get_inode ${inode})"
# Skip stripping out possible files from ELF's & data files
if [ $(is_compiled ${inode}) -gt 0 ]; then
tmp_files+=("${inode}")
continue
fi
# Use extract_filenames() to obtain array of binaries from ${inode}
tmp_files+=( $(extract_filenames ${inode}) )
done
# Remove dupes
files=( ${files[@]} $(remove_duplicates "${tmp_files[@]}") )
# Filter for actual files
files=( $(test_file "${files[@]}") )
# Iterate ${files[@]} and create haystack to find
for inode in ${files[@]}; do
# Bail if ${inode} is null
[ "${inode}" == "" ] && continue
# Handle symlinks
inode="$(get_inode ${inode})"
# Bail if ${inode} is null
[ "${inode}" == "" ] && continue
# Get current owner & group
owner="$(get_inode_user ${inode})"
group="$(get_inode_group ${inode})"
perms=$(get_octal ${inode})
# If ${perms}, ${owner} or ${group} of ${inode} doesn't existin in ${allowed[@]} or minimum octal notation flag it
[ $(in_array ${owner} ${allowed[@]}) -ne 0 ] && perms+=("${inode}") || vals+=("${inode}")
[ $(in_array ${group} ${allowed[@]}) -ne 0 ] && perms+=("${inode}") || vals+=("${inode}")
[ ${perms} -gt ${perm} ] && perms+=("${inode}") || vals+=("${inode}")
# Skip creating a haystack from ${inode} if is_binary is < 0
[ $(is_compiled ${inode}) -gt 0 ] && continue
# Extract possible PATH|LD_LIBRARY_PATH|LD_PRELOAD from ${inode} to an array as ${haystack[@]}
haystack=( $(${nawk} '$0 ~ /LD_LIBRARY_PATH|PATH|LD_PRELOAD=/{if ($0 ~ /LD_LIBRARY_PATH|PATH|LD_PRELOAD=/){split($0, obj, "=");if(obj[2] ~ /;/){split(obj[2], fin, ";")}else{fin[2]=obj[2]}print fin[2]}}' ${inode} 2>/dev/null) )
# Skip ${inode} if PATH not found
[ ${#haystack[@]} -eq 0 ] && continue
# Iterate ${haystack[@]}
for haybail in ${haystack[@]}; do
# Examine ${haybail} for invalid path
# TODO: An eval() might be good here to accommodate for possible variables in ${haybail}
chk=$(echo "${haybail}" | egrep -c '^:|::|:$|:[a-zA-Z0-9-_~.]+')
# Add ${inode} to ${errs[@]} array if ${chk} > 0 OR add it to ${vals[@]} array
[ ${chk} -gt 0 ] && errs+=("${inode}") || vals+=("${inode}")
done
done
# Remove dupes
perms=($(remove_duplicates ${perms[@]}))
errs=($(remove_duplicates ${errs[@]}))
vals=($(remove_duplicates ${vals[@]}))
# Print what was examined and what failed
cat <<EOF
Errors (permissions/ownership): ${#perms[@]}/${#files[@]}
${perms[@]}
Errors (LD_LIBRARY_PATH/PATH/LD_PRELOAD): ${#errs[@]}/${#files[@]}
${errs[@]}
Validated: ${#vals[@]}/${#files[@]}
${vals[@]}
EOF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment