[go: up one dir, main page]

Source/WebCore/ChangeLog

 12016-12-02 John Wilander <wilander@apple.com>
 2
 3 Require preflight for non-standard CORS-safelisted request headers Accept, Accept-Language, and Content-Language
 4 https://bugs.webkit.org/show_bug.cgi?id=165178
 5 <rdar://problem/18792250>
 6
 7 Reviewed by Youenn Fablet.
 8
 9 Fetch currently only restricts the header Content-Type for simple requests:
 10 https://fetch.spec.whatwg.org/#cors-safelisted-request-header
 11
 12 This means simple CORS requests can send unexpected characters in Accept,
 13 Accept-Language, and Content-Language header values.
 14
 15 RFC 7231 implies restrictions on these header values:
 16 - Accept https://tools.ietf.org/html/rfc7231#section-5.3.2
 17 - Accept-Language https://tools.ietf.org/html/rfc7231#section-5.3.5
 18 - Content-Language https://tools.ietf.org/html/rfc7231#section-3.1.3.2
 19
 20 As per discussions in the W3C WebAppSec group we should try to restrict
 21 these header values to help protect servers that do not expect simple CORS
 22 requests.
 23
 24 Non-standard, safelisted header values should trigger a preflight and require
 25 the headers to be whitelisted in the response's Access-Control-Allow-Headers.
 26 For Fetch in no-cors mode this change means non-standard header values are not
 27 allowed to be set.
 28
 29 Test: http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight.html
 30
 31 * loader/CrossOriginAccessControl.cpp:
 32 (WebCore::isSimpleCrossOriginAccessRequest):
 33 Now calls WebCore::isCrossOriginSafeRequestHeader() instead of
 34 WebCore::isOnAccessControlSimpleRequestHeaderWhitelist().
 35 (WebCore::isOnAccessControlSimpleRequestHeaderWhitelist): Deleted.
 36 It was a duplicate of WebCore::isCrossOriginSafeRequestHeader().
 37 * loader/CrossOriginAccessControl.h:
 38 * loader/CrossOriginPreflightResultCache.cpp:
 39 (WebCore::CrossOriginPreflightResultCacheItem::allowsCrossOriginHeaders):
 40 Now calls WebCore::isCrossOriginSafeRequestHeader() instead of
 41 WebCore::isOnAccessControlSimpleRequestHeaderWhitelist().
 42 * platform/network/HTTPParsers.cpp:
 43 (WebCore::isValidAcceptHeaderValue):
 44 Basic check that the characters are all ASCII alphanumeric, ' ', '*', '.',
 45 '/', ';', or '='.
 46 (WebCore::isValidLanguageHeaderValue):
 47 Basic check that the characters are all ASCII alphanumeric, ' ', '*', '-',
 48 '.', ';', or '='.
 49 (WebCore::isSimpleHeader):
 50 Removed duplicate code. Now calls WebCore::isCrossOriginSafeRequestHeader().
 51 (WebCore::isCrossOriginSafeRequestHeader):
 52 Now makes a call to WebCore::isValidAcceptHeaderValue() for Accept
 53 headers and WebCore::isValidLanguageHeaderValue() for Accept-Language
 54 and Content-Language headers.
 55 * platform/network/HTTPParsers.h:
 56
1572016-12-02 Dave Hyatt <hyatt@apple.com>
258
359 [CSS Parser] Add support for the SVG 'kerning' property
209249

Source/WebCore/loader/CrossOriginAccessControl.cpp

@@bool isOnAccessControlSimpleRequestMetho
4545 return method == "GET" || method == "HEAD" || method == "POST";
4646}
4747
48 bool isOnAccessControlSimpleRequestHeaderWhitelist(HTTPHeaderName name, const String& value)
49 {
50  switch (name) {
51  case HTTPHeaderName::Accept:
52  case HTTPHeaderName::AcceptLanguage:
53  case HTTPHeaderName::ContentLanguage:
54  return true;
55  case HTTPHeaderName::ContentType: {
56  // Preflight is required for MIME types that can not be sent via form submission.
57  String mimeType = extractMIMETypeFromMediaType(value);
58  return equalIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded")
59  || equalIgnoringASCIICase(mimeType, "multipart/form-data")
60  || equalIgnoringASCIICase(mimeType, "text/plain");
61  }
62  default:
63  return false;
64  }
65 }
66 
6748bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap& headerMap)
6849{
6950 if (!isOnAccessControlSimpleRequestMethodWhitelist(method))
7051 return false;
7152
7253 for (const auto& header : headerMap) {
73  if (!header.keyAsHTTPHeaderName || !isOnAccessControlSimpleRequestHeaderWhitelist(header.keyAsHTTPHeaderName.value(), header.value))
 54 if (!header.keyAsHTTPHeaderName || !isCrossOriginSafeRequestHeader(header.keyAsHTTPHeaderName.value(), header.value))
7455 return false;
7556 }
7657
209182

Source/WebCore/loader/CrossOriginAccessControl.h

@@class URL;
4040
4141bool isSimpleCrossOriginAccessRequest(const String& method, const HTTPHeaderMap&);
4242bool isOnAccessControlSimpleRequestMethodWhitelist(const String&);
43 bool isOnAccessControlSimpleRequestHeaderWhitelist(HTTPHeaderName, const String& value);
4443bool isOnAccessControlResponseHeaderWhitelist(const String&);
4544
4645void updateRequestForAccessControl(ResourceRequest&, SecurityOrigin&, StoredCredentials);
209182

Source/WebCore/loader/CrossOriginPreflightResultCache.cpp

2929
3030#include "CrossOriginAccessControl.h"
3131#include "HTTPHeaderNames.h"
 32#include "HTTPParsers.h"
3233#include "ResourceResponse.h"
3334#include <wtf/MainThread.h>
3435#include <wtf/NeverDestroyed.h>

@@bool CrossOriginPreflightResultCacheItem
127128bool CrossOriginPreflightResultCacheItem::allowsCrossOriginHeaders(const HTTPHeaderMap& requestHeaders, String& errorDescription) const
128129{
129130 for (const auto& header : requestHeaders) {
130  if (header.keyAsHTTPHeaderName && isOnAccessControlSimpleRequestHeaderWhitelist(header.keyAsHTTPHeaderName.value(), header.value))
 131 if (header.keyAsHTTPHeaderName && isCrossOriginSafeRequestHeader(header.keyAsHTTPHeaderName.value(), header.value))
131132 continue;
132133 if (!m_headers.contains(header.key)) {
133134 errorDescription = "Request header field " + header.key + " is not allowed by Access-Control-Allow-Headers.";
209182

Source/WebCore/platform/network/HTTPParsers.cpp

3434#include "HTTPParsers.h"
3535
3636#include "HTTPHeaderNames.h"
 37#include "Language.h"
3738#include <wtf/DateMath.h>
3839#include <wtf/NeverDestroyed.h>
3940#include <wtf/text/CString.h>

@@bool isValidHTTPHeaderValue(const String
126127 return true;
127128}
128129
 130// See RFC 7231, Section 5.3.2
 131bool isValidAcceptHeaderValue(const String& value)
 132{
 133 for (unsigned i = 0; i < value.length(); ++i) {
 134 UChar c = value[i];
 135 if (isASCIIAlphanumeric(c) || c == ' ' || c == '*' || c == '.' || c == '/' || c == ';' || c == '=')
 136 continue;
 137 return false;
 138 }
 139
 140 return true;
 141}
 142
 143// See RFC 7231, Section 5.3.5 and 3.1.3.2
 144bool isValidLanguageHeaderValue(const String& value)
 145{
 146 for (unsigned i = 0; i < value.length(); ++i) {
 147 UChar c = value[i];
 148 if (isASCIIAlphanumeric(c) || c == ' ' || c == '*' || c == '-' || c == '.' || c == ';' || c == '=')
 149 continue;
 150 return false;
 151 }
 152
 153 // FIXME: Validate further by splitting into language tags and optional quality
 154 // values (q=) and then check each language tag.
 155 // Language tags https://tools.ietf.org/html/rfc7231#section-3.1.3.1
 156 // Language tag syntax https://tools.ietf.org/html/bcp47#section-2.1
 157 return true;
 158}
 159
129160// See RFC 7230, Section 3.2.6.
130161bool isValidHTTPToken(const String& value)
131162{

@@void parseAccessControlExposeHeadersAllo
732763 }
733764}
734765
735 // Implememtnation of https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
 766// Implementation of https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
736767bool isForbiddenHeaderName(const String& name)
737768{
738769 HTTPHeaderName headerName;

@@bool isSimpleHeader(const String& name,
776807 HTTPHeaderName headerName;
777808 if (!findHTTPHeaderName(name, headerName))
778809 return false;
779  switch (headerName) {
780  case HTTPHeaderName::Accept:
781  case HTTPHeaderName::AcceptLanguage:
782  case HTTPHeaderName::ContentLanguage:
783  return true;
784  case HTTPHeaderName::ContentType: {
785  String mimeType = extractMIMETypeFromMediaType(value);
786  return equalLettersIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded") || equalLettersIgnoringASCIICase(mimeType, "multipart/form-data") || equalLettersIgnoringASCIICase(mimeType, "text/plain");
787  }
788  default:
789  return false;
790  }
 810 return isCrossOriginSafeRequestHeader(headerName, value);
791811}
792812
793813bool isCrossOriginSafeHeader(HTTPHeaderName name, const HTTPHeaderSet& accessControlExposeHeaderSet)

@@bool isCrossOriginSafeRequestHeader(HTTP
824844{
825845 switch (name) {
826846 case HTTPHeaderName::Accept:
 847 return isValidAcceptHeaderValue(value);
827848 case HTTPHeaderName::AcceptLanguage:
828849 case HTTPHeaderName::ContentLanguage:
829  return true;
 850 return isValidLanguageHeaderValue(value);
830851 case HTTPHeaderName::ContentType: {
 852 // Preflight is required for MIME types that can not be sent via form submission.
831853 String mimeType = extractMIMETypeFromMediaType(value);
832854 return equalLettersIgnoringASCIICase(mimeType, "application/x-www-form-urlencoded") || equalLettersIgnoringASCIICase(mimeType, "multipart/form-data") || equalLettersIgnoringASCIICase(mimeType, "text/plain");
833855 }
209182

Source/WebCore/platform/network/HTTPParsers.h

@@enum XFrameOptionsDisposition {
6969
7070bool isValidReasonPhrase(const String&);
7171bool isValidHTTPHeaderValue(const String&);
 72bool isValidAcceptHeaderValue(const String&);
 73bool isValidLanguageHeaderValue(const String&);
7274bool isValidHTTPToken(const String&);
7375bool parseHTTPRefresh(const String& refresh, double& delay, String& url);
7476std::optional<std::chrono::system_clock::time_point> parseHTTPDate(const String&);
209182

LayoutTests/ChangeLog

 12016-12-02 John Wilander <wilander@apple.com>
 2
 3 Require preflight for non-standard CORS-safelisted request headers Accept, Accept-Language, and Content-Language
 4 https://bugs.webkit.org/show_bug.cgi?id=165178
 5 <rdar://problem/18792250>
 6
 7 Reviewed by Youenn Fablet.
 8
 9 Fetch currently only restricts the header Content-Type for simple requests:
 10 https://fetch.spec.whatwg.org/#cors-safelisted-request-header
 11
 12 This means simple CORS requests can send unexpected characters in Accept,
 13 Accept-Language, and Content-Language header values.
 14
 15 RFC 7231 implies restrictions on these header values:
 16 - Accept https://tools.ietf.org/html/rfc7231#section-5.3.2
 17 - Accept-Language https://tools.ietf.org/html/rfc7231#section-5.3.5
 18 - Content-Language https://tools.ietf.org/html/rfc7231#section-3.1.3.2
 19
 20 As per discussions in the W3C WebAppSec group we should try to restrict
 21 these header values to help protect servers that do not expect simple CORS
 22 requests.
 23
 24 Non-standard, safelisted header values should trigger a preflight and require
 25 the headers to be whitelisted in the response's Access-Control-Allow-Headers.
 26 For Fetch in no-cors mode this change means non-standard header values are not
 27 allowed to be set.
 28
 29 * http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight-expected.txt: Added.
 30 * http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight.html: Added.
 31 Tests that:
 32 - Normal Accept, Accept-Language, and Content-Language headers don't trigger
 33 a preflight.
 34 - Abnormal Accept, Accept-Language, and Content-Language headers do trigger
 35 a preflight.
 36 - Abnormal Accept, Accept-Language, and Content-Language headers are
 37 accepted if the server whitelists them.
 38 * http/tests/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php: Added.
 39
1402016-12-01 Antoine Quint <graouts@apple.com>
241
342 [Modern Media Controls] Provide a UI object to show a list of tracks
209182

LayoutTests/http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight-expected.txt

 1CONSOLE MESSAGE: XMLHttpRequest cannot load http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php. Request header field Accept is not allowed by Access-Control-Allow-Headers.
 2CONSOLE MESSAGE: XMLHttpRequest cannot load http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php. Request header field Accept-Language is not allowed by Access-Control-Allow-Headers.
 3CONSOLE MESSAGE: XMLHttpRequest cannot load http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php. Request header field Content-Language is not allowed by Access-Control-Allow-Headers.
 4CONSOLE MESSAGE: XMLHttpRequest cannot load http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php. Request header field Content-Language is not allowed by Access-Control-Allow-Headers.
 5CONSOLE MESSAGE: XMLHttpRequest cannot load http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php. Request header field Accept is not allowed by Access-Control-Allow-Headers.
 6PASS Accept header with normal value SHOULD NOT cause a preflight
 7PASS Accept header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight
 8PASS Accept-Language header with normal value SHOULD NOT cause a preflight
 9PASS Accept-Language header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight
 10PASS Content-Language header with normal value SHOULD NOT cause a preflight
 11PASS Content-Language header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight
 12PASS Accept header with abnormal value SHOULD cause a preflight
 13PASS Accept-Language header with abnormal value SHOULD cause a preflight
 14PASS Content-Language header with abnormal value SHOULD cause a preflight
 15PASS Accept header with normal value, Accept-Language header with normal value, and Content-Language header with abnormal value SHOULD cause a preflight
 16PASS Accept header with normal value and then another Accept header with abnormal value SHOULD cause a preflight
 17PASS Accept header with abnormal value and explicitly allowed headers SHOULD be allowed
 18PASS Content-Language header with abnormal value and explicitly allowed headers SHOULD be allowed
 19PASS Accept header with normal value, Accept-Language header with normal value, Content-Language header with abnormal value, and explicitly allowed headers SHOULD be allowed
 20PASS Accept header with normal value, then another Accept header with abnormal value, and explicitly allowed headers SHOULD be allowed
 21
nonexistent

LayoutTests/http/tests/xmlhttprequest/cors-non-standard-safelisted-headers-should-trigger-preflight.html

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4 <meta charset="UTF-8">
 5 <title>Non-Standard Safelisted Headers SHOULD Trigger a Preflight</title>
 6 <script src="../resources/js-test-pre.js"></script>
 7</head>
 8<body>
 9<!-- https://fetch.spec.whatwg.org/#cors-safelisted-request-header -->
 10<script>
 11 if (window.testRunner) {
 12 testRunner.dumpAsText();
 13 testRunner.waitUntilDone();
 14 }
 15
 16 var xhr;
 17 var url = 'http://localhost:8000/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php';
 18
 19 function createReadyStateHandler (description, testNumber) {
 20 return function handler (e) {
 21 if (xhr.readyState === XMLHttpRequest.DONE) {
 22 testPassed(description);
 23 nextStep(testNumber);
 24 }
 25 }
 26 }
 27
 28 function createOnErrorHandler (description, testNumber) {
 29 return function handler (e) {
 30 e.preventDefault();
 31 testPassed(description);
 32 nextStep(testNumber);
 33 }
 34 }
 35
 36 var abnormalSimpleCorsHeaderValue = "() { :;};"
 37 var allAllowedNonAlphanumericCharactersForAcceptHeader = " *./;="
 38 var allAllowedNonAlphanumericCharactersForAcceptAndContentLanguageHeader = " *-.;="
 39 var testCases = [
 40 // Positive test cases with normal headers
 41 {
 42 headersToAdd: [{ name : "Accept", value: "text/*" }],
 43 explicitlyAllowHeaders: false,
 44 shouldCausePreflight: false,
 45 description: "Accept header with normal value SHOULD NOT cause a preflight"
 46 }
 47 ,{
 48 headersToAdd: [{ name : "Accept", value: allAllowedNonAlphanumericCharactersForAcceptHeader }],
 49 explicitlyAllowHeaders: false,
 50 shouldCausePreflight: false,
 51 description: "Accept header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight"
 52 }
 53 ,{
 54 headersToAdd: [{ name : "Accept-Language", value: "en" }],
 55 explicitlyAllowHeaders: false,
 56 shouldCausePreflight: false,
 57 description: "Accept-Language header with normal value SHOULD NOT cause a preflight"
 58 }
 59 ,{
 60 headersToAdd: [{ name : "Accept-Language", value: allAllowedNonAlphanumericCharactersForAcceptAndContentLanguageHeader }],
 61 explicitlyAllowHeaders: false,
 62 shouldCausePreflight: false,
 63 description: "Accept-Language header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight"
 64 }
 65 ,{
 66 headersToAdd: [{ name : "Content-Language", value: "en" }],
 67 explicitlyAllowHeaders: false,
 68 shouldCausePreflight: false,
 69 description: "Content-Language header with normal value SHOULD NOT cause a preflight"
 70 }
 71 ,{
 72 headersToAdd: [{ name : "Content-Language", value: allAllowedNonAlphanumericCharactersForAcceptAndContentLanguageHeader }],
 73 explicitlyAllowHeaders: false,
 74 shouldCausePreflight: false,
 75 description: "Content-Language header value with all allowed non-alphanumeric characters SHOULD NOT cause a preflight"
 76 }
 77 // Negative test cases with abnormal headers
 78 ,{
 79 headersToAdd: [{ name : "Accept", value: abnormalSimpleCorsHeaderValue }],
 80 explicitlyAllowHeaders: false,
 81 shouldCausePreflight: true,
 82 description: "Accept header with abnormal value SHOULD cause a preflight"
 83 }
 84 ,{
 85 headersToAdd: [{ name : "Accept-Language", value: abnormalSimpleCorsHeaderValue }],
 86 explicitlyAllowHeaders: false,
 87 shouldCausePreflight: true,
 88 description: "Accept-Language header with abnormal value SHOULD cause a preflight"
 89 }
 90 ,{
 91 headersToAdd: [{ name : "Content-Language", value: abnormalSimpleCorsHeaderValue }],
 92 explicitlyAllowHeaders: false,
 93 shouldCausePreflight: true,
 94 description: "Content-Language header with abnormal value SHOULD cause a preflight"
 95 }
 96 ,{
 97 headersToAdd: [{ name : "Accept", value: "text/*" }, { name : "Accept-Language", value: "en" }, { name : "Content-Language", value: abnormalSimpleCorsHeaderValue }],
 98 explicitlyAllowHeaders: false,
 99 shouldCausePreflight: true,
 100 description: "Accept header with normal value, Accept-Language header with normal value, and Content-Language header with abnormal value SHOULD cause a preflight"
 101 }
 102 ,{
 103 headersToAdd: [{ name : "Accept", value: "text/*" }, { name : "Accept", value: abnormalSimpleCorsHeaderValue }],
 104 explicitlyAllowHeaders: false,
 105 shouldCausePreflight: true,
 106 description: "Accept header with normal value and then another Accept header with abnormal value SHOULD cause a preflight"
 107 }
 108 // Positive test cases with abnormal headers
 109 ,{
 110 headersToAdd: [{ name : "Accept", value: abnormalSimpleCorsHeaderValue }],
 111 explicitlyAllowHeaders: true,
 112 shouldCausePreflight: true,
 113 description: "Accept header with abnormal value and explicitly allowed headers SHOULD be allowed"
 114 }
 115 ,{
 116 headersToAdd: [{ name : "Content-Language", value: abnormalSimpleCorsHeaderValue }],
 117 explicitlyAllowHeaders: true,
 118 shouldCausePreflight: true,
 119 description: "Content-Language header with abnormal value and explicitly allowed headers SHOULD be allowed"
 120 }
 121 ,{
 122 headersToAdd: [{ name : "Accept", value: "text/*" }, { name : "Accept-Language", value: "en" }, { name : "Content-Language", value: abnormalSimpleCorsHeaderValue }],
 123 explicitlyAllowHeaders: true,
 124 shouldCausePreflight: true,
 125 description: "Accept header with normal value, Accept-Language header with normal value, Content-Language header with abnormal value, and explicitly allowed headers SHOULD be allowed"
 126 }
 127 ,{
 128 headersToAdd: [{ name : "Accept", value: "text/*" }, { name : "Accept", value: abnormalSimpleCorsHeaderValue }],
 129 explicitlyAllowHeaders: true,
 130 shouldCausePreflight: true,
 131 description: "Accept header with normal value, then another Accept header with abnormal value, and explicitly allowed headers SHOULD be allowed"
 132 }
 133 ];
 134
 135 function runTestCase(testNumber) {
 136 var testCase = testCases[testNumber];
 137 xhr = new XMLHttpRequest();
 138 xhr.open('GET', url + (testCase.explicitlyAllowHeaders ? "/?explicitlyAllowHeaders=true" : ""), true);
 139 for (var i = 0; i < testCase.headersToAdd.length; i++) {
 140 xhr.setRequestHeader(testCase.headersToAdd[i].name, testCase.headersToAdd[i].value);
 141 }
 142 if (testCase.shouldCausePreflight && !testCase.explicitlyAllowHeaders)
 143 xhr. testNumber);
 144 else
 145 xhr. testNumber);
 146 xhr.send();
 147 }
 148
 149 function nextStep (testNumber) {
 150 if (testNumber === (testCases.length - 1)) {
 151 if (window.testRunner)
 152 testRunner.notifyDone();
 153 } else
 154 runTestCase(testNumber + 1);
 155 }
 156
 157 runTestCase(0);
 158</script>
 159</body>
 160</html>
nonexistent

LayoutTests/http/tests/xmlhttprequest/resources/cors-preflight-safelisted-headers-responder.php

 1<?php
 2header('Access-Control-Allow-Origin: http://127.0.0.1:8000');
 3
 4if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS' && isset($_GET['explicitlyAllowHeaders'])) {
 5 header('Access-Control-Allow-Methods: GET, OPTIONS');
 6 header('Access-Control-Allow-Headers: Accept, Accept-Language, Content-Language');
 7}
 8?>
nonexistent