Web application vulnerabilities and how to prevent them

No Comments

Understanding security vulnerabilities

I’ve noticed developers worry about vulnerabilities in applications they develop. I also think most of them try to do their best to prevent any problems. And I must confess I’ve lost a couple of nights of sleep because I convinced myself an application I worked on had a security problem. Knowledge about vulnerabilities is key for safe applications. Scanning the code for web application vulnerabilities and actively testing a running application for any signs of weakness are helpful steps to stop worrying and actually improve security.

Knowledge of security vulnerabilities, as a developer, can reduce the number of vulnerabilities introduced and it makes fixing them easier. Next to these benefits, confidence is a big one and it makes communication with security experts easier. Finishing new features and validating security often don’t go hand in hand. More importance is given to creating new functionality and less to security testing. I agree it is hard, but not impossible, to go fast and also deliver secure software. Secure software and fast development can be achieved by automating security testing and increasing the overall knowledge of developers about security. Both will cost time at first before having effect. On top of these actions it is advised to have the security of an application tested by an expert, as expert knowledge is irreplaceable.

A lot of developers are no experts in security, and it’s also unrealistic to expect this from them. There’s a certain kick you get from developing a new application or functionality which is, for most developers, different from the thrill of finding a vulnerability. Not often you find people who are equally motivated or skilled in performing both. Learning about security vulnerabilities has another benefit for developers: you get to know the tools you work with better, specifically the limitations and the risks of those tools.

To understand why knowledge of vulnerabilities is interesting from a developer perspective, I will provide a couple of examples in the Node.js environment.

The danger of ReDoS

When manipulated data can reach certain regular expressions, a regular expression denial of service(ReDoS) attack can be very effective. Not all regular expression implementations are vulnerable, but you should be aware of those which are using backtracking. A vulnerable expression is created rather easily. All you need is to add repetition to a sub-expression of the regex like in the expressions below:

b(b+)+
(0|00)+
(\/.+)+$
(([a-z])+.)+[A-Z]([a-z])+

Another way a regular expression can be exploited is when a repeatable sub-expression has a valid match which is also a part of another match. Catastrophic backtracking is triggered at the point where the actual input becomes invalid. Something that should also be avoided are the ‘|’ with overlapping values on both sides. Given the second example expression, if we provide an input with many zeroes and at the end a forward slash, then the execution would take quite long. Testing the input defined below did cost me around 550 ms to finish execution. Adding one more zero increased the execution time to around  800 ms (EMCAScript regex).

00000000000000000000000000000000000/

We shouldn’t ever allow a user to find and exploit such a vulnerability. To help avoiding unsafe regular expressions during development a linting tool like safe-regex is useful. It will scan the source code for problematic regular expressions. If it is not feasible to change the problematic regular expressions it is important to filter user input on size, format or certain characters.

For applications running on Node.js, this kind of regular expression attack can have disastrous consequences. Because the event-loop can be blocked by the regular expression, and the event loop also notices each new client connection. The result of this ReDoS is an unresponsive application for many clients. To make it broader, any running process on the event loop that is affected by user input could endanger the application. An example of an intensive process that also can block the event loop is the parsing of a very big JSON file.

Path Traversal and cross-site scripting

Path Traversal is a probing approach used by attackers in exploring unsecure locations in applications. Most networked applications use resources located on (url) paths to expose entry points of the application. Detecting if path traversal on a site is possible can be done by adding ‘../’ to an URL like the examples below.

https://randomsite.com/../../../dir/sensitive.txt
https://randomsite.com/download?fileName=../../../dir/sensitive.txt

If a vulnerability exists, you can then navigate to a sensitive file path somewhere else on the system. Sanitizing this URL input is a tricky thing because you should take into account encoded characters. Below I provide a couple of values that represent ‘../’

%2e%2e%2f
..%2f
..%c0%af

The URL encoding %c0%af are two pairs of hexadecimal digits that together form a character /. There are multiple ways to represent a unicode character and most libraries don’t enforce strict rules when decoding.
Often an application applies a first decoding and then does a sanity check, but attackers will then respond with double encoding where %25 is %.The encoded value and its translation would look like this:

%252E%252E%252F%2e%2e%2f../

This double encoding can also play a role in bypassing cross-site scripting (XSS) filters. Attackers can craft a new URL with the query parameter value containing a URL double encoded HTML.

<script>…</script>%253Cscript%253E ... %253C%252Fscript%253E

Often in an application a first decoding is done and after that a sanitation check. It is possible that the now single-encoded HTML is not detected by the application. This won’t be a problem if the data is not decoded again. But when it is decoded again before it is saved in a database, then there is a problem. The malicious HTML will be stored unencoded and is potentially displayed at a client when the specific data is retrieved.

Preventing and reducing effects of cross-site scripting

Security configuration can reduce the danger of malicious scripts that somehow are inserted into a page. A cookie can have sensitive data and it’s important to prevent attackers from stealing this data. An easy solution is using the HttpOnly flag in the ‘Set-Cookie’ HTTP response header on the server. The browser then won’t allow scripts to access the cookie. Configuring this header with Express, which is a web application framework for Node.js, can be done with the following code.

app.use(
  express.session({
    secret: "notadifficultpassword",
    cookie: {httpOnly: true}
  }));

Another way to reduce the impact of XSS vulnerability is to use the Content-Security-Policy response header. The browser will restrict the web page using resources that are not defined. Content-Security-Policy works like a white list allowing only the defined secure resources. It is possible with this header to restrict use of resources that exist outside the web page domain. In that case the header would look like this:

Content-Security-Policy: default-src 'self'

To add security headers to requests in Node.js the library Helmet can help. The code you need to define in the Node.js application using Helmet is simple.

const helmet = require('helmet')
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"]
   }
 }))

A way to prevent most effects of injected scripts on a website is to sanitze the displayed values for certain HTML. This can be done by using a library like Bleach (https://github.com/mozilla/bleach) or relying on built-in sanitation or escaping to frameworks like Angular 2 and React. A framework will do a lot of sanitation for you when used correctly but it does not cover everything. For example, in React the data you pass to the properties of a React HTML element are not escaped. So then the malicious JavaScript can still be executed using the hrefproperty value.

ReactDOM.render(
  <a href="javascript: alert(1)">Click me!</a>,
  document.getElementById('root') 
)

To prevent this attack, you can maybe best use a whitelist of allowed protocols in the href property. These are only a couple of ways of how to prevent XSS. As you can see in the previous examples, a vulnerability is easily overlooked and is often a matter of details. I hope to have shown a couple of interesting vulnerabilities and solutions to fix them. Reading about vulnerabilities, and playing around with them, is a really nice way to learn more about tools and techniques we developers use everyday.

What’s next?

I have a couple of more suggestions for making sure that a application is safe. The first one is: check dependencies for vulnerabilities. Use an application like Snyk or npm-audit to automatically find and report any known vulnerabilities in the dependencies. Dependencies are often a large part of the code running in applications and this code should also be secure. The second advice is to do security testing, both manually and automatically, because mistakes will be made. A free and simple testing tool is the OWASP ZAP, which can do multiple security checks, also for XSS and Path Traversal vulnerabilities.

It is possible to integrate OWASP ZAP testing into your Gitlab or Jenkins pipeline using some useful plugins:
https://docs.gitlab.com/ee/ci/examples/dast.html
https://wiki.jenkins.io/display/JENKINS/zap+plugin.

I hope I have shared some interesting security stories and piqued your interest in some issues, if you were not already interested.

Jan Martijn Roetman

Jan Martijn Roetman is a software craftsman working at codecentric Netherlands. He is a web application developer with experience in Java and JavaScript. He is especially interested in security of applications and artificial intelligence.

Comment

Your email address will not be published. Required fields are marked *