GHSA-hwqm-qvj9-4jr2: gosaml2 CBC Padding Panic — Unauthenticated Process Crash
SummaryThe AES-CBC decryption path in `DecryptBytes()` panics on crafted ciphertext whose plaintext is all zero bytes. After decryption, `bytes.TrimRight(data, "\x00")` empties the slice, then `data[len(data)-1]` panics with `index out of range [-1]`. There is no `recover()` in the library. The panic propagates through `ValidateEncodedResponse` and kills the goroutine (or the entire process in non-`net/http` servers). An attacker needs only the SP's public RSA key (published in SAML metadata) to construct the payload — no valid signature is required.Affected VersionAll versions of `github.com/russellhaering/gosaml2` through latest (`v0.9.0` and HEAD) that support AES-CBC encrypted assertions.Vulnerable Code**`types/encrypted_assertion.go:65-79`** — `DecryptBytes`, AES-CBC branch:Attack Details| Property | Value |
|---|---|
| **Attack Vector** | Network (unauthenticated HTTP POST to ACS endpoint) |
| **Authentication Required** | None |
| **Attacker Knowledge** | SP's public RSA certificate (published in SAML metadata) |
| **Signature Required** | No — decryption happens before assertion signature validation |
| **Payload Size** | Single HTTP POST (~2 KB) |
| **Repeatability** | Unlimited — attacker can send the payload repeatedly |
| **Affected Configurations** | Any SP with `SPKeyStore` configured (encrypted assertion support) |
| **Trigger Condition** | AES-CBC plaintext that is all `0x00` bytes after decryption |Impact**Process crash**: In gRPC servers, custom frameworks, CLI tools, and background workers, the unrecovered panic kills the entire OS process immediately.**Goroutine crash**: In `net/http` servers, the built-in per-goroutine recovery catches the panic, returning HTTP 500 and logging the full stack trace. The server survives but the request-handling goroutine is terminated abnormally.**Denial of service**: The attack is unauthenticated and repeatable. A single crafted HTTP request is sufficient. Automated retries can keep the service down indefinitely.**No valid signature needed**: The SAML Response does not need to be signed. On the unsigned-response code path (`decode_response.go:346`), `decryptAssertions()` is called **before** any assertion signature validation.ReproductionPrerequisitesDocker (for the vulnerable server)Python 3.8+ with `cryptography` and `requests` packagesFiles| File | Description |
|---|---|
| `server.go` | Minimal SAML SP using gosaml2 — the victim |
| `poc.py` | Attacker script — builds and sends the crafted payload |
| `Dockerfile` | Multi-stage build for the vulnerable server |
| `run.sh` | Build and orchestration script |StepsExpected Output**Attacker terminal (`poc.py`):****Server logs (panic stack trace):**Suggested FixReplace the unsafe zero-byte trimming and unchecked index with proper PKCS#7 unpadding and bounds checks:Key changes:**Remove `bytes.TrimRight(data, "\x00")`** entirely — it corrupts valid PKCS#7-padded data and creates the empty-slice condition.**Bounds-check `padLength`** before using it as a slice index.**Validate all padding bytes** match (proper PKCS#7 verification).**Return errors** instead of panicking on malformed input.
CVSS v4.0: 8.7