Feb 13, 2021

How to write secure code

We all know that writing bug-free code is hard.
That writing readable code is hard.
That writing testable code is also hard.
Well, I could keep going but you get it.
Writing code that is also secure is even harder.

What can we do about it?

  • Apply threat modeling as if there is no tomorrow
  • Research for all known vulnerabilities and apply the suggested countermeasures
  • Apply all the OWASP guidelines, in particular the Top 10 Proactive Controls

Although these practices are strongly recommended, there is also another point to consider, which is often overlooked.

A strong Design

Let's suppose to have a simple domain object that represents a Developer in a typical web application with the Developer's username displayed in a public profile page.

case class Developer(id: Long, username: String)

The Developer is quite simple, it holds an id and its username.

If we take a closer look at the code we can identify at least one security vulnerability.
Can you figure out where?
(Here a knowledge of the most common web vulnerabilities is needeed)

The username is displayed in a public page and is Developer-controlled.

A malicious actor could name itself something along the lines of '><script>alert('I'm an evil script!')</script> and manage to execute JavaScript code on the visitor's browser.
This vulnerability is called Cross Site Scripting (XSS).
Or he can even store its name as a malicious SQL query and perform a blind SQL injection attack.

These are two injection vulnerabilities that affect two different layers. The presentation layer in the case of XSS and the persistence layer in the case of SQL injection.
With the traditional approach we usually do one or more of the following:

  • To mitigate the first security vulnerability (XSS) we could encode the username when displaying it in the profile page, to strip out all the dangerous characters by hand or by using a modern JavaScript framework being careful not to disable the encoding for whatever reason.
  • To mitigate the second security vulnerability we could add a filter to strip out everything except all the allowed non-dangerous characters.

We could even employ a web application firewall that manages this filtering automagically.

But these approaches, if not rigorously applied, tends to provide a false sense of security.

Moreover if a new attack vector is discovered, for which the above mitigations are not suitable, we are in trouble.

An alternative method is a variation of the "Rule of least power".
Why representing the username as a whole String when we don't need all the UTF-16 charset (in this case)?

Secure By Design

What if our Developer was represented in the following way?


object refinements {
    type Id = Long Refined Positive
    type Username = String Refined AllOf[
        NonEmpty
        MatchesRegex["[A-Za-z0-9_]+"]
        MinSize[5]
        MaxSize [20]
    ]
    
    final case class Developer(id: Id, username: Username)
}

Here I'm using Scala with refined, but the same concepts apply to all the other programming languages.

We need to validate our domain model as strictly as possible.

In our example the username can only be a non-empty String with a length between 5 and 20 characters and can only contain alfanumeric characters plus the underscore. This makes the aforementioned attacks very unlikely to happen.

If we complement this design with the traditional approach previously explained and explicit security awareness, our code becomes even more secure.

Go and try it out!

Keep Learning.
Until next time emoji-sunglasses