You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

9 yıl önce
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. package middleware
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/sha1"
  6. "crypto/subtle"
  7. "encoding/base64"
  8. "encoding/hex"
  9. "fmt"
  10. "net/http"
  11. "net/url"
  12. "regexp"
  13. "strconv"
  14. "time"
  15. "github.com/goanywhere/crypto"
  16. )
  17. const (
  18. xsrfCookieName = "xsrf"
  19. xsrfHeaderName = "X-XSRF-Token"
  20. xsrfFieldName = "xsrftoken"
  21. xsrfMaxAge = 3600 * 24 * 365
  22. xsrfTimeout = time.Hour * 24 * 365
  23. )
  24. var (
  25. errXSRFReferer = "Referer URL is missing from the request or the value was malformed."
  26. errXSRFToken = "Invalid XSRF tokens"
  27. xsrfPattern = regexp.MustCompile("[^0-9a-zA-Z-_]")
  28. unsafeMethods = regexp.MustCompile("^(DELETE|POST|PUT)$")
  29. )
  30. type xsrf struct {
  31. *http.Request
  32. http.ResponseWriter
  33. token string
  34. }
  35. // See http://en.wikipedia.org/wiki/Same-origin_policy
  36. func (self *xsrf) checkOrigin() bool {
  37. if self.Request.URL.Scheme == "https" {
  38. // See [OWASP]; Checking the Referer Header.
  39. referer, err := url.Parse(self.Request.Header.Get("Referer"))
  40. if err != nil || referer.String() == "" ||
  41. referer.Scheme != self.Request.URL.Scheme ||
  42. referer.Host != self.Request.URL.Host {
  43. return false
  44. }
  45. }
  46. return true
  47. }
  48. func (self *xsrf) checkToken(token string) bool {
  49. // Header always takes precedance of form field since some popular
  50. // JavaScript frameworks allow global custom headers for all AJAX requests.
  51. query := self.Request.Header.Get(xsrfFieldName)
  52. if query == "" {
  53. query = self.Request.FormValue(xsrfFieldName)
  54. }
  55. // 1) basic length comparison.
  56. if query == "" || len(query) != len(token) {
  57. return false
  58. }
  59. // *sanitize* incoming masked token.
  60. query = xsrfPattern.ReplaceAllString(query, "")
  61. // 2) byte-based comparison.
  62. a, _ := base64.URLEncoding.DecodeString(token)
  63. b, _ := base64.URLEncoding.DecodeString(query)
  64. if subtle.ConstantTimeCompare(a, b) != 1 {
  65. return false
  66. }
  67. // 3) issued time checking.
  68. index := bytes.LastIndex(b, []byte{'|'})
  69. if index != 40 {
  70. return false
  71. }
  72. nanos, err := strconv.ParseInt(string(b[index+1:]), 10, 64)
  73. if err != nil {
  74. return false
  75. }
  76. now := time.Now()
  77. issueTime := time.Unix(0, nanos)
  78. if now.Sub(issueTime) >= xsrfTimeout {
  79. return false
  80. }
  81. // Ensure the token is not from the *future*, allow 1 minute grace period.
  82. if issueTime.After(now.Add(1 * time.Minute)) {
  83. return false
  84. }
  85. return true
  86. }
  87. func (self *xsrf) generate() {
  88. // Ensure we have XSRF token in the cookie first.
  89. var token string
  90. if cookie, err := self.Request.Cookie(xsrfCookieName); err == nil {
  91. if cookie.Value != "" {
  92. token = cookie.Value
  93. }
  94. }
  95. if token == "" {
  96. // Generate a base64-encoded token.
  97. nano := time.Now().UnixNano()
  98. hash := hmac.New(sha1.New, []byte(crypto.Random(32)))
  99. fmt.Fprintf(hash, "%s|%d", crypto.Random(12), nano)
  100. raw := fmt.Sprintf("%s|%d", hex.EncodeToString(hash.Sum(nil)), nano)
  101. token = base64.URLEncoding.EncodeToString([]byte(raw))
  102. // The max-age directive takes priority over Expires.
  103. // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
  104. cookie := new(http.Cookie)
  105. cookie.Name = xsrfCookieName
  106. cookie.Value = token
  107. cookie.MaxAge = xsrfMaxAge
  108. cookie.Path = "/"
  109. cookie.HttpOnly = true
  110. http.SetCookie(self.ResponseWriter, cookie)
  111. }
  112. self.ResponseWriter.Header()[xsrfHeaderName] = []string{token}
  113. self.token = token
  114. }
  115. // XSRF serves as Cross-Site Request Forgery protection middleware.
  116. func XSRF(next http.Handler) http.Handler {
  117. fn := func(w http.ResponseWriter, r *http.Request) {
  118. x := new(xsrf)
  119. x.Request = r
  120. x.ResponseWriter = w
  121. x.generate()
  122. if unsafeMethods.MatchString(r.Method) {
  123. // Ensure the URL came for "Referer" under HTTPS.
  124. if !x.checkOrigin() {
  125. http.Error(w, errXSRFReferer, http.StatusForbidden)
  126. }
  127. // length => bytes => issue time checkpoints.
  128. if !x.checkToken(x.token) {
  129. http.Error(w, errXSRFToken, http.StatusForbidden)
  130. }
  131. }
  132. // ensure browser will invalidate the cached XSRF token.
  133. w.Header().Add("Vary", "Cookie")
  134. next.ServeHTTP(w, r)
  135. }
  136. return http.HandlerFunc(fn)
  137. }