不,在 HTML 上下文中,如果不允许左括号后的字母,则不能注入新标签。尽管如此,这种过滤技术还是有不必要的风险。
Web 浏览器的 HTML 解析器将代码解析为状态机。要了解您的选择,请查看HTML 语法规范和可能的状态转换。
您的注入点恰好处于数据状态(这是“默认”状态,在任何标签之外):
8.2.4.1 Data state
Consume the next input character:
U+0026 AMPERSAND (&)
Switch to the character reference in data state.
"<" (U+003C)
Switch to the tag open state.
U+0000 NULL
Parse error. Emit the current input character as a character token.
EOF
Emit an end-of-file token.
Anything else
Emit the current input character as a character token.
对于 XSS,唯一有趣的延续是打开标签<并切换到标签打开状态:
8.2.4.8 Tag open state
Consume the next input character:
"!" (U+0021)
Switch to the markup declaration open state.
"/" (U+002F)
Switch to the end tag open state.
Uppercase ASCII letter
Create a new start tag token, set its tag name to the lowercase version of the current input character (add 0x0020 to the character's code point), then switch to the tag name state. (Don't emit the token yet; further details will be filled in before it is emitted.)
Lowercase ASCII letter
Create a new start tag token, set its tag name to the current input character, then switch to the tag name state. (Don't emit the token yet; further details will be filled in before it is emitted.)
"?" (U+003F)
Parse error. Switch to the bogus comment state.
Anything else
Parse error. Switch to the data state. Emit a U+003C LESS-THAN SIGN character token. Reconsume the current input character.
在这里,您的选项是a-z、A-Z、和。
您还不清楚特殊字符是否也被列入黑名单。但即使他们不是,你也不走运:!/?
- 您
<!只能得到一个注释 ( <!--)、一个文档类型声明 ( <!DOCTYPE) 或一个 CDATA 部分 ( <![CDATA)。这些不是真正的 DOM 节点,因此它们对 XSS 毫无用处。(例如,您将无法将事件处理程序附加到评论。)
<? 在 XML 中可能很有趣,但在 HTML 中被视为注释。
</ 只会让你关闭标签。
您可能已经注意到该规范也不允许任何填充字符,例如空格、制表符、换行符或控制字符。
如果您想更深入地挖掘并验证实现,您可以随时查看源代码。例如,此摘录是Mozilla Firefox 中使用的 HTML5 标记器的一部分。如您所见,标签打开状态非常符合规范:
case NS_HTML5TOKENIZER_TAG_OPEN: {
for (; ; ) {
if (++pos == endPos) {
NS_HTML5_BREAK(stateloop);
}
c = checkChar(buf, pos);
if (c >= 'A' && c <= 'Z') {
endTag = false;
clearStrBufAndAppend((char16_t) (c + 0x20));
state = P::transition(mViewSource, NS_HTML5TOKENIZER_TAG_NAME, reconsume, pos);
NS_HTML5_BREAK(tagopenloop);
} else if (c >= 'a' && c <= 'z') {
endTag = false;
clearStrBufAndAppend(c);
state = P::transition(mViewSource, NS_HTML5TOKENIZER_TAG_NAME, reconsume, pos);
NS_HTML5_BREAK(tagopenloop);
}
switch(c) {
case '!': {
state = P::transition(mViewSource, NS_HTML5TOKENIZER_MARKUP_DECLARATION_OPEN, reconsume, pos);
NS_HTML5_CONTINUE(stateloop);
}
case '/': {
state = P::transition(mViewSource, NS_HTML5TOKENIZER_CLOSE_TAG_OPEN, reconsume, pos);
NS_HTML5_CONTINUE(stateloop);
}
case '\?': {
if (viewingXmlSource) {
state = P::transition(mViewSource, NS_HTML5TOKENIZER_PROCESSING_INSTRUCTION, reconsume, pos);
NS_HTML5_CONTINUE(stateloop);
}
if (P::reportErrors) {
errProcessingInstruction();
}
clearStrBufAndAppend(c);
state = P::transition(mViewSource, NS_HTML5TOKENIZER_BOGUS_COMMENT, reconsume, pos);
NS_HTML5_CONTINUE(stateloop);
}
case '>': {
if (P::reportErrors) {
errLtGt();
}
tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 2);
cstart = pos + 1;
state = P::transition(mViewSource, NS_HTML5TOKENIZER_DATA, reconsume, pos);
NS_HTML5_CONTINUE(stateloop);
}
default: {
if (P::reportErrors) {
errBadCharAfterLt(c);
}
tokenHandler->characters(nsHtml5Tokenizer::LT_GT, 0, 1);
cstart = pos;
reconsume = true;
state = P::transition(mViewSource, NS_HTML5TOKENIZER_DATA, reconsume, pos);
NS_HTML5_CONTINUE(stateloop);
}
}
}
tagopenloop_end: ;
}
因此,根据 HTML 规范,您描述的 XSS 过滤器似乎是安全的。冰很薄,很硬。你永远不知道,如果某个供应商提出了一个仍然可以启用漏洞利用的古怪实现。(微软,我在看着你!)
因此,正确的 XSS 保护是简单地转义括号。