Using CVE-2023-43641 as an example, I’ll explain how to develop an exploit for a memory corruption vulnerability on Linux. The exploit has to bypass several mitigations to achieve code execution.
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.
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.
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
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.
The primary defense for XSS is “output encoding.” What does that mean? As an example, it means rendering a user input that was
< 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 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)
- Cascading Style Sheets (CSS, content between
<script> … </script> tags. Likewise, you encode for HTML attributes between
>, including tag names, attribute names, and attribute values. Encode for HTML body between tags and style between
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
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.
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:
- AngularJS (See the Angular security page for more detail.)
- 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.
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.
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.
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
4) that map to other predefined values (maybe file names or paths, for example) where you process the input.
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.