No, en un contexto HTML no puedes inyectar nuevas etiquetas sin permitir letras después del corchete de apertura. Aún así, esta técnica de filtrado es innecesariamente arriesgada.
El analizador HTML de su navegador web analiza el código como una máquina de estado . Para comprender cuáles son sus opciones, eche un vistazo a la especificación de sintaxis HTML y las posibles transiciones de estado.
Su punto de inyección se encuentra en el estado de los datos (que es el " estado predeterminado, fuera de cualquier etiqueta):
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.
Para XSS, la única continuación interesante es abrir una etiqueta con <
y cambiar a etiqueta estado abierto :
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.
Aquí sus opciones son a-z
, A-Z
, !
, /
y ?
.
No ha sido claro si los caracteres especiales también están en la lista negra. Pero incluso si no lo están, no tiene suerte:
- Desde
<!
solo se obtiene un comentario ( <!--
), una declaración doctype ( <!DOCTYPE
) o una sección CDATA ( <![CDATA
). Estos no son nodos DOM reales y, por lo tanto, son inútiles para XSS. (Por ejemplo, no podría adjuntar un controlador de eventos a un comentario).
-
<?
puede ser interesante en XML, pero se trata como un comentario en HTML.
-
</
solo te permitirá cerrar etiquetas.
Es posible que hayas notado que la especificación tampoco admite caracteres de relleno, como espacios, pestañas, nuevas líneas o caracteres de control.
Si desea profundizar un poco más y verificar la implementación, siempre puede consultar el código fuente. Por ejemplo, este extracto es parte del tokenizer HTML5 utilizado en Mozilla Firefox . Como puede ver, el estado de etiqueta abierta se adhiere estrechamente a la especificación:
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: ;
}
Entonces, el filtro XSS que usted describe parece ser seguro según la especificación HTML. Es un hielo muy delgado, resistente. Nunca se sabe, si algún proveedor tiene una implementación peculiar que aún podría habilitar un exploit. (Microsoft, te estoy mirando!)
La protección XSS correcta es, por lo tanto, simplemente escapar del corchetes .