Closed Bug 330773 Opened 18 years ago Closed 18 years ago

DOM property setter override (remote compromise)

Categories

(Core :: JavaScript Engine, defect, P1)

defect

Tracking

()

RESOLVED FIXED
mozilla1.9alpha1

People

(Reporter: pvnick, Assigned: mrbkap)

Details

(Keywords: testcase, verified1.8.0.4, verified1.8.1, Whiteboard: [sg:critical] [patch])

Attachments

(10 files, 3 obsolete files)

297 bytes, text/html
Details
299 bytes, text/html
Details
381 bytes, text/html
Details
1.12 KB, patch
jst
: review+
Details | Diff | Splinter Review
2.29 KB, patch
jst
: review+
bzbarsky
: superreview+
Details | Diff | Splinter Review
264 bytes, text/html
Details
2.93 KB, patch
jst
: review+
brendan
: superreview+
Details | Diff | Splinter Review
1.24 KB, patch
bzbarsky
: review+
bzbarsky
: superreview+
Details | Diff | Splinter Review
5.26 KB, patch
Details | Diff | Splinter Review
3.38 KB, patch
Details | Diff | Splinter Review
Messing around, I found that running HTMLDocument.__proto__.__defineSetter__("prototype",function(){alert(location.href)}) will cause alert(location.href) to be executed in the context of the current page periodically (for example, when the page is right-clicked or when view page info is clicked). I can't think of a way to exploit this, but it doesn't seem like proper behavior, so I'm just throwing it out there to see if anyone with more experience in this area can make something of it.

Also, I was wondering what has been done with __defineGetter__ overriding DOM properties. Has this type of attack been blocked completely or just specific holes that use it?
If I do

javascript:HTMLDocument.__proto__.__defineSetter__("prototype",function(){try{d.d.d}catch(e){alert(e.stack)}})

on http://www.squarefree.com/start/ and then bring up a context menu or Page Info, I get a bunch of alerts suggesting that things like getElementsByTagName are calling that setter.  jst, any idea why this is happening, why chrome isn't protected from these content-defined setters, and/or how bad this is?
Assignee: nobody → jst
Whiteboard: [sg:investigate]
Thanks for ccing me, Paul.

When the following conditions are satisfied, an attacker can run arbitrary
code.

  1. A caller of the |prototype| setter has chrome privilege.
  2. Content can control the return value of toString() method of a object that
     is passed to the |prototype| setter.

Also, Object.prototype can be used instead of HTMLDocument.__proto__.
Whiteboard: [sg:investigate] → [sg:critical]
(In reply to comment #4)
> Created an attachment (id=215479) [edit]
> testcase2 - arbitrary code execution using Object.prototype
> 

Fantastic work, moz_bug_r_a4!!!

Cheers,
Paul
Severity: normal → critical
Flags: blocking1.8.0.2?
Summary: DOM property setter override (possible vuln?) → DOM property setter override (remote compromise)
This works on the trunk, fx 1.5.0.x, fx 1.0.x and moz 1.7.x.
(testcase1 and testcase2 don't work on fx 1.0.x and moz 1.7.x.)
We have bits-in-hand for 1.0.8 and 1.5.0.2, so without a patch this goes in the next set of releases. We may reconsider if we get a small, safe patch very quickly so please ask for approval on it.
Component: General → JavaScript Engine
Flags: blocking1.8.0.3+
Flags: blocking1.8.0.2?
Flags: blocking-aviary1.0.9+
Product: Firefox → Core
Version: 1.5.0.x Branch → Trunk
Attached patch Hack (obsolete) — Splinter Review
I think this is wrong on at least one or two levels, but it fixes the testcases in this bug by bypassing the content-defined setter for the "prototype" property, all XPCNativeWrapper-style
Attachment #215694 - Flags: review?(jst)
(In reply to comment #0)
> Also, I was wondering what has been done with __defineGetter__ overriding DOM
> properties. Has this type of attack been blocked completely or just specific
> holes that use it?

In general, any code dealing with DOM nodes (or content nodes of any kind, really) that has chrome privileges will be dealing with XPCNativeWrappers, which don't call user-defined getters, and go straight to the source as it were, and use only IDL-defined properties.
Attached patch Fix, part 1Splinter Review
This is a partial fix that fixes the testcases in this bug, but jst and I are trying to find a better and more general solution. I'll explain what's going on in a seperate comment.
Assignee: jst → mrbkap
Attachment #215694 - Attachment is obsolete: true
Status: NEW → ASSIGNED
Attachment #215736 - Flags: superreview?(jst)
Attachment #215736 - Flags: review?(jst)
Attachment #215694 - Flags: review?(jst)
Comment on attachment 215736 [details] [diff] [review]
Fix, part 1

r+sr=jst
Attachment #215736 - Flags: superreview?(jst)
Attachment #215736 - Flags: superreview+
Attachment #215736 - Flags: review?(jst)
Attachment #215736 - Flags: review+
I check attachment 215736 [details] [diff] [review] into the trunk, but I'm leaving this bug open for some additional investigation.

What's happening here is that we're trying to define the .prototype property on a class that's being resolved. In attachment 215551 [details]'s case, this happens to be HTMLParagraphElement. As "expected", we find the setter for the .prototype property on object.prototype and call it. An important aside is that Components.utils.lookupMethod returns a function whose |this| is bound (i.e., not dynamic) so that when setTimeout is called as a setter, its |this| is correct. setTimeout binds its timeouts with the principals of whoever is calling it, so it finds the chrome principals on the stack and runs the given code with those principals. The fix that I just checked in makes us use JS_DefineProperty instead of JS_SetProperty, so we define the .prototype property with no getter or setter, and therefore don't run the evil setter.

jst and I want to look into fixing this attack, even with the setter defined.
Priority: -- → P1
Target Milestone: --- → mozilla1.9alpha
Why didn't the fix for bug 223041 prevent setTimeout from being used in this exploit?
(In reply to comment #13)
> Why didn't the fix for bug 223041 prevent setTimeout from being used in this
> exploit?

I don't see why it would have -- that bug never actually touched setTimout.
Hmm, doesn't leaving out setTimeout negate the benefit of that fix?  Do you know why setTimeout was left out?
Comment on attachment 215736 [details] [diff] [review]
Fix, part 1

Should the prototype property have JSPROP_READONLY and JSPROP_PERMANENT too?  That would be the case for built-in ECMA-262 classes.  Blame me for any deviation for the DOM level 0 (which I actually forget! someone please remind me).

/be
That isn't a bad idea, though I think it's jst's call. I was aiming for compat with the existing code with my patch.
OS: Windows XP → All
Hardware: PC → All
(In reply to comment #16)
> Should the prototype property have JSPROP_READONLY and JSPROP_PERMANENT too? 

I'm all for that!
I don't think JSPROP_ENUMERATE was used in the old days or now.  All native class constructors' 'prototype' properties have attributes JSPROP_READONLY | JSPROP_PERMAMENT (ECMA-262 Ed. 3 would say, {DontEnum, ReadOnly, DontDelete}).

/be
Unfortunately for me, my build isn't reproducing the exploit that uses setTimeout right now, but this is something like what I had in mind. It lets content setTimeout on chrome windows (and run in content privs) and it lets same-origin sites do the same, but chrome gets downgraded principals if it tries to setTimeout on a content window.
Attachment #215932 - Flags: review?(jst)
Attached file testcase 4 (obsolete) —
make sure to try this testcase with the patches. it has two changes: first, it sets the prototype to null (so it will be reset); second, it uses setInterval instead of setTimeout.
Comment on attachment 215937 [details]
testcase 4

btw, setInterval will cause the browser to go into a continuous loop can only be stopped by killing the process
Attachment #215937 - Attachment is obsolete: true
Attached file testcase 4 (update)
sorry, i was missing a quote in the script on the last testcase. btw, is it possible to edit html attachments without having to reupload them?
Attached patch More JS_Set(UC)Property nukage (obsolete) — Splinter Review
I think these are the rest of the JS_Set(UC)Property calls that need to be JS_DefineProperty calls instead.
Attachment #215939 - Flags: review?(jst)
Whiteboard: [sg:critical] → [sg:critical] [patch]
Attachment #215932 - Flags: review?(jst) → review+
Comment on attachment 215939 [details] [diff] [review]
More JS_Set(UC)Property nukage

- In nsDOMClassInfo::ResolveConstructor():

+    if (!::JS_DefineUCProperty(cx, obj, ::JS_GetStringChars(str),
+                               ::JS_GetStringLength(str), val, NULL, NULL,
+                               JSPROP_PERMANENT | JSPROP_READONLY)) {

This should have JSPROP_ENUMERATE as well since the old code made the constructors enumerable.

- In nsWindowSH::GlobalResolve():

     if (!::JS_DefineProperty(cx, class_obj, "prototype", v, NULL, NULL,
-                             JSPROP_ENUMERATE)) {
+                             JSPROP_PERMANENT | JSPROP_READONLY)) {

Why is this not enumerable any more, ECMA?

- In nsHTMLDocumentSH::DocumentAllHelperNewResolve():

-      ::JS_SetProperty(cx, helper, "all", &v);
+      if (!::JS_DefineProperty(cx, helper, "all", v, NULL, NULL,
+                               JSPROP_READONLY)) {

JSPROP_ENUMERATE here too, no need to change the current behaviour, at least not in this patch.

r=jst with that looked into.
Attachment #215939 - Flags: review?(jst) → review+
(In reply to comment #25)
> (From update of attachment 215939 [details] [diff] [review] [edit])
> - In nsDOMClassInfo::ResolveConstructor():
> 
> +    if (!::JS_DefineUCProperty(cx, obj, ::JS_GetStringChars(str),
> +                               ::JS_GetStringLength(str), val, NULL, NULL,
> +                               JSPROP_PERMANENT | JSPROP_READONLY)) {
> 
> This should have JSPROP_ENUMERATE as well since the old code made the
> constructors enumerable.

Yeah, I made DOM class constructors enumerable, back in Netscape 2.  Not sure why -- to let people find 'em with for-in?  To distinguish them from the core language ctors, which are not enumerable?  Memory lane is closed.

> - In nsWindowSH::GlobalResolve():
> 
>      if (!::JS_DefineProperty(cx, class_obj, "prototype", v, NULL, NULL,
> -                             JSPROP_ENUMERATE)) {
> +                             JSPROP_PERMANENT | JSPROP_READONLY)) {
> 
> Why is this not enumerable any more, ECMA?

Yes, and Netscape 2.  See http://lxr.mozilla.org/classic/.

> - In nsHTMLDocumentSH::DocumentAllHelperNewResolve():
> 
> -      ::JS_SetProperty(cx, helper, "all", &v);
> +      if (!::JS_DefineProperty(cx, helper, "all", v, NULL, NULL,
> +                               JSPROP_READONLY)) {
> 
> JSPROP_ENUMERATE here too, no need to change the current behaviour, at least
> not in this patch.

What does IE do?

/be
Attachment #215932 - Flags: superreview?(bzbarsky)
Comment on attachment 215932 [details] [diff] [review]
Proposed setTimeout belt-and-braces

Rename "prin" to "ourPrincipal" and sr=bzbarsky.
Attachment #215932 - Flags: superreview?(bzbarsky) → superreview+
(In reply to comment #26)
> What does IE do?

It's enumerable in IE. I'll attach a new patch tomorrow.
I checked attachment 215932 [details] [diff] [review] into the trunk.
I think this patch uses the correct bits.
Attachment #215939 - Attachment is obsolete: true
Attachment #216133 - Flags: review?(jst)
Attached patch FollowupSplinter Review
Ugh, we _really_ need to figure out how to use nsRefPtr with nsTimeouts...
Attachment #216144 - Flags: superreview?(bzbarsky)
Attachment #216144 - Flags: review?(bzbarsky)
Comment on attachment 216144 [details] [diff] [review]
Followup

Verily.  :(
Attachment #216144 - Flags: superreview?(bzbarsky)
Attachment #216144 - Flags: superreview+
Attachment #216144 - Flags: review?(bzbarsky)
Attachment #216144 - Flags: review+
Attachment 216144 [details] [diff] has been checked in.
Comment on attachment 216133 [details] [diff] [review]
JS_Set(UC)Property nukage, v2

r=jst fwiw.
Attachment #216133 - Flags: review?(jst) → review+
Attachment #216133 - Flags: superreview?(brendan)
Comment on attachment 216133 [details] [diff] [review]
JS_Set(UC)Property nukage, v2

>+      if (!::JS_DefineProperty(cx, helper, "all", JSVAL_VOID, nsnull, nsnull,
>+                               JSPROP_ENUMERATE | JSPROP_READONLY)) {

READONLY without PERMANENT is not all that useful, since someone could delete document.all and assign a new .all that was different.  But maybe that's what is intended, for backward compatibility (with some pre-Firefox DHTML lib that attempts to emulate document.all in JS?).  If so, however, READONLY is problematic.

So I think you should use just ENUMERATE here.  sr=me with that.

/be
Attachment #216133 - Flags: superreview?(brendan) → superreview+
Final fix checked into trunk. I'll make a rollup patch for the 1.8 branches shortly.
Status: ASSIGNED → RESOLVED
Closed: 18 years ago
Resolution: --- → FIXED
'cvs up -j' is my friend.
Attachment #217039 - Flags: approval1.8.0.3?
Attachment #217039 - Flags: approval-branch-1.8.1?(jst)
Flags: blocking1.8.1+
Attachment #217039 - Flags: approval-branch-1.8.1?(jst) → approval-branch-1.8.1+
Fix checked into the 1.8 branch.
Keywords: fixed1.8.1
Comment on attachment 217039 [details] [diff] [review]
Roll-up patch for branches

approved for 1.8.0 branch, a=dveditz for drivers
Attachment #217039 - Flags: approval1.8.0.3? → approval1.8.0.3+
Fix checked into the 1.8.0 branch.
Keywords: fixed1.8.0.3
added to the security tests on the qa farm.
Flags: in-testsuite+
verified with Windows and Mac Ffx 1.5.0.4 builds from 20060508
Flags: blocking1.7.14+
Attached patch 1.0.x trySplinter Review
ff2b2 verified 1.8 fixed windows/linux
Group: security
Flags: in-testsuite+ → in-testsuite?
Keywords: testcase
Flags: in-testsuite? → in-testsuite-
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: