Cross-site request forgery (CSRF) is a serious web attack that every developer needs to reckon with. Hackers have used it against web applications to steal valuable user information. Damaging attacks have been found on major sites like ING Direct, Netflix, and YouTube. The best defense is limiting web apps to a single domain, but that's not always wise or possible. You need a way to make cross-domain requests safe. That's where cross-origin resource sharing (CORS) comes in. It's a tool that allows applications to use multiple web and API servers.
Lua is a popular scripting language with many uses, including web applications. So, it's susceptible to CSRF attacks, too. You need to set up Lua CORS?
This post will look at what CORS is, the problems it avoids, and how to implement CORS with a Lua application.
CORS in a Nutshell
Before diving into Lua CORS, let's briefly look at what CORS is.
Cross-origin resource sharing is a security mechanism that defines what an app can request based on its origin—the domain the browser loaded them from. All modern web browsers implement it by default, protecting users from CSRF and similar attacks. Therefore, if you want your application to access more than one domain, you need CORs. The good news is most web infrastructure will handle it for you.
CORS Errors in Action
CORS uses HTTP headers to determine who can interact with your specific destinations. "Out of the box," CORS blocks all requests from a different origin. Imagine a web application with HTML served from a web server at https://example.com:443. In addition, it uses an API server at https://example.com:8083. This is common when an app shares an API between web, desktop, and mobile versions. Without any additional configuration, CORS would block the calls to the API server. So, the web application would fail.
If you opened the browser's web console, you would see an error like this:
Access to XMLHttpRequest at 'https://example.com:8083' from origin 'https://example.com' has been blocked by CORS policy
What happened here? The application made both requests to a server at example.com. Why did the browser deny the second one?
CORS defines an origin with three distinct parts:
Protocol - in this case both were HTTPS
Domain - both were example.com
Port - one was 443 and the other was 8083
According to CORS rules, different ports mean different origins. So, it blocked the request. With no overriding CORS policy in place, modern web browsers default to the same-origin policy; code can only request resources from the origin the browser retrieved it from.
How do you get an application like this to work with CORS?
When CORS Works
Now that we understand what happens without CORS let's talk about what happens when a CORS-enabled browser allows an application to make a cross-origin request. What does the upstream application need to do to make it happen?
As soon as a script makes a request to a different origin, the CORS workflow kicks in. The browser initiates the process with a "preflight" request to the first origin. The request uses the HTTP OPTIONS method and contains details about the request.
The server validates the request and responds with the set of acceptable requests. This response details what is allowed. It contains information about the permitted:
Origins the script can make a request from.
Request methods the script can use with the origins.
Custom headers the script can send to the origins.
Authentication information the script can send to the origins.
Read our detailed CORS guide if you want to learn more about the details behind CORS, including how not to set it up and what a typical attack looks like.
Lua CORS
Lua is a popular scripting language with bindings and implementations for countless different environments. There are a few situations where you need to keep CORS in mind when using it.
When you look at the risks a CSRF attack presents and the power of a language like LUA, which has access to nearly all the C runtime libraries, you might want to code your own CORS library.
Don't.
CORS is part of the HTTP protocol, and you don't want to mix its implementation with your application code. You also don't want to reinvent the wheel, especially when there's already a tried-and-tested wheel available for you to use.
OpenResty
OpenResty is an application server based on Nginx and Lua. It combines the Nginx core with a custom-built just-in-time (Jit) compiler for Lua. So, it's a platform for running extensions inside Nginx, taking advantage of its event model. Since Lua can call C extensions directly, you can use OpenResty to run nearly anything. You can also develop web apps with Lapis, a web framework that runs inside OpenResty.
If you think an application that can do nearly anything sounds like an application that needs extraordinary security protections, you're on the right track. It's almost inevitable that application servers will be subject to CSRF attacks, so you need to protect yourself from them.
The OpenResty project has a CORS library called lua-resty-cors that integrates into the platform and adds configuration options for permitted origins, methods, headers, and other critical CORS settings. It's a backport of a similar filter for Nginx.
If you read a few forum posts and other messages online, you'll see advice telling you to set your permitted domains or origins to * so your application will work. Do not do this. If this were to work, it would open your application to attack. Chances are, it won't work anyway.
If the example.com application were running on OpenResty, we'd configure the platform like this.
http {
init_by_lua_block {
local cors = require('lib.resty.cors');
cors.allow_host([==[.*\.example\.com]==])
cors.allow_method('GET')
cors.allow_method('POST')
cors.allow_method('PUT')
cors.allow_method('DELETE')
cors.max_age(7200)
cors.allow_credentials(false)
}
header_filter_by_lua_block {
local cors = require('lib.resty.cors');
cors.run()
}
}
This configuration allows code to make requests from any host in the example.com domain, including api.example.com. It explicitly permits the GET, PUT, POST, and DELETE verbs but does not allow scripts to send credentials to cross-origin destinations.
Nginx
If you're only looking to run a few simple scripts or write your own web code, Nginx's lua-nginx-module makes it possible to run Lua scripts in response to events.
Nginx has the ngx_http_cors_filter above, which will implement CORS messaging for you. Its configuration is the same as the OpenResty fork above. There are detailed configuration instructions here, including an example of how not to configure it at the top.
Apache
Apache's mod_lua is an analog to Nginx's Lua plugin. You use it to write Lua scripts that will run inside the server.
Here again, you'll want to configure the webserver for CORS and let it implement the HTTP messaging to you.
If you do a quick search for "apache cors," you'll come across recommendations like this.
<Directory /var/www/html>
Order Allow,Deny
Allow from all
AllowOverride all
Header set Access-Control-Allow-Origin "*"
</Directory>
This is terrible advice. Line #5 opens a wide security hole in the server, so wide that some browsers may even ignore and reject cross-origin requests.
Instead of opening your server to any request, use a regular expression:
<Directory /var/www/html>
Order Allow,Deny
Allow from all
AllowOverride all
SetEnvIf Origin "^http(s)?://(.+\.)?example\.com$" AccessControlAllowOrigin=$0
Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
</Directory>
This is a safer design and emulates the Nginx configuration above.
Wrapping It Up
We've covered what CORS is and how to use it with Lua web applications. Your best bet is to put your code behind Nginx or Apache and let them handle CORS for you. Both web servers have robust CORS support and give you the control you need to serve up powerful applications that are safe and secure.
Now that you know who to safely set up Lua apps, get to work!
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!).