# validateSignature Loop Variable Capture Signature Bypass in goxmldsig

Loop variable capture in goxmldsig validateSignature makes ref point to the last SignedInfo reference, letting attackers swap signed element content and still pass validation.

- Payout: Research
- Severity: high
- Program: goxmldsig
- Found by: tomasilluminati
- Disclosed: 2026-03-18
- CVE: CVE-2026-33487
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-479m-364c-43vc
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-479m-364c-43vc
- Tags: area:oss domain:oss impact:integrity kind:research platform:ghsa sev:high tech:go technique:reverse-engineering technique:source-review vuln:auth-bypass

**Target:** Go XML digital signature validation library (validate.go)
**Root cause:** The loop takes the address of the range variable `_ref` (&_ref), which is allocated once per loop in Go <1.22, so `ref` always ends up pointing to the last SignedInfo reference rather than the matching one.
**Steps:**
1. Construct a signed document with two elements each having a Reference in SignedInfo.
2. In validateSignature, the loop `for _, _ref := range signedInfo.References { ... ref = &_ref }` captures the loop variable address.
3. After the loop, `ref` points to the last reference regardless of which URI matched the target ID.
4. Alter the first element's content to match the second element's content; validation still succeeds because the wrong reference is checked.
5. Run the provided Go PoC: sign two elements, mutate the first, call valCtx.Validate and observe 'validation ok'.
**How found:** Reporter read validate.go, spotted `ref = &_ref` taking the address of a range variable, and confirmed via Radare2 disassembly at 0x1001c5908 that no runtime.newobject allocation occurs inside the loop, proving the pointer aliases the single loop variable slot.
**Exploitation:** An attacker who can modify a signed XML document replaces the content of one signed element with content from another element referenced in the same signature; the validator checks the wrong reference and accepts the tampered document, bypassing integrity checks.
**Insight:** In Go <1.22, `&rangeVar` inside a loop aliases one slot; grep security-sensitive Go code for `&_x`/`&v` taken from range variables in signature, auth, or selection logic, and confirm with disassembly that no per-iteration allocation exists.

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

**Vulnerable loop taking address of loop variable**

```
for _, _ref := range signedInfo.References {
		if _ref.URI == "" || _ref.URI[1:] == idAttr {
			ref = &_ref // <- Capture var address of loop
		}
	}
```

