Escaping Interpolated Values in Hippo Templates

March 15th 2019 FreeMarker Hippo CMS

If untrusted values are not correctly escaped when included in web page markup, they can easily make the site susceptible to attacks. To reduce the risk of developer mistakes, many template engines can take care of escaping by default. FreeMarker template engine is no exception. Unfortunately, Hippo CMS default configuration doesn't enable automatic escaping in FreeMarker templates.

Because of that, developer mistakes can make the site vulnerable to Cross-site scripting (XSS) attacks as in the following example:

<button id="the-button" data-id="${id}">Click me!</button>

<script type="application/javascript">
    const button = document.getElementById('the-button');
    button.addEventListener('click', function(event) {
        const id = event.target.getAttribute('data-id');
        alert("Id value: " + id);
    });
</script>

Let's imagine that the id value comes from an untrusted source, e.g. a query parameter:

public class SampleComponent extends CommonComponent {

    @Override
    public void doBeforeRender(final HstRequest request, final HstResponse response) {
        super.doBeforeRender(request, response);

        request.setAttribute("id", request.getRequestContext().getServletRequest().getParameter("id"));
    }
}

If the site visitor clicks on a carefully crafted URL provided by an attacker, injected JavaScript code could be executed on the page. For example, the query string value of ?id=5%22%20onclick%3D%22console.log(%27injected%27) would result in the following HTML sent to the client:

<button id="the-button" data-id="5" onclick="console.log('injected')">Click me!</button>

Although Chrome would protect the user from such an attack, this isn't true for all browsers. In the latest version of Firefox (65.0.1 at the time of writing), the button would still work as expected, but injected would be emitted to the console at the same time. Depending on the contents of the site, the attacker could perform a more malicious action, e.g. send some data from the site to his server.

The developer can prevent such attacks if he always escapes untrusted values before using them (e.g. by using the html FreeMarker built-in):

<button id="the-button" data-id="${id?html}">Click me!</button>

There are several ways to configure automatic escaping of values in FreeMarker. Although Hippo CMS doesn't do that yet by default, it can still be done with certain restrictions.

Configuring Output Format Globally

HTML output format can be configured globally in the site web.xml file by adding the following init-param element to Freemarker's servlet configuration:

<init-param>
    <param-name>output_format</param-name>
    <param-value>HTMLOutputFormat</param-value>
</init-param>

This will enable auto-escaping. As a result, the html built-in is not allowed to be used anymore. Any occurrences will result in an error:

Using ?html (legacy escaping) is not allowed when auto-escaping is on with a markup output format (HTML), to avoid double-escaping mistakes.

If you decide to go this route, you will need to remove all usages of this built-in. Not only from your templates, but also from default templates generated by the Essentials setup application. If that means to much work or is too risky for your current state of the project, you can still enable auto-escaping selectively.

File Extension Based Output Format

FreeMarker supports file extension based output formats which can be used to only enable auto-escaping in files with a specific file extensions (i.e. .ftlh for HTML format instead of the default .ftl). This approach allows you to only change the behavior in a subset of files with the different file extension.

To enable this behavior, the following init-param element must be added to Freemarker's servlet configuration the site web.xml file:

<init-param>
    <param-name>recognize_standard_file_extensions</param-name>
    <param-value>true</param-value>
</init-param>

Additionally, the corresponding servlet-mapping element in the same file must be updated to include the new .ftlh extension:

<servlet-mapping>
    <servlet-name>freemarker</servlet-name>
    <url-pattern>*.ftl</url-pattern>
    <url-pattern>*.ftlh</url-pattern>
</servlet-mapping>

Using the Hippo CMS console, the extension must also be added to the webfiles module configuration; its includedFiles property in the /hippo:configuration/hippo:modules/webfiles/hippo:moduleconfig node, to be exact.

However, even with all this set up, the files still don't refresh if they are changed while the site is running which makes testing of changes in templates very time consuming. Based on this issue comment, there might be other problems with this approach in Hippo CMS that aren't immediately obvious. Therefore, I don't recommend it.

Specifying Output Format in Templates

Output format can also be specified in each individual FreeMarker template, no matter its extension:

<#ftl output_format="HTML">

This will enable auto-escaping for that file. The only downside is that the developers can forget to add the header to a file and therewith potentially expose the page to attacks.

Excluding Values from Auto-Escaping

In any file with HTML output format specified (implicitly or explicitly), all values will be escaped. For most cases this is ok. But there might be some trusted values for which you know that they are already escaped. One such example is the action URL value for forms, as provided by the hst:actionURL tag:

<@hst.actionURL var="actionLink"/>

If used in a template with auto-escaping enabled, the special characters in it will be escaped (all occurences of & will become &amp;) and the POST request will not reach the doAction method in the corresponding component. To avoid this, the no_esc built-in must be used:

<form action="${actionLink?no_esc}" method="post">
    <!-- form fields here -->
    <button type="submit">Submit</button>
</form>

This will keep the URL unmodified and ensure that the POST request will be processed by the component as expected.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License