Security for later stage web apps
Giving continuity to my previous article regarding security for modern web applications, I'm here to talk about later stage web applications. Later stage in this article means web applications that became somewhat popular, have more users and are grabbing more eye balls from threat agents, i.e., attackers.
Some steps here can be (and ideally should be) applied since the development of the app, i.e., together with the previous article advises. The reason for this division is because it makes the MVP lighter and make the business more sustainable. From a security perspective it is easy to throw a bunch of requirements from the start without caring about business. There is no point in protecting something that is worthless (supposing that you're a startup). That said, let's get started.
1# Clean the mess
At this point you barely made the MVP. You probably rushed a lot to deliver some value and some security considerations got left behind. It's time to start mapping your entry points, technologies, vendors and credentials.
Check all your routes
1.1 Make sure there are no unnecessary routes: commonly used libraries (e.g., ruby gems) automatically mount routes that you may not need. That's the case of Devise gem. Although it is a gem that is widely used, increase development speed, etc, it also may enable more routes than you need, such as
DELETE users. Too much magic get in the way.
1.2 Disable unnecessary HTTP Verbs: make it simple and lean. It's easier to audit. Don't limit yourself to your applications' routes, but to your web server as well. If your API only allows POST and PUT, disable everything else.
1.3 Verify Authentication/Authorization: you may think that some routes are protected (require authentication), when they don't. Check each of them and make unit tests. Yes, unit tests for security.
Revogate all committed credentials and do not upload them again: it is hard to think a faster way to deploy than
git pull & service <server> start, right? That's why so many credentials got committed into their respective repositories. Besides 1 in every 600 websites has .git repository exposed when it's done recklessly. But then, you hire a new developer, whose responsibility is limited to ship to development environment and now he/she has production credentials.
Don't panic. Just generate new credentials, revogate previously committed credentials, even if was only 1 time because can be seen in history, and configure properly your
.gitignoreto do not upload files with sensitive information. Don't bother with the revogated credentials being persisted in the repository as they are useless now.
You can use dotenv gem, ejson library from Shopify that stores secrets encrypted and are decrypted in the server or delegate it to your deployment system such as chef, puppet, ansible, etc. There is also Vault that is worth looking at.
2# Fix the basics
From what I've seen, the majority of bugs and flaws are related to the lack of cyber hygiene. In other words, the absence of the very basic protection measures.
Defeat Automatic Scanners: well, the first and commonly used technique among hackers is the automatic vulnerability scan. It is done by running multiple tools to test for several attack categories such as Injection Attacks, Security Misconfiguration, Backup files and so on.
To defeat such attacks, you need to hack yourself first. Basically you need to do the same and fix all findings that you could. So in every company that I worked for we did this, but was time consuming to set up scanners, to configure each one and to, in the end, consolidate all findings. Because of that I've created Gauntlet.io, which is a hub of scanners that fixes this problem. If you want to this in the hard way the choice is yours, but the point is that you must do it before hackers do.
Security Unit Tests: as I wrote above, security unit tests are not commonly discussed, but I strongly believe that is the way to go. You just need to map your critical functions and create misuse unit tests. Make sure that a non authenticated user cannot access such feature. Make sure that inputting invalid characters return a warning and do not reach the database, and so on.
Hardening: that's the process of locking down your app/container/O.S/cloud account, to reduce your attack surface and basically allow the bare minimum to enable your service to run. As it is a general concept, you need to adapt to each technology. So, if you're using CentOS, just google for CentOS hardening, follow a trusted source, and implement. Don't do this blindly. Try to understand what every configuration means, it's implications and if is there any way to do it better. Thanks to recipes, you have automated hardening solutions out there.
Leveraging the fact that we are talking about hardening, I'd like to highlight that secure connections (https) need hardening as well. The majority of cryptographic vulnerabilities are related to misconfiguration or use of broken algorithms. Check this article for more information and consider using this tool to test the quality of your SSL/TLS configuration. Also note that SSL is out of the game as I mentioned in the previous article. PCI-DSS 3.1 already updated it.
Update everything: OS packages, framework dependencies, libraries, everything. New versions tend to fix new bugs and hopefully do not introduce more. As more applications tend to have outdated versions, few people use the updated versions, making you a less attractive target than the outdated ones. For Rails, you have bundler-audit gem.
Limit scope of API Keys: don't allow everything. Check all your keys again. It's like using
chmod 777or running your application as root. Follow the concept of Least Privilege to minimize the impact of eventual breaches. Back to your API key scope and fix it.
3# Move Forward
Prevent Race Conditions
You may have seen race conditions out there on Facebook, Digital Ocean and others. Race condition attack, in this case also known as Time of Check / Time of Use (TOC/TOU), represents some problems introduced by concurrency when it needs to change the state of a shared object. Such race conditions can only be prevented using a pessimist lock (damn weird name). Rails example:
account = Account.find(1) account.with_lock do # This block is called within a transaction, # account is already locked. account.balance -= 100 account.save! end
In the end, for PostgreSQL, it looks like this:
BEGIN; SELECT * FROM accounts WHERE id=1 FOR UPDATE; UPDATE accounts SET BALANCE=20 WHERE id=1; COMMIT;
So your application is API oriented, but someone starts sending a lot of requests, e.g., creating a lot of users through the API. Actually when someone is creating a lot of users, I'd recommend Google's reCaptcha, but for other routes, you can limit the requests by IP Address or, if is an authenticated call, throttle by API Key, username, etc. For Rails, you have rack-throttle although doesn't look that active. It may also be interesting to limit API calls from countries that you do not operate e.g., your business is limited to UK and you're receiving requests from China. You can look up for the UK IP addresses block and whitelist those IPs only. Better than nothing, but not that useful because of proxies and cloud computing though ...
4# Raise the bar
There are a lot of ways to raise the bar when it comes to security. Here are some to guide you into your next steps:
- Threat Modeling: map your threats before you even code. Take a look at Microsoft Threat Modeling Book, Adam Shostack's Threat Modeling Book, Mozilla Sea Sponge and Microsoft Threat Modeling Tool. The cool thing about threats is that you will be able to map the danger surrounding your application and even if you properly validate the input for dynamic SQL queries to prevent SQL injection, the threat is still there. Be you careless and remove the mitigation control to result in threat materialization.
- Phishing Protection: as soon as your web app got more popular, phishing will be common. Prepare yourself to respond to cloned websites legally, identify the provider and ask for it be taken down. Use HTTPS and take a look at some good advices here.
- Source code Review: it includes open source software that you plug into your web application. Analyze everything, if possible manually, otherwise some solutions out there may help including CodeClimate.
- Learn about pentesting: there are good projects such as OWASP Testing Project and PCI-DSS Pentest Guidance. Although knowledge is good, leave the job for a professional unless you're one. Pentests are great before release to validate infrastructure and test from the beginning to end using a perspective of an attacker.
Credits: Vinicius Osiro for pointing PostgreSQL lock example and for pointing rails docs