[fix][core] test for a plain container by prototype, not by reading constructor - #7905
Open
ar2rsawseen wants to merge 1 commit into
Open
[fix][core] test for a plain container by prototype, not by reading constructor#7905ar2rsawseen wants to merge 1 commit into
ar2rsawseen wants to merge 1 commit into
Conversation
…onstructor
escape_html_entities decided whether to walk a value by comparing value.constructor
against Object and Array. constructor is an ordinary property name, so a JSON body
can carry its own: {"constructor": true, ...} makes value.constructor evaluate to
true, the comparison fails, and the object is returned with its keys and values
unescaped.
This is the replacer for every returnOutput and returnMessage, so the consequence
is not local. Any response object whose property names come from ingested data
could carry markup through the encoder untouched, and any consumer that renders
those names, of which there are several using v-html, would then render it. The
public ingestion endpoint accepts arbitrary property names inside the consent
object, so no account is needed to plant one.
Prototype identity cannot be spoofed by an own property. isPlainContainer accepts
arrays and objects whose prototype is Object.prototype or null, which keeps the
original intent exactly: ObjectIDs, Dates and other class instances are still
escaped as scalars rather than walked into. Checked against both, and against
plain objects, nested objects, arrays, strings, numbers and booleans, all of which
serialize as they did before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
escape_html_entitiesdecided whether to walk into a value by reading itsconstructor:constructoris an ordinary property name, so a JSON body can carry its own.{"constructor": true, ...}makesvalue.constructorevaluate totrue, the comparison fails, and the object is returned with its keys and values unescaped.Why this is not a local problem
This function is the replacer passed to
JSON.stringifyby everyreturnOutputandreturnMessage. It is the control that lets the rest of the product assume API output is escaped. So any response object whose property names originate in ingested data could carry markup through it untouched, and any consumer rendering those names would render the markup.The public ingestion endpoint accepts arbitrary property names inside the
consentobject, so planting one needs no account.The fix
Prototype identity, which an own property cannot spoof:
This keeps the original intent exactly. The alternative of
Object.prototype.toString.call(value) === '[object Object]'would have been wrong: a MongoDB ObjectID reports[object Object]and would start being walked into rather than serialized as an id, changing responses across the API.Verification
The real function was lifted and run through
JSON.stringifybefore and after. Before, the attack input serializes with the markup key verbatim:{"constructor":true,"<img src=x onerror=alert(1)>":true}. After, it is escaped, both at the top level and nested one level down as a consent object would be.Unchanged, checked explicitly: plain nested objects, arrays of strings, plain text, numbers, booleans,
ObjectIDstill serializing as a scalar id, andDatestill serializing as a date string.