We continue to post the series of articles about AppSec Manifesto by Alex Tatulchenkov, Senior Software Developer at Intetics. Here is the next part.
Introduction
This is the second article in the series of articles about the adoption of the AppSec Manifesto. In the previous article, we discussed how one can adopt Rule #0. Also, I mentioned that XSS may be prevented by context-specific escaping.
Rule #1
The Lord of the Sinks
Do context-specific escaping on context boundary. Caution is the parent of safety!
Escape data as close to the sink as possible
What is escaping? Escaping is a process of adding a special character before the character/string to prevent misinterpretation.
From the AppSec Manifesto perspective
escaping is a way to reduce ambiguity and prevent interpretation of one language as another language.
Let’s take a look at the example
<input type="text" name="username" value="<?=$_GET['username'];?>" />
If an attacker provided "%20onmouseover=%27alert(1)%27"
as the username then JS alert with a message 1
will always be triggered when the user hovers the input field.
This occurs because "
(double quote) is ambiguous in the HTML attribute context. So, the username (the first language) got interpreted as the HTML markup (the second language). To prevent this and to reduce ambiguity we have to escape "
for HTML attribute context. In PHP it may be done with htmlspecialchars()
<input type="text" name="username" value="<?=htmlspecialchars($_GET['username']);?>" />
The list of the characters that have to be escaped depends on the target context. There are a lot of contexts: URL, CSS, JS, HTML tag, HTML attribute etc. There is a good guide from OWASP on how to prevent XSS with details and explanations for each context.
More importantly, XSS — is an example of an injection attack. And all injection attacks have a common root — ambiguity!
So, no matter what kind of injection you are looking at:
- XSS
- SQL Injection
- NoSQL Injection
- OS Command Injection
- Code Injection
- XXE
- XPATH Injection
- etc.
The key to preventing injection is reducing ambiguity.
Ok, now it’s clear that we should escape user input respecting the target context, but why should we do it at the very end (as close to the sink as possible)? There is always a temptation to escape data at the very beginning because in such a case you can escape data for several sinks in a single place. Even some SAST tools may advise you to escape/sanitize data in a specific place to resolve several issues.
For example, why should I not do like this:
$get = array_map(function($item) { return htmlspecialchars($item);}, $_GET); foreach ($get as $k => $v) { $_GET[$k] = $v; }
There are few reasons for this:
- At that stage the target context is unknown. So you can’t know for sure which characters to escape. HTML escaping as shown above makes sense the body of the HTML document, e.g as inside a <p> tag. It may work for HTML attributes if you are using quotes around your attributes. But it doesn’t work inside <script>, or an event handler attribute (
onclick
,onmouseover
, etc.) or CSS or URL. It even may doesn’t make sense at all e.g if you are running CLI application and do output into the console or don’t have an output at all and just store everything into the file or database. - You may need access to raw (unescaped) input, so you have to escape/unescape data several times. In such a case, you may never know for sure if your data was already escaped before. This may cause injections or double encoding.
Also it worth mentioning that you should escape data near the specific sink, not just any sink. Let’s recall what is sink:
Sink: The program point that writes to the external resource.
So, if we are going to mitigate XSS we should escape data just before outputting it to the browser, not before saving it to the database, despite the fact that the place where data is written into the database is also a sink.
Escape any data no matter if it’s user-provided or system generated.
It’s quite obvious that we should somehow escape/sanitize user controlled input, e.g $_GET, $_POST, $argv etc. But what about hardcoded values that are part of our code base? They can’t be (almost) manipulated by the user so what is the reason to escape them?
Multilingual applications can serve as a good example here. Assume we have a message: “Hello world”. And our application supports several languages. To translate UI programmers usually use the next approach (simplified):
There is a file with translations:
//ru.php <?php return [ 'Hello world' => 'Привет мир'];
and template that is translated with _()
function
<h1><?=_('Hello world');?></h1>
It’s obvious that the result will be:
<h1>Привет мир</h1>
so we are definitely in safe. It seems that there is no security risk here and we should not overcomplicate the stuff and apply HTML encoding on top of _()
like
<h1><?=htmlspecialchars(_('Hello world'));?></h1>
But this assumption is wrong:
- Translation strings may contain ambiguous characters
<, >, "
and translation may break the markup. It’s not a security vulnerability yet, but this is a bug. You should escape translation because you are inserting one language into another and want to prevent misinterpreting. - Tomorrow you may decide to keep translations in the database to let users with the
editor
role to update translations via admin panel. Right after that you may appear in a situation when your translations have been updated by the malicious user due to SQL Injection, CSRF, XSS (you name it!). In such case<h1><?=_('Hello world');?></h1>
may not be so secure anymore.
Conclusion: Security is always a side effect of the proper architecture.