Skip to content

@shrikeh /csrf-lua.conf
Created

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
A simple nginx host file that, using the lua module, handles CSRF, rather than the backend having to (and thus generally breaking caching by having to use Set-Cookie). Here, the front end takes care of CSRF, and sends an X-CSRF-Valid header to the backend regarding the validity of the POST, so that it is advisory (the backend then choose whether…
server {
listen 80;
root /root/to/your/docroot;
proxy_redirect off;
proxy_intercept_errors on;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Request-URI $request_uri;
proxy_set_header X-Backend example;
proxy_pass_header Set-Cookie;
location ~ \..*/.*\.php$ {
return 403;
}
location / {
# This is cool because no php is touched for static content
expires max;
try_files $uri @backend;
}
location /validate-csrf {
# Only accessible as a subrequest
internal;
content_by_lua
'
if ngx.var.cookie_csrf then
-- user has a CSRF cookie, validate it
local redis = require "redis"
local client = redis.connect("127.0.0.1", 6379)
local csrf_cookie = "csrf_" .. ngx.var.cookie_csrf
local value = client:get(csrf_cookie)
if value then
ngx.say(value) -- useful for testing
return ngx.exit(ngx.HTTP_OK)
end
end
-- no cookie or csrf provided was not found
return ngx.exit(ngx.HTTP_NOT_FOUND)
';
}
location @backend {
# You can't set variables in nginx dynamically, so set this up as empty first
set $csrf_validate "";
access_by_lua
'
if ngx.req.get_method() == "POST" then
-- set up forbidden as default
ngx.var.csrf_validate = ngx.HTTP_FORBIDDEN
if ngx.var.cookie_csrf then
local res = ngx.location.capture("/validate-csrf")
if ngx.HTTP_OK == res.status then
ngx.req.read_body()
local args = ngx.req.get_post_args()
local posted_token = tostring(args["csrf"])
if posted_token == res.body then
ngx.var.csrf_validate = ngx.HTTP_OK
end
end
end
end
';
# Pass the result as a header to the backend
proxy_set_header X-Csrf-Valid $csrf_validate;
proxy_pass http://127.0.0.1:6081;
# Now filter the response from the backend in as lightweight way as possible
set $csrf_form_token "";
set $csrf_cookie_token "";
header_filter_by_lua
'
if ngx.var.upstream_http_x_csrf_tokenize then
-- the backend requested a CSRF token be set
local csrf_cookie_token = nil
if ngx.var.cookie_csrf then
-- they have a cookie, just re-use it
local csrf_cookie_token = ngx.var.cookie_csrf
end
local resty_random = require "resty.random"
local str = require "resty.string"
if not csrf_cookie_token then
-- no valid csrf cookie found, let us make one
local cookie_random = resty_random.bytes(16,true)
while cookie_random == nil do
-- attempt to generate 16 bytes of
-- cryptographically strong (enough) random data
cookie_random = resty_random.bytes(16,true)
end
ngx.var.csrf_cookie_token = str.to_hex(cookie_random)
end
-- we are about to mess around with the content of the page
-- so we need to clear this as it will be wrong
ngx.header.Content_Length = ""
-- set the Cookie for the CSRF token
ngx.header.Set_Cookie = "csrf=" .. ngx.var.csrf_cookie_token
ngx.header.tokenize = ngx.var.upstream_http_x_csrf_tokenize
-- now generate one for the form token
while form_random == nil do
form_random = resty_random.bytes(16,true)
end
ngx.var.csrf_form_token = str.to_hex(form_random)
local redis = require "redis"
local client = redis.connect("127.0.0.1", 6379)
client:set("csrf_" .. ngx.var.csrf_cookie_token, ngx.var.csrf_form_token)
end
';
# Parse the body for csrf placeholder(s) and replace them with a hash
body_filter_by_lua
'
local csrf_form_token = ngx.var.csrf_form_token or ""
if ngx.var.csrf_form_token then
local placeholder = ngx.var.upstream_http_x_csrf_tokenize or ""
ngx.arg[1] = ngx.re.gsub(ngx.arg[1], placeholder, ngx.var.csrf_form_token)
end
';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.