Full Report
Golang's parsing for JSON, XML, and YAML has some peculiar properties that the author of this post decided to investigate. When unmarshalling JSON, the fields in Golang can be explicitly set with the settings for decoding. For instance: type User struct { Username string `json:"username_json_key,omitempty"` Password string `json:"password"` IsAdmin bool `json:"is_admin"` } If the json: string is not included, then Golang will still unmarshal it to the exact name of the field - in this case Username. A less-senior developer may not know this and assume that a field without the `json:"is_admin"` cannot be set at all. To actually tell the parser to skip something - text can be used. There's a funny quirk about this though! If - is used with any other data then the parser will assume that - is the literal field name! For instance, the definition `json:"-,omitempty"`. The author found two occurrences of this that they reported as vulnerabilities, and several thousand are currently on GitHub. Another misuse is setting omitempty in the JSON as the keyname instead of a setting. Both of these can be trivially found with semgrep rules. The next class of issues revolves around parser differentials. They label several common issues of misuse: duplicate fields, and case-insensitive key matching. This mostly applies when parsing data in one language and then having it be processed by another. The final bug class is data format confusion. In some cases, parsers are too lenient and try to get valid data out of whatever you need. The example they use is parsing a JSON file with an XML parser. In the case of Hashicorp, Felix from P0 found that they could smuggle in XML to an endpoint intended for JSON. By doing this, the controlled XML was processed instead of the legitimate one. Eventually this led to an auth bypass. The XML parser will accept leading or trailing garbage data. All of the parsers in Golang will accept unknown keys that don't match the struct. Although this doesn't have an impact by itself, it helps construct malicious payloads when exchanged between parsers. Overall, a good post into the weirdness of parsing libraries in Golang.
Analysis Summary
# Vulnerability: Security Footguns in Go Parsers (JSON, XML, YAML)
## CVE Details
- **CVE ID**: CVE-2020-16250 (Example cited in research)
- **CVSS Score**: 9.8 (Critical) - High impact for Auth Bypass
- **CWE**: CWE-20 (Improper Input Validation), CWE-436 (Interpretation Conflict), CWE-172 (Encoding/Parsing Issues)
## Affected Systems
- **Products**: Go-based applications utilizing `encoding/json`, `encoding/xml`, or `gopkg.in/yaml.v3`.
- **Versions**:
- Go standard library (up to `go1.24.1` for JSON/XML)
- `yaml.v3` version 3.0.1
- **Configurations**: Applications that parse untrusted input into Go structs without strict configuration, or services that share data between different parser types.
## Vulnerability Description
Research by Trail of Bits identifies several critical "footguns" in Go's parsing logic:
1. **Tag Misuse (`json:"-,..."`)**: The `-` tag is intended to skip a field. However, if any other data follows the dash (e.g., `json:"-,omitempty"`), Go treats the literal character `-` as the JSON key name, rather than skipping the field.
2. **Parser Differentials**:
- **Case Insensitivity**: Go's default JSON parser permits case-insensitive matching for keys, which can lead to "shadowing" where an attacker provides a key that overrides an internal field.
- **Duplicate Keys**: Default behavior often processes the last occurrence of a key, which can be used to bypass security filters that only check the first occurrence.
3. **Data Format Confusion**: Go's XML parser is highly lenient, accepting leading and trailing "garbage" data. This allows attackers to smuggle XML payloads inside formats intended to be JSON, potentially leading to authentication bypasses when backend systems interpret the "garbage" as the primary payload.
## Exploitation
- **Status**: Exploited in the wild (referencing Hashicorp Vault CVE-2020-16250); PoC conceptualized for struct tag misuse.
- **Complexity**: Medium
- **Attack Vector**: Network (via any endpoint accepting JSON/XML/YAML)
## Impact
- **Confidentiality**: High (Exposure of internal struct fields intended to be private)
- **Integrity**: High (Ability to overwrite security-critical fields like `IsAdmin`)
- **Availability**: Medium (Potential for malformed input to cause logic errors)
## Remediation
### Patches
- **Go JSON v2**: Currently a proposal/experimental. Once released, it will support `RejectUnknownMembers` and mandatory case-sensitivity. Owners of Go applications should migrate when v2 is GA.
- **Library Updates**: Use the latest versions of `encoding/json` and `yaml.v3`.
### Workarounds
- **Strict Decoding**: Always use `Decoder.DisallowUnknownFields()` for JSON and `KnownFields(true)` for YAML to prevent unexpected data injection.
- **Manual Validation**: Implement wrapper functions to verify that the start and end of a buffer match the expected format (e.g., ensuring a JSON buffer starts with `{` and ends with `}`).
## Detection
- **Indicators of Compromise**:
- JSON payloads containing duplicate keys (e.g., `{"is_admin": false, "is_admin": true}`).
- Payloads containing unexpected "garbage" characters before or after valid structures.
- **Detection Tools**:
- **Semgrep**: Use the following rules to find struct tag errors:
- `semgrep -c r/trailofbits.go.unmarshal_tag_is_dash.unmarshal-tag-is-dash`
- `semgrep -c r/trailofbits.go.unmarshal_tag_is_omitempty.unmarshal-tag-is-omitempty`
## References
- **Trail of Bits Blog**: hxxps[://]blog[.]trailofbits[.]com/2025/06/17/unexpected-security-footguns-in-gos-parsers/
- **NVD CVE-2020-16250**: hxxps[://]nvd[.]nist[.]gov/vuln/detail/cve-2020-16250
- **Go JSON v2 Proposal**: hxxps[://]github[.]com/golang/go/issues/71497