Web Security 101: Defend Against CSRF and Brute Force Attacks
What is CSRF (Cross Site Request Forgery)? Let’s say you are logged in your bank account on a tab and you decided to open a new tab and navigate to a different site. Unfortunately, you went to a malicious site and the site makes a “fake” request to the servers. These HTTP requests are fired with the users’ credentials and session information. It could be a link, an image tag, iframe, or any html elements that can embed a link. The malicious code can potentially do a lot to your bank account (transfer funds, view payment history, and change your account PIN).
How do we defend these CSRF attacks on our web applications in the backend?
- Make web sessions shorter: Most web sessions last around 30 minutes. Bank accounts shorten their sessions to five minutes. There is a tradeoff here since, if you shorten the session length, the user will have to retype password and account information multiple times for security purposes. The longer the session is, the higher the risk that the user will be exposed to vulnerabilities.
2. Associate a CSRF token to each user’s session during login: For the login form, assign a CSRF token to the user session id. On the client side, it’s like giving you a puzzle piece (a unique key that uses a hashing algorithm that is impossible to crack) and the server will see if the puzzle piece is the missing piece to the whole puzzle. So in the HTTP request from the client,
X-CSRF-Token: super2unique3crAzYKeyThatNO1CANfigureoUt
On the server side, just validate the header and the value of the header.
if X-CSRF_TOKEN not in request.headers: ABORT
3. Check HTTP Referrer headers or HTTP Custom Headers
This is the simplest but not-as-secure solution to preventing CSRF attacks. How an HTTP Referrer header works is that the server will drop requests that have no Referrer headers or have unauthorized Referrer headers. The downside to this is it might actually block good requests that omit Referrer headers for privacy reasons.
Another way is to do a custom HTTP header like “X-Requested-With” and drop the request from the server side if the custom header is not present. However, an attacker can add a custom HTTP header if they can spoof the request payload and just forge a request with that custom HTTP header.
Another popular web security attack is a brute force login attack.
A brute force attack is a simple trial and error attack that tries out different account and password combination in order to get access to users’ information. Brute force attacks may sound naive, but if you have multiple computers running on multiple processes trying out password combinations, you have a potential vulnerability in your login system.
Let’s take a real world example. To log into your social media account, a computer will run a brute force attack that has a long list of common usernames. It will go through the list and try to potentially crack the username. Then it’s going to start cracking the password after the username is correct. If the user’s password is not secure, then it is very likely that an attacker with multiple password crackers can actually access your account.
So what are some steps to prevent a brute force attack:
- Enforce strong/difficult-to-crack password when the user signs up:
This one requires the server to validate if a password created contains certain special characters (@#~!^*), has a minimum length of 8~9 or does not contain your name or account info.
2. Delay brute force attack by preventing login for X amount of time:
Keep a cache with the login_id and set the maximum login attempts as 5~10. (Set the attempts to 3 wrong passwords so the user doesn’t even notice) but it can really prevent an attacker from performing a brute force attack.
Here is some pseudocode:
KEY_LIFE_SPAN = 180
MAX_FAILED_LOGIN_ATTEMPTS = 5key = user.login_id
attempted_logins = cache.get(key)if attempted_logins:
if attempted_logins >= MAX_FAILED_LOGIN_ATTEMPTS:
//user will be logged out for 3 minutesif not authenticated:
if attempted_logins:
attempted_logins += 1
cache.set(key, attempted_logins, KEY_LIFE_SPAN)
else:
attempted_logins = 1
cache.set(key, attempted_logins, KEY_LIFE_SPAN)
abort(403) // User not authenticated
You basically keep the user login id as the key. If the same user uses the same login_id, keep track of how many login attempts the user has. Then, once it exceeds the MAX_FAILED_LOGIN_ATTEMPTS, the cache will delete the key after its life span, which you set as 3 minutes. Then the user can attempt to login again. This delays the brute force attack and prevents an attacker from trying. You can also exponentially increase the KEY_LIFE_SPAN with # of login attempts.
3. reCAPTCHA to verify you are not a robot
Verify that the user is not a robot that is trying to hack the login system.
There are ways to solve different security issues. I’m giving a high overview how these security vulnerabilities can exploit many websites and apps and access users’ confidential information.