flawopen.com/SSTI/FreeMarker y Velocity
¿Son seguras las plantillas FreeMarker y Velocity con entradas de usuario?
Los valores del usuario siguen siendo datos cuando se insertan en una plantilla confiable. El código fuente de plantilla controlado por el usuario es distinto: permite escribir expresiones en el lenguaje del motor. El alcance depende de la versión, el wrapper o introspector, los objetos del modelo y los permisos de la aplicación. No supongas que toda inyección de plantillas ejecuta comandos del sistema; trata las plantillas no confiables como lógica ejecutable y mantenlas fuera del proceso de la aplicación.
// Riesgo: analizar datos de la solicitud como plantilla String src = "Hello " + userSuppliedName; Template t = new Template("t", src, cfg); t.process(model, writer); // Riesgo: evaluar una cadena VTL controlada por el usuario Velocity.evaluate(context, writer, "log", userTemplate);
// Plantilla confiable del classpath; modelo solo con escalares Configuration cfg = new Configuration( Configuration.VERSION_2_3_34); cfg.setTemplateLoader(new ClassTemplateLoader( App.class, "/templates")); cfg.setNewBuiltinClassResolver( TemplateClassResolver.ALLOWS_NOTHING_RESOLVER); cfg.setObjectWrapper(new SimpleObjectWrapper( Configuration.VERSION_2_3_34)); Template t = cfg.getTemplate("hello.ftl"); Map<String,Object> model = Map.of( "name", userSuppliedName); t.process(model, writer); // Velocity: plantilla confiable e introspección restringida (defensa en profundidad) Properties p = new Properties(); p.setProperty("resource.loaders", "class"); p.setProperty("resource.loader.class.class", ClasspathResourceLoader.class.getName()); p.setProperty("runtime.introspector.uberspect", SecureUberspector.class.getName()); VelocityEngine engine = new VelocityEngine(); engine.init(p); Template vt = engine.getTemplate("hello.vm", "UTF-8"); VelocityContext vc = new VelocityContext(); vc.put("name", userSuppliedName); vt.merge(vc, writer);
Por qué los motores Java pueden exponer capacidades peligrosas
Las plantillas evalúan expresiones sobre un modelo de datos, pero la superficie Java disponible depende de la configuración. El wrapper predeterminado de FreeMarker puede exponer propiedades y métodos JavaBean de POJO arbitrarios; los miembros accesibles dependen del wrapper y de la política de acceso. El Uberspect predeterminado de Velocity relaciona propiedades VTL con métodos Java públicos. Esto no significa que cualquier modelo permita reflexión sin restricciones: el riesgo aumenta si contiene objetos de servicio, cargadores de clases, objetos de solicitud u otras capacidades potentes. Prefiere mapas de cadenas y valores simples; expón solo los campos que necesite cada plantilla confiable.
El built-in ?new de FreeMarker puede instanciar clases que implementan TemplateModel. Apache recomienda restringir la resolución de clases para plantillas cargadas por usuarios; SAFER_RESOLVER aún permite algunas clases, mientras que ALLOWS_NOTHING_RESOLVER deniega toda resolución. Esto no sustituye un wrapper restringido ni un modelo mínimo.
Cómo reducir el riesgo y conocer los límites
- ✓FreeMarker: para plantillas no confiables, establece
new_builtin_class_resolverenALLOWS_NOTHING_RESOLVERy usa un wrapper comoSimpleObjectWrapper, que rechaza POJO arbitrarios. Pasa solo valores simples y específicos. - ✓Velocity: usa
SecureUberspectorcon plantillas no confiables y limita el contexto a los valores simples necesarios. Bloquea rutas documentadas de reflexión y creación de objetos; no es un sandbox del sistema operativo. - ✓Ambos: carga plantillas de la aplicación desde un origen confiable y protegido contra cambios. Nunca conviertas parámetros de solicitud ni filas editables por usuarios en código fuente de plantilla.
- ✓Si las plantillas escritas por usuarios son un requisito: procésalas en un proceso separado con privilegios mínimos y sin red, secretos ni archivos de aplicación modificables; limita CPU, memoria y tiempo fuera del motor.
Trata las restricciones del motor como defensa en profundidad, no como aislamiento de procesos. Usa versiones compatibles, revisa la configuración exacta según la guía vigente y mantén el renderizador lejos de secretos y objetos privilegiados.
Preguntas frecuentes
Es un riesgo relacionado, pero distinto. Un nombre de vista controlado por el usuario no constituye automáticamente una inyección de expresiones. Revisa el flujo de datos concreto hacia la evaluación de Thymeleaf o Spring Expression Language y no construyas fragmentos de expresión con entradas no confiables. Mantén las integraciones actualizadas y selecciona vistas mediante lógica confiable de la aplicación.
Quienes escriben plantillas pueden usar las capacidades expuestas por el motor y el modelo; eso no siempre significa ejecutar comandos del sistema operativo. Trata el permiso de edición como un privilegio sensible de autoría de código, limítalo a mantenedores de confianza y registra los cambios.
Un motor sin lógica como Mustache puede reducir la superficie de expresiones y llamadas a métodos, pero siguen importando el escape de salida, el control del origen de la plantilla y las capacidades de los helpers. Elige un motor diseñado para ese modelo de confianza y aísla el renderizado; no uses un motor JVM general como sandbox.
Fuentes oficiales y referencia
- Apache FreeMarker — FAQ sobre seguridad de plantillas subidas
- Apache FreeMarker — API SimpleObjectWrapper
- Apache FreeMarker — API TemplateClassResolver
- Apache Velocity — guía del desarrollador
- Apache Velocity 2.3 — API ClasspathResourceLoader
- PortSwigger — Server-Side Template Injection
- CWE-1336 — Server-Side Template Injection