Not all content you render in your templates or snippets can be trusted. For example, it may come from user-generated content like a registration or comment form. Or you might use content from external sources such as an API or external database. Panel editors can also be a risk, especially in larger organizations.
When you cannot fully trust the content, it is important to escape it correctly before it's sent to the browser to prevent cross-site scripting (XSS) attacks.
It is not necessary to apply the information from this guide to every single field you output. Keep the level of risk in mind for each case – escaping is important in the scenarios explained above, but not necessary for content from a trusted source.
What is an XSS attack?
XSS works by injecting code into the HTML output of your site in a place where you didn't expect it when writing the template code. Here's an example of a vulnerable snippet:
Looks harmless, right? But what if the
copyright field of your
site.txt contains this:
The HTML output you will get from the snippet is this:
This is just one example of an XSS attack. XSS can also occur with attribute values, strings printed inside legitimate
<script> blocks etc. Often the attacker needs to be a bit more clever than in our example, but without protection it's always possible to misuse your template or snippet for an XSS attack.
Attackers can only make use of such vulnerabilities if they find a way to inject their malicious content into your site. For example, they could be an editor with access to your Kirby Panel. But it could also be as simple as submitting a contact, comment or registration form on your site. Don't underestimate the chances.
How to protect against XSS attacks
There are three common ways to prevent XSS attacks:
- Data validation
This involves making 100 % sure that the content you output in your templates and snippets has the exact format you expect and doesn't contain unexpected characters that could be misued. While validating your content fields can be very useful to make sure your content fits your site and works correctly, it is hard to get right as it's often difficult to make the validator strict enough to block any harmful content. It's also difficult to even anticipate every possible attack.
- Data sanitization
Sanitizing data means removing any unwanted characters from it. Basically it is a stricter form of validation and can be useful in use-cases like comment forms, but it shares its disadvantages with data validation – it's hard to implement it securely.
Escaping means replacing every unsafe character with an escape sequence that tells the browser it should interpret the character as text and not as a special character. When printing content in the HTML body, for example, a harmful character could be escaped with an HTML entity: The
<character would become
<. It's important to use the right escaper for each output context, i.e. the place where your content is being output (HTML body, HTML attribute, inside a
<script>tag, as part of a URL...). Otherwise attackers will be able to circumvent your escaper.
Depending on your use-case, one or more of these strategies might be a good fit. You can combine multiple of them and sometimes you need to – e.g. your validator might not know in which places your content is being output, so you need a context-sensitive escaper on top of your validator. The validator then only checks if the content follows the right structure.
Escaping data in templates and snippets
esc() helper and
esc() field methods
Often the most effective and also easiest solution to prevent XSS on your Kirby site is to use context-sensitive escaping.
For that, we provide the
esc() helper and the
esc() field methods.
Let's take our example snippet from above and add escaping to it:
It's that simple! Now let's see what the output looks like for the malicious content:
Because all special characters in HTML have been escaped, the browser will no longer execute the malicious script but will print the field contents as-is (so the malicious HTML code itself will be displayed without doing any harm).
You might wonder why Kirby doesn't escape everything automatically if it's so simple. As you've read above, escaping always needs to be context-sensitive. You cannot use the same escaping method for HTML text, attributes or URLs. This is why you need to tell Kirby what type of escaping you need. Here are a few examples of different contexts:
As you can see, you can pass the context type to the helper or field method and Kirby will automatically escape the content correctly.
Always pass the correct context to the
esc() helper and field methods, otherwise the escaping won't have an effect and your site will still be vulnerable.
As KirbyText is an extension of Markdown, it also supports raw HTML:
If you can't trust your KirbyText content but do want to allow formatting, you can combine Kirby's escaper with KirbyText:
This will create the following output:
Make sure to always put
kirbytext(), otherwise the allowed KirbyText formatting will be escaped as well.
Please note that not all Markdown formatting will survive the escaping, even with the right method order. For example simple Markdown links like
<https://example.com> and blockquotes like
> This is a quote will currently break during escaping. This is a limitation in our Markdown parser and we are working on a solution.