CSRF protection middleware for Go.

Overview

nosurf

Build Status GoDoc

nosurf is an HTTP package for Go that helps you prevent Cross-Site Request Forgery attacks. It acts like a middleware and therefore is compatible with basically any Go HTTP application.

Why?

Even though CSRF is a prominent vulnerability, Go's web-related package infrastructure mostly consists of micro-frameworks that neither do implement CSRF checks, nor should they.

nosurf solves this problem by providing a CSRFHandler that wraps your http.Handler and checks for CSRF attacks on every non-safe (non-GET/HEAD/OPTIONS/TRACE) method.

nosurf requires Go 1.1 or later.

Features

  • Supports any http.Handler (frameworks, your own handlers, etc.) and acts like one itself.
  • Allows exempting specific endpoints from CSRF checks by an exact URL, a glob, or a regular expression.
  • Allows specifying your own failure handler. Want to present the hacker with an ASCII middle finger instead of the plain old HTTP 400? No problem.
  • Uses masked tokens to mitigate the BREACH attack.
  • Has no dependencies outside the Go standard library.

Example

package main

import (
	"fmt"
	"github.com/justinas/nosurf"
	"html/template"
	"net/http"
)

var templateString string = `
<!doctype html>
<html>
<body>
{{ if .name }}
<p>Your name: {{ .name }}</p>
{{ end }}
<form action="/" method="POST">
<input type="text" name="name">

<!-- Try removing this or changing its value
     and see what happens -->
<input type="hidden" name="csrf_token" value="{{ .token }}">
<input type="submit" value="Send">
</form>
</body>
</html>
`
var templ = template.Must(template.New("t1").Parse(templateString))

func myFunc(w http.ResponseWriter, r *http.Request) {
	context := make(map[string]string)
	context["token"] = nosurf.Token(r)
	if r.Method == "POST" {
		context["name"] = r.FormValue("name")
	}
	
	templ.Execute(w, context)
}

func main() {
	myHandler := http.HandlerFunc(myFunc)
	fmt.Println("Listening on http://127.0.0.1:8000/")
	http.ListenAndServe(":8000", nosurf.New(myHandler))
}

Manual token verification

In some cases the CSRF token may be send through a non standard way, e.g. a body or request is a JSON encoded message with one of the fields being a token.

In such case the handler(path) should be excluded from an automatic verification by using one of the exemption methods:

	func (h *CSRFHandler) ExemptFunc(fn func(r *http.Request) bool)
	func (h *CSRFHandler) ExemptGlob(pattern string)
	func (h *CSRFHandler) ExemptGlobs(patterns ...string)
	func (h *CSRFHandler) ExemptPath(path string)
	func (h *CSRFHandler) ExemptPaths(paths ...string)
	func (h *CSRFHandler) ExemptRegexp(re interface{})
	func (h *CSRFHandler) ExemptRegexps(res ...interface{})

Later on, the token must be verified by manually getting the token from the cookie and providing the token sent in body through: VerifyToken(tkn, tkn2 string) bool.

Example:

func HandleJson(w http.ResponseWriter, r *http.Request) {
	d := struct{
		X,Y int
		Tkn string
	}{}
	json.Unmarshal(ioutil.ReadAll(r.Body), &d)
	if !nosurf.VerifyToken(nosurf.Token(r), d.Tkn) {
		http.Errorf(w, "CSRF token incorrect", http.StatusBadRequest)
		return
	}
	// do smth cool
}

Contributing

  1. Find an issue that bugs you / open a new one.
  2. Discuss.
  3. Branch off, commit, test.
  4. Make a pull request / attach the commits to the issue.
Comments
  • Seems to be broken with Go 1.7

    Seems to be broken with Go 1.7

    When compiled in Go 1.7, nosurf.Token(r) returns empty string. I've tried the -gcflags=-ssa=0 as suggested by the 1.7 release note, but it didn't help. https://golang.org/doc/go1.7

    opened by jack-chung 13
  • nosurf breaks MultipartReader()

    nosurf breaks MultipartReader()

    First, thank you for fixing enctype="multipart/form-data".

    However, now processing a form with reader, err := r.MultipartReader() results in the following error: http: multipart handled by ParseMultipartForm.

    Using MultipartReader() is advantageous for my use case because it allows me to process the request body (most importantly the file uploads) as a stream.

    opened by bryanjeal 12
  • SetBaseCookie not having effect

    SetBaseCookie not having effect

    Hi,

    My code:

    func (mr *Router) Handle(method string, path string, handler http.Handler) {
    	mr.router.Handler(method, path,
    		csrfHandler(
    			logHandler(logger.Debug)(
    				securityHeaderHandler()(
    						handler,
    				),
    			),
    		),
    	)
    }
    
    func csrfHandler(next http.Handler) http.Handler {
    	csrfHandler := nosurf.New(next)
    	csrfHandler.SetBaseCookie(http.Cookie{
    		Name: csrfCookieName, // "csrf"
    		Path: "/",
    		Domain: "",
    		Secure: true,
    		HttpOnly: true,
    		MaxAge: int(sessionTimeoutSec.Seconds()),
    		SameSite: http.SameSiteStrictMode,
    	})
    	return csrfHandler
    }
    

    However, every request without cookie returns:

    $ curl -v http://localhost:8080/login 1>/dev/null
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
      0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying ::1...
    * TCP_NODELAY set
    * Connected to localhost (::1) port 8080 (#0)
    > GET /login HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.54.0
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < access-control-allow-methods: GET,POST,HEAD,OPTIONS
    < x-content-type-options: nosniff
    < content-security-policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self' fonts.gstatic.com
    < set-cookie: csrf_token=5BeFIyTJI/fipPPgEcdPcw4t4vTfOZDjYoOCt8/iRSI=; Path=/; Max-Age=900; HttpOnly; Secure; SameSite=Strict
    
    opened by hazcod 8
  • Signing Cookies

    Signing Cookies

    nosurf does not currently sign cookies as the standard http.Cookie implementation only defines the "basic" attributes of a cookie.

    CSRF cookies should be signed (so we can identify attempts to tamper) with HMAC-SHA256, and then authenticated before checking the cookie against the submitted request. An example of a solid authentication implementation can be found here.

    • Authenticated cookies should (really) really be the default, but I'm not sure how to reconcile this without breaking the existing API. I would argue that the benefit from authenticating cookie values outweighs the downside of breaking the API. Any major change would be a compile-time error too, and is therefore easier to resolve (no weird gremlins at run-time).
    • I would also consider updating the README to stress (as it's extremely important) that package users serve their site over HTTPS (SSL/TLS), as otherwise CSRF tokens are effectively lip service given that the cookies themselves can be hijacked (and as their contents aren't authenticated, changed at whim).
    • Encrypted cookies would be an additional "nice to have" but do not really circumvent the need for SSL/TLS. The existing "encryption" references in the docs should also be changed to "masked" or "masking".
    • You would facilitate authentication/encryption by allowing a user to pass in keys in a func (h *CSRFHandler) SetAuthKeys(key []byte, keys ...[]byte) and func (h *CSRFHandler) SetEncryptionKeys(key []byte, keys ...[]byte), with the variadic param allowing a package user to pass in multiple key pairs (which facilitates cycling keys). This would be similar to how gorilla/securecookie handles key rotation, but you could probably get away with just accepting a single key.
    • Leveraging gorilla/securecookie itself may not be a bad idea. You could wrap its exported functions with your own to maintain as much of your existing API as possible, or have nosurf.New accept an options struct that then calls securecookie API before then returning a configured *CSRFHandler.
    • I'd also suggest bringing the default expiry way down to something like a week, tops. Even a day would be fine—users don't take a day to fill out a form.
    opened by elithrar 8
  • Validation fails with X-CSRF-Token

    Validation fails with X-CSRF-Token

    I'm only able to get a 400 Bad Request with POST/DELETE requests to my REST application.

    Running an app in localhost I have this value in my csrf_token cookie: 0bYcWmFvvMpZXMSgau2Jx3uxQGhyfEtTxOEC6zrtlfs=

    And the value passed back in X-CSRF-Token: 0bYcWmFvvMpZXMSgau2Jx3uxQGhyfEtTxOEC6zrtlfs=

    My set up:

    http.Handle("/", nosurf.New(myRoutes))
    http.ListenAndServe(":"+port, nil)
    

    I had this working fine until a recent go get update, so some kind of regression maybe?

    opened by danjac 8
  • Resolve critical vulnerability allowing arbitrary tokens to pass as matching

    Resolve critical vulnerability allowing arbitrary tokens to pass as matching

    Before applying the patch to token.go, VerifyToken would incorrectly allow the following pairs to appear as equal:

        token_test.go:82: VerifyToken returned a false positive for: [foo bar]
        token_test.go:82: VerifyToken returned a false positive for: [foo ]
        token_test.go:82: VerifyToken returned a false positive for: [ bar]
        token_test.go:82: VerifyToken returned a false positive for: [ ]
    

    This opens up attack vectors when developers rely on VerifyToken in cases where the attacker could trick the first parameter (realToken) into being an arbitrary value. Given that VerifyToken does not rely on r *http.Request to extract the token, this could happen when misusing the API or when other exploits are found to the way nosurf.Token(r) works.

    This patch handles empty values and base64 decoding errors as verification failures and allows the failing test cases to pass.

    opened by aeneasr 7
  • Employ techniques to mitigate BREACH.

    Employ techniques to mitigate BREACH.

    The CSRF token as it is now might be acquired by an attacker using the BREACH technique (assuming the server has compression turned on).

    breach-mitigation-rails and django-debreach both take up an interesting approach with this, encrypting the CSRF token with a new random string on each request. It seems like this could be easily applied to nosurf.

    opened by justinas 7
  • Token value error

    Token value error

    ------ context_legacy.go --------- (good) func Token(req *http.Request) string { cmMutex.RLock() defer cmMutex.RUnlock()

    ctx, ok := contextMap[req]
    
    if !ok {
    	return ""
    }
    
    return ctx.token
    

    }

    ---------- context.go ---------------------(error) // Token takes an HTTP request and returns // the CSRF token for that request // or an empty string if the token does not exist. <----------- ERROR // func Token(req *http.Request) string { ctx := req.Context().Value(nosurfKey).(*csrfContext)

    return ctx.token
    

    }

    ...an empty string if the token does not exist. --> panic occurred !! PANIC: interface conversion: interface {} is nil, not *nosurf.csrfContext

    sorry for poor english.

    opened by hellower 6
  • How does nosurf OTP protect against BREACH?

    How does nosurf OTP protect against BREACH?

    The BREACH paper states

    In order for the attack to be successful,several things are required. To be vulnerable to this side-channel, a web app must:

    • Be served from a server that uses HTTP-level compression
    • Reflect user-input in HTTP response bodies
    • Reflect a secret (such as a CSRF token) in HTTP response bodies

    Additionally, while not strictly a requirement, the attack is helped greatly by responses that remain mostly the same modulo the attacker’s guess.

    Just so I have this correct, the mask done to the token in crypto.go is to make sure the HTTP cookie header and HTML body do not contain the same string/bytes correct?

    This is to avoid BREACH / CRIME styled deconstruction of the compression to find repeated strings? Am I understanding this correctly?

    If the bytes are random to begin with, why is the token XOR with the one-time-pad (OTP) since it will be unique with every response anyway?

    opened by Xeoncross 5
  • Is this normal behavior?

    Is this normal behavior?

    Hi,

    Thanks for this great package.

    To test this I did the following, I'm using JSON on the responses and requests:

    I have 2 handlers, both on root "/", one is a GET and the other a POST. On the GET I only return a token. On the POST I verify the token and send a new one, like:

    ...
            // Get token from JSON into jd.
    
    	tkn := nosurf.Token(r)
    	if !nosurf.VerifyToken(tkn, jd.Token) {
    		w.WriteHeader(http.StatusBadRequest)
    		w.Write([]byte(`{"message":"Different tokens"}`))
    		return
    	}
    
            // Send the new token.
    	w.Write([]byte(fmt.Sprintf(`{"token":"%s"}`, tkn)))
    

    What I would like to know if it is the normal behavior is that after getting the token from the GET I can do all the requests to POST with that first token that it always validates ok. I can even do e.g. 10 request to POST with the first token, next do another request to POST with the newest token sent by POST, then again start using the first token and it still validates.

    Thanks for your help.

    opened by NCSantos 5
  • Cookie tokens not masked?

    Cookie tokens not masked?

    It seems like the tokens sent out in cookies are never masked. They are masked before being stored in the context, but then the unmasked token is sent out in the cookie. That seems incorrect to me, but I'm not a crypto expert.

    This is based trying to use this library, and based on reading: https://github.com/justinas/nosurf/blob/master/handler.go#L182

    opened by paulbellamy 5
  • Installing command of `nosurf` is missing in README.md

    Installing command of `nosurf` is missing in README.md

    Description:

    We should add the Installation command to the README.md otherwise a new user might get confused! command have to use:- go get github.com/justinas/nosurf

    opened by bishal7679 0
  • Ability to handle multiple cookies in context

    Ability to handle multiple cookies in context

    Is there an ~easy~ way to handle the scenario of multiple cookies in context and not just the default csrf_token ?

    It's regarding an iframe integration scenario that the same frame will be included multiple times in the page and the way the library is now, each frame will overwrite the csrf_token meaning if the 1st form submits then it will have a different token than the latest in the context.

    For setting the tokens with different names I've managed to simply append the frameId inside the HandlerFunc but the problem is in the verification step where the context is the same.

    opened by stefanoschrs 5
  • Inappropriate key in call to `context.WithValue`

    Inappropriate key in call to `context.WithValue`

    handler_go17_test.go

    should not use built-in type string as key for value; define your own type to avoid collisions

    image

    Description

    To prevent clashes across packages using context, the provided key must be similar and should not be of type string or any other built-in type. Users of WithValue must provide their key types.

    To avoid allocating when assigning to an interface{}, context keys often have concrete type struct{}. Alternatively, exported context key variables’ static type should be a pointer or interface.

    opened by raheel0x01 0
  • Add failing test case for double cookie setting

    Add failing test case for double cookie setting

    The problem is that ServeHTTP detects that the cookie is missing. So it calls RegenerateToken() to create a new token. Because the downstream handler doesn't know that (and really has no way of currently knowing), it also calls RegenerateToken(). This then adds a second cookie to the HTTP response, causing this behavior.

    See #61

    opened by aeneasr 3
  • RegenerateToken generates two CSRF cookies when no previous CSRF cookie was set

    RegenerateToken generates two CSRF cookies when no previous CSRF cookie was set

    Calling RegenerateToken() in a request context where the client is not sending a CSRF cookie, two CSRF cookies will be generated:

    map[Set-Cookie:[csrf_token=aZA5CKCpmzGwlyfyFZp1akOOo4dSbZEdSAziaN+nRYE=; Path=/; Domain=example.com; Max-Age=31536000; HttpOnly; Secure csrf_token=xe/JUh5YavyzQtmIqU018swoHmPN5nQsTSqSJscKJU4=; Path=/; Domain=example.com; Max-Age=31536000; HttpOnly; Secure] Vary:[Cookie]]
    

    Depending on the order of the browser stores the cookie, this can lead to false-positive CSRF detection.

    opened by aeneasr 0
CSRF prevention for the Golang Revel framework.

revel-csrf revel-csrf implements Cross-Site Request Forgery (CSRF) attacks prevention for the Revel framework. Code is based on the nosurf package imp

Christophe Bonello 50 Apr 5, 2022
CSRF protection middleware for Go.

nosurf nosurf is an HTTP package for Go that helps you prevent Cross-Site Request Forgery attacks. It acts like a middleware and therefore is compatib

Justinas Stankevičius 1.3k Dec 4, 2022
gorilla/csrf provides Cross Site Request Forgery (CSRF) prevention middleware for Go web applications & services 🔒

gorilla/csrf gorilla/csrf is a HTTP middleware library that provides cross-site request forgery (CSRF) protection. It includes: The csrf.Protect middl

Gorilla Web Toolkit 854 Nov 25, 2022
fastglue-csrf implements CSRF middleware for fastglue.

fastglue-csrf Overview fastglue-csrf implements CSRF middleware for fastglue.

Joe Paul 13 Jan 5, 2022
Package csrf is a middleware that generates and validates CSRF tokens for Flamego

csrf Package csrf is a middleware that generates and validates CSRF tokens for Flamego.

Flamego 8 Nov 25, 2022
Easy to use cryptographic framework for data protection: secure messaging with forward secrecy and secure data storage. Has unified APIs across 14 platforms.

Themis provides strong, usable cryptography for busy people General purpose cryptographic library for storage and messaging for iOS (Swift, Obj-C), An

Cossack Labs 1.6k Dec 3, 2022
Easy to use cryptographic framework for data protection: secure messaging with forward secrecy and secure data storage. Has unified APIs across 14 platforms.

Themis provides strong, usable cryptography for busy people General purpose cryptographic library for storage and messaging for iOS (Swift, Obj-C), An

Cossack Labs 1.6k Nov 29, 2022
CSRF prevention for the Golang Revel framework.

revel-csrf revel-csrf implements Cross-Site Request Forgery (CSRF) attacks prevention for the Revel framework. Code is based on the nosurf package imp

Christophe Bonello 50 Apr 5, 2022
A high-performance, zero allocation, dynamic JSON Threat Protection in pure Go

Package gojtp provides a fast way to validate the dynamic JSON and protect against vulnerable JSON content-level attacks (JSON Threat Protection) based on configured properties.

Ankur Anand 54 Nov 9, 2022
evilginx2 is a man-in-the-middle attack framework used for phishing login credentials along with session cookies, which in turn allows to bypass 2-factor authentication protection.

evilginx2 is a man-in-the-middle attack framework used for phishing login credentials along with session cookies, which in turn allows to bypass 2-fac

null 0 Nov 4, 2021
Sensitive information protection toolkit

godlp 一、简介 为了保障企业的数据安全和隐私安全,godlp 提供了一系列针对敏感数据的识别和处置方案, 其中包括敏感数据识别算法,数据脱敏处理方式,业务自定义的配置选项和海量数据处理能力。 godlp 能够应用多种隐私合规标准,对原始数据进行分级打标、判断敏感级别和实施相应的脱敏处理。 In

Bytedance Inc. 638 Nov 28, 2022
K8s-delete-protection - Kubernetes admission controller to avoid deleteing master nodes

k8s-delete-protection Admission Controller If you want to make your Kubernetes c

null 1 Nov 2, 2022
golang csrf react example, using gorilla/mux and gorilla/mux

Demo REST backend Gorilla csrf middleware and Js frontend Use gorilla/mux and gorilla/csrf How to run open goland IDE, run middleware_test.go by click

Mike Cat 0 Feb 2, 2022
Gin-errorhandling - Gin Error Handling Middleware is a middleware for the popular Gin framework

Gin Error Handling Middleware Gin Error Handling Middleware is a middleware for

Joseph Woodward 9 Sep 19, 2022
This package provides json web token (jwt) middleware for goLang http servers

jwt-auth jwt auth middleware in goLang. If you're interested in using sessions, checkout my sessions library! README Contents: Quickstart Performance

Adam Hanna 224 Nov 20, 2022
:closed_lock_with_key: Middleware for keeping track of users, login states and permissions

Permissions2 Middleware for keeping track of users, login states and permissions. Online API Documentation godoc.org Features and limitations Uses sec

Alexander F. Rødseth 468 Oct 26, 2022
A dead simple, highly performant, highly customizable sessions middleware for go http servers.

If you're interested in jwt's, see my jwt library! Sessions A dead simple, highly performant, highly customizable sessions service for go http servers

Adam Hanna 68 Nov 11, 2022
Golang telegram bot API wrapper, session-based router and middleware

go-tgbot Pure Golang telegram bot API wrapper generated from swagger definition, session-based routing and middlewares. Usage benefits No need to lear

Oleg Lebedev 118 Nov 16, 2022
Goa is a web framework based on middleware, like koa.js.

Goa Goa is under construction, if you are familiar with koa or go and interested in this project, please join us. What is goa? goa = go + koa Just lik

null 47 Sep 27, 2022
⚡ Rux is an simple and fast web framework. support middleware, compatible http.Handler interface. 简单且快速的 Go web 框架,支持中间件,兼容 http.Handler 接口

Rux Simple and fast web framework for build golang HTTP applications. NOTICE: v1.3.x is not fully compatible with v1.2.x version Fast route match, sup

Gookit 83 Oct 3, 2022