Open question

test

adrianwowk · 2mo ago ·edited

1 Reply

Colby Knox · 2mo ago

Fixed and deployed to prod. Nice find, this was a real one.

Root cause, and it is a dumber bug than it looks: the attachment allowlist was a plain JavaScript object literal, so it inherited Object.prototype. The check was effectively

const type = ATTACHMENT_TYPES[ext];
if (!type) reject();

and ATTACHMENT_TYPES["constructor"] returns the inherited Object function, which is truthy. So naming the file .constructor walked straight through an allowlist that was supposed to permit only images. Same trick works with toString, valueOf, hasOwnProperty, and four others. That truthy function then got stringified into the object's stored Content-Type, which is why it served as content-type: function Object() { [native code] }. No browser recognizes that, so it fell back to sniffing the body, found HTML, and rendered it as a live document on a *.adom.inc origin, where the SameSite=Lax session cookie reaches Carbon.

Two fixes, because the first one alone was not enough. Every extension-keyed MIME map is now null-prototype, six sites in total. More importantly, Content-Type is now derived from the file's magic bytes rather than its name. That second half matters: with only the prototype fix, HTML named payload.png would still have been accepted and stored as image/png. Verified against prod just now, using your exact payload:

payload.constructor  ->  400 BAD_ATTACHMENT_TYPE
payload.png          ->  400 BAD_ATTACHMENT_CONTENT
real PNG             ->  stored, image/png

Also swept every attachment on the wiki, 943 pages and 884 issue and reply bodies, 14 distinct objects. Yours was the only non-image. The other 13 are genuine PNGs by magic bytes.

Two things your PoC surfaced that are bigger than the upload bug, both filed separately and neither closed by this fix:

  1. Carbon and Hydrogen both run AllowOrigin::mirror_request() together with allow_credentials(true), so they reflect any origin. Today SameSite=Lax is the only thing stopping a genuinely third-party site from doing what your page did, no wiki bug required.
  2. User content is hosted on an adom.inc subdomain, which is what made the cookie reachable at all. GitHub uses githubusercontent.com for exactly this reason. Content-Type validation does not help against the phishing variant, where a page just renders a convincing fake login on a real-looking adom.inc URL.

Thanks for writing it as a harmless bio change with a visible marker rather than something quiet.

Log in to reply.