nosurf

package module
v0.0.1-20240522-0011 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 22, 2024 License: MIT Imports: 12 Imported by: 0

README

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.

Documentation

Overview

Package nosurf implements an HTTP handler that mitigates Cross-Site Request Forgery Attacks.

Index

Constants

View Source
const (
	// the name of CSRF cookie
	CookieName = "csrf_token"
	// the name of the form field
	FormFieldName = "csrf_token"
	// the name of CSRF header
	HeaderName = "X-CSRF-Token"
	// the HTTP status code for the default failure handler
	FailureCode = 400

	// Max-Age in seconds for the default base cookie. 365 days.
	MaxAge = 365 * 24 * 60 * 60
)

Variables

View Source
var (
	ErrNoReferer  = errors.New("A secure request contained no Referer or its value was malformed")
	ErrBadReferer = errors.New("A secure request's Referer comes from a different Origin" +
		" from the request's URL")
	ErrBadToken = errors.New("The CSRF token in the cookie doesn't match the one" +
		" received in a form/header.")
)

reasons for CSRF check failures

View Source
var MaskPassword = []byte("yoursuperpasswordhere!")

Functions

func DecodeData

func DecodeData(data string) []byte

func EncodeData

func EncodeData(data []byte) string

func NewPure

func NewPure(handler http.Handler) http.Handler

The same as New(), but has an interface return type.

func Reason

func Reason(req *http.Request) error

Reason takes an HTTP request and returns the reason of failure of the CSRF check for that request

Note that the same availability restrictions apply for Reason() as for Token().

func Token

func Token(req *http.Request) string

Token takes an HTTP request and returns the CSRF token for that request or an empty string if the token does not exist.

Note that the token won't be available after CSRFHandler finishes (that is, in another handler that wraps it, or after the request has been served)

func VerifyToken

func VerifyToken(realToken, sentToken string) bool

VerifyToken verifies the sent token equals the real one and returns a bool value indicating if tokens are equal. Supports masked tokens. realToken comes from Token(r) and sentToken is token sent unusual way.

func VerifyTokenDebug

func VerifyTokenDebug(realToken, sentToken string) bool

VerifyTokenDebug verifies the sent token equals the real one and returns a bool value indicating if tokens are equal. Supports masked tokens. realToken comes from Token(r) and sentToken is token sent unusual way.

Types

type CSRFHandler

type CSRFHandler struct {
	// contains filtered or unexported fields
}

func New

func New(handler http.Handler) *CSRFHandler

Constructs a new CSRFHandler that calls the specified handler if the CSRF check succeeds.

func (*CSRFHandler) ExemptFunc

func (h *CSRFHandler) ExemptFunc(fn func(r *http.Request) bool)

func (*CSRFHandler) ExemptGlob

func (h *CSRFHandler) ExemptGlob(pattern string)

Exempts URLs that match the specified glob pattern (as used by filepath.Match()) from CSRF checks

Note that ExemptGlob() is unable to detect syntax errors, because it doesn't have a path to check it against and filepath.Match() doesn't report an error if the path is empty. If we find a way to check the syntax, ExemptGlob MIGHT PANIC on a syntax error in the future. ALWAYS check your globs for syntax errors.

func (*CSRFHandler) ExemptGlobs

func (h *CSRFHandler) ExemptGlobs(patterns ...string)

A variadic argument version of ExemptGlob()

func (*CSRFHandler) ExemptPath

func (h *CSRFHandler) ExemptPath(path string)

Exempts an exact path from CSRF checks With this (and other Exempt* methods) you should take note that Go's paths include a leading slash.

func (*CSRFHandler) ExemptPaths

func (h *CSRFHandler) ExemptPaths(paths ...string)

A variadic argument version of ExemptPath()

func (*CSRFHandler) ExemptRegexp

func (h *CSRFHandler) ExemptRegexp(re interface{})

Accepts a regular expression string or a compiled *regexp.Regexp and exempts URLs that match it from CSRF checks.

If the given argument is neither of the accepted values, or the given string fails to compile, ExemptRegexp() panics.

func (*CSRFHandler) ExemptRegexps

func (h *CSRFHandler) ExemptRegexps(res ...interface{})

A variadic argument version of ExemptRegexp()

func (*CSRFHandler) IsExempt

func (h *CSRFHandler) IsExempt(r *http.Request) bool

Checks if the given request is exempt from CSRF checks. It checks the ExemptFunc first, then the exact paths, then the globs and finally the regexps.

func (*CSRFHandler) RegenerateToken

func (h *CSRFHandler) RegenerateToken(w http.ResponseWriter, r *http.Request) string

Generates a new token, sets it on the given request and returns it

func (*CSRFHandler) ServeHTTP

func (h *CSRFHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*CSRFHandler) SetBaseCookie

func (h *CSRFHandler) SetBaseCookie(cookie http.Cookie)

Sets the base cookie to use when building a CSRF token cookie This way you can specify the Domain, Path, HttpOnly, Secure, etc.

func (*CSRFHandler) SetFailureHandler

func (h *CSRFHandler) SetFailureHandler(handler http.Handler)

Sets the handler to call in case the CSRF check fails. By default it's defaultFailureHandler.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL