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:
2025-12-27 03:35:36 -06:00
parent 621b54092d
commit bd7fd93a00
3 changed files with 64 additions and 15 deletions

View File

@@ -107,4 +107,3 @@ func TestHandlers(t *testing.T) {
}
})
}

View File

@@ -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)

View File

@@ -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) {