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 have become somewhat popular, have more users, and are attracting more attention 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's advice. The reason for this division is that it makes the MVP lighter and makes the business more sustainable. From a security perspective, it is easy to throw a bunch of requirements from the start without caring about the 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've 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 with the Devise gem. Although it is a widely used gem that increases development speed, etc., it may enable more routes than you need, such as
DELETE users
. Too much magic gets in the way.1.2 Disable unnecessary HTTP Verbs: keep it simple and lean. It's easier to audit. Don't limit yourself to your application's routes but also consider your web server. 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're not. Check each of them and create unit tests. Yes, unit tests for security.
Revoke all committed credentials and do not upload them again: there is hardly a faster way to deploy than
git pull & service <server> start
, right? That's why so many credentials get committed to 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 shipping to the development environment, and now he/she has production credentials.Don't panic. Just generate new credentials, revoke previously committed credentials (even if it was only done once and can be seen in the history), and configure your
.gitignore
properly to avoid uploading files with sensitive information. Don't worry about the revoked credentials being persisted in the repository; they are useless now.You can use the dotenv gem, ejson library from Shopify that stores secrets encrypted and decrypts them on the server, or delegate it to your deployment system such as Chef, Puppet, Ansible, etc. You can also explore Vault, which is worth looking into.
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 come across. In every company that I've worked for, we did this, but it was time-consuming to set up scanners, configure each one, and, in the end, consolidate all findings. That's why I created Gauntlet.io, which is a hub of scanners that fixes this problem. If you want to do it the hard way, the choice is yours, but the point is that you must do it before hackers do.
Security Unit Tests: as I mentioned above, security unit tests are not commonly discussed, but I strongly believe that they are 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 features. Make sure that inputting invalid characters returns a warning and does 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 it to each technology. So, if you're using CentOS, just google "CentOS hardening," follow a trusted source, and implement it. Don't do this blindly. Try to understand what every configuration means, its implications, and if there is 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) also need hardening. The majority of cryptographic vulnerabilities are related to misconfiguration or the 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 has already updated it.
Update everything: OS packages, framework dependencies, libraries, everything. New versions tend to fix new bugs and, hopefully, not introduce more. As more applications tend to have outdated versions, fewer people use the updated versions, making you a less attractive target than the outdated ones. For Rails, you have the bundler-audit gem.
Limit the scope of API Keys: don't allow everything. Review all your keys. It's like using
chmod 777
or running your application as root. Follow the concept of Least Privilege to minimize the impact of eventual breaches. Review your API key scopes and fix them.
3# Move Forward
Prevent Race Conditions
You may have seen race conditions out there on Facebook, Digital Ocean, and others. A 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 pessimistic lock (a weird name, indeed). Here's a 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;
For more information, check the documentation on locking for Rails, PostgreSQL, and MySQL.
API Throttling
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 using Google's reCaptcha, but for other routes, you can limit the requests by IP Address or, if it is an authenticated call, throttle by API Key, username, etc. For Rails, you have rack-throttle, although it doesn't seem that active. It may also be interesting to limit API calls from countries where you do not operate, e.g., if your business is limited to the UK and you're receiving requests from China. You can look up the UK IP addresses block and whitelist only those IPs. It's 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 in your next steps:
Threat Modeling: map out your threats before you even start coding. Take a look at the 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 dangers surrounding your application. Even if you properly validate the input for dynamic SQL queries to prevent SQL injection, the threat is still there. Be careful and do not remove the mitigation control, or else the threat can materialize.
Phishing Protection: as soon as your web app becomes more popular, phishing will become common. Prepare yourself to respond to cloned websites legally, identify the provider, and ask for it to be taken down. Use HTTPS and take a look at some good advice here.
Source code Review: this includes open-source software that you plug into your web application. Analyze everything, if possible manually. Otherwise, there are some solutions out there that 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 to a professional unless you are one. Pentests are great before release to validate infrastructure and test from beginning to end using the perspective of an attacker.
Credits: Vinicius Osiro for pointing out the PostgreSQL lock example and for pointing to the Rails docs.