Commit 8fd99238 authored by mohammed@chromium.org's avatar mohammed@chromium.org

Implementing mixed content for forms posting to insecure location from secure ones.

When a form "action" attribute is pointing to insecure location from a secure one, this is flagged as mixed content. This is even checked if the pages dynamically changes the "action" attribute after the initial loading. The mixed content is non-blocking by default, however, this can be overridden in the preferences, and this will cause a submit to silently fail, other than a console message.

Committed: https://src.chromium.org/viewvc/blink?view=rev&revision=175714

Review URL: https://codereview.chromium.org/311033003

git-svn-id: svn://svn.chromium.org/blink/trunk@175926 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 27e08ca5
CONSOLE WARNING: line 2: The page at 'https://127.0.0.1:8443/security/mixedContent/resources/frame-with-insecure-formSubmission.html' was loaded over HTTPS, but is submitting data to an insecure location at 'http://127.0.0.1:8080/security/resources/boring.html': this content should also be submitted over HTTPS.
This test opens a window that shows a form with "action" pointing to insecure location. We should trigger a mixed content callback even though we've set the preference to block this, because we've overriden the preferences via a web permission client callback.
<html>
<body>
<script>
if (window.testRunner) {
testRunner.waitUntilDone();
testRunner.dumpAsText();
testRunner.setCanOpenWindows();
testRunner.setCloseRemainingWindowsWhenComplete(true);
testRunner.overridePreference("WebKitAllowDisplayingInsecureContent", false);
testRunner.setAllowDisplayOfInsecureContent(true);
}
window.addEventListener("message", function (e) {
if (window.testRunner)
testRunner.notifyDone();
}, false);
</script>
<p>This test opens a window that shows a form with "action" pointing to insecure
location. We should trigger a mixed content callback even though we've set the preference to block this, because we've overriden the preferences via a web permission client callback.</p>
<script>
window.open("https://127.0.0.1:8443/security/mixedContent/resources/frame-with-insecure-formSubmission.html");
</script>
</body>
</html>
CONSOLE ERROR: line 2: [blocked] The page at 'https://127.0.0.1:8443/security/mixedContent/resources/frame-with-insecure-formSubmission.html' was loaded over HTTPS, but is submitting data to an insecure location at 'http://127.0.0.1:8080/security/resources/boring.html': this content should also be submitted over HTTPS.
This test opens a window that shows a form with "action" pointing to an insecure location. We should not trigger a mixed content callback even though the main frame in the window is HTTPS and the form is pointing to insecure content, because we've set the preference to block this.
<html>
<body>
<script>
if (window.testRunner) {
testRunner.waitUntilDone();
testRunner.dumpAsText();
testRunner.setCanOpenWindows();
testRunner.setCloseRemainingWindowsWhenComplete(true);
testRunner.overridePreference("WebKitAllowDisplayingInsecureContent", false);
}
window.addEventListener("message", function (e) {
if (window.testRunner)
testRunner.notifyDone();
}, false);
</script>
<p>This test opens a window that shows a form with "action" pointing to an insecure
location. We should not trigger a mixed content callback even though the main frame in the window is HTTPS and the form is pointing to insecure content, because we've set the preference to block this.</p>
<script>
window.open("https://127.0.0.1:8443/security/mixedContent/resources/frame-with-insecure-formSubmission.html");
</script>
</body>
</html>
CONSOLE WARNING: line 2: The page at 'https://127.0.0.1:8443/security/mixedContent/resources/frame-with-insecure-formSubmission.html' was loaded over HTTPS, but is submitting data to an insecure location at 'http://127.0.0.1:8080/security/resources/boring.html': this content should also be submitted over HTTPS.
This test opens a window that shows a form with "action" pointing to an insecure location. We should trigger a mixed content callback because the main frame in the window is HTTPS but is posting to insecure location.
<html>
<body>
<script>
if (window.testRunner) {
testRunner.waitUntilDone();
testRunner.dumpAsText();
testRunner.setCanOpenWindows();
testRunner.setCloseRemainingWindowsWhenComplete(true);
}
window.addEventListener("message", function (e) {
if (window.testRunner)
testRunner.notifyDone();
}, false);
</script>
<p>This test opens a window that shows a form with "action" pointing to an insecure
location. We should trigger a mixed content callback because the main frame
in the window is HTTPS but is posting to insecure location.</p>
<script>
window.open("https://127.0.0.1:8443/security/mixedContent/resources/frame-with-insecure-formSubmission.html");
</script>
</body>
</html>
<form action="http://127.0.0.1:8080/security/resources/boring.html"
method="post">
</form>
<script>
window.onload = function() {
if (window.opener)
window.opener.postMessage('done', '*');
};
</script>
<form action="http://127.0.0.1:8000/security/originHeader/resources/print-origin.cgi" <form action="https://127.0.0.1:8443/security/originHeader/resources/print-origin.cgi"
method="POST"> method="POST">
</form> </form>
<script>document.forms[0].submit();</script> <script>document.forms[0].submit();</script>
...@@ -438,6 +438,8 @@ public: ...@@ -438,6 +438,8 @@ public:
MixedContentImage = 438, MixedContentImage = 438,
MixedContentMedia = 439, MixedContentMedia = 439,
DocumentFonts = 440, DocumentFonts = 440,
MixedContentFormsSubmitted = 441,
FormsSubmitted = 442,
// Add new features immediately above this line. Don't change assigned // Add new features immediately above this line. Don't change assigned
// numbers of any item, and don't reuse removed slots. // numbers of any item, and don't reuse removed slots.
// Also, run update_use_counter_feature_enum.py in chromium/src/tools/metrics/histograms/ // Also, run update_use_counter_feature_enum.py in chromium/src/tools/metrics/histograms/
......
...@@ -37,6 +37,10 @@ ...@@ -37,6 +37,10 @@
#include "core/events/Event.h" #include "core/events/Event.h"
#include "core/events/GenericEventQueue.h" #include "core/events/GenericEventQueue.h"
#include "core/events/ScopedEventQueue.h" #include "core/events/ScopedEventQueue.h"
#include "core/frame/DOMWindow.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/UseCounter.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/HTMLCollection.h" #include "core/html/HTMLCollection.h"
#include "core/html/HTMLDialogElement.h" #include "core/html/HTMLDialogElement.h"
#include "core/html/HTMLImageElement.h" #include "core/html/HTMLImageElement.h"
...@@ -46,12 +50,10 @@ ...@@ -46,12 +50,10 @@
#include "core/html/forms/FormController.h" #include "core/html/forms/FormController.h"
#include "core/loader/FrameLoader.h" #include "core/loader/FrameLoader.h"
#include "core/loader/FrameLoaderClient.h" #include "core/loader/FrameLoaderClient.h"
#include "core/frame/DOMWindow.h" #include "core/loader/MixedContentChecker.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/UseCounter.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/rendering/RenderTextControl.h" #include "core/rendering/RenderTextControl.h"
#include "platform/UserGestureIndicator.h" #include "platform/UserGestureIndicator.h"
#include "wtf/text/AtomicString.h"
using namespace std; using namespace std;
...@@ -392,7 +394,7 @@ void HTMLFormElement::scheduleFormSubmission(PassRefPtr<FormSubmission> submissi ...@@ -392,7 +394,7 @@ void HTMLFormElement::scheduleFormSubmission(PassRefPtr<FormSubmission> submissi
} }
if (protocolIsJavaScript(submission->action())) { if (protocolIsJavaScript(submission->action())) {
if (!document().contentSecurityPolicy()->allowFormAction(KURL(submission->action()))) if (!document().contentSecurityPolicy()->allowFormAction(submission->action()))
return; return;
document().frame()->script().executeScriptIfJavaScriptURL(submission->action()); document().frame()->script().executeScriptIfJavaScriptURL(submission->action());
return; return;
...@@ -409,6 +411,14 @@ void HTMLFormElement::scheduleFormSubmission(PassRefPtr<FormSubmission> submissi ...@@ -409,6 +411,14 @@ void HTMLFormElement::scheduleFormSubmission(PassRefPtr<FormSubmission> submissi
if (!targetFrame->page()) if (!targetFrame->page())
return; return;
if (MixedContentChecker::isMixedContent(document().securityOrigin(), submission->action())) {
UseCounter::count(document(), UseCounter::MixedContentFormsSubmitted);
if (!document().frame()->loader().mixedContentChecker()->canSubmitToInsecureForm(document().securityOrigin(), submission->action()))
return;
} else {
UseCounter::count(document(), UseCounter::FormsSubmitted);
}
submission->setReferrer(Referrer(document().outgoingReferrer(), document().referrerPolicy())); submission->setReferrer(Referrer(document().outgoingReferrer(), document().referrerPolicy()));
submission->setOrigin(document().outgoingOrigin()); submission->setOrigin(document().outgoingOrigin());
...@@ -476,9 +486,14 @@ void HTMLFormElement::finishRequestAutocomplete(AutocompleteResult result) ...@@ -476,9 +486,14 @@ void HTMLFormElement::finishRequestAutocomplete(AutocompleteResult result)
void HTMLFormElement::parseAttribute(const QualifiedName& name, const AtomicString& value) void HTMLFormElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
{ {
if (name == actionAttr) if (name == actionAttr) {
m_attributes.parseAction(value); m_attributes.parseAction(document(), value);
else if (name == targetAttr) // If the new action attribute is pointing to insecure "action" location from a secure page
// it is marked as "passive" mixed content.
KURL actionURL = m_attributes.action().isEmpty() ? document().url() : m_attributes.action();
if (MixedContentChecker::isMixedContent(document().securityOrigin(), actionURL))
document().frame()->loader().mixedContentChecker()->canSubmitToInsecureForm(document().securityOrigin(), actionURL);
} else if (name == targetAttr)
m_attributes.setTarget(value); m_attributes.setTarget(value);
else if (name == methodAttr) else if (name == methodAttr)
m_attributes.updateMethodType(value); m_attributes.updateMethodType(value);
......
...@@ -83,10 +83,9 @@ static void appendMailtoPostFormDataToURL(KURL& url, const FormData& data, const ...@@ -83,10 +83,9 @@ static void appendMailtoPostFormDataToURL(KURL& url, const FormData& data, const
url.setQuery(query.toString()); url.setQuery(query.toString());
} }
void FormSubmission::Attributes::parseAction(const String& action) void FormSubmission::Attributes::parseAction(const Document& document, const String& action)
{ {
// FIXME: Can we parse into a KURL? m_action = action.isEmpty() ? KURL() : document.completeURL(stripLeadingAndTrailingHTMLSpaces(action));
m_action = stripLeadingAndTrailingHTMLSpaces(action);
} }
AtomicString FormSubmission::Attributes::parseEncodingType(const String& type) AtomicString FormSubmission::Attributes::parseEncodingType(const String& type)
...@@ -180,7 +179,7 @@ PassRefPtr<FormSubmission> FormSubmission::create(HTMLFormElement* form, const A ...@@ -180,7 +179,7 @@ PassRefPtr<FormSubmission> FormSubmission::create(HTMLFormElement* form, const A
if (submitButton) { if (submitButton) {
AtomicString attributeValue; AtomicString attributeValue;
if (!(attributeValue = submitButton->fastGetAttribute(formactionAttr)).isNull()) if (!(attributeValue = submitButton->fastGetAttribute(formactionAttr)).isNull())
copiedAttributes.parseAction(attributeValue); copiedAttributes.parseAction(form->document(), attributeValue);
if (!(attributeValue = submitButton->fastGetAttribute(formenctypeAttr)).isNull()) if (!(attributeValue = submitButton->fastGetAttribute(formenctypeAttr)).isNull())
copiedAttributes.updateEncodingType(attributeValue); copiedAttributes.updateEncodingType(attributeValue);
if (!(attributeValue = submitButton->fastGetAttribute(formmethodAttr)).isNull()) if (!(attributeValue = submitButton->fastGetAttribute(formmethodAttr)).isNull())
...@@ -196,7 +195,7 @@ PassRefPtr<FormSubmission> FormSubmission::create(HTMLFormElement* form, const A ...@@ -196,7 +195,7 @@ PassRefPtr<FormSubmission> FormSubmission::create(HTMLFormElement* form, const A
} }
Document& document = form->document(); Document& document = form->document();
KURL actionURL = document.completeURL(copiedAttributes.action().isEmpty() ? document.url().string() : copiedAttributes.action()); KURL actionURL = copiedAttributes.action().isEmpty() ? document.url() : copiedAttributes.action();
bool isMailtoForm = actionURL.protocolIs("mailto"); bool isMailtoForm = actionURL.protocolIs("mailto");
bool isMultiPartForm = false; bool isMultiPartForm = false;
AtomicString encodingType = copiedAttributes.encodingType(); AtomicString encodingType = copiedAttributes.encodingType();
......
...@@ -67,8 +67,8 @@ public: ...@@ -67,8 +67,8 @@ public:
void updateMethodType(const String&); void updateMethodType(const String&);
static String methodString(Method); static String methodString(Method);
const String& action() const { return m_action; } const KURL& action() const { return m_action; }
void parseAction(const String&); void parseAction(const Document&, const String&);
const AtomicString& target() const { return m_target; } const AtomicString& target() const { return m_target; }
void setTarget(const AtomicString& target) { m_target = target; } void setTarget(const AtomicString& target) { m_target = target; }
...@@ -87,7 +87,7 @@ public: ...@@ -87,7 +87,7 @@ public:
Method m_method; Method m_method;
bool m_isMultiPartForm; bool m_isMultiPartForm;
String m_action; KURL m_action;
AtomicString m_target; AtomicString m_target;
AtomicString m_encodingType; AtomicString m_encodingType;
String m_acceptCharset; String m_acceptCharset;
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include "core/loader/FrameLoader.h" #include "core/loader/FrameLoader.h"
#include "core/loader/FrameLoaderClient.h" #include "core/loader/FrameLoaderClient.h"
#include "platform/weborigin/SecurityOrigin.h" #include "platform/weborigin/SecurityOrigin.h"
#include "wtf/text/StringBuilder.h"
namespace WebCore { namespace WebCore {
...@@ -58,14 +59,14 @@ bool MixedContentChecker::isMixedContent(SecurityOrigin* securityOrigin, const K ...@@ -58,14 +59,14 @@ bool MixedContentChecker::isMixedContent(SecurityOrigin* securityOrigin, const K
return !SecurityOrigin::isSecure(url); return !SecurityOrigin::isSecure(url);
} }
bool MixedContentChecker::canDisplayInsecureContent(SecurityOrigin* securityOrigin, const KURL& url) const bool MixedContentChecker::canDisplayInsecureContentInternal(SecurityOrigin* securityOrigin, const KURL& url, const MixedContentType type) const
{ {
if (!isMixedContent(securityOrigin, url)) if (!isMixedContent(securityOrigin, url))
return true; return true;
Settings* settings = m_frame->settings(); Settings* settings = m_frame->settings();
bool allowed = client()->allowDisplayingInsecureContent(settings && settings->allowDisplayOfInsecureContent(), securityOrigin, url); bool allowed = client()->allowDisplayingInsecureContent(settings && settings->allowDisplayOfInsecureContent(), securityOrigin, url);
logWarning(allowed, "displayed", url); logWarning(allowed, url, type);
if (allowed) if (allowed)
client()->didDisplayInsecureContent(); client()->didDisplayInsecureContent();
...@@ -73,15 +74,15 @@ bool MixedContentChecker::canDisplayInsecureContent(SecurityOrigin* securityOrig ...@@ -73,15 +74,15 @@ bool MixedContentChecker::canDisplayInsecureContent(SecurityOrigin* securityOrig
return allowed; return allowed;
} }
bool MixedContentChecker::canRunInsecureContentInternal(SecurityOrigin* securityOrigin, const KURL& url, bool isWebSocket) const bool MixedContentChecker::canRunInsecureContentInternal(SecurityOrigin* securityOrigin, const KURL& url, const MixedContentType type) const
{ {
if (!isMixedContent(securityOrigin, url)) if (!isMixedContent(securityOrigin, url))
return true; return true;
Settings* settings = m_frame->settings(); Settings* settings = m_frame->settings();
bool allowedPerSettings = settings && (settings->allowRunningOfInsecureContent() || (isWebSocket && settings->allowConnectingInsecureWebSocket())); bool allowedPerSettings = settings && (settings->allowRunningOfInsecureContent() || ((type == WebSocket) && settings->allowConnectingInsecureWebSocket()));
bool allowed = client()->allowRunningInsecureContent(allowedPerSettings, securityOrigin, url); bool allowed = client()->allowRunningInsecureContent(allowedPerSettings, securityOrigin, url);
logWarning(allowed, "ran", url); logWarning(allowed, url, type);
if (allowed) if (allowed)
client()->didRunInsecureContent(securityOrigin, url); client()->didRunInsecureContent(securityOrigin, url);
...@@ -89,11 +90,25 @@ bool MixedContentChecker::canRunInsecureContentInternal(SecurityOrigin* security ...@@ -89,11 +90,25 @@ bool MixedContentChecker::canRunInsecureContentInternal(SecurityOrigin* security
return allowed; return allowed;
} }
void MixedContentChecker::logWarning(bool allowed, const String& action, const KURL& target) const void MixedContentChecker::logWarning(bool allowed, const KURL& target, const MixedContentType type) const
{ {
String message = String(allowed ? "" : "[blocked] ") + "The page at '" + m_frame->document()->url().elidedString() + "' was loaded over HTTPS, but " + action + " insecure content from '" + target.elidedString() + "': this content should also be loaded over HTTPS.\n"; StringBuilder message;
message.append((allowed ? "" : "[blocked] "));
message.append("The page at '" + m_frame->document()->url().elidedString() + "' was loaded over HTTPS, but ");
switch (type) {
case Display:
message.append("displayed insecure content from '" + target.elidedString() + "': this content should also be loaded over HTTPS.\n");
break;
case Execution:
case WebSocket:
message.append("ran insecure content from '" + target.elidedString() + "': this content should also be loaded over HTTPS.\n");
break;
case Submission:
message.append("is submitting data to an insecure location at '" + target.elidedString() + "': this content should also be submitted over HTTPS.\n");
break;
}
MessageLevel messageLevel = allowed ? WarningMessageLevel : ErrorMessageLevel; MessageLevel messageLevel = allowed ? WarningMessageLevel : ErrorMessageLevel;
m_frame->document()->addConsoleMessage(SecurityMessageSource, messageLevel, message); m_frame->document()->addConsoleMessage(SecurityMessageSource, messageLevel, message.toString());
} }
} // namespace WebCore } // namespace WebCore
...@@ -45,24 +45,41 @@ class MixedContentChecker { ...@@ -45,24 +45,41 @@ class MixedContentChecker {
public: public:
MixedContentChecker(LocalFrame*); MixedContentChecker(LocalFrame*);
bool canDisplayInsecureContent(SecurityOrigin*, const KURL&) const; bool canDisplayInsecureContent(SecurityOrigin* securityOrigin, const KURL& url) const
{
return canDisplayInsecureContentInternal(securityOrigin, url, MixedContentChecker::Display);
}
bool canSubmitToInsecureForm(SecurityOrigin* securityOrigin, const KURL& url) const
{
return canDisplayInsecureContentInternal(securityOrigin, url, MixedContentChecker::Submission);
}
bool canRunInsecureContent(SecurityOrigin* securityOrigin, const KURL& url) const bool canRunInsecureContent(SecurityOrigin* securityOrigin, const KURL& url) const
{ {
return canRunInsecureContentInternal(securityOrigin, url, false); return canRunInsecureContentInternal(securityOrigin, url, MixedContentChecker::Execution);
} }
bool canConnectInsecureWebSocket(SecurityOrigin* securityOrigin, const KURL& url) const bool canConnectInsecureWebSocket(SecurityOrigin* securityOrigin, const KURL& url) const
{ {
return canRunInsecureContentInternal(securityOrigin, url, true); return canRunInsecureContentInternal(securityOrigin, url, MixedContentChecker::WebSocket);
} }
static bool isMixedContent(SecurityOrigin*, const KURL&); static bool isMixedContent(SecurityOrigin*, const KURL&);
private: private:
enum MixedContentType {
Display,
Execution,
WebSocket,
Submission
};
// FIXME: This should probably have a separate client from FrameLoader. // FIXME: This should probably have a separate client from FrameLoader.
FrameLoaderClient* client() const; FrameLoaderClient* client() const;
bool canRunInsecureContentInternal(SecurityOrigin*, const KURL&, bool isWebSocket) const; bool canDisplayInsecureContentInternal(SecurityOrigin*, const KURL&, const MixedContentType) const;
bool canRunInsecureContentInternal(SecurityOrigin*, const KURL&, const MixedContentType) const;
void logWarning(bool allowed, const String& action, const KURL&) const; void logWarning(bool allowed, const KURL& i, const MixedContentType) const;
LocalFrame* m_frame; LocalFrame* m_frame;
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment