Improve request fingerprinting and security middleware
- Updated GetRequestFingerprint to include additional headers (Sec-CH-UA-Platform, Sec-CH-UA-Mobile) and UID cookie for improved uniqueness. - Modified SecurityMiddleware to set a new UID cookie if not present, enhancing user tracking and security. - Adjusted test cases to reflect changes in fingerprinting logic and ensure accurate validation of request parameters.
This commit is contained in:
@@ -107,4 +107,3 @@ func TestHandlers(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package security
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
@@ -92,13 +93,29 @@ func GetRequestFingerprint(r *http.Request, s *stats.Service) string {
|
||||
|
||||
ua := r.Header.Get("User-Agent")
|
||||
chUA := r.Header.Get("Sec-CH-UA")
|
||||
chPlatform := r.Header.Get("Sec-CH-UA-Platform")
|
||||
chMobile := r.Header.Get("Sec-CH-UA-Mobile")
|
||||
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte("v2|"))
|
||||
hash.Write([]byte(ipStr))
|
||||
hash.Write([]byte("|"))
|
||||
hash.Write([]byte(ua))
|
||||
hash.Write([]byte("|"))
|
||||
hash.Write([]byte(chUA))
|
||||
hash.Write([]byte("|"))
|
||||
hash.Write([]byte(chPlatform))
|
||||
hash.Write([]byte("|"))
|
||||
hash.Write([]byte(chMobile))
|
||||
|
||||
if r.TLS != nil {
|
||||
hash.Write([]byte(fmt.Sprintf("|%d|%d", r.TLS.Version, r.TLS.CipherSuite)))
|
||||
}
|
||||
|
||||
if cookie, err := r.Cookie("_ss_uid"); err == nil {
|
||||
hash.Write([]byte("|"))
|
||||
hash.Write([]byte(cookie.Value))
|
||||
}
|
||||
|
||||
fingerprint := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
@@ -182,6 +199,24 @@ func SecurityMiddleware(s *stats.Service, bb *BotBlocker) func(http.Handler) htt
|
||||
start := time.Now()
|
||||
path := strings.ToLower(r.URL.Path)
|
||||
ua := r.UserAgent()
|
||||
|
||||
if _, err := r.Cookie("_ss_uid"); err != nil {
|
||||
uid := make([]byte, 16)
|
||||
if _, err := rand.Read(uid); err == nil {
|
||||
uidStr := hex.EncodeToString(uid)
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "_ss_uid",
|
||||
Value: uidStr,
|
||||
Path: "/",
|
||||
Expires: time.Now().Add(365 * 24 * time.Hour),
|
||||
HttpOnly: true,
|
||||
Secure: r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https",
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
r.AddCookie(&http.Cookie{Name: "_ss_uid", Value: uidStr})
|
||||
}
|
||||
}
|
||||
|
||||
fingerprint := GetRequestFingerprint(r, s)
|
||||
|
||||
ctx := context.WithValue(r.Context(), FingerprintKey, fingerprint)
|
||||
|
||||
@@ -59,36 +59,41 @@ func TestThrottledReader(t *testing.T) {
|
||||
func TestGetRequestFingerprint(t *testing.T) {
|
||||
statsService := stats.NewService("test-hashes.json")
|
||||
|
||||
// IPv4
|
||||
// Same IP, same headers
|
||||
req1 := httptest.NewRequest("GET", "/", nil)
|
||||
req1.RemoteAddr = "1.2.3.4:1234"
|
||||
req1.Header.Set("User-Agent", "Mozilla/5.0")
|
||||
req1.Header.Set("Sec-CH-UA", `"Google Chrome";v="123"`)
|
||||
f1 := GetRequestFingerprint(req1, statsService)
|
||||
|
||||
req2 := httptest.NewRequest("GET", "/", nil)
|
||||
req2.RemoteAddr = "1.2.3.4:5678"
|
||||
req2.Header.Set("User-Agent", "Mozilla/5.0")
|
||||
req2.Header.Set("Sec-CH-UA", `"Google Chrome";v="123"`)
|
||||
f2 := GetRequestFingerprint(req2, statsService)
|
||||
if f1 != f2 {
|
||||
t.Error("fingerprints should match for same IPv4")
|
||||
t.Error("fingerprints should match for same parameters")
|
||||
}
|
||||
|
||||
// X-Forwarded-For
|
||||
// Different UID cookie
|
||||
req3 := httptest.NewRequest("GET", "/", nil)
|
||||
req3.Header.Set("X-Forwarded-For", "5.6.7.8, 1.2.3.4")
|
||||
req3.RemoteAddr = "1.2.3.4:1234"
|
||||
req3.Header.Set("User-Agent", "Mozilla/5.0")
|
||||
req3.Header.Set("Sec-CH-UA", `"Google Chrome";v="123"`)
|
||||
req3.AddCookie(&http.Cookie{Name: "_ss_uid", Value: "uid1"})
|
||||
f3 := GetRequestFingerprint(req3, statsService)
|
||||
if f1 == f3 {
|
||||
t.Error("fingerprints should differ for different IPs")
|
||||
t.Error("fingerprints should differ with different UID cookies")
|
||||
}
|
||||
|
||||
// IPv6 masking
|
||||
// Different Client Hint
|
||||
req4 := httptest.NewRequest("GET", "/", nil)
|
||||
req4.RemoteAddr = "[2001:db8::1]:1234"
|
||||
req4.RemoteAddr = "1.2.3.4:1234"
|
||||
req4.Header.Set("User-Agent", "Mozilla/5.0")
|
||||
req4.Header.Set("Sec-CH-UA", `"Brave";v="123"`)
|
||||
f4 := GetRequestFingerprint(req4, statsService)
|
||||
|
||||
req5 := httptest.NewRequest("GET", "/", nil)
|
||||
req5.RemoteAddr = "[2001:db8::2]:1234"
|
||||
f5 := GetRequestFingerprint(req5, statsService)
|
||||
if f4 != f5 {
|
||||
t.Error("fingerprints should match for same IPv6 /64 prefix")
|
||||
if f1 == f4 {
|
||||
t.Error("fingerprints should differ with different Client Hints")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,13 +121,23 @@ func TestSecurityMiddleware(t *testing.T) {
|
||||
t.Errorf("expected 403 for forbidden pattern, got %d", rr.Code)
|
||||
}
|
||||
|
||||
// Test normal request
|
||||
// Test normal request and cookie setting
|
||||
req = httptest.NewRequest("GET", "/api/software", nil)
|
||||
rr = httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected 200 for normal request, got %d", rr.Code)
|
||||
}
|
||||
cookieFound := false
|
||||
for _, c := range rr.Result().Cookies() {
|
||||
if c.Name == "_ss_uid" {
|
||||
cookieFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !cookieFound {
|
||||
t.Error("expected _ss_uid cookie to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPrivateIP_Extended(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user