From d76672ae804f55e4b991229492516ed517953423 Mon Sep 17 00:00:00 2001 From: Salastil Date: Fri, 17 Oct 2025 23:15:14 -0400 Subject: [PATCH] More attempts to fix Kiwiflare bypass --- Sneedchat-Discord-Bridge.go | 177 +++++++++++++++++++++--------------- 1 file changed, 104 insertions(+), 73 deletions(-) diff --git a/Sneedchat-Discord-Bridge.go b/Sneedchat-Discord-Bridge.go index 6203180..fbd7134 100644 --- a/Sneedchat-Discord-Bridge.go +++ b/Sneedchat-Discord-Bridge.go @@ -308,9 +308,18 @@ func NewCookieRefreshService(username, password, domain string) (*CookieRefreshS }, nil } + + func (crs *CookieRefreshService) getClearanceToken() (string, error) { baseURL := fmt.Sprintf("https://%s/", crs.domain) - resp, err := crs.client.Get(baseURL) + + req, _ := http.NewRequest("GET", baseURL, nil) + req.Header.Set("User-Agent", randomUserAgent()) + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") + req.Header.Set("Accept-Language", "en-US,en;q=0.9") + req.Header.Set("Connection", "keep-alive") + + resp, err := crs.client.Do(req) if err != nil { return "", err } @@ -321,115 +330,109 @@ func (crs *CookieRefreshService) getClearanceToken() (string, error) { return "", err } - // Try multiple patterns for KiwiFlare challenge detection - patterns := []*regexp.Regexp{ - regexp.MustCompile(`]*id=["']sssg["'][^>]*data-sssg-challenge=["']([^"']+)["'][^>]*data-sssg-difficulty=["'](\d+)["']`), - regexp.MustCompile(`]*id=["']sssg["'][^>]*data-sssg-difficulty=["'](\d+)["'][^>]*data-sssg-challenge=["']([^"']+)["']`), - regexp.MustCompile(`data-sssg-challenge=["']([^"']+)["'][^>]*data-sssg-difficulty=["'](\d+)["']`), - } - - var salt string - var difficulty int - found := false - - for i, pattern := range patterns { - matches := pattern.FindStringSubmatch(string(body)) - if len(matches) >= 3 { - if i == 1 { - // Pattern has difficulty first, then challenge - difficulty, _ = strconv.Atoi(matches[1]) - salt = matches[2] - } else { - salt = matches[1] - difficulty, _ = strconv.Atoi(matches[2]) - } - found = true - break - } - } - - if !found { + // Detect challenge + pattern := regexp.MustCompile(`data-sssg-challenge=["']([^"']+)["'][^>]*data-sssg-difficulty=["'](\d+)["']`) + m := pattern.FindStringSubmatch(string(body)) + if len(m) < 3 { log.Println("No KiwiFlare challenge required") return "", nil } + salt := m[1] + difficulty, _ := strconv.Atoi(m[2]) if difficulty == 0 { return "", nil } log.Printf("Solving KiwiFlare challenge (difficulty=%d)", difficulty) + time.Sleep(time.Duration(500+rand.Intn(750)) * time.Millisecond) // human delay before compute nonce, err := crs.solvePoW(salt, difficulty) if err != nil { return "", err } + // Delay between solve and submit to mimic browser JS runtime + time.Sleep(time.Duration(700+rand.Intn(900)) * time.Millisecond) + submitURL := fmt.Sprintf("https://%s/.sssg/api/answer", crs.domain) formData := url.Values{"a": {salt}, "b": {nonce}} - submitResp, err := crs.client.PostForm(submitURL, formData) + submitReq, _ := http.NewRequest("POST", submitURL, strings.NewReader(formData.Encode())) + submitReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + submitReq.Header.Set("User-Agent", randomUserAgent()) + submitReq.Header.Set("Origin", fmt.Sprintf("https://%s", crs.domain)) + submitReq.Header.Set("Referer", baseURL) + + submitResp, err := crs.client.Do(submitReq) if err != nil { return "", err } defer submitResp.Body.Close() - var result map[string]interface{} - if err := json.NewDecoder(submitResp.Body).Decode(&result); err != nil { - return "", err + if submitResp.StatusCode != http.StatusOK { + return "", fmt.Errorf("KiwiFlare submit returned HTTP %d", submitResp.StatusCode) } - if auth, ok := result["auth"].(string); ok { - // Manually add the clearance cookie to the jar - cookieURL, _ := url.Parse(baseURL) - clearanceCookie := &http.Cookie{ - Name: "sssg_clearance", - Value: auth, - Path: "/", - Domain: crs.domain, + // Pause before reading cookie jar + time.Sleep(time.Duration(1200+rand.Intn(800)) * time.Millisecond) + + cookieURL, _ := url.Parse(baseURL) + cookies := crs.client.Jar.Cookies(cookieURL) + for _, c := range cookies { + if c.Name == "sssg_clearance" { + log.Printf("✅ KiwiFlare clearance cookie confirmed: %s...", c.Value[:min(10, len(c.Value))]) + return c.Value, nil } - crs.client.Jar.SetCookies(cookieURL, []*http.Cookie{clearanceCookie}) - log.Println("✅ KiwiFlare clearance cookie set") - return auth, nil } - return "", fmt.Errorf("no auth token in response") + return "", fmt.Errorf("no sssg_clearance cookie after solve") } func (crs *CookieRefreshService) solvePoW(salt string, difficulty int) (string, error) { - nonce := rand.Int63() + start := time.Now() requiredBytes := difficulty / 8 requiredBits := difficulty % 8 - maxAttempts := 10_000_000 - - for attempts := 0; attempts < maxAttempts; attempts++ { - nonce++ + for nonce := rand.Int63(); ; nonce++ { input := fmt.Sprintf("%s%d", salt, nonce) hash := sha256.Sum256([]byte(input)) - valid := true + for i := 0; i < requiredBytes; i++ { if hash[i] != 0 { valid = false break } } - if valid && requiredBits > 0 && requiredBytes < len(hash) { mask := byte(0xFF << (8 - requiredBits)) if hash[requiredBytes]&mask != 0 { valid = false } } - if valid { + elapsed := time.Since(start) + // Minimum 2–4 second human-like response window + minDelay := time.Duration(2+rand.Intn(3)) * time.Second + if elapsed < minDelay { + time.Sleep(minDelay - elapsed) + } return fmt.Sprintf("%d", nonce), nil } } - - return "", fmt.Errorf("failed to solve PoW within %d attempts", maxAttempts) } +func randomUserAgent() string { + agents := []string{ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", + } + return agents[rand.Intn(len(agents))] +} + + func (crs *CookieRefreshService) FetchFreshCookie() (string, error) { attempt := 0 retryDelay := CookieRetryDelay @@ -465,12 +468,9 @@ func (crs *CookieRefreshService) FetchFreshCookie() (string, error) { } func (crs *CookieRefreshService) attemptFetchCookie() (string, error) { - // Always start with a fresh cookie jar - jar, err := cookiejar.New(nil) - if err != nil { - return "", err - } - crs.client.Jar = jar + // DON'T create a fresh jar - reuse the existing one so clearance cookie persists + // Only reset the transport for fresh TLS + crs.client.Transport = &http.Transport{} log.Println("Step 1: Checking for KiwiFlare challenge...") @@ -480,20 +480,17 @@ func (crs *CookieRefreshService) attemptFetchCookie() (string, error) { } if clearanceToken != "" { log.Println("✅ KiwiFlare challenge solved") - log.Println("⏳ Waiting 2 seconds for cookie propagation...") - time.Sleep(3 * time.Second) // Increased from 1s to 2s + log.Println("⏳ Waiting 3 seconds for cookie propagation...") + time.Sleep(3 * time.Second) } - // Force a new TLS session to avoid stale keep-alive - crs.client.Transport = &http.Transport{} - // Step 2: Fetch login page log.Println("Step 2: Fetching login page...") loginURL := fmt.Sprintf("https://%s/login", crs.domain) req, _ := http.NewRequest("GET", loginURL, nil) req.Header.Set("Cache-Control", "no-cache") req.Header.Set("Pragma", "no-cache") - req.Header.Set("User-Agent", "Sneedchat-Discord-Go-Bridge") + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") req.URL.RawQuery = fmt.Sprintf("r=%d", rand.Intn(999999)) resp, err := crs.client.Do(req) @@ -511,7 +508,7 @@ func (crs *CookieRefreshService) attemptFetchCookie() (string, error) { // Small delay after getting login page log.Println("⏳ Waiting 1 second before processing login page...") - time.Sleep(3 * time.Second) + time.Sleep(1 * time.Second) // Step 3: Extract CSRF token log.Println("Step 3: Extracting CSRF token...") @@ -583,9 +580,40 @@ func (crs *CookieRefreshService) attemptFetchCookie() (string, error) { log.Println("⚠️ No Set-Cookie headers in login response!") } + // If we got HTTP 200, login failed - read the error + if loginResp.StatusCode == 200 { + bodyBytes, _ := io.ReadAll(loginResp.Body) + bodyText := string(bodyBytes) + + // Look for XenForo error messages + errorPatterns := []*regexp.Regexp{ + regexp.MustCompile(`
]*data-message="([^"]+)"`), + regexp.MustCompile(`"errors":\s*\[(.*?)\]`), + } + + var errorMsg string + for _, pattern := range errorPatterns { + if matches := pattern.FindStringSubmatch(bodyText); len(matches) >= 2 { + errorMsg = matches[1] + // Strip HTML tags + errorMsg = regexp.MustCompile(`<[^>]+>`).ReplaceAllString(errorMsg, "") + break + } + } + + if errorMsg != "" { + log.Printf("❌ Login error from server: %s", strings.TrimSpace(errorMsg)) + } else { + log.Printf("❌ Login failed (HTTP 200). Response snippet:\n%s", bodyText[:min(1000, len(bodyText))]) + } + + return "", fmt.Errorf("login failed with HTTP 200 - server rejected credentials or challenge not accepted") + } + // Delay before checking cookies log.Println("⏳ Waiting 1 second for login to process...") - time.Sleep(3 * time.Second) + time.Sleep(1 * time.Second) // Step 5: Extract cookies log.Println("Step 5: Extracting authentication cookies...") @@ -615,17 +643,20 @@ func (crs *CookieRefreshService) attemptFetchCookie() (string, error) { if !hasXfUser && loginResp.StatusCode >= 300 && loginResp.StatusCode < 400 { if loc := loginResp.Header.Get("Location"); loc != "" { log.Printf("Following redirect to %s to check for xf_user...", loc) - time.Sleep(3 * time.Second) // Wait before following redirect + time.Sleep(1 * time.Second) // Wait before following redirect followURL := loc if !strings.HasPrefix(loc, "http") { followURL = fmt.Sprintf("https://%s%s", crs.domain, loc) } - followResp, err := crs.client.Get(followURL) + followReq, _ := http.NewRequest("GET", followURL, nil) + followReq.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") + + followResp, err := crs.client.Do(followReq) if err == nil { followResp.Body.Close() - time.Sleep(3 * time.Second) // Wait after redirect + time.Sleep(1 * time.Second) // Wait after redirect cookies = crs.client.Jar.Cookies(cookieURL) cookieStrs = []string{} // Reset for _, c := range cookies { @@ -1894,4 +1925,4 @@ func main() { cookieService.Stop() log.Println("Bridge stopped successfully") -} +} \ No newline at end of file