bug 761655 - Support for multiple X-Frame-Options policies (multiple headers or comma in header value). r=jst

This commit is contained in:
Sid Stamm 2012-06-11 09:17:35 -07:00
parent ecc29d4288
commit 7ae666a35f
5 changed files with 107 additions and 36 deletions

View File

@ -14,6 +14,11 @@ window.addEventListener('load', parent.testFramesLoaded, false);
<iframe id="deny" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=deny&xfo=deny"></iframe><br>
<iframe id="sameorigin1" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin1&xfo=sameorigin"></iframe><br>
<iframe id="sameorigin2" src="http://example.com/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin2&xfo=sameorigin"></iframe><br>
<iframe id="sameorigin5" src="http://example.com/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin5&xfo=sameorigin2"></iframe><br>
<iframe id="sameorigin6" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin6&xfo=sameorigin2"></iframe><br>
<iframe id="sameorigin7" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin7&xfo=sameorigin3"></iframe><br>
<iframe id="sameorigin8" src="http://example.com/tests/content/base/test/file_x-frame-options_page.sjs?testid=sameorigin8&xfo=sameorigin3"></iframe><br>
<iframe id="mixedpolicy" src="http://mochi.test:8888/tests/content/base/test/file_x-frame-options_page.sjs?testid=mixedpolicy&xfo=mixedpolicy"></iframe><br>
</body>
</html>

View File

@ -17,6 +17,15 @@ function handleRequest(request, response)
else if (query['xfo'] == "sameorigin") {
response.setHeader("X-Frame-Options", "SAMEORIGIN", false);
}
else if (query['xfo'] == "sameorigin2") {
response.setHeader("X-Frame-Options", "SAMEORIGIN, SAMEORIGIN", false);
}
else if (query['xfo'] == "sameorigin3") {
response.setHeader("X-Frame-Options", "SAMEORIGIN,SAMEORIGIN , SAMEORIGIN", false);
}
else if (query['xfo'] == "mixedpolicy") {
response.setHeader("X-Frame-Options", "DENY,SAMEORIGIN", false);
}
// from the test harness we'll be checking for the presence of this element
// to test if the page loaded

View File

@ -83,6 +83,31 @@ var testFramesLoaded = function() {
var test5 = frame.contentDocument.getElementById("test");
is(test5, null, "test sameorigin2");
// iframe from different origin, X-F-O: SAMEORIGIN, SAMEORIGIN - should not load
frame = harness.contentDocument.getElementById("sameorigin5");
var test6 = frame.contentDocument.getElementById("test");
is(test6, null, "test sameorigin5");
// iframe from same origin, X-F-O: SAMEORIGIN, SAMEORIGIN - should load
frame = harness.contentDocument.getElementById("sameorigin6");
var test7 = frame.contentDocument.getElementById("test").textContent;
is(test7, "sameorigin6", "test sameorigin6");
// iframe from same origin, X-F-O: SAMEORIGIN,SAMEORIGIN, SAMEORIGIN - should load
frame = harness.contentDocument.getElementById("sameorigin7");
var test8 = frame.contentDocument.getElementById("test").textContent;
is(test8, "sameorigin7", "test sameorigin7");
// iframe from same origin, X-F-O: SAMEORIGIN,SAMEORIGIN, SAMEORIGIN - should not load
frame = harness.contentDocument.getElementById("sameorigin8");
var test9 = frame.contentDocument.getElementById("test");
is(test9, null, "test sameorigin8");
// iframe from same origin, X-F-O: DENY,SAMEORIGIN - should not load
frame = harness.contentDocument.getElementById("mixedpolicy");
var test10 = frame.contentDocument.getElementById("test");
is(test10, null, "test mixedpolicy");
// call tests to check principal comparison, e.g. a document can open a window
// to a data: or javascript: document which frames an
// X-Frame-Options: SAMEORIGIN document and the frame should load

View File

@ -15,6 +15,7 @@
#include "nsIHttpChannel.h"
#include "nsIScriptSecurityManager.h"
#include "nsNetError.h"
#include "nsCharSeparatedTokenizer.h"
#include "mozilla/Preferences.h"
using namespace mozilla;
@ -269,29 +270,18 @@ nsDSURIContentListener::SetParentContentListener(nsIURIContentListener*
return NS_OK;
}
// Check if X-Frame-Options permits this document to be loaded as a subdocument.
bool nsDSURIContentListener::CheckFrameOptions(nsIRequest* request)
{
// If X-Frame-Options checking is disabled, return true unconditionally.
if (sIgnoreXFrameOptions) {
bool nsDSURIContentListener::CheckOneFrameOptionsPolicy(nsIRequest *request,
const nsAString& policy) {
// return early if header does not have one of the two values with meaning
if (!policy.LowerCaseEqualsLiteral("deny") &&
!policy.LowerCaseEqualsLiteral("sameorigin"))
return true;
}
nsCAutoString xfoHeaderValue;
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
if (!httpChannel) {
return true;
}
httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Frame-Options"),
xfoHeaderValue);
// return early if header does not have one of the two values with meaning
if (!xfoHeaderValue.LowerCaseEqualsLiteral("deny") &&
!xfoHeaderValue.LowerCaseEqualsLiteral("sameorigin"))
return true;
if (mDocShell) {
// We need to check the location of this window and the location of the top
// window, if we're not the top. X-F-O: SAMEORIGIN requires that the
@ -321,8 +311,10 @@ bool nsDSURIContentListener::CheckFrameOptions(nsIRequest* request)
nsresult rv;
nsCOMPtr<nsIScriptSecurityManager> ssm =
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
if (!ssm)
if (!ssm) {
NS_ASSERTION(ssm, "Failed to get the ScriptSecurityManager.");
return false;
}
// Traverse up the parent chain to the top docshell that doesn't have
// a system principal
@ -333,6 +325,7 @@ bool nsDSURIContentListener::CheckFrameOptions(nsIRequest* request)
if (topDoc) {
if (NS_SUCCEEDED(ssm->IsSystemPrincipal(topDoc->NodePrincipal(),
&system)) && system) {
// Found a system-principled doc: last docshell was top.
break;
}
}
@ -347,34 +340,71 @@ bool nsDSURIContentListener::CheckFrameOptions(nsIRequest* request)
if (curDocShellItem == thisDocShellItem)
return true;
// If the value of the header is DENY, and the previous condition is
// not met (current docshell is not the top docshell), prohibit the
// load.
if (policy.LowerCaseEqualsLiteral("deny")) {
return false;
}
// If the X-Frame-Options value is SAMEORIGIN, then the top frame in the
// parent chain must be from the same origin as this document.
if (xfoHeaderValue.LowerCaseEqualsLiteral("sameorigin")) {
if (policy.LowerCaseEqualsLiteral("sameorigin")) {
nsCOMPtr<nsIURI> uri;
httpChannel->GetURI(getter_AddRefs(uri));
topDoc = do_GetInterface(curDocShellItem);
nsCOMPtr<nsIURI> topUri;
topDoc->NodePrincipal()->GetURI(getter_AddRefs(topUri));
rv = ssm->CheckSameOriginURI(uri, topUri, true);
if (NS_SUCCEEDED(rv))
return true;
if (NS_FAILED(rv))
return false; /* wasn't same-origin */
}
}
return true;
}
// Check if X-Frame-Options permits this document to be loaded as a subdocument.
// This will iterate through and check any number of X-Frame-Options policies
// in the request (comma-separated in a header, multiple headers, etc).
bool nsDSURIContentListener::CheckFrameOptions(nsIRequest *request)
{
// If X-Frame-Options checking is disabled, return true unconditionally.
if (sIgnoreXFrameOptions) {
return true;
}
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
if (!httpChannel) {
return true;
}
nsCAutoString xfoHeaderCValue;
httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Frame-Options"),
xfoHeaderCValue);
NS_ConvertUTF8toUTF16 xfoHeaderValue(xfoHeaderCValue);
// if no header value, there's nothing to do.
if (xfoHeaderValue.IsEmpty())
return true;
// iterate through all the header values (usually there's only one, but can
// be many. If any want to deny the load, deny the load.
nsCharSeparatedTokenizer tokenizer(xfoHeaderValue, ',');
while (tokenizer.hasMoreTokens()) {
const nsSubstring& tok = tokenizer.nextToken();
if (!CheckOneFrameOptionsPolicy(request, tok)) {
// cancel the load and display about:blank
httpChannel->Cancel(NS_BINDING_ABORTED);
if (mDocShell) {
nsCOMPtr<nsIWebNavigation> webNav(do_QueryObject(mDocShell));
if (webNav) {
webNav->LoadURI(NS_LITERAL_STRING("about:blank").get(),
0, nsnull, nsnull, nsnull);
}
}
return false;
}
else {
// If the value of the header is DENY, then the document
// should never be permitted to load as a subdocument.
NS_ASSERTION(xfoHeaderValue.LowerCaseEqualsLiteral("deny"),
"How did we get here with some random header value?");
}
// cancel the load and display about:blank
httpChannel->Cancel(NS_BINDING_ABORTED);
nsCOMPtr<nsIWebNavigation> webNav(do_QueryObject(mDocShell));
if (webNav) {
webNav->LoadURI(NS_LITERAL_STRING("about:blank").get(),
0, nsnull, nsnull, nsnull);
}
return false;
}
return true;

View File

@ -38,6 +38,8 @@ protected:
// Determine if X-Frame-Options allows content to be framed
// as a subdocument
bool CheckFrameOptions(nsIRequest* request);
bool CheckOneFrameOptionsPolicy(nsIRequest* request,
const nsAString& policy);
protected:
nsDocShell* mDocShell;