GitHub Advisoryadvisory · Go
GHSA-hwqm-qvj9-4jr2 gosaml2 CBC Padding Panic — Unauthenticated Process Crash
HIGH
Official page ↗
- Ecosystem
- Go
- Packages
- github.com/russellhaering/gosaml2
- Severity
- High
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
- Weaknesses
- CWE-129
- Reviewed
- 2026-03-18
Affected packages
• Go github.com/russellhaering/gosaml2: < 0.11.0 → fixed in 0.11.0
Description
Summary
The 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 Version
All 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:
```go
case MethodAES128CBC, MethodAES256CBC, MethodTripleDESCBC:
if len(data)%k.BlockSize() != 0 {
return nil, fmt.Errorf("encrypted data is not a multiple of the expected CBC block size %d: actual size %d", k.BlockSize(), len(data))
}
nonce, data := data[:k.BlockSize()], data[k.BlockSize():]
c := cipher.NewCBCDecrypter(k, nonce)
c.CryptBlocks(data, data)
// Remove zero bytes
data = bytes.TrimRight(data, "\x00") //
[+] RSA-OAEP encrypted AES key (256 bytes)
[+] AES-128-CBC ciphertext: IV() + 16 bytes
[*] Plaintext is all zeros — will trigger empty-slice panic after TrimRight
[+] Built SAML Response (xxx bytes XML, xxx bytes b64)
========================================================
Phase 3: Send payload to /acs
========================================================
[*] POST http://localhost:9999/acs
[*] The server will decrypt our ciphertext, hit the all-zero
plaintext edge case, and panic in DecryptBytes()...
[*] Got HTTP 500 — goroutine panicked but net/http recovered it
========================================================
Phase 4: Verify server status
========================================================
[*] Server is still responding (net/http recovered the goroutine panic)
[*] But the panic stack trace in server logs confirms the vulnerability.
[*] In non-HTTP servers, the process would be dead.
========================================================
VULNERABILITY CONFIRMED
types/encrypted_assertion.go:77 — index out of range [-1]
Stack trace:
types/encrypted_assertion.go:77 (padLength := data[len(data)-1])
decode_response.go:176 (decryptAssertions)
decode_response.go:346 (ValidateEncodedResponse)
========================================================
```
Server logs (panic stack trace):
```
http: panic serving 127.0.0.1:xxxxx: runtime error: index out of range [-1]
goroutine XX [running]:
net/http.(*conn).serve.func1()
/usr/local/go/src/net/http/server.go:1898 +0xbe
github.com/russellhaering/gosaml2/types.(*EncryptedAssertion).DecryptBytes(...)
types/encrypted_assertion.go:77 +0x...
github.com/russellhaering/gosaml2.(*SAMLServiceProvider).decryptAssertions.func1(...)
decode_response.go:176 +0x...
github.com/russellhaering/gosaml2.(*SAMLServiceProvider).decryptAssertions(...)
decode_response.go:196 +0x...
github.com/russellhaering/gosaml2.(*SAMLServiceProvider).ValidateEncodedResponse(...)
decode_response.go:346 +0x...
```
Suggested Fix
Replace the unsafe zero-byte trimming and unchecked index with proper PKCS#7 unpadding and bounds checks:
```go
case MethodAES128CBC, MethodAES256CBC, MethodTripleDESCBC:
if len(data)%k.BlockSize() != 0 {
return nil, fmt.Errorf("ciphertext not multiple of block size")
}
nonce, data := data[:k.BlockSize()], data[k.BlockSize():]
c := cipher.NewCBCDecrypter(k, nonce)
c.CryptBlocks(data, data)
// Validate decrypted data is non-empty
if len(data) == 0 {
return nil, fmt.Errorf("decrypted data is empty")
}
// Proper PKCS#7 unpadding with bounds checks
padLength := int(data[len(data)-1])
if padLength k.BlockSize() || padLength > len(data) {
return nil, fmt.Errorf("invalid padding length: %d", padLength)
}
// Verify all padding bytes are consistent
for i := len(data) - padLength; i < len(data); i++ {
if data[i] != byte(padLength) {
return nil, fmt.Errorf("invalid PKCS#7 padding")
}
}
return data[:len(data)-padLength], nil
```
Key changes:
1. Remove `bytes.TrimRight(data, "\x00")` entirely — it corrupts valid PKCS#7-padded data and creates the empty-slice condition.
2. Bounds-check `padLength` before using it as a slice index.
3. Validate all padding bytes match (proper PKCS#7 verification).
4.…
Related
Weakness · CWE (1)
- CWE-129 Improper Validation of Array Index weakness