Commit 8a35262
authored
fix(json-schema): prevent prototype injection when coercing objects (#1726)
Fixes two prototype chain bugs in the `object` branch of
`JsonSchemaCoercer#coerce`. The coercer runs on untrusted input before
validation (`SmartCoercionPlugin`), so the keys below are attacker
controlled.
### 1. Coerced properties were collected into a plain `{}`
`coercedItems['__proto__'] = value` triggers the inherited `__proto__`
setter instead of creating an own property:
```
schema: { type: 'object', properties: { a: { type: 'number' } } }
input: {"a":"1","__proto__":{"isAdmin":true}}
out = { a: 1 } // __proto__ entry dropped from the output
out.isAdmin === true // attacker object became the prototype
```
`Object.prototype` is not touched, but the returned value inherits
attacker controlled properties, and anything reading them through the
prototype chain sees them.
Fix: collect into `NullProtoObj`, the helper already used for untrusted
keys in `bracket-notation.ts` and `rpc-matcher.ts`.
### 2. `schema.properties?.[key]` returned inherited members
For keys like `constructor`, `toString` and `__proto__` the lookup
resolves to `Object.prototype` members, which were then used as
sub-schemas. They coerce nothing and report `satisfied: true`, so those
keys skipped the unknown property path and selected the wrong union
branch:
```
anyOf: [{ type: 'object', properties: { a: { type: 'number' } } },
{ type: 'object', additionalProperties: { type: 'boolean' } }]
{ z: 'true' } => { z: true } // correct branch
{ constructor: 'true' } => { constructor: 'true' } // before: wrong branch, not coerced
```
Fix: guard the lookup with `Object.hasOwn`.
### Verification
`prevents prototype injection when coercing objects` covers both cases
and fails without the fix. The json-schema, openapi and openapi-client
suites pass (412 tests). `tsc -b` and eslint are clean.1 parent 54ad8ac commit 8a35262
2 files changed
Lines changed: 36 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
165 | 194 | | |
166 | 195 | | |
167 | 196 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| |||
166 | 166 | | |
167 | 167 | | |
168 | 168 | | |
169 | | - | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
170 | 174 | | |
171 | 175 | | |
172 | 176 | | |
173 | 177 | | |
174 | 178 | | |
175 | 179 | | |
176 | | - | |
| 180 | + | |
177 | 181 | | |
178 | 182 | | |
179 | 183 | | |
| |||
0 commit comments