Esigenza Reale
Visualizzare correttamente descrizioni formattate (RTF) inserite dagli utenti senza esporsi a iniezioni di script.
Analisi Tecnica
Problema: Iniezione di JavaScript malevolo che viene eseguito nel browser di altri utenti quando visualizzano contenuti non filtrati.
Perché: Sanitizzazione “White-list”. Ho scelto di pulire il markup a livello di backend prima di inviarlo al modello, garantendo che solo i tag HTML sicuri (b, i, p) arrivino al motore di rendering.
Esempio Implementativo
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
/* Aggiungo OWASP Java HTML Sanitizer al pom.xml: */
// <dependency> // <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
// <artifactId>owasp-java-html-sanitizer</artifactId> //
<version>20220608.1</version> // </dependency> /* Definisco le policy di
sanitizzazione per diversi contesti di utilizzo. */ @Configuration public
class HtmlSanitizerConfig
{
/* Policy per commenti e descrizioni utente: solo formattazione base. */
@Bean("basicHtmlPolicy") public PolicyFactory basicHtmlPolicy() {
return new HtmlPolicyBuilder() .allowElements("b", "i", "em", "strong",
"u", "s", "br", "p")
// Nessun tag strutturale, nessun link, nessuna immagine .toFactory()
;
}
/* Policy per articoli del CMS: formattazione ricca ma senza JS. */
@Bean("richHtmlPolicy") public PolicyFactory richHtmlPolicy() {
return new HtmlPolicyBuilder() .allowElements("b", "i", "em", "strong",
"u", "s", "br", "p", "h2", "h3", "h4", "ul", "ol", "li",
"blockquote", "code", "pre") .allowElements("a") .onElements("a")
.allowAttributes("href").matching(Pattern.compile("https?:
//.*")) // Solo URL assoluti HTTPS
.allowAttributes("target").matching(Pattern.compile("_blank")).onEle
ments("a") .requireRelNofollowOnLinks() // Aggiunge rel="nofollow"
automaticamente .allowElements("img") .onElements("img")
.allowAttributes("src").matching(Pattern.compile("https://.*")) //
Solo immagini HTTPS .allowAttributes("alt", "width",
"height").onElements("img") .toFactory()
;
}
}
/* Service che applica la sanitizzazione prima che il dato raggiunga il modello.
*/
@Service public class ContentSanitizationService {
@Autowired
@Qualifier("basicHtmlPolicy") private PolicyFactory basicHtmlPolicy;
@Autowired
@Qualifier("richHtmlPolicy") private PolicyFactory richHtmlPolicy;
/* Sanitizzazione di base per commenti e bio utente. */
public String sanitizeBasic(String userInput) {
if (userInput == null) return "";
return basicHtmlPolicy.sanitize(userInput);
}
/* Sanitizzazione ricca per articoli del CMS. */
public String sanitizeRich(String cmsContent) {
if (cmsContent == null) return "";
return richHtmlPolicy.sanitize(cmsContent);
}
/* Sanitizzazione per output JSON (prevenzione XSS in API REST): */
public String sanitizeForJson(String input) {
if (input == null) return null;
// Escape caratteri pericolosi in contesti JSON
return input.replace("<", "\\u003C").replace(">",
"\\u003E").replace("&", "\\u0026");
}
}
/* Controller che usa la sanitizzazione prima di passare i dati al modello: */
@Controller public class ArticleController {
@Autowired private ContentSanitizationService sanitizer;
@Autowired private ArticleRepository articleRepository;
@GetMapping("/articles/{
id
}
") public String showArticle(
@PathVariable Long id, Model model) {
Article article = articleRepository.findById(id).orElseThrow();
// Sanitizzo nel controller: il modello contiene solo HTML sicuro
model.addAttribute("title", article.getTitle())
;
// th:text: escaped automaticamente model.addAttribute("safeBody",
sanitizer.sanitizeRich(article.getBody()))
;
// th:utext: sicuro perché già sanitizzato
model.addAttribute("safeExcerpt",
sanitizer.sanitizeBasic(article.getExcerpt()))
;
return "articles/show";
}
}
<!-- Template: th:utext è sicuro perché i dati sono già stati sanitizzati nel
Service. --> <!-- th:text per contenuto plain text: Thymeleaf fa l'escaping
automaticamente --> <h1 th:text="${
title
}
">Titolo Articolo</h1> <!-- th:utext SOLO per contenuto pre-sanitizzato: il
commento documenta la scelta --> <!-- safeBody è stato passato attraverso
richHtmlPolicy: nessun tag script possibile --> <div th:utext="${
safeBody
}
">Corpo dell'articolo...</div> <!-- th:utext per excerpt pre-sanitizzato con
basicHtmlPolicy --> <p th:utext="${
safeExcerpt
}
">Anteprima...</p>