# simplesamlphp/xml-security: Missing AES-GCM Authentication Tag Validation on Encrypted Nodes Allows for Unauthorized Decryption

xmlseclibs AES-GCM decryption never validates auth tag length, letting an attacker brute-force a 1-byte tag, recover the GHASH key, and decrypt or forge encrypted XML nodes.

- Payout: Research
- Severity: high
- Program: simplesamlphp/xml-security
- Found by: Sideni, tvdijen
- Disclosed: 2026-03-13
- CVE: CVE-2026-32600
- Write-up quality: 9/10
- Original: https://github.com/advisories/GHSA-r353-4845-pr5p
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-r353-4845-pr5p
- Tags: area:web domain:oss domain:web impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:php tech:saml technique:source-review vuln:crypto vuln:info-disclosure vuln:oracle-manipulation

**Target:** XML encryption (xmlseclibs AES-GCM decrypt path) in SAML/XML security library
**Root cause:** XMLSecurityKey::decrypt() extracts the GCM auth tag via substr() without checking strlen($authTag) == AUTHTAG_LENGTH; PHP's openssl_decrypt accepts a truncated tag matching only the prefix of the real tag.
**Steps:**
1. Base64-decode the <xenc:CipherValue> content; first 12 bytes = nonce, last 16 bytes = tag.
2. Brute-force the tag of an empty ciphertext: for each of 256 byte values, concatenate nonce + byte_tag_attempt, base64-encode, replace <xenc:CipherValue>, and send; HTTP 500 indicates a valid tag.
3. Repeat byte-by-byte until all 16 tag bytes are recovered.
4. With the empty-ciphertext tag, compute the GHASH key (per frereit.de/aes_gcm method).
5. Use the GHASH key to compute auth tags offline for arbitrary ciphertexts.
6. Decrypt by observing XML parsing errors (HTTP 500) after modifying ciphertext.
7. Run the full chain via `sage -python nonce_reuse_with_fmt_val_oracle.py -s '<url-encoded base64 SAMLResponse>'` against poc.php served with `php -S 127.0.0.1:8888`.
**How found:** Code review of the xmlseclibs AES-GCM decrypt path spotted substr()-derived $authTag with no length check, then consulted PHP docs noting openssl_decrypt does not verify tag length, and built a test harness showing 1-byte tags are accepted.
**Exploitation:** Attacker with a captured legit AES-GCM-encrypted XML node (e.g. SAMLResponse) submits truncated-tag variants to an endpoint that decrypts and parses the XML; 500 vs non-500 responses form a tag-validation oracle. Brute-forcing 16 tag bytes for an empty ciphertext yields the GHASH key, enabling offline tag computation, decryption of the original ciphertext, and forgery of arbitrary ciphertexts. Secrets embedded in the XML (e.g. IdP-encrypted symmetric keys) can be recovered.
**Escalation:** Truncated-tag oracle -> GHASH key recovery -> offline decryption and arbitrary ciphertext forgery; impact amplified when static symmetric keys are used, requiring rotation.
**Insight:** When a crypto library delegates to openssl_decrypt, verify the caller enforces tag length — PHP accepts prefix-matching truncated GCM tags, turning any decrypt-then-parse endpoint into a byte-at-a-time tag oracle that leaks the GHASH key.

## Proof of concept (copied exactly from the original report)

**PHP snippet showing short auth tag accepted**

```
function test($data) {
    $ivSize = 12;
    $tagSize = 16;

    $iv = substr($data, 0, $ivSize);
    $data = substr($data, $ivSize);
    $offset = 0 - $tagSize;
    $tag = substr($data, $offset);
    $ct = substr($data, 0, $offset);

    echo 'IV: "' . $iv . '"' . PHP_EOL;
    echo 'Tag: "' . $tag . '"' . PHP_EOL;
    echo 'CT: "' . $ct . '"' . PHP_EOL;
}

/* Outputs:
php > test('myNonceNoncet');
IV: "myNonceNonce"
Tag: "t"
CT: ""
php > test('myNonceNonceta');
IV: "myNonceNonce"
Tag: "ta"
CT: ""
php > test('myNonceNoncetag');
IV: "myNonceNonce"
Tag: "tag"
CT: ""
*/
```

