Can thymeleaf be used with user supplied templates safely?

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Can thymeleaf be used with user supplied templates safely?

yankee
I already posted this question on stackoverflow, but even though it has a (still open bounty) it's getting few views and no answer, so I hope it is OK to repost the question here:

In a SaaS application I have some templates that are used in order to generate notification emails or certain HTML pages. So far I am not using thymeleaf and so far all the templates are hard coded, but I'd love to change that, so that users of the application can edit those templates by themselves. The thing is that if I allow users to edit templates themselves, the users can possibly call any Java method and that would totally compromise system security.

Can thymeleaf be "sandboxed" or can all features that are dangerous in the context of user edited templates be disabled? (For execution the template receives a POJO with only getters and setters or a java.util.Map, so calling methods on the model is not a problem)

What I tried
The most obvious problem is OGNL/SpringEL. The power these expressions have can be great, but they are also very dangerous. All I need is to call getters from the model. So I tried to implement my own expression parser like this (the following is just something quick&dirty as a proof of concept, it is not "done"):

   
final TemplateEngine templateEngine = new TemplateEngine();
    final StandardDialect dialect = new StandardDialect();
    dialect.setExpressionParser(new IStandardExpressionParser() {
        @Override
        public IStandardExpression parseExpression(final IExpressionContext context, final String input) {
            if (!input.startsWith("${") || !input.endsWith("}")) {
                throw new IllegalArgumentException("Only variable expressions allowed, not " + input);
            }
            final String[] path = StringUtils.split(input.substring("${".length(), input.length() - "}".length()), '.');
            return new IStandardExpression() {
                @Override
                public String getStringRepresentation() {
                    return "Variable " + Arrays.toString(path);
                }

                @Override
                public Object execute(final IExpressionContext context) {
                    Object result = context.getVariable(path[0]);
                    for (int i = 1; i < path.length; i++) {
                        try {
                            result = BeanUtils.getProperty(result, path[i]);
                        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                            throw new Error(e);
                        }
                    }
                    return result;
                }

                @Override
                public Object execute(final IExpressionContext context, final StandardExpressionExecutionContext expContext) {
                    return execute(context);
                }
            };
        }
    });
    templateEngine.setDialect(dialect);
    System.out.println(templateEngine.process(
        "<html xmlns:th=\"http://www.thymeleaf.org\"><p th:text=\"${someVar}\"></p></html>",
        new Context(Locale.ENGLISH, Collections.singletonMap("someVar", "someValue"))
    ));

And it looks like that works, but will this be enough? Or are there other security holes?
Loading...