Closed
Bug 86984
Opened 23 years ago
Closed 23 years ago
trying to access history.length gives "access denied"
Categories
(Core :: Security, defect, P1)
Tracking
()
VERIFIED
FIXED
mozilla0.9.4
People
(Reporter: kchen, Assigned: security-bugs)
References
()
Details
(Whiteboard: patch)
Attachments
(1 file)
793 bytes,
patch
|
Details | Diff | Splinter Review |
I have a frameset page with 3 frames in it, frame0, frame1, and frame2. FRame0 is for header page. Frame1 is for text page, and frme2 is navigation page which contains three buttons, "previous", "next", or "cancel". After frame1 is loaded, it will load frame2 with another navigation page. When the "next" button on frame2 is clicked, frame1 signal frame1 to submit. when the "previous" button on frame2 is licked, frrame2 signal frame1 to go back history and then it execute parent.frames[2].history.back(); frame1 constantly check if "previous" button in frame2 is clicked(the signal). If it is clicked, frame1 execute parent.frames[1].history.back(). Therefore, click the "previous" button at frame2 let frame1 and frame2 both go back to their previous pages. This Works in NN 4.7 and IE5. In the build 2001060703, If I click "next", then "previous", then "next" and then "previous", frame1 first loads a new page and it then load frame2 a new page after first "next", then frame1 and frame2 both load the previos history pages after first "previous", then frame1 loads a new page and then loads frame2 a new page after second "next", but frame1 and frame2 do not load their history pages after second "previous". When I check the history of frame2 by parent.alert("history=" + parent.frames[2].history.length), I got "Error: uncaught exception : Permission denied to access property". If I check the history.length using Netsape 6.01, I got history.length = -1. The following are code for frames and pages. //-----------------in frames.asp <html><head> <title>Expandable Web Development</title> </head> <frameset rows="44, *, 78" frameborder=0 framespacing=0 noresize scrolling="no" cols="*" border=0> <frame marginwidth="0" marginheight="0" src="Header.asp" name="heading" noresize scrolling="no"> <frame marginwidth="10" marginheight="0" src="page1.asp" name="maintext"> <frame marginwidth="0" marginheight="0" src="Navigation.asp? prevs=Prev&nexts=Next&closes=Cancel" noresize scrolling="no"> </frameset> </html> //----------------in Header.asp <HTML><HEAD><TITLE></TITLE></HEAD> <body bgcolor=blue> </body></html> //----------------in page1.asp <%@ Language=JavaScript %> <!--#include file="exi.button.asp"--> <HTML><HEAD><TITLE></TITLE> </HEAD> <body onLoad="change_button(); IntervalID = setInterval('button_clicked()', 250);"> <form action="page2.asp" method=post id=form1 name=form1><h1> Page1</h1> <input name=prevs type=hidden value="none"> <input name=nexts type=hidden value="Next2"> <input name=closes type=hidden value="Cancel"> </form> </body> </html> //-----------------in page2.asp <%@ Language=JavaScript %> <!--#include file="exi.button.asp"--> <HTML><HEAD><TITLE></TITLE> </HEAD> <body onLoad="change_button(); IntervalID = setInterval('button_clicked()', 250);"> <form action="page2.asp" method=post id=form1 name=form1><h1> Page2</h1> <input name=prevs type=hidden value="prev1"> <input name=nexts type=hidden value="Next3"> <input name=closes type=hidden value="Cancel"> </form> </body> </html> //----------- in include file exi.button.asp <script LANGUAGE="JavaScript"> <!-- var npage= false; var submit_clicked = 0; var back_clicked = 0; var IntervalID; function button_clicked(){ if (submit_clicked != 0) { document.forms[0].submit(); submit_clicked = 0; } if (back_clicked != 0) { //parent.alert("history=" + parent.frames [1].history.length); parent.frames[1].history.back(); back_clicked = 0; } } function change_button() { //parent.alert("start change"); var s = "Navigation.asp"; if (document.forms[0].prevs.value) if (document.forms[0].prevs.value != "none") { if (s.indexOf("?") > 0) s += "&prevs=" + document.forms [0].prevs.value; else s += "?prevs=" + document.forms [0].prevs.value; } if (document.forms[0].nexts.value) if (document.forms[0].nexts.value != "none") { if (s.indexOf("?") > 0) s += "&nexts=" + document.forms [0].nexts.value; else s += "?nexts=" + document.forms [0].nexts.value; } if (document.forms[0].closes.value) if (document.forms[0].closes.value != "none") { if (s.indexOf("?") > 0) s += "&closes=" + document.forms [0].closes.value; else s += "?closes=" + document.forms [0].closes.value; } parent.frames[2].location = s; } //--> </script> //----------------- in Navigation.asp <%@ Language=JavaScript %> <html> <script language="JavaScript"> var prevs; var nexts; var closes; function go_backward() { if (parent.frames[1].back_clicked == 0) { parent.frames[1].back_clicked = 1; parent.alert("history=" + parent.frames[2].history.length); parent.frames[2].history.back(); } } function go_forward() { if (parent.frames[1].submit_clicked == 0) { parent.frames[1].submit_clicked = 1; if (parent.frames[1].document.forms[0] != null) if (parent.frames[1].document.forms[0].nextpage != null) parent.frames[1].document.forms [0].nextpage.value = ""; } } function close_windows() { parent.close(); } </script> <body background="images/bksample2.jpg"> <form name="navibar" onSubmit="return false;"> <% if (Request.QueryString("closes").Count != 0) { %> <table width=100%> <td align=center> <% if (Request.QueryString("prevs").Count != 0) { prevs = Request.QueryString("prevs").Item; Response.Write('<input type="button" value="' + prevs + '" style="font-weight:bold; " onClick="go_backward(); return false;" id=button1 name=button1>'); } if (Request.QueryString("nexts").Count != 0) { nexts = Request.QueryString("nexts").Item; Response.Write('<input type="button" value="' + nexts + '" style="font-weight:bold; " onClick="go_forward();" id=button2 name=button2>'); } if (Request.QueryString("closes").Count != 0) { closes = Request.QueryString("closes").Item; Response.Write('<input type="button" value="' + closes + '" style="font-weight:bold; " onClick="close_windows();" id=button3 name=button3>'); } %> </td> </table> <% } %> </form> </body> </html>
Comment 1•23 years ago
|
||
-> security
Assignee: asa → mstoltz
Component: Browser-General → Security: General
QA Contact: doronr → ckritzer
Assignee | ||
Comment 2•23 years ago
|
||
Wow, that was a lot of description, and quite confusing. I believe this boils down to being able to read the value of history.length. We've recently made the history.length function inaccessible to untrusted Javascript. We assumed there was no legitimate reason for a script to read history.length. Can you do what you need to do without using history.length? If not, I'll consider making it accessible again. I know it's accessible in NS4 and IE5. If you're having problems besides history.length, could you file those in a separate bug and attach a testcase? Your description is difficult to follow. Please let me know if you really need history.length. Otherwise this is a WONTFIX.
Status: UNCONFIRMED → ASSIGNED
Ever confirmed: true
Priority: -- → P4
Target Milestone: --- → mozilla0.9.3
Updated•23 years ago
|
Summary: got error : uncaught exception: Permission deneied to access property. → trying to access history.length gives "access denied"
Comment 4•23 years ago
|
||
I don't really think this should be a wontfix if it's easy enough to fix; etour.com uses this all of their site.
Comment 5•23 years ago
|
||
History.length gives information about how long you've been browsing and/or how you browse. It would be nice to continue preventing web sites from accessing this information.
Assignee | ||
Comment 6•23 years ago
|
||
Blake et al, How many sites out there use history.length? I realize it's a de facto standard to allow access to that one. I'm thinking about allowing access to that one again. How great a privacy risk is posed by history.length? Yes, it gives a little information about your browsing habits, but is it much more than a site could get from its server logs?
Comment 7•23 years ago
|
||
> How many sites out there use history.length?
So, I can't answer that one, but I can give you one very high-profile site that
depends on (at least) not aborting the script due to an attempt to access that
property -- travelocity.com. Go to www.travelocity.com and click on Flights or
Cars/Rail; the javascript console will show "uncaught exception: Permission
denied to access property". This aborts the top level script; I'm not sure how
much is broken (the popup calendars are one thing), but I'd rather not find out
later about how you can't do 'X' on Travelocity (and others?) with Netscape's
latest browser release.
I vote for allowing legacy access to this property (i.e., do what Nav4.x did,
even if it was in error). I don't see this as a substantive privacy issue.
Comment 8•23 years ago
|
||
Hmmmnn.... I see the points of view from Jesse, Mitch and JRGM's comments, and I'm inclined to move we re-enable the ability to view history.length, since it returns only an integer indicating how many websites you have in your history, and *nothing else*1. The one thing I'm wondering is: If we keep things as they are, do we have a simple work-around to offer to websites such as http://www.travelocity.com/ and others to avoid (effectively) killing their scripts? Bottom line: My vote is we re-enable the ability to view history.length, and cite legacy with NS4.x & IE. 1. Bad Monty Python reference
Comment 9•23 years ago
|
||
I would really like to see this change landed on the RTM branch. I don't believe there is a compelling privacy issue, and this flat out breaks de facto compatibility with other implementations (even if exposing this property occurred as an oversight at some point in the past). I assume the change is as simple as : Index: all.js =================================================================== RCS file: /cvsroot/mozilla/modules/libpref/src/init/all.js,v retrieving revision 3.251 diff -u -r3.251 all.js --- all.js 2001/07/03 01:05:00 3.251 +++ all.js 2001/07/15 05:14:26 @@ -174,7 +174,7 @@ pref("capability.policy.default.History.forward", "allAccess"); pref("capability.policy.default.History.go", "allAccess"); pref("capability.policy.default.History.item", "UniversalBrowserRead"); -pref("capability.policy.default.History.length", "UniversalBrowserRead"); +pref("capability.policy.default.History.length", "allAccess"); pref("capability.policy.default.History.next", "UniversalBrowserRead"); pref("capability.policy.default.History.previous", "UniversalBrowserRead"); pref("capability.policy.default.History.toString", "UniversalBrowserRead"); So, that's (1) a "zero" risk patch, (2) maintains backward compatibility with the de facto DOM 0 standard, (3) avoids breaking a top100 site, (4) exposes nothing that has not been exposed by previous shipping versions of Navigator 4.x, and that value is the number of URLs previously loaded in the current window, which in practice is pretty much a random number.
Comment 10•23 years ago
|
||
... and note that setting that to 'allAccess' does not open the ability to set that property since it is 'readonly attribute nsIDOMHistory history;' To answer ckritzer's question: we could return '0' to all getters instead of throwing an exception, but that would require extra C++ code to implement and hence risk. The only "workaround" as a JS author would be to add some kludge like var len; if (parseInt(navigator.appVersion) > 4) { //XXX need better sniff eval("try { len = history.length; } catch(e) { len = 0; }"); } else { len = history.length; } to existing scripts, but that's not practical.
Comment 11•23 years ago
|
||
This change landed 5/8/01 in jst's XPCDOM landing. cc'ing jst for input on why.
Comment 12•23 years ago
|
||
I don't know why this was changed, but I see very little reason for not exposing history.length to scripts, and since the urls of huge sites are mentioned here I don't think disabling access to something that's not protected in 4.x and IE is worth it. We should get this into the RTM branch! sr=jst for John's fix. Nisheeth, nsBranch?
Keywords: mozilla0.9.3
Comment 13•23 years ago
|
||
Opening history.length would not just allow web sites to figure out your
browsing habits. It would also allow sites to find out whether you have an
account at any site that redirects from a "logged-in" URL to a "please log in"
page when you're not logged in.
Note: making history.back() allAccess rather than sameOrigin, and allowing
history.go(-2) to jump over URLs at another domain, both lead to the same
privacy hole as opening history.length.
>The only "workaround" as a JS author would be to add some kludge like...
Or they could avoid using history.length when it isn't necessary.
By the way, an alternate solution to the history.length privacy problem that
might not break as many sites: make history.length only report the number of
contiguous history entires *at the same domain* unless the script has the
UniversalBrowserRead privilege.
Comment 14•23 years ago
|
||
Jesse sez: "Opening history.length would not just allow web sites to figure out your browsing habits. It would also allow sites to find out whether you have an account at any site that redirects from a "logged-in" URL to a "please log in" page when you're not logged in." ??? I'm confused by both these statements. Could you provide an example (either a script attachment or text in the bug) of how this is possible? The reason I ask is because I'm not seeing how you could do this when all history.length returns is an integer - I'm sure I'm missing something here.
Comment 15•23 years ago
|
||
For the record, 'history.length' was readable in Navigator 2.02, which was in public beta 6 years ago.
Comment 16•23 years ago
|
||
Here's how opening history.length would allow sites to find out whether you have an account at a site that redirects slowly from a "logged-in" URL to a "please log in" page when you're not logged in: 1. Open http://www.paysite.com/members in a frame. 2. Wait 10 seconds (because the redirect at paysite.com is 5 seconds). 3. In the frame, set location.href to a URL whose hostname is the same as the attacker's. 4. Wait 3 seconds so the page can load. 5. Ask the frame for its history.length. If history.length is 2, no redirect (or a fast redirect) occured; if history.length is 3, a slow redirect occurred.
Comment 17•23 years ago
|
||
Travelocity is broken. This one-line all.js file fix takes care of it. N4.X, Opera and IE give access to history.length. This makes us "common denominator" rather than different. PDT+ as per Clayton.
Whiteboard: [pdt+]
Comment 18•23 years ago
|
||
> -pref("capability.policy.default.History.length", "UniversalBrowserRead");
> +pref("capability.policy.default.History.length", "allAccess");
If we need to do this for 6.1, let's just remove the line and let it default to
sameOrigin, instead of making it allAccess. It's good practice to limit
allAccess to things that really need to be allAccess.
Internet Explorer doesn't even make window.history allAccess, so we won't break
IE-compatible sites by only letting scripts access history.length from windows
they own. I tested Travelocity with the line removed from my all.js and it
works fine.
Comment 19•23 years ago
|
||
Sure, just remove it if that is a better way to allow access to history.length. By the way, is Mitch checking this in? (I don't have access).
Comment 20•23 years ago
|
||
Checked into the branch using jst's cvs account. Here are some possibilities for the trunk: 1. Use the same fix on the trunk that was used on the branch. Advantages: doesn't break any sites. Disadvantages: leads to several privacy holes. 2. Make history.length only count contiguous sites at the same domain. Advantages: breaks very few or no sites Disadvantages: confusing to web page authors and creators of other browsers. 3. Leave history.length as UniversalBrowserRead, and try to convince travelocity to change their site. Advantages: sets a simple precedent that newer browsers can follow, says that Mozilla is committed to privacy. Disadvantages: breaks a few sites.
Updated•23 years ago
|
Comment 21•23 years ago
|
||
I appreciate that Security comes at this from a different, and valued, perspective, but I still don't see any compelling privacy issue here. In fact, I think that I had the right fix originally as 'allAccess', since that is what it has ~always been (or at least in Nav2.x and Nav4.x, haven't checked Nav3.x). This has been like this since Javascript first came out! And now, because this was unintentionally closed off for a while, this becomes something that needs "fixing"? At the end of the day, this is a cage match for jst and mstolz, but I cringe at the suggestion that we just cavalierly break any sites without really compelling reasons.
Assignee | ||
Comment 22•23 years ago
|
||
I need to correct some misconceptions here. History.length was intentionally restricted, because of the potential privacy violations Jesse mentions above. These are not inconsequential, but must be weighed against the inconvenience of breaking several sites that use this property. Compatibility with major websites is a compelling argument, "We've always done it this way" is not. If we have to do things the same way they were done in version 2, then why continue to release new versions? I'm OK with removing that line in all.js, causing history.length to revert to the default sameOrigin policy. As Jesse says, this will give us parity with IE. Let's not make it allAccess only because Netscape has always done it that way. That's not progress.
Assignee | ||
Comment 23•23 years ago
|
||
*** Bug 91283 has been marked as a duplicate of this bug. ***
Assignee | ||
Comment 24•23 years ago
|
||
Target is now 0.9.4, Priority P1.
Priority: P4 → P1
Target Milestone: mozilla0.9.3 → mozilla0.9.4
Assignee | ||
Comment 25•23 years ago
|
||
*** Bug 92353 has been marked as a duplicate of this bug. ***
Assignee | ||
Comment 26•23 years ago
|
||
Assignee | ||
Updated•23 years ago
|
Whiteboard: patch
Comment 27•23 years ago
|
||
sr=jst
Assignee | ||
Comment 28•23 years ago
|
||
Fix checked in.
Status: ASSIGNED → RESOLVED
Closed: 23 years ago
Resolution: --- → FIXED
Comment 29•23 years ago
|
||
Actually, Mitch, I think you missed this with your checkin yesterday.
Status: RESOLVED → REOPENED
Resolution: FIXED → ---
Updated•23 years ago
|
Comment 30•23 years ago
|
||
a=asa on behalf of drivers.
Assignee | ||
Comment 31•23 years ago
|
||
Fix checked in (for real this time).
Status: REOPENED → RESOLVED
Closed: 23 years ago → 23 years ago
Resolution: --- → FIXED
Comment 32•23 years ago
|
||
Verified on 2001-09-19-03 build on WinNT Wrote the following test case to test the history.length. <html> <head> <script> function openHistoryWindow () { // Open a new window var w = window.open ("", "historyWindow", "width=500,height=300,menubar,resizable"); var d = w.document; //Request a privilege netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); // Output the browsing history of this window as links in the new window for (var i = 0; i < history.length; i++) { d.write('<A TARGET = "new" HREF="' + history[i] + '">'); d.write(history[i]); d.writeln('</A>'); } d.close (); //return the new window return w; } </script> </head> <body> <form> <input type="button" onclick="openHistoryWindow();" value="Click to get the History"> </form> </body> </html> The test works as expected
Status: RESOLVED → VERIFIED
Comment 33•23 years ago
|
||
changed the QA contact to bsharma@netscape.com
QA Contact: ckritzer → bsharma
Comment 34•23 years ago
|
||
slight nit. That testcase requests priveleges. The point of this bug was that (for better or worse) 'history.length' was, by default, readable. This is the minimal testcase: <p>is there a number (even 0) below? or was an exception thrown</p> <script> var foo = history.length; document.write(foo); </script>
Comment 35•23 years ago
|
||
Verified above test case, and got '6' on the browser window. So, history.length is accessible without privileges which is ecpected.
You need to log in
before you can comment on or make changes to this bug.
Description
•