GitHub Advisoryadvisory · Go

GHSA-hwqm-qvj9-4jr2 gosaml2 CBC Padding Panic — Unauthenticated Process Crash

HIGH Published 2026-03-18 Modified 2026-03-18 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

References