N-able N-central: From N-days to 0-days

Zach Hanley  |
  November 17, 2025  |

Earlier this year, N-able N-central popped up on the CISA KEV for two vulnerabilities: CVE-2025-8875 and CVE-2025-8876. This pair of vulnerabilities allow an authenticated attacker to achieve remote code execution via deserialization and command injection. Any time an authenticated vulnerability pops up in widely deployed enterprise software, it piques our interest to see if there is something more to find.

During the investigation, we uncovered several other critical security issues that existed in the most up-to-date version of N-central. The issues, when combined, allow for an unauthenticated attacker to interact with legacy APIs and read sensitive files on the filesystem – including files that leak credentials and other sensitive information. In nearly all deployments, we found that the leaked credentials led to compromising the N-central database which contains all integration secrets such as domain credentials, N-central user API keys, integrated device and service API keys, SSH private keys, and more.

The vulnerabilities reported were assigned:

  1. CVE-2025-9316: Authentication Bypass via Weak Authentication Method
  2. CVE-2025-11700: Authenticated XML External Entity (XXE) Information Leak

N-central Overview

N-central is N-able’s remote managing and monitoring solution (RMM) allowing for large enterprises and MSPs to easily manage, configure, patch, and run reporting and analytics.

Figure 1. N-central Dashboard

It is not uncommon to find deployed on the internet, with approximately 3000 instances listed on Shodan. Given the recent addition N-central to the CISA KEV and threat actors commonly targeting RMM software (SimpleHelp, ConnectWise, and BeyondTrust as fairly recent examples), this is definitely a target of interest.

Figure 2. Shodan Exposure

Investigating the N-Days

CVE-2025-8875: Authenticated Insecure Deserialization

The pair of vulnerabilities were patched in N-central 2025.3.0.14 released 15 July 2025. Patch diffing this release with an earlier release we quickly stumble upon the insecure deserialization in both the LicenseResponse0008 and LicenseReponseUnsolicited0008 classes. The class’s getObject method takes in a user controlled byte array, creates an ObjectInputStream and calls readObject() on it.

Figure 3. Patching insecure deserialization

This functionality is reachable via the legacy SOAP APIs on the /dms/services/ServerUI endpoint via the ActivateServer method which calls the below ServerActivate.activate().

Figure 4. ServerUI SOAP API exposes serverActivate method
Figure 5. ServerActivate.activate() calls vulnerable getObject methods

CVE-2025-8876: Authenticated Command Injection

The patch included many areas of code where shell commands were constructed with user input and called via helper utilities that attempted to sanitize any user input, but not all functions utilized this helper. One such function that stood out was MotherShipMonitoringFeatureServiceImpl.activateMotherShipMonitoring(), now removed in the patch, which constructed a shell command and passed user input directly to a execution utility function.

Figure 6. Construct command from user input without sanitization

The RuntimeHelper utility also makes no effort to sanitize user input before calling Runtime.getRuntime().execute().

Figure 7. RuntimeHelper utility

The activateMothershipMonitoring() method was reachable via the /dms2/services2/ServerUI2 legacy SOAP API via the mothershipMonitoringActivate API method, but has since been removed from the API entirely.

Finding Initial Access

Both API endpoints and vulnerable paths to the code required a valid session ID to interact with them, so the plan was to find a way to get a session ID. The patches also contained a significant amount of hardening around API Refresh and Access tokens, and it seemed like there might be something there there, but before we could suss out any insecurities, we stumbled upon the legacy SOAP API method sessionHello.

Figure 8. sessionHello exposed in ServerMMS API endpoint

The sessionHello method is exposed across many of the legacy SOAP API endpoints, takes in several arguments, and passes control to SessionHello.sessionHello(). The function inspects the user controlled input, and conditionally returns a valid session ID intended for a network-peer appliance to communicate with the N-central server. Unfortunately, in the default configuration of N-central, the database comes preconfigured with several builtin appliances for which all values needed to retrieve a session ID are static and known across installs.

Figure 9. SessionHello.sessionHello() conditionally returns valid Appliance Session ID

Sending the crafted SOAP API request to the /dms/services/ServerMMS endpoint with the crafted inputs allows an attacker to retrieve a valid session ID.

Figure 10. Retrieving a valid session ID

Fortunately for N-central, the legacy SOAP APIs scope session IDs differently for appliances versus users. This session ID, while valid for the /dms/services/ServerMMS and ServerMMS2 endpoints, does not work on other endpoints, such as ServerUI and ServerUI2 where the previous vulnerability was found, which expect a user session ID.

This vulnerability existed both in the unpatched version and patched version (2025.3.09). After discovering this issue, we promptly reported it to N-able and was assigned CVE-2025-9316.

Continuing the Hunt – Reading Lots of Code

With a valid session ID for an appliance, we continued auditing the ServerMMS and ServerMMS2 APIs for where authenticated access might lead to some softer areas of code. Much of the code seemed less interesting, allowing the appliance to interact with the N-central database for tasking and reporting information about itself, but no code-level vulnerabilities were discovered like SQL injection or command injection. One function did stand out, applianceLogSubmit, which allows writing arbitrary content to a specific file path on disk: /opt/nable/webapps/ROOT/applianceLog/network_check_log_<appliance_ID>.log.

Figure 11. Arbitrary Content to Limited File Write Primitive

Coming up short on finding an abusable set of functions in the ServerMMS APIs, we began searching for other vulnerability classes like XML External Entity (XXE). Turning to grep, we reviewed every call in the API libraries that constructed XML parser objects like SAXParser. One instance in XMLValidator.validateXML() creates a SAXParser instance without setting secure defaults that would disallow external entities via DTDs.

Figure 12. Insecure SAXParser configuration

The call chains discovered that utilized the vulnerable XMLValidator parser, however, all originated from the ServerUI API endpoint. When testing several ServerUI API methods with our previously obtained session ID, the server would return access denied due to the scoping issue of having an appliance session ID.

But! Further inspection of all the available ServerUI methods showed that some methods do not validate the passed in session ID. To our great fortune, the call chains in the ServerUI endpoint that lead to the vulnerable XMLValidator code do not validate the session ID in any way. This XXE vulnerability was assigned CVE-2025-11700.

The ServerUI endpoint we abuse is importServiceTemplateFromFile. We supply any session ID, a customer ID of 1 (exists by default), and supply a filepath to a file that exists on disk on the N-central appliance. Our limited file write discovered above now has value.

Figure 13. ServerUI API – importServiceTemplateFromFile

Following the code path, we enter ImportServiceTemplateFromFile.importServiceTemplate().

Figure 14. importServiceTemplate()

Which in turn calls ImportServiceTemplateFromFile.doImport(). This function lightly validates that file exists, and that the XML contains a version string. Simple enough to pass validation so far.

Figure 15. doImport() light XML validation

The code eventually reaches ServiceTemplateParser.parse(), which calls our vulnerable function XMLValidator.validateXML().

Figure 16. ServiceTemplateParser.parse() calls vulnerable sink

Putting It All Together

To summarize what we’ve found so far:

  1. CVE-2025-9316 – An authentication bypass via weak authentication method
  2. An authenticated limited file write (not assigned a CVE)
  3. CVE-2025-11700 – An unauthenticated file-based parsing XXE

To weaponize these we:

  1. Send a sessionHello request to /dms/services/ServerMMS to retrieve an appliance session ID
  2. Send an applianceLogSubmit request to /dms/services/ServerMMS with our newly obtained session ID and a crafted base64 encoded string that becomes the XML content at the file location /opt/nable/webapps/ROOT/applianceLog/network_check_log_<appliance_ID>.log
  3. Send an importServiceFromTemplate request to /dms/services/ServerUI with an arbitrary session ID and the file path above
Figure 17. Example XXE Payload

Lets chain all these together to retrieve a file on disk.

Figure 18. Leaking /etc/passwd via XXE

The end-to-end proof of concept exploit to leak files can be found on our GitHub.

Impact

There are many files that exist on the appliance that are valuable to exfiltrate. Log files verbosely log the session IDs of other users and appliances, which can be used to directly interact with API endpoints to manage the appliance. The most valuable data resides in the N-central database, which we found is backed up at regular interval in most client environments. The backup credentials are stored in cleartext, along with server location, in the file /opt/nable/var/ncsai/etc/ncbackup.conf. The N-central backup is essentially an entire filesystem backup of the appliance, including the N-central database.

Retrieving the backup via the credentials in from the XXE chain, we find that its a tar archive.

Figure 19. N-central Backups

The files of interest are:

  1. var/opt/n-central/tmp/ncbackup/ncbackup.bin
    • A postrges dump file that can be restored
    • sudo -u postgres pg_restore -d ncentral_restore var/opt/n-central/tmp/ncbackup/ncbackup.bin
  2. opt/nable/etc/keystore.bcfks
    • A password protected keystore file used in encrypting and decrypting sensitive fields (passwords, API keys, etc) in the database
  3. opt/nable/etc/masterPassword
    • The password that protects the above keystore file
  4. etc/shadow
    • Potentially crack the builtin root or admin or service account users to use over SSH (we did not try hard to crack these)

Once the database is restored, sensitive fields (prefixed ENCR2#1:nc#) can be decrypted to reveal credentials, private SSH keys, and more.

Figure 20. Example N-central Database Credentials

With a little Claude code magic, re-implementing the decryption logic given the cryptographic key files in the backup, we can decrypt all the secrets.

Figure 21. Decrypting secrets given masterPassword and keystore.bcfks

We reported a handful of security issues in total, many variants of the assigned vulnerabilities, which include:

  1. Authentication bypass via the vulnerable sessionHello method was exposed across several legacy SOAP API endpoints with various different method signatures and unique code paths
  2. XXE via the importServiceFromFile and also importServiceTemplateFromFile
  3. A file upload path traversal variant allowing writing mostly-fixed filename writing to arbitrary locations

Indicators of Compromise

The N-central appliance allows administrative users to retrieve logs from Administration Utilities -> View Logs.

Indicators that the XXE has been abused will be apparent in multiple log locations.

  1. dmsservice.log
    • “Failed to import service template from file” which is immediately followed by the contents of the file leaked by the XXE
    • “Exception calling ServerUI:importServiceTemplateFromFile” which again is immediately followed by the contents of the leaked file
  2. dmsservice_soap.log
    • “servicetemplate xml could not be imported” which also includes the maliciously uploaded XML which could be inspected for external DTD references to identify attacker infrastructure and files targeted
Figure X. dmsservice.log “Failed to import service”
Figure X. dmservice.log “Exception calling ServerUI”
Figure X. dmsservice_soap.log “servicetemplate xml could not be imported”

Evidence of abuse of the authentication bypass was not observable in the default log configurations.

Timeline

18 August 2025 – Began N-central N-day investigation

19 August 2025 – Discovered authentication bypass

20 August 2025 – Reported authentication bypass to N-able

21 August 2025 – N-able acknowledges and assigns CVE-2025-9316

21 August 2025 – Discovered authentication bypass variants, file write, and XXE vulnerabilities

26 August 2025 – Reported authentication bypass variants, file write, and XXE vulnerabilities to N-able

26 August 2025 – N-able acknowledges and forwards report to internal teams

5 September 2025 – Discovered another XXE variant and file write path traversal

8 September 2025 – Reported XXE variant and file write path traversal to N-able

9 September 2025 – Horizon3.ai proactively notifies its internet exposed customers and communicates an effective interim mitigation

15 October 2025 – Horizon3 asks for an update

15 October 2025 – N-able acknowledges security issues

5 November 2025 – N-able mitigates vulnerabilities in release 2025.4.0.9 by making the specific legacy SOAP APIs abused not available in the default configuration

This image has an empty alt attribute; its file name is image-16-1024x603.png
Figure X. 2025.4.0.9 Patch Notes

13 November 2025 – CVE-2025-9316 and CVE-2025-11700 publicly assigned

17 November 2025 – This blog post

How can NodeZero help you?
Let our experts walk you through a demonstration of NodeZero®, so you can see how to put it to work for your organization.
Get a Demo
Share: