OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2011 Adam Barth. All Rights Reserved. | 2 * Copyright (C) 2011 Adam Barth. All Rights Reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
6 * are met: | 6 * are met: |
7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
(...skipping 25 matching lines...) Expand all Loading... |
36 #include "HTMLParserIdioms.h" | 36 #include "HTMLParserIdioms.h" |
37 #include "Settings.h" | 37 #include "Settings.h" |
38 #include "TextEncoding.h" | 38 #include "TextEncoding.h" |
39 #include "TextResourceDecoder.h" | 39 #include "TextResourceDecoder.h" |
40 #include <wtf/text/CString.h> | 40 #include <wtf/text/CString.h> |
41 | 41 |
42 namespace WebCore { | 42 namespace WebCore { |
43 | 43 |
44 using namespace HTMLNames; | 44 using namespace HTMLNames; |
45 | 45 |
46 namespace { | 46 static bool isNonCanonicalCharacter(UChar c) |
47 | |
48 bool isNonCanonicalCharacter(UChar c) | |
49 { | 47 { |
50 // We remove all non-ASCII characters, including non-printable ASCII charact
ers. | 48 // We remove all non-ASCII characters, including non-printable ASCII charact
ers. |
51 // | 49 // |
52 // Note, we don't remove backslashes like PHP stripslashes(), which among ot
her things converts "\\0" to the \0 character. | 50 // Note, we don't remove backslashes like PHP stripslashes(), which among ot
her things converts "\\0" to the \0 character. |
53 // Instead, we remove backslashes and zeros (since the string "\\0" =(remove
backslashes)=> "0"). However, this has the | 51 // Instead, we remove backslashes and zeros (since the string "\\0" =(remove
backslashes)=> "0"). However, this has the |
54 // adverse effect that we remove any legitimate zeros from a string. | 52 // adverse effect that we remove any legitimate zeros from a string. |
55 // | 53 // |
56 // For instance: new String("http://localhost:8000") => new String("http://l
ocalhost:8"). | 54 // For instance: new String("http://localhost:8000") => new String("http://l
ocalhost:8"). |
57 return (c == '\\' || c == '0' || c == '\0' || c >= 127); | 55 return (c == '\\' || c == '0' || c == '\0' || c >= 127); |
58 } | 56 } |
59 | 57 |
60 String canonicalize(const String& string) | 58 static String canonicalize(const String& string) |
61 { | 59 { |
62 return string.removeCharacters(&isNonCanonicalCharacter); | 60 return string.removeCharacters(&isNonCanonicalCharacter); |
63 } | 61 } |
64 | 62 |
65 bool isRequiredForInjection(UChar c) | 63 static bool isRequiredForInjection(UChar c) |
66 { | 64 { |
67 return (c == '\'' || c == '"' || c == '<' || c == '>'); | 65 return (c == '\'' || c == '"' || c == '<' || c == '>'); |
68 } | 66 } |
69 | 67 |
70 bool hasName(const HTMLToken& token, const QualifiedName& name) | 68 static bool hasName(const HTMLToken& token, const QualifiedName& name) |
71 { | 69 { |
72 return equalIgnoringNullity(token.name(), static_cast<const String&>(name.lo
calName())); | 70 return equalIgnoringNullity(token.name(), static_cast<const String&>(name.lo
calName())); |
73 } | 71 } |
74 | 72 |
75 bool findAttributeWithName(const HTMLToken& token, const QualifiedName& name, si
ze_t& indexOfMatchingAttribute) | 73 static bool findAttributeWithName(const HTMLToken& token, const QualifiedName& n
ame, size_t& indexOfMatchingAttribute) |
76 { | 74 { |
77 for (size_t i = 0; i < token.attributes().size(); ++i) { | 75 for (size_t i = 0; i < token.attributes().size(); ++i) { |
78 if (equalIgnoringNullity(token.attributes().at(i).m_name, name.localName
())) { | 76 if (equalIgnoringNullity(token.attributes().at(i).m_name, name.localName
())) { |
79 indexOfMatchingAttribute = i; | 77 indexOfMatchingAttribute = i; |
80 return true; | 78 return true; |
81 } | 79 } |
82 } | 80 } |
83 return false; | 81 return false; |
84 } | 82 } |
85 | 83 |
86 bool isNameOfInlineEventHandler(const Vector<UChar, 32>& name) | 84 static bool isNameOfInlineEventHandler(const Vector<UChar, 32>& name) |
87 { | 85 { |
88 const size_t lengthOfShortestInlineEventHandlerName = 5; // To wit: oncut. | 86 const size_t lengthOfShortestInlineEventHandlerName = 5; // To wit: oncut. |
89 if (name.size() < lengthOfShortestInlineEventHandlerName) | 87 if (name.size() < lengthOfShortestInlineEventHandlerName) |
90 return false; | 88 return false; |
91 return name[0] == 'o' && name[1] == 'n'; | 89 return name[0] == 'o' && name[1] == 'n'; |
92 } | 90 } |
93 | 91 |
94 bool containsJavaScriptURL(const Vector<UChar, 32>& value) | 92 static bool isDangerousHTTPEquiv(const String& value) |
| 93 { |
| 94 String equiv = value.stripWhiteSpace(); |
| 95 return equalIgnoringCase(equiv, "refresh") || equalIgnoringCase(equiv, "set-
cookie"); |
| 96 } |
| 97 |
| 98 static bool containsJavaScriptURL(const Vector<UChar, 32>& value) |
95 { | 99 { |
96 static const char javaScriptScheme[] = "javascript:"; | 100 static const char javaScriptScheme[] = "javascript:"; |
97 static const size_t lengthOfJavaScriptScheme = sizeof(javaScriptScheme) - 1; | 101 static const size_t lengthOfJavaScriptScheme = sizeof(javaScriptScheme) - 1; |
98 | 102 |
99 size_t i; | 103 size_t i; |
100 for (i = 0; i < value.size(); ++i) { | 104 for (i = 0; i < value.size(); ++i) { |
101 if (!isHTMLSpace(value[i])) | 105 if (!isHTMLSpace(value[i])) |
102 break; | 106 break; |
103 } | 107 } |
104 | 108 |
105 if (value.size() - i < lengthOfJavaScriptScheme) | 109 if (value.size() - i < lengthOfJavaScriptScheme) |
106 return false; | 110 return false; |
107 | 111 |
108 return equalIgnoringCase(value.data() + i, javaScriptScheme, lengthOfJavaScr
iptScheme); | 112 return equalIgnoringCase(value.data() + i, javaScriptScheme, lengthOfJavaScr
iptScheme); |
109 } | 113 } |
110 | 114 |
111 String decodeURL(const String& string, const TextEncoding& encoding) | 115 static String decodeURL(const String& string, const TextEncoding& encoding) |
112 { | 116 { |
113 String workingString = string; | 117 String workingString = string; |
114 workingString.replace('+', ' '); | 118 workingString.replace('+', ' '); |
115 workingString = decodeURLEscapeSequences(workingString); | 119 workingString = decodeURLEscapeSequences(workingString); |
116 CString workingStringUTF8 = workingString.utf8(); | 120 CString workingStringUTF8 = workingString.utf8(); |
117 String decodedString = encoding.decode(workingStringUTF8.data(), workingStri
ngUTF8.length()); | 121 String decodedString = encoding.decode(workingStringUTF8.data(), workingStri
ngUTF8.length()); |
118 // FIXME: Is this check necessary? | 122 // FIXME: Is this check necessary? |
119 if (decodedString.isEmpty()) | 123 if (decodedString.isEmpty()) |
120 return canonicalize(workingString); | 124 return canonicalize(workingString); |
121 return canonicalize(decodedString); | 125 return canonicalize(decodedString); |
122 } | 126 } |
123 | 127 |
124 } | |
125 | |
126 XSSFilter::XSSFilter(HTMLDocumentParser* parser) | 128 XSSFilter::XSSFilter(HTMLDocumentParser* parser) |
127 : m_parser(parser) | 129 : m_parser(parser) |
128 , m_isEnabled(false) | 130 , m_isEnabled(false) |
129 , m_xssProtection(XSSProtectionEnabled) | 131 , m_xssProtection(XSSProtectionEnabled) |
130 , m_state(Uninitialized) | 132 , m_state(Uninitialized) |
131 { | 133 { |
132 ASSERT(m_parser); | 134 ASSERT(m_parser); |
133 if (Frame* frame = parser->document()->frame()) { | 135 if (Frame* frame = parser->document()->frame()) { |
134 if (Settings* settings = frame->settings()) | 136 if (Settings* settings = frame->settings()) |
135 m_isEnabled = settings->xssAuditorEnabled(); | 137 m_isEnabled = settings->xssAuditorEnabled(); |
(...skipping 277 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
413 } | 415 } |
414 | 416 |
415 bool XSSFilter::eraseAttributeIfInjected(HTMLToken& token, const QualifiedName&
attributeName, const String& replacementValue) | 417 bool XSSFilter::eraseAttributeIfInjected(HTMLToken& token, const QualifiedName&
attributeName, const String& replacementValue) |
416 { | 418 { |
417 size_t indexOfAttribute; | 419 size_t indexOfAttribute; |
418 if (findAttributeWithName(token, attributeName, indexOfAttribute)) { | 420 if (findAttributeWithName(token, attributeName, indexOfAttribute)) { |
419 const HTMLToken::Attribute& attribute = token.attributes().at(indexOfAtt
ribute); | 421 const HTMLToken::Attribute& attribute = token.attributes().at(indexOfAtt
ribute); |
420 if (isContainedInRequest(snippetForAttribute(token, attribute))) { | 422 if (isContainedInRequest(snippetForAttribute(token, attribute))) { |
421 if (attributeName == srcAttr && isSameOriginResource(String(attribut
e.m_value.data(), attribute.m_value.size()))) | 423 if (attributeName == srcAttr && isSameOriginResource(String(attribut
e.m_value.data(), attribute.m_value.size()))) |
422 return false; | 424 return false; |
| 425 if (attributeName == http_equivAttr && !isDangerousHTTPEquiv(String(
attribute.m_value.data(), attribute.m_value.size()))) |
| 426 return false; |
423 token.eraseValueOfAttribute(indexOfAttribute); | 427 token.eraseValueOfAttribute(indexOfAttribute); |
424 if (!replacementValue.isEmpty()) | 428 if (!replacementValue.isEmpty()) |
425 token.appendToAttributeValue(indexOfAttribute, replacementValue)
; | 429 token.appendToAttributeValue(indexOfAttribute, replacementValue)
; |
426 return true; | 430 return true; |
427 } | 431 } |
428 } | 432 } |
429 return false; | 433 return false; |
430 } | 434 } |
431 | 435 |
432 String XSSFilter::snippetForRange(const HTMLToken& token, int start, int end) | 436 String XSSFilter::snippetForRange(const HTMLToken& token, int start, int end) |
(...skipping 30 matching lines...) Expand all Loading... |
463 // probably not an XSS attack, so we reduce false positives by allowing the | 467 // probably not an XSS attack, so we reduce false positives by allowing the |
464 // request. If the resource has a query string, we're more suspicious, | 468 // request. If the resource has a query string, we're more suspicious, |
465 // however, because that's pretty rare and the attacker might be able to | 469 // however, because that's pretty rare and the attacker might be able to |
466 // trick a server-side script into doing something dangerous with the query | 470 // trick a server-side script into doing something dangerous with the query |
467 // string. | 471 // string. |
468 KURL resourceURL(m_parser->document()->url(), url); | 472 KURL resourceURL(m_parser->document()->url(), url); |
469 return (m_parser->document()->url().host() == resourceURL.host() && resource
URL.query().isEmpty()); | 473 return (m_parser->document()->url().host() == resourceURL.host() && resource
URL.query().isEmpty()); |
470 } | 474 } |
471 | 475 |
472 } | 476 } |
OLD | NEW |