Malware in 82 NPM Packages targets hospital billing system

By Paul McCarty

Malware in 82 NPM Packages targets hospital billing system

UPDATE October 24, 2025 AEST:

The Safety research team worked with NPM to remove all of the malicious packages from this user, and NPM removed the publishers account as well.

"DHSollutionsBot" Malware

On October 22, 2025 AEST, The Safety research team's automated malicious package detection engine has identified a threat campaign targeting Asian banks with a new JavaScript RAT and C2 that we have dubbed "DHSollutionsBot". The name is based on a AI generated profile image the threat actor created for their NPM user profile. The threat actor's NPM account is named "ongtrieuhau861.001" and they use the full name "Ông Triệu Hậu".

__wf_reserved_inherit

In total the ongtrieuhau861.001 user has 94 active NPM packages in the registry. Many, but not all of those packages are malicious. When the Safety research team looked at all 94 packages from this user, we realized there are two distinct campaigns phases. Let's drill into the most recent one.

Coordinated attack on Vietnamese hospitals from May to October 2025

The bulk of these packages where published between May and July of 2025, but some of them are more than 10 months old. This threat actor created their NPM account in 2019, or alternatly, it's possible that this account was sold or stolen by the threat actor. These 82 packages are part of a coordinated campaign targeting Vietnamese a hospital management and billing system.

The most recent campaign included 82 packages in total, with versions of the NPM packages mostly use the naming construct dhhdbankxxxxx, dhpgemrdhsxxxxx, ldhemrdhsxxxxx, and ldhpgemrdhsxxxxx, where the "x" are numbers. So for example dhhdbank83600. All of the packages named in this way appear to be malicious in the same way, while many of the other, older packages are malicious, but use different, simpler payloads.

This NPM attack represents a particularly gnarly example of supply chain malware, targeting Asian banks while operating as a full-featured Remote Access Trojan. The attack architecture is elegant in its simplicity—leveraging Firebase Realtime Database as a command and control server while exfiltrating stolen data through Discord webhooks. This dual-channel approach provides both resilience and stealth, as both services are legitimate platforms that rarely trigger security alerts.

Important information is found in the package.json file

While its common for threat actors to use the install scripts in package.json, it's less common to use the manifest file for storing important variables. However, with this malware, you can see that the threat actor has defined the "databaseUrl" key with a value of the Firebase URL "o23863162-02-default-rtdb.asia-southeast1[.]firebasedatabase[.]app". They have also defined a "discordUrl" key with the value "discord[.]com/api/webhooks/1396760653687029901/"

__wf_reserved_inherit

One of the other things the threat actor does is set what is effectively an alias that sets the command dhhdbank83600 to the pm2.js file. The pm2.js file in turn runs pm2.updater.js.

pm2.updater.js: Gathering sensitive data

The purpose of the pm2.updater.js file is to gather data about the compromised host. To do this it needs to know where its C2 and exfiltration endpoints are. In a relatively unusual twist, the threat actor sources those values from the package.json file:

__wf_reserved_inherit

I've seen this before when threat actors want to sneak past malicious package detection by putting important components like exfiltration URLs or C2 servers in markdown or other files, as they aren't analyzed in the same way.  However, it means that these values are in plain site in the package manifest file.

How it starts

The payload's infection chain begins innocuously through npm installation, immediately establishing persistence through multiple redundant mechanisms. The malware identifies if its on a Linux host, and if so, installs itself as a PM2-managed process, ensuring automatic restart on failure or system reboot.

For Windows systems, it goes further by deploying a Windows service configuration via winsw.xml, embedding itself deep into the operating system's service layer:

__wf_reserved_inherit

Installing the Windows Service Wrapper

How it does this is sneaky. The package.json calls a dependency named "dhwinsw" which is another malicious package published by the threat actor. The dhwinsw package downloads and installs the legitimate tool "winsw" from GitHub. Winsw is an open source project that wraps applications and manages them as a windows service.  After winsw is installed via the dependency, a number of node actions are executed:

__wf_reserved_inherit

Using pm2 for redundancy is smart, but adding the windows service wrapper is additional noise and complexity but it means the threat actors can standardize on using pm2 for peristence regardless of whether the package is installed on a Windows or Linux host.

Gathering data about the compromised host

The malware harvests information about the system it has compromised. Upon execution, the malware immediately collects the victim's public IP address through api.ipify.org, then enumerates all local network interfaces, hostnames, and system architecture details. It gathers CPU core counts, memory usage statistics, process uptime, and working directory paths—essentially creating a complete profile of the compromised system.

__wf_reserved_inherit

It then aggregates all the compromised data and creates a massive array named "embed":

__wf_reserved_inherit

Discord exfiltration

Discord has quickly become the preferred method to exfiltrate data, and this attack uses a Discord webhook with the username "svr-dh-badt".  It uses the large JSON payload created in the embed action above to exfil details about the compromised host. This reconnaissance data is formatted into a detailed report and immediately transmitted to the attacker's Discord webhook, providing real-time notification of each successful compromise.

__wf_reserved_inherit

Remote Access Trojan

The main payload, housed in an obfuscated 1.9MB index.js file, contains the core RAT functionality. Buried within the minified code are database connection handlers and Socket.IO implementations that establish bidirectional communication channels with the command and control infrastructure. This allows the attacker to send arbitrary commands to compromised systems and receive responses in near real-time. The use of child_process.exec provides the capability to execute system-level commands, effectively granting the attacker shell access to the victim's machine.

At the heart of the malware's operational capability is its update mechanism, which polls the Firebase database every 60 seconds for new commands or payload updates. When an update is detected, the malware downloads a .tgz archive from a remote source, extracts it, and systematically overwrites its own files with the new version. This self-modifying behavior allows the attacker to deploy new capabilities, evade detection signatures, or pivot attack strategies without requiring any user interaction. The malware also checks the npm registry every five minutes for version updates, creating a secondary update channel that blends seamlessly with legitimate npm traffic.

__wf_reserved_inherit

What makes this malware particularly dangerous is its operational security posture. By using Firebase and Discord—both legitimate, widely-trusted services—the malware's network traffic appears benign to most security monitoring systems. The Firebase database URL (o23863162-02-default-rtdb.asia-southeast1.firebasedatabase.app) serves as the primary command channel, while the Discord webhook provides a convenient, API-based method for data exfiltration that requires no custom infrastructure. The attacker can simply monitor a Discord channel to receive system information from every compromised machine.

The malware's file operations demonstrate sophistication in maintaining operational integrity. It implements extensive logging infrastructure to track its own activities, likely for debugging purposes during development. The automatic binary download and code replacement mechanisms allow for modular payload delivery—the attacker can push new malicious components or remove forensic evidence dynamically. The PM2 process management integration ensures that even if the malware crashes or is manually terminated, it will automatically restart within moments.

From a threat intelligence perspective, this package exhibits characteristics of a mature, well-engineered attack platform rather than a proof-of-concept. The multiple persistence mechanisms, dual communication channels, self-update capabilities, and comprehensive system profiling suggest this is designed for long-term persistent access rather than quick data theft. Any system that has executed this package should be considered fully compromised, with the attacker potentially having complete administrative access to all system resources and data.

Indicators of Compromise (IOCs)

Based on my research this threat campaign has several IOCs you can look for:

NPM Packages - all versions:

dhchukyso dhemrdhs51214 dhemrdhs60015 dhemrdhs60152 dhemrdhs60214 dhemrdhs79029 dhemrdhs83600 dhemrdhs84006 dhemrdhs92004 dhemrdhs92006 dhemrdhs92007 dhemrdhs920072 dhemrdhs92009 dhemrdhs92010 dhemrdhs92011 dhemrdhs92092 dhemrdhs94006 dhemrdhs94010 dhemrdhs95005 dhemrdhs95006 dhhdbank83600 dhhdbank87015 dhhdbank92013 dhhdbank92015 dhhdbank92086 dhkioshdbank dhnas dhpgemrdhs51214 dhpgemrdhs60015 dhpgemrdhs60152 dhpgemrdhs60214 dhpgemrdhs79029 dhpgemrdhs83600 dhpgemrdhs84006 dhpgemrdhs92004 dhpgemrdhs92006 dhpgemrdhs92007 dhpgemrdhs920072 dhpgemrdhs92009 dhpgemrdhs92010 dhpgemrdhs92011 dhpgemrdhs92092 dhpgemrdhs94006 dhpgemrdhs94010 dhpgemrdhs95005 dhpgemrdhs95006 dhpm2 dhqr92004 dhqr92010 dhrunner dhrunner00 dhsvrkioshdbank dhwinsw env-app ldhchukyso ldhemrdhs51214 ldhemrdhs60015 ldhemrdhs60152 ldhemrdhs60214 ldhemrdhs79029 ldhemrdhs83600 ldhemrdhs84006 ldhemrdhs92004 ldhemrdhs92006 ldhemrdhs92007 ldhemrdhs92009 ldhemrdhs92010 ldhemrdhs92011 ldhemrdhs92092 ldhemrdhs94006 ldhemrdhs94010 ldhemrdhs95005 ldhemrdhs95006 ldhpgemrdhs51214 ldhpgemrdhs60015 ldhpgemrdhs60152 ldhpgemrdhs60214 ldhpgemrdhs79029 ldhpgemrdhs83600 ldhpgemrdhs84006 ldhpgemrdhs92004 ldhpgemrdhs92006 ldhpgemrdhs92007 ldhpgemrdhs92009 ldhpgemrdhs92010 ldhpgemrdhs92011 ldhpgemrdhs92092 ldhpgemrdhs94006 ldhpgemrdhs94010 ldhpgemrdhs95005 ldhpgemrdhs95006 npm-cli-00 o-pgdump ongtrieuhau861.001

URL/Domains:

mabvbh[.]dangkykol[.]dh-his[.]com:8443

o23863162-02-default-rtdb[.]asia-southeast1[.]firebasedatabase[.]app/dhhdbank83600-updater

discord[.]com/api/webhooks/1396760653687029901/QH0qkmS3cNXmztClLUhNqWQLvzAS_oCDDi5VlsnDtW9kcTCIN2Tbb_P98FoYXCGH8nEh?thread_id=1396760381980147792

How can Safety help protect you from these attacks?

Traditional vulnerability scanning happens too late - after potentially malicious code is already in your system. Which means that ASPM and EDR solutions don't protect you from this type of threat.

But all is not lost, as the Safety Firewall protects develoeprs and CI pipelines proactively. Every package installation request is analyzed before reaching public repositories. Malicious, vulnerable, and policy-violating packages are automatically blocked before they can enter your systems, preventing rather than just detecting threats.Every package installation request is analyzed before reaching public repositories. Malicious, vulnerable, and policy-violating packages are automatically blocked before they can enter your systems, preventing rather than just detecting threats.Every package installation request is analyzed before reaching public repositories. Malicious, vulnerable, and policy-violating packages are automatically blocked before they can enter your systems, preventing rather than just detecting threats.

You can sign up for a free Safety account and try the Safety Firewall HERE. Feel free to reach out to me with any questions!

Let us know if this blog post helped you

I hope this blog post has helped you. Feel free to hit me up directly if you have any questions about this campaign.

__wf_reserved_inherit

Paul McCarty - Head of Research, Safety

You can find me on LinkedIn and BlueSky.

Read the full article