Esigenza Reale
Blindare i controller che gestiscono percorsi dinamici o temi grafici selezionabili dall’utente.
Analisi Tecnica
Problema: Esecuzione di codice remoto (RCE) tramite la manipolazione dei parametri che determinano quale file di template deve essere renderizzato.
Perché: Sanitizzazione rigorosa dei path. Ho scelto di mappare gli input utente su una Enum predefinita prima di restituire il nome della stringa al DispatcherServlet.
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
/* Dimostro l'attacco SSTI su Thymeleaf per capire cosa stiamo prevenendo. Un
attaccante invia: GET
/page?lang=__$%7BT(java.lang.Runtime).getRuntime().exec('id')%7D__::.x
Thymeleaf interpreta la sequenza __${...}__:: come preprocessing e valuta
l'espressione, eseguendo comandi di sistema con i privilegi della JVM. */
// CODICE VULNERABILE: concatenazione diretta di input utente nel nome della
view @GetMapping("/page") public String VULNERABLE_render(@RequestParam
String lang, Model model)
{
return "pages/" + lang + "/index";
// VULNERABILE: lang = "../../../etc/passwd" o payload SSTI
}
/* SOLUZIONE 1: Allow-list tramite Enum. Solo le viste esplicitamente definite
nell'Enum possono essere caricate. Qualsiasi input non riconosciuto viene
rifiutato con un errore controllato. */
public enum AllowedPage {
HOME("pages/home/index"), ABOUT("pages/about/index"),
CONTACT("pages/contact/index"), IT("pages/it/index"),
EN("pages/en/index"), DE("pages/de/index");
private final String templatePath;
AllowedPage(String templatePath) {
this.templatePath = templatePath;
}
public String getTemplatePath() {
return templatePath;
}
public static Optional<AllowedPage> fromCode(String code) {
return Arrays.stream(values()) .filter(p ->
p.name().equalsIgnoreCase(code)) .findFirst();
}
}
@GetMapping("/page") public String safeRender(
@RequestParam String code, Model model) {
// Mapping sicuro: solo valori nell'Enum passano al motore Thymeleaf
AllowedPage page = AllowedPage.fromCode(code) .orElseThrow(() -> new
ResponseStatusException(HttpStatus.BAD_REQUEST, "Pagina non
riconosciuta: " + code))
;
model.addAttribute("pageCode", page.name());
return page.getTemplatePath();
// Percorso hardcoded nell'Enum: zero input utente nel path
}
/* SOLUZIONE 2: Allow-list tramite Map statica per logiche più dinamiche. */
@Controller public class ThemeController {
// Mappa statica: i percorsi sono hardcoded, l'utente sceglie solo la chiave
private static final Map<String, String> ALLOWED_THEMES = Map.of(
"light", "themes/light/index", "dark", "themes/dark/index",
"high-contrast", "themes/high-contrast/index" )
;
@GetMapping("/theme") public String renderTheme(
@RequestParam String theme, Model model) {
String templatePath = ALLOWED_THEMES.get(theme);
if (templatePath == null) {
log.warn("Tentativo di accesso a tema non autorizzato: '{
}
' da IP: {
}
", theme, request.getRemoteAddr());
return "error/400";
// Pagina di errore predefinita
}
return templatePath;
}
}
/* SOLUZIONE 3: Per l'internazionalizzazione, uso LocaleChangeInterceptor di
Spring invece di costruire percorsi manualmente. */
@Configuration public class WebMvcConfig implements WebMvcConfigurer {
@Bean public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang");
// Spring gestisce il cambio locale in modo sicuro
return interceptor;
}
@Override public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
/* Con LocaleChangeInterceptor, il controller non tocca mai il parametro lang:
*/
@GetMapping("/page") public String safePage(Model model) {
// Thymeleaf usa il Locale corrente per #messages: zero manipolazione del
path
return "pages/index";
// Percorso fisso: Spring risolve l'i18n dai messaggi
}