Encoding and escaping untrusted data to prevent injection attacks
Practical tips on how to apply OWASP Top 10 Proactive Control C4.
This is part five of GitHub Security Lab’s series on the OWASP Top 10 Proactive Controls, where we provide practical guidance for OSS developers and maintainers on improving your security posture.
A vast majority of injection attacks come from what we would term tampered data: unexpected data or formatting in inputs with the intent of discovering or exploiting vulnerabilities. In this post, I’ll discuss ways to defend yourself using guidance from OWASP Top 10 Proactive Controls C4: Encode and Escape Data—including the “why” and “what” of that control.
Encoding and injection according to OWASP
From the OWASP document about this control:
Encoding and escaping are defensive techniques meant to stop injection attacks.
Until 2017, OWASP’s list of Top 10 Risks listed cross-site scripting (XSS) separately from “injection.” There are many (myself included) that consider XSS a form of injection. So, saying that output encoding prevents injection attacks is accurate in that light.
Injection attacks, encoding and interpreters
The real danger of injection attacks is that they are usually of a what-you-see-is-NOT-what-you-get nature. Using different encoding schemes that our interpreters will often “helpfully” decode later, attackers bypass simple denylist approaches. I recall once (early in my security awareness days, still working as a software engineer) trying to defend against SQL injection (SQLi) by looking for unexpected commands like DROP
or INSERT
where they might not be expected. Similarly, some web applications have looked for <script>
in inputs to defend against XSS. This sort of approach is fragile, difficult to maintain from a code perspective, and ineffective from a security perspective. Also, the real defense against SQLi is parameterized queries (which encode things for you, more on that later), but let’s get back to encoding.
Encoding can be used in attacks as well as defense. In an attack, a malicious user might send %3Cscript%3E
instead of <script>
to evade an oversimplified denylist employed as a defense. Output encoding, which I’ll talk about shortly, is a defensive technique.
In most cases, the helpful interpreter is your browser, but it could also be a command-line environment or other bit of software such as a database driver. The browser, and hence XSS, represents a large target surface for which output encoding is the prevention technique. I’ll also cover some other examples. Let’s dig in on XSS first.
XSS and output encoding
Cross-site scripting (XSS) is a vulnerability. When exploited, a malicious user injects their JavaScript to run in your browser in a malicious way and usually without your knowledge.
The primary defense for XSS is “output encoding.” What does that mean? As an example, it means rendering a user input that was <
as <
so that the input renders the <
on the page (viewable as content) and not as HTML source. In short, output encoding enables safe rendering of certain characters to the target interpreter.
Context is key
Context is very important in any discussion of XSS, browsers and encoding. In the browser, when we talk about context, we are primarily talking about where content is rendered. There are four contexts.
- HTML body (text between tags)
- HTML attributes (text within the tags)
- JavaScript (content between
<script>
and</script>
tags) - Cascading Style Sheets (CSS, content between
<style>
and</style>
tags)
Each context has its own encoding system. What this means is that you encode for JavaScript if you are outputting content between <script> … </script>
tags. Likewise, you encode for HTML attributes between <
and >
, including tag names, attribute names, and attribute values. Encode for HTML body between tags and style between <style>
tags.
Encoding properly is hard
The conventional and wise advice is to encode any data you output from an untrusted source (user or any external source) for the proper context (see above). This is a lot to get right.
I once did training for a group that had an XSS finding in an app. We paused the training to dig into the affected application and code to do a hands-on exercise. When we checked the git blame
and the comments, the XSS finding was introduced because of a change in the encoding context. It was encoded for JavaScript but was output into an HTML attribute. It had been encoded for a different context. The wrong encoding context opened the application up to the vulnerability. It’s an easy mistake to make with potentially high impact.
Bang for your buck for application developers
So, when you are looking to defend against something as difficult and potentially pervasive as XSS by encoding, you need that encoding to be automatic. It should be something you don’t have to think about constantly. OWASP lists it as a “bonus” rule in their cross-site scripting prevention doc. I recommend starting here. Select your templating/output engine such that encoding happens automatically for the right context. This means it would need to be explicitly overridden or disabled to make it insecure.
Auto-encoding frameworks/templating engines
The OWASP doc regarding this control also says:
Output encoding is best applied just before the content is passed to the target interpreter.
This is where frameworks and templating engines come into play. To make your anti-XSS life easier, use a framework that defaults to safely performing output encoding (which it will do as it passes content to the target interpreter). Here are a few:
- ReactJS
- AngularJS (See the Angular security page for more detail.)
- Handlebars
- LiquidJS
- Rails
- Java Encoder Project (From OWASP. Not an actual framework or template engine, but a well-written library to help in the fight against XSS.)
- .Net (Generally does well by default, but best to read their security doc on the topic.)
Are these all guaranteed 100% XSS-free forever? No, but they have a solid record out of the box. This list is not exhaustive. If you would like to use something not on it, spend some time researching and maybe even ask around on whether that templating engine or library auto-escapes or performs output encoding according to the context by default.
Don’t escape the escaping
When you do use a library or framework that handles the output encoding (or escaping) for you by default, don’t bypass it. Some frameworks make it painfully obvious that you are doing it (like React and dangerouslySetInnerHTML
). Others are more ambiguous, like Rails with html_safe
. If you are unsure, read the docs.
Other encoding or escaping scenarios
XSS and the browser are the prominent example of using encoding defensively. However, the ‘front end’ of web apps is not the only place you should use encoding to keep your applications safe.
As I mentioned earlier, using parameterized queries is a form of encoding/escaping potentially malicious input intended to cause SQL injection. A great explanation offered to me is that malicious input can be used to mix the control plane (the query) and the data plane (like values in the WHERE
clause that you want to use from the user). This gives the user control over the control plane, allowing them to restructure or rewire the query. In the case of SQLi, this is most commonly done by introducing an unexpected ’
to imbalance those planes. Parameterized queries automatically encode properly for you, negating that scenario of mixing control and data planes. Object-relational mapping (ORM) libraries do this by default in most cases too.
In command injection, unexpected newline characters (\n
) may be introduced in order to bypass brittle validation. Normalize or encode the input to ensure that it represents a single line before validating it. Check the regular expression references for your language to ensure you are using single-line anchors in your validation. In general, this sort of encoding/normalization is a good idea for code quality.
Indirection as encoding
There will be times (namely when trying to prevent command injection) where you cannot encode (or regex) your way into safe usage. In such cases, consider indirection or abstraction. What this usually means is that you offer predefined values (like 1
,2
,3
,4
) that map to other predefined values (maybe file names or paths, for example) where you process the input.
Good friction
Encoding and escaping in addition to validation will always add some friction to your application or service, but not all friction is bad. In the case of attack prevention, think of it like brakes on the car: it’s what allows you to go fast the rest of the time.
Check out the rest of our OWASP Top 10 Proactive Controls series, or follow GitHub Security Lab on Twitter for the latest in security research.
Tags:
Written by
Related posts
Execute commands by sending JSON? Learn how unsafe deserialization vulnerabilities work in Ruby projects
Can an attacker execute arbitrary commands on a remote server just by sending JSON? Yes, if the running code contains unsafe deserialization vulnerabilities. But how is that possible? In this blog post, we’ll describe how unsafe deserialization vulnerabilities work and how you can detect them in Ruby projects.
10 years of the GitHub Security Bug Bounty Program
Let’s take a look at 10 key moments from the first decade of the GitHub Security Bug Bounty program.
Where does your software (really) come from?
GitHub is working with the OSS community to bring new supply chain security capabilities to the platform.