Cómo filtrar contenido HTML de forma segura

Escrito por el , actualizado el .
java programacion planeta-codigo
Enlace permanente Comentarios

Java

Algunos sitios y aplicaciones pueden tener la necesidad de «scrapear» el contenido de sitios web para extraer información de ellos y posteriormente usarla de alguna forma. El contenido «scrapeado» o obtenido de una fuente externa debe ser filtrado, si no es filtrado y posteriormente es servido a los usuarios puede enviárseles principalmente scripts con contenido malicioso (provocando un ataque cross-site scripting, XSS) o causar una desmaquetación al visualizar el contenido. A la hora de implementar la agregación de contenido de forma segura en Blog Stack, contenido obtenido de una fuente RSS o Atom pero que en esencia es HTML he usado la librería jsoup, de tal forma que solo el contenido considerado seguro o confiable de los artículos sea agregado.

¿Que puede pasar si en una fuente envía elementos <script>, <iframe> u <object>? Los <script> son código que se envía al navegador del usuario y que podrían explotar algún fallo de seguridad del navegador que usen, los <iframe> permiten cargar contenido de una tercera fuente. En definitiva podrían hacer que visitar Blog Stack fuese inseguro. Pero no permitir incluir estos elementos también haría que no se pudiesen mostrar vídeos de YouTube, Vimeo, Gist de GitHub, presentaciones de SpeackerDeck y se perdería parte del contenido original. La solución que he aplicado en Blog Stack es permitir el contenido de esos elementos que provienen de una fuente considerada confiable, es decir, si se trata de un iframe cuyo elemento src proviene de YouTube se permite el contenido ya que se supone que YouTube y su contenido es seguro. De esta forma el contenido puede agregarse de forma segura sin perder nada del contenido original.

Para hacer el filtrado de HTML en java podemos usar jsoup, para ello deberemos usar la clase Whitelist que proporciona jsoup o implementar una clase que la extienda con las etiquetas y sus atributos que consideramos seguros. En Blog Stack he necesitado implementar una clase Whitelist agregándole la funcionalidad que deseaba.

Esta es la implementación de la clase Whitelist, con el método addTag se indican los tags permitidos, con addAttributes se indican los atributos permitidos para cada etiqueta, addProtocols se indican los protocolos permitidos para cada etiqueta y atributo, finalmente el método addAttribute permite usar una expresión regular para el valor del atributo, esto se comprueba en el método isSafeAttribute:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package info.blogstack.misc;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jsoup.nodes.Attribute;
import org.jsoup.nodes.Element;
import org.jsoup.safety.Whitelist;

public class AppWhitelist extends Whitelist {

	private List<Map<String, String>> attributes;

	private AppWhitelist() {
		attributes = new ArrayList<>();
	}

	public static Whitelist relaxed() {
		Whitelist wl = new AppWhitelist()
				.addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i",
						"img", "li", "ol", "p", "pre", "q", "small", "strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul")

				.addAttributes("a", "href", "title")
				.addAttributes("blockquote", "cite")
				.addAttributes("col", "span", "width")
				.addAttributes("colgroup", "span", "width")
				.addAttributes("img", "align", "alt", "height", "src", "title", "width")
				.addAttributes("ol", "start", "type")
				.addAttributes("q", "cite")
				.addAttributes("table", "summary", "width")
				.addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width")
				.addAttributes("th", "abbr", "axis", "colspan", "rowspan", "scope", "width")
				.addAttributes("ul", "type")

				.addProtocols("a", "href", "ftp", "http", "https", "mailto")
				.addProtocols("blockquote", "cite", "http", "https")
				.addProtocols("cite", "cite", "http", "https")
				.addProtocols("img", "src", "http", "https")
				.addProtocols("q", "cite", "http", "https")
				
				//
				.addTags("script", "iframe", "noscript")
				.addAttributes("div", "style")
				.addAttributes("img", "style")
				.addAttributes("script", "src", "data-.*")
				.addAttributes("iframe", "src", "width", "height", "frameborder", "allowfullscreen", "style", "marginwidth", "marginheight", "frameborder", "scrolling")
				.addAttributes("object", "width", "height")
				.addAttributes("param", "name", "value")
				.addAttributes("embed", "src", "type", "width", "height", "allowscriptaccess", "allowfullscreen");
		return wl;
	}

	public Whitelist addAttribute(String tag, String key, String regexp) {
		Map<String, String> attribute = new HashMap<>();
		attribute.put("tag", tag);
		attribute.put("key", key);
		attribute.put("regexp", regexp);
		return this;
	}

	@Override
	protected boolean isSafeAttribute(String tagName, Element el, Attribute attr) {
		for (Map<String, String> attribute : attributes) {
			String tag = attribute.get("tag");
			String key = attribute.get("key");
			String regexp = attribute.get("regexp");

			if (tag.equals(tagName) && attr.getKey().matches(key) && attr.getValue().matches(regexp)) {
				return true;
			}
		}
		return super.isSafeAttribute(tagName, el, attr);
	}
}
AppWhitelist.java

Y esta es la forma de usar la clase a través de Jsoup.clean:

1
2
3
4
5
6
7
8
AppWhitelist whitelist = (AppWhitelist) AppWhitelist.relaxed();
whitelist.addAttribute("script", "src", "^http[s]?://speakerdeck.com/.*$");
whitelist.addAttribute("script", "src", "^http[s]?://gist.github.com/.*$");
whitelist.addAttribute("iframe", "src", "^http[s]?://www.youtube.com/embed/.*$");
whitelist.addAttribute("iframe", "src", "^http[s]?://player.vimeo.com/video/.*$");
whitelist.addAttribute("iframe", "src", "^http[s]?://rcm-eu.amazon-adsystem.com/.*$");
whitelist.addAttribute("embed", "src", "^http[s]?://www.youtube.com/v/.*$");
String content = Jsoup.clean(postContent.toString(), source.getPageUrl(), whitelist);
IndexServiceImpl.java

El código fuente completo de BS junto con el «scrapeado» y el uso de esta clase está disponible en un repositorio de GitHub.


Comparte el artículo: