The cover images for this post is by Helloquence
“Begin at the beginning," the King said, very gravely, "and go on till you come to the end: then stop.”
- Lewis Carroll, Alice in Wonderland
For those who may have missed it, this is part one in a series of posts on how you can secure a web app. Here is a link to part 0, which lays out what I'm going to achieve with this series of posts.
As a quick reminder (and a form of tl;dr): I'm in the process of building a web application for a real client, and I'm going to be covering what I'll be doing in order to ensure that the app has security baked in from the get-go.
The point of this post is to get you thinking about the things you should be including in your design decisions at the start of your project. Some of these features require explicit design from the start (multifactor auth, for example), and some can be "bolted on" afterwards.
Before we start, I'm not a security expert. I'm simply collecting all of the knowledge that I've gained over the years into a series of posts. I want to give you a great place to start in discovering the ways to lock down the systems that you are designing. It's also not meant to be an exhaustive list of things to include but should be enough to get you out of the unknown-unknowns and into the known-unknowns
i.e. you should have enough information to do some more research of your own
The first thing that you need to be thinking about when you start a new project is how you can keep the bad guysTM out. The easiest way to achieve this is to not build the app. Seriously, if it doesn't exist then they can't break-in.
How can someone steal your car, if you don't have one?
But your clients are going to get quite upset with you if you take their money and don't produce anything in return. So we need to take the next step up: add security in from the get-go.
Imagine that the building you work in has those card-based entry points. You know the ones: you have to present a card at each door for it to unlock and if you're not authorised to get in, then the door doesn't unlock.
Then you look up and realise that the building has those ceiling tiles which can be pushed slightly out of the way, revealing a walk-way.
How secure is that door now? Not very, right? Sure the bad guys need to bring a ladder, but that's totally doable.
Worse yet, you have floor to ceiling glass doors with a single lock on them; bolting them both closed and to the floor. What if I cut out the lock?
this may or may not have actually happened at a previous employer's office
With that in mind, we want to include security at the very beginning.
And this is the best time to start. Adding things like CSP in after the fact will just give you headaches.
Adding security at this stage (i.e. before there's a design) will inform how the design comes together.
Let's say that your client wants an e-commerce site. They want to be able to show off their wares, have users make orders, and take payments. Well, you don't have to build all of that functionality. Imagine having to create all of that, secure it, and be the person to blame when something goes wrong.
because it's going to go wrong
You don't need all that stress, so just drag in Stripe (or your payment processor of choice) and throw together a forms over data view for the database.
But even that needs security adding to it.
Putting aside all of the complex stuff that you can to in order to make an app more secure, let's start right at the beginning: the single responsibility principle states that a user can do one thing only:
- Is the user a blog post editor? Then they don't need access to the database settings
- Is the user someone who packs customer orders into a box? Then they don't need access to credit card details
This goes hand-in-hand with the Principle of Least Privilege. Average users of Windows (i.e. usually not tech workers) fall afoul of this more often than Unix and Linux users, as the default type of user on Windows is an Administrator.
Create an account for your two-year-old so that she can play around with the keyboard and make the lights flash, and suddenly she's making system-wide changes to your computer, for instance.
that may or may not have happened to someone I know
I don't mean to pick on Windows here, I just use it as an example.
So now that you've decided that you have a number of user types, and you've started to slice the functionality into pieces, next is how the users log in.
Multi-factor authentication isn't necessarily a new thing
hardly anything in tech really is new, these days
but it has been shown to vastly improve the security of a system with very little downsides. Sure it can be a headache for some users, but we're seeing the widescale adoption of multi-factor auth in systems. This means that - from an auth perspective at least - things are getting better.
But what is multifactor auth? We're all familiar with single-factor auth (i.e. username and password), as something that we know. Multifactor auth is an extension on this and includes:
- Something you know (username and password, or pin)
- Something you have (cell phone, YubiKey)
- Something you are (biometrics, geolocation)
Multifactor auth can help in protecting both users and the software that they use by adding an extra step in the auth cycle. That way, if the credentials of a user are discovered, a malicious party won't be able to use them to log in - they'll need to figure out how to beat the multifactor auth steps.
Make no mistake, multifactor auth isn't a silver bullet and can still be beaten. But you only need to be more secure than your competitors.
Imagine that you have a series of antiques in your living room. One day, you have friends over and their three-year-old child causes some kind of Rube Goldberg machine like effect by knocking one of your chairs over, causing a chain reaction which destroys all of your antiques.
You question the little one, asking for the truth. "No one will be upset if you tell the truth," you say. And when the little one in question opens their mouth, they tell the most fantastical story involving aliens, soldiers, knights, and a crocodile.
There's a chance that you live in a country which has constant issues with aliens and crocs, but most of us don't.
The point is: do you trust what the little one said?
Just like the little one's story, you should never trust user input. It's ridiculously easy to target websites with things like SQL injection attacks - remember little Johnny Drop-Tables:
Each time you allow a user to supply any kind of input (text, files, telemetry, etc.) you are opening your system up to attack. Most of these attacks can be mitigated by enabling validation on three surfaces:
It has been pointed out in the comments, that the original version of this paragraph seemed at odds with my intent. I'll leave the original paragraph here, but will also include a revised one
You can enable validation built into the HTML standard, or use libraries like jQuery Validation, for the client. Checking the input before it is sent to the server can be a lifesaver, and will save you from having to validate on the server - not to mention the roundtrip back to the server.
You can enable validation built into the HTML standard, or use libraries like jQuery Validation, for the client. Checking the input before it is sent to the server can save bandwidth, but should never be trusted. You can't trust client-side validation for a number of reasons: chief of them being that I can turn JS off, alter the JS before it's executed, or add breakpoints to alter it whilst it's executing.
Relying on client-side validation alone is effectively suicide, as Pablo pointed out in the comments:
Client: is the application making the request to the server. This can be a web app, a mobile app, a script, or a tool like Postman.
The server cannot trust data from the client, but if you only do validation in the client side, your web app, then your server is trusting in client data.
For me the message you are passing is that once you validate the data the user inputs on the client side, then the server doesn't necessarily need to check it again, and this his why I said that is a suicide.
All major server-side frameworks include some form of validation. I'm a .NET developer and I know that support for validation is built into ASP .NET Core. It requires a call to
ModelState.IsValid in a controller method, in order to check the model. You can even have those errors sent back to the client to be displayed.
Validation at the database level is a little harder to achieve and is very specific to the database technology that you are using. Some database technologies don't have validation and will blindly accept any data that they are fed, whereas some have validation enabled out of the box.
If you want to go the whole hog, you can validate the data on the way out of the database, too. This might be required for your industry, or it might be seen as a little too much. YMMV.