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:
- CVE-2025-9316: Authentication Bypass via Weak Authentication Method
- 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.
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.
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.
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().
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.
The RuntimeHelper utility also makes no effort to sanitize user input before calling Runtime.getRuntime().execute().
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.
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.
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.
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.
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.
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.
Following the code path, we enter ImportServiceTemplateFromFile.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.
The code eventually reaches ServiceTemplateParser.parse(), which calls our vulnerable function XMLValidator.validateXML().
Putting It All Together
To summarize what we’ve found so far:
- CVE-2025-9316 – An authentication bypass via weak authentication method
- An authenticated limited file write (not assigned a CVE)
- CVE-2025-11700 – An unauthenticated file-based parsing XXE
To weaponize these we:
- Send a
sessionHellorequest to/dms/services/ServerMMSto retrieve an appliance session ID - Send an
applianceLogSubmitrequest to/dms/services/ServerMMSwith 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 - Send an
importServiceFromTemplaterequest to/dms/services/ServerUIwith an arbitrary session ID and the file path above
Lets chain all these together to retrieve a file on disk.
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 . The N-central backup is essentially an entire filesystem backup of the appliance, including the N-central database./opt/nable/var/ncsai/etc/ncbackup.conf
Retrieving the backup via the credentials in from the XXE chain, we find that its a tar archive.
The files of interest are:
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
opt/nable/etc/keystore.bcfks- A password protected keystore file used in encrypting and decrypting sensitive fields (passwords, API keys, etc) in the database
opt/nable/etc/masterPassword- The password that protects the above keystore file
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.
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.
We reported a handful of security issues in total, many variants of the assigned vulnerabilities, which include:
- Authentication bypass via the vulnerable
sessionHellomethod was exposed across several legacy SOAP API endpoints with various different method signatures and unique code paths - XXE via the
importServiceFromFileand alsoimportServiceTemplateFromFile - 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.
- 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
- 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
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
13 November 2025 – CVE-2025-9316 and CVE-2025-11700 publicly assigned
17 November 2025 – This blog post
