javascript - html sanitization
Sanitize/Rewrite HTML en el lado del cliente (9)
Necesito mostrar los recursos externos cargados a través de solicitudes de dominios cruzados y asegurarme de mostrar solo el contenido " seguro ".
Podría usar String#stripScripts de String#stripScripts para eliminar bloques de script. Pero los controladores como onclick
o onerror
siguen ahí.
¿Hay alguna biblioteca que pueda al menos
- bloques de secuencias de comandos,
- matar a los controladores DOM,
- eliminar las etiquetas en la lista negra (por ej .:
embed
uobject
).
¿Existen enlaces y ejemplos relacionados con JavaScript?
Actualización 2016: ahora hay un paquete de cierre de Google basado en el sanitizador de Caja.
Tiene una API más limpia, se reescribió para tener en cuenta las API disponibles en los navegadores modernos e interactúa mejor con Closure Compiler.
Plug desvergonzado: consulte caja/plugin/html-sanitizer.js para un sanitizador html del lado del cliente que ha sido revisado minuciosamente.
Está en la lista blanca, no en la lista negra, pero las listas blancas son configurables según las listas de CajaWhitelists
Si desea eliminar todas las etiquetas, haga lo siguiente:
var tagBody = ''(?:[^"/'>]|"[^"]*"|/'[^/']*/')*'';
var tagOrComment = new RegExp(
''<(?:''
// Comment body.
+ ''!--(?:(?:-*[^->])*--+|-?)''
// Special "raw text" elements whose content should be elided.
+ ''|script//b'' + tagBody + ''>[//s//S]*?</script//s*''
+ ''|style//b'' + tagBody + ''>[//s//S]*?</style//s*''
// Regular name
+ ''|/?[a-z]''
+ tagBody
+ '')>'',
''gi'');
function removeTags(html) {
var oldHtml;
do {
oldHtml = html;
html = html.replace(tagOrComment, '''');
} while (html !== oldHtml);
return html.replace(/</g, ''<'');
}
La gente te dirá que puedes crear un elemento y asignar innerHTML
y luego obtener el texto innerText
o el contenido de textContent
, y luego escapar de las entidades en eso. No hagas eso. Es vulnerable a la inyección XSS ya que <img src=bogus onerror=alert(1337)>
ejecutará el controlador onerror
incluso si el nodo nunca está conectado al DOM.
Ahora que todos los principales navegadores admiten iframes de espacio aislado, hay una forma mucho más simple que creo que puede ser segura. Me encantaría que esta respuesta pudiera ser revisada por personas que están más familiarizadas con este tipo de problema de seguridad.
NOTA: Este método definitivamente no funcionará en IE 9 y anteriores. Consulte esta tabla para ver las versiones de navegador que admiten sandboxing.
La idea es crear un iframe oculto con JavaScript desactivado, pegar el código HTML que no es de confianza y dejar que lo analice. Luego puede recorrer el árbol DOM y copiar las etiquetas y atributos que se consideran seguros.
Las listas blancas que se muestran aquí son solo ejemplos. Lo mejor para la lista blanca dependerá de la aplicación. Si necesita una política más sofisticada que solo las listas blancas de etiquetas y atributos, eso puede ser acomodado por este método, aunque no por este código de ejemplo.
var tagWhitelist_ = {
''A'': true,
''B'': true,
''BODY'': true,
''BR'': true,
''DIV'': true,
''EM'': true,
''HR'': true,
''I'': true,
''IMG'': true,
''P'': true,
''SPAN'': true,
''STRONG'': true
};
var attributeWhitelist_ = {
''href'': true,
''src'': true
};
function sanitizeHtml(input) {
var iframe = document.createElement(''iframe'');
if (iframe[''sandbox''] === undefined) {
alert(''Your browser does not support sandboxed iframes. Please upgrade to a modern browser.'');
return '''';
}
iframe[''sandbox''] = ''allow-same-origin'';
iframe.style.display = ''none'';
document.body.appendChild(iframe); // necessary so the iframe contains a document
iframe.contentDocument.body.innerHTML = input;
function makeSanitizedCopy(node) {
if (node.nodeType == Node.TEXT_NODE) {
var newNode = node.cloneNode(true);
} else if (node.nodeType == Node.ELEMENT_NODE && tagWhitelist_[node.tagName]) {
newNode = iframe.contentDocument.createElement(node.tagName);
for (var i = 0; i < node.attributes.length; i++) {
var attr = node.attributes[i];
if (attributeWhitelist_[attr.name]) {
newNode.setAttribute(attr.name, attr.value);
}
}
for (i = 0; i < node.childNodes.length; i++) {
var subCopy = makeSanitizedCopy(node.childNodes[i]);
newNode.appendChild(subCopy, false);
}
} else {
newNode = document.createDocumentFragment();
}
return newNode;
};
var resultElement = makeSanitizedCopy(iframe.contentDocument.body);
document.body.removeChild(iframe);
return resultElement.innerHTML;
};
Puedes probarlo here .
Tenga en cuenta que estoy denegando los atributos y las etiquetas de estilo en este ejemplo. Si los permitió, probablemente quiera analizar el CSS y asegurarse de que sea seguro para sus propósitos.
Lo probé en varios navegadores modernos (Chrome 40, Firefox 36 Beta, IE 11, Chrome para Android) y en uno antiguo (IE 8) para asegurarme de que funcionaba antes de ejecutar cualquier script. Me interesaría saber si hay navegadores que tengan problemas con él, o cualquier caso marginal que esté pasando por alto.
El desinfectante HTML de Google Caja se puede hacer "listo para la web" incrustándolo en un trabajador web . Cualquier variable global introducida por el desinfectante estará contenida dentro del trabajador, además el procesamiento se lleva a cabo en su propio hilo.
Para los navegadores que no son compatibles con Web Workers, podemos usar un iframe como un entorno separado para que funcione el desinfectante. Timothy Chien tiene un polyfill que hace exactamente esto, usa iframes para simular Web Workers, por lo que esa parte se hace por nosotros.
El proyecto Caja tiene una página wiki sobre cómo usar Caja como desinfectante independiente del lado del cliente :
- Verifica la fuente, luego construye ejecutando
ant
- Incluye
html-sanitizer-minified.js
ohtml-css-sanitizer-minified.js
en tu página - Llamar a
html_sanitize(...)
El script de trabajador solo necesita seguir esas instrucciones:
importScripts(''html-css-sanitizer-minified.js''); // or ''html-sanitizer-minified.js''
var urlTransformer, nameIdClassTransformer;
// customize if you need to filter URLs and/or ids/names/classes
urlTransformer = nameIdClassTransformer = function(s) { return s; };
// when we receive some HTML
self.onmessage = function(event) {
// sanitize, then send the result back
postMessage(html_sanitize(event.data, urlTransformer, nameIdClassTransformer));
};
(Se necesita un poco más de código para que funcione la biblioteca simworker, pero no es importante para esta discusión).
Demostración: https://dl.dropbox.com/u/291406/html-sanitize/demo.html
Entonces, es 2016, y creo que muchos de nosotros estamos usando módulos npm
en nuestro código ahora. sanitize-html
parece ser la opción principal en npm, aunque hay others .
Otras respuestas a esta pregunta brindan una gran información sobre cómo rodar la suya propia, pero este es un problema bastante complicado que las soluciones comunitarias bien probadas son probablemente la mejor respuesta.
Ejecute esto en la línea de comando para instalar: npm install --save sanitize-html
ES5: var sanitizeHtml = require(''sanitize-html''); // ... var sanitized = sanitizeHtml(htmlInput);
var sanitizeHtml = require(''sanitize-html''); // ... var sanitized = sanitizeHtml(htmlInput);
ES6: import sanitizeHtml from ''sanitize-html''; // ... let sanitized = sanitizeHtml(htmlInput);
import sanitizeHtml from ''sanitize-html''; // ... let sanitized = sanitizeHtml(htmlInput);
La biblioteca de Google Caja sugerida anteriormente era demasiado compleja de configurar e incluir en mi proyecto para una aplicación web (por lo tanto, se ejecuta en el navegador). Lo que recurrí en su lugar, dado que ya usamos el componente CKEditor, es utilizar su función incorporada de desinfección de HTML y listas blancas, que es mucho más fácil de configurar. Por lo tanto, puede cargar una instancia de CKEditor en un iframe oculto y hacer algo como:
CKEDITOR.instances[''myCKEInstance''].dataProcessor.toHtml(myHTMLstring)
Ahora, si no está usando CKEditor en su proyecto, esto puede ser un poco exagerado, ya que el componente en sí mismo está a medio megabyte (minimizado), pero si tiene las fuentes, tal vez pueda aislar el código haciendo la lista blanca ( CKEDITOR.htmlParser
?) y la hace mucho más corta.
No puedes anticipar todos los posibles tipos extraños de etiquetas mal formadas en las que algún navegador podría tropezar para escapar de la lista negra, por lo que no debes incluirlas en la lista negra. Hay muchas más estructuras que puede necesitar eliminar que solo script / embed / object y handlers.
En su lugar, intente analizar el HTML en elementos y atributos en una jerarquía, luego ejecute todos los nombres de elementos y atributos contra una lista blanca lo más mínima posible. También verifique los atributos de URL que permite contra una lista blanca (recuerde que hay más protocolos peligrosos que javascript :).
Si la entrada es XHTML bien formada, la primera parte de lo anterior es mucho más fácil.
Como siempre con la desinfección de HTML, si puede encontrar otra forma de evitar hacerlo, haga eso en su lugar. Hay muchos, muchos agujeros potenciales. Si los principales servicios de correo web siguen encontrando exploits después de tantos años, ¿qué te hace pensar que puedes hacerlo mejor?
Nunca confíes en el cliente. Si está escribiendo una aplicación de servidor, suponga que el cliente siempre enviará datos maliciosos e insalubres. Es una regla de oro que te mantendrá alejado de problemas. Si puede, le aconsejaría que haga toda la validación y el saneamiento en el código del servidor, que usted sabe (en un grado razonable) no se manipulará. ¿Quizás podría usar una aplicación web en el servidor como un proxy para su código del lado del cliente, que obtiene de un tercero y realiza el saneamiento antes de enviarlo al cliente mismo?
[edit] Lo siento, he entendido mal la pregunta. Sin embargo, estoy de acuerdo con mi consejo. Sus usuarios probablemente estarán más seguros si desinfecta el servidor antes de enviárselo.
Recomiendo cortar los marcos de su vida, haría las cosas excesivamente más fáciles para usted a largo plazo.
cloneNode: la clonación de un nodo copia todos sus atributos y sus valores, pero NO copia los detectores de eventos .
https://developer.mozilla.org/en/DOM/Node.cloneNode
Lo siguiente no se ha probado, aunque he usado treewalkers desde hace un tiempo y son una de las partes más infravaloradas de JavaScript. Aquí hay una lista de los tipos de nodos que puede rastrear, generalmente utilizo SHOW_ELEMENT o SHOW_TEXT .
http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal.html#Traversal-NodeFilter
function xhtml_cleaner(id)
{
var e = document.getElementById(id);
var f = document.createDocumentFragment();
f.appendChild(e.cloneNode(true));
var walker = document.createTreeWalker(f,NodeFilter.SHOW_ELEMENT,null,false);
while (walker.nextNode())
{
var c = walker.currentNode;
if (c.hasAttribute(''contentEditable'')) {c.removeAttribute(''contentEditable'');}
if (c.hasAttribute(''style'')) {c.removeAttribute(''style'');}
if (c.nodeName.toLowerCase()==''script'') {element_del(c);}
}
alert(new XMLSerializer().serializeToString(f));
return f;
}
function element_del(element_id)
{
if (document.getElementById(element_id))
{
document.getElementById(element_id).parentNode.removeChild(document.getElementById(element_id));
}
else if (element_id)
{
element_id.parentNode.removeChild(element_id);
}
else
{
alert(''Error: the object or element /''' + element_id + ''/' was not found and therefore could not be deleted.'');
}
}
String.prototype.sanitizeHTML=function (white,black) {
if (!white) white="b|i|p|br";//allowed tags
if (!black) black="script|object|embed";//complete remove tags
var e=new RegExp("(<("+black+")[^>]*>.*<///2>|(?!<[/]?("+white+")(//s[^<]*>|[/]>|>))<[^<>]*>|(?!<[^<>//s]+)//s[^</>]+(?=[/>]))", "gi");
return this.replace(e,"");
}
-black list -> eliminar etiqueta y contenido
-blanco lista -> conservar etiquetas
-otras etiquetas se eliminan pero el contenido de la etiqueta se conserva
-todos los atributos de las etiquetas de la lista blanca (las restantes) se eliminan