It's hard to imagine the world without the web. It was a world where we had to conduct most of our business in person! It was a world where we had to mail checks to pay our bills and visit banks to deposit and withdraw money. Now we can do almost everything online, and sending a check seems unthinkable when we simply pay a bill or complete a purchase with a tap on a screen or click a mouse.
But that convenience doesn't come without significant risk. Users have to ask if they should tap on that button and you, the developer, have to make sure your buttons are safe. Are you protecting your users from Cross-site request forgery (CSRF)?
CSRF is a type of attack where a hacker figures out how to get a user to execute a dangerous web query. This attack is also known as XSRF, Session Riding, Hostile Linking, and several other names.
Let's talk about CSRF attacks and how you can protect your Lua web applications from them.
What Is CSRF?
An attacker uses cross-site request forgery when they harness a user's entitlements over a web session to submit a malicious request. The attacker can work around the usual security limitations by deceiving the user into sending a forged query like a POST, PUT or GET that deletes or steals data or even submits a dangerous funds transfer.
Like many of the most effective attacks, CSRF often relies on social engineering. These hacks only work when a user clicks on a dangerous link. One effective way to make that happen is to trick a user into clicking on a link in an email or a chat.
These links trigger damaging transactions on a vulnerable site if the user has a session open. So, for example, if a link in an email points to a website that the user has already logged in to, the attack will take advantage of a valid session cookie.
These attacks are depressingly common. Websites can't protect themselves from social engineering, so developers are left to figure out how to blunt CSRF hacks without sacrificing user convenience and satisfaction.
So, it's crucial that you ensure that you protect your web application from CSRF attacks.
Let's look at two examples of CSRF attacks. Then we'll see how you can protect your Lua web code from them.
Here's an example of an HTML email.
This is a barebones example, but imagine a hacker sending this email with branding lifted from a well-known payment service. (We don't want to do that here for obvious reasons.) The attacker has purchased an email list and generates and sends a message for each email. They're gambling that at least a few targets will have an open session with the service and aren't paying close attention.
Let's look at the link.
Rather than confirming a transaction, this link initiates a transfer to someone else! The link sends a GET request with parameters that send a lot of money to a user named techbro.
How can a user protect themselves from this attack? They can pay attention to when and why their bank and payment services send emails and what they look like. They can read emails closely and look for incorrect branding and awkward grammar. They might look at links before clicking on them if they're really savvy.
But most users don't do these things, and it only takes one for an attack to succeed.
It's up to developers to make sure that an attack like this doesn't work. Let's look at one more before we talk about solutions.
The previous example needed a user to click on a link to work. When they did, it sent a rogue GET request to the bank. What if the email had an embedded form instead?
Here's the source for a new version of the email attack.
<center> <b>Congratulation!</b></br> <p>$500.00 has been sent to email@example.com <p>Click <a href="https://www.example.com/xfer?target=techbro&amount=25000">here</a> to confirm and get your cash! <form method="post" action="https://www.example.com/xfer"> <input name="amount" type="hidden" value="25000" /> <input name="target" type="hidden" value="techbro" /> </form> <script>document.forms.submit();</script> </center>
The email looks exactly the same, but instead of waiting for the user to click on a link, it sends a POST request as soon as the HTML renders! For good measure, the link has an attack, too. A similar threat might attach the confirmation link to a form with hidden values instead of concealing it. There are many ways to fashion an attack using HTML mail and HTML chat messages.
These examples are rudimentary and, as mentioned above, not formatted like more sophisticated phishing attacks. But it's not hard to imagine attacks with "trade dress" stolen from popular banking and shopping websites.
So, how do we protect our users?
Lua CSRF Protection
The most common approach to protecting a web application from CSRF attacks is generating a token and returning it to users in page responses. If subsequent requests don't include the token, the application knows that the request is unsafe.
There are three approaches you can take with CSRF tokens.
Synchronized tokens are unique for each client session. This means that the server has to maintain state information for each session, collate CSRF tokens with them, and dispose of tokens when sessions are destroyed or expire.
Encrypted tokens are shared across all clients. These tokens contain a value that the application encrypts with a private key so only they can verify that the token is legitimate.
Origin headers put the CSRF token in a header that the server expects to see echoed back on requests.
So how can you use these techniques with Lua applications?
Write Your Own
You can write code to generate a token, embed it in pages, and ensure that it's echoed back to your application when you require it. This requires extra work and extensive testing, but it gives you complete control over the solution. Lua has tools for adding tokens to web pages or writing headers, so you can pick your approach.
This GitHub Gist has sample code for a solution that runs in Nginx's Lua module.
If you're running Nginx's Lua module or the OpenResty web platform, you can use Nginx's web application firewall. It has many features to protect your applications from attacks, including CSRF. Nginx generates an origin header that acts as a CSRF token. You can configure Nginx to require this header for specific URLs, such as POST, PUT and DELETE actions.
Or, you can use the gist linked above.
Apache doesn't have any built features for CSRF protection. There are a few third-party modules, but they are older and don't appear to be actively supported. If you're using Apache, you'll need to write your own code.
Lapis has built-in CSRF protection features. It will generate a shared cryptographic token and check for it on web requests.
Protecting Your Web Applications
This post looked at what CSRF attacks are and the danger they present to you and your web users. We examined a pair of examples. Both examples demonstrated how dangerous fraudulent emails are and how easy it is to wage a CSRF attack against an unprotected application. Then we looked at typical protections against a CSRF attack and how you can either code your own or take advantage of CSRF protection from Nginx and Lapis. Unfortunately, Apache applications have to risk a third-party module or code their own CSRF tokens.
Cross-site request forgeries are dangerous attacks, but you can protect your applications from them with well-documented tools and processes. While you're taking a close look at application security, check out StackHawk and see how it can help protect your users from attacks like CSRF! There's a free plan with unlimited scans that you can try today!
This post was written by Eric Goebelbecker. Eric has worked in the financial markets in New York City for 25 years, developing infrastructure for market data and financial information exchange (FIX) protocol networks. He loves to talk about what makes teams effective (or not so effective!).