More attempts to fix Kiwiflare bypass
This commit is contained in:
@@ -308,9 +308,18 @@ func NewCookieRefreshService(username, password, domain string) (*CookieRefreshS
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func (crs *CookieRefreshService) getClearanceToken() (string, error) {
|
func (crs *CookieRefreshService) getClearanceToken() (string, error) {
|
||||||
baseURL := fmt.Sprintf("https://%s/", crs.domain)
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -321,115 +330,109 @@ func (crs *CookieRefreshService) getClearanceToken() (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try multiple patterns for KiwiFlare challenge detection
|
// Detect challenge
|
||||||
patterns := []*regexp.Regexp{
|
pattern := regexp.MustCompile(`data-sssg-challenge=["']([^"']+)["'][^>]*data-sssg-difficulty=["'](\d+)["']`)
|
||||||
regexp.MustCompile(`<html[^>]*id=["']sssg["'][^>]*data-sssg-challenge=["']([^"']+)["'][^>]*data-sssg-difficulty=["'](\d+)["']`),
|
m := pattern.FindStringSubmatch(string(body))
|
||||||
regexp.MustCompile(`<html[^>]*id=["']sssg["'][^>]*data-sssg-difficulty=["'](\d+)["'][^>]*data-sssg-challenge=["']([^"']+)["']`),
|
if len(m) < 3 {
|
||||||
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 {
|
|
||||||
log.Println("No KiwiFlare challenge required")
|
log.Println("No KiwiFlare challenge required")
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
salt := m[1]
|
||||||
|
difficulty, _ := strconv.Atoi(m[2])
|
||||||
if difficulty == 0 {
|
if difficulty == 0 {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Solving KiwiFlare challenge (difficulty=%d)", difficulty)
|
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)
|
nonce, err := crs.solvePoW(salt, difficulty)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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)
|
submitURL := fmt.Sprintf("https://%s/.sssg/api/answer", crs.domain)
|
||||||
formData := url.Values{"a": {salt}, "b": {nonce}}
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer submitResp.Body.Close()
|
defer submitResp.Body.Close()
|
||||||
|
|
||||||
var result map[string]interface{}
|
if submitResp.StatusCode != http.StatusOK {
|
||||||
if err := json.NewDecoder(submitResp.Body).Decode(&result); err != nil {
|
return "", fmt.Errorf("KiwiFlare submit returned HTTP %d", submitResp.StatusCode)
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth, ok := result["auth"].(string); ok {
|
// Pause before reading cookie jar
|
||||||
// Manually add the clearance cookie to the jar
|
time.Sleep(time.Duration(1200+rand.Intn(800)) * time.Millisecond)
|
||||||
cookieURL, _ := url.Parse(baseURL)
|
|
||||||
clearanceCookie := &http.Cookie{
|
cookieURL, _ := url.Parse(baseURL)
|
||||||
Name: "sssg_clearance",
|
cookies := crs.client.Jar.Cookies(cookieURL)
|
||||||
Value: auth,
|
for _, c := range cookies {
|
||||||
Path: "/",
|
if c.Name == "sssg_clearance" {
|
||||||
Domain: crs.domain,
|
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) {
|
func (crs *CookieRefreshService) solvePoW(salt string, difficulty int) (string, error) {
|
||||||
nonce := rand.Int63()
|
start := time.Now()
|
||||||
requiredBytes := difficulty / 8
|
requiredBytes := difficulty / 8
|
||||||
requiredBits := 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)
|
input := fmt.Sprintf("%s%d", salt, nonce)
|
||||||
hash := sha256.Sum256([]byte(input))
|
hash := sha256.Sum256([]byte(input))
|
||||||
|
|
||||||
valid := true
|
valid := true
|
||||||
|
|
||||||
for i := 0; i < requiredBytes; i++ {
|
for i := 0; i < requiredBytes; i++ {
|
||||||
if hash[i] != 0 {
|
if hash[i] != 0 {
|
||||||
valid = false
|
valid = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if valid && requiredBits > 0 && requiredBytes < len(hash) {
|
if valid && requiredBits > 0 && requiredBytes < len(hash) {
|
||||||
mask := byte(0xFF << (8 - requiredBits))
|
mask := byte(0xFF << (8 - requiredBits))
|
||||||
if hash[requiredBytes]&mask != 0 {
|
if hash[requiredBytes]&mask != 0 {
|
||||||
valid = false
|
valid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if valid {
|
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.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) {
|
func (crs *CookieRefreshService) FetchFreshCookie() (string, error) {
|
||||||
attempt := 0
|
attempt := 0
|
||||||
retryDelay := CookieRetryDelay
|
retryDelay := CookieRetryDelay
|
||||||
@@ -465,12 +468,9 @@ func (crs *CookieRefreshService) FetchFreshCookie() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (crs *CookieRefreshService) attemptFetchCookie() (string, error) {
|
func (crs *CookieRefreshService) attemptFetchCookie() (string, error) {
|
||||||
// Always start with a fresh cookie jar
|
// DON'T create a fresh jar - reuse the existing one so clearance cookie persists
|
||||||
jar, err := cookiejar.New(nil)
|
// Only reset the transport for fresh TLS
|
||||||
if err != nil {
|
crs.client.Transport = &http.Transport{}
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
crs.client.Jar = jar
|
|
||||||
|
|
||||||
log.Println("Step 1: Checking for KiwiFlare challenge...")
|
log.Println("Step 1: Checking for KiwiFlare challenge...")
|
||||||
|
|
||||||
@@ -480,20 +480,17 @@ func (crs *CookieRefreshService) attemptFetchCookie() (string, error) {
|
|||||||
}
|
}
|
||||||
if clearanceToken != "" {
|
if clearanceToken != "" {
|
||||||
log.Println("✅ KiwiFlare challenge solved")
|
log.Println("✅ KiwiFlare challenge solved")
|
||||||
log.Println("⏳ Waiting 2 seconds for cookie propagation...")
|
log.Println("⏳ Waiting 3 seconds for cookie propagation...")
|
||||||
time.Sleep(3 * time.Second) // Increased from 1s to 2s
|
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
|
// Step 2: Fetch login page
|
||||||
log.Println("Step 2: Fetching login page...")
|
log.Println("Step 2: Fetching login page...")
|
||||||
loginURL := fmt.Sprintf("https://%s/login", crs.domain)
|
loginURL := fmt.Sprintf("https://%s/login", crs.domain)
|
||||||
req, _ := http.NewRequest("GET", loginURL, nil)
|
req, _ := http.NewRequest("GET", loginURL, nil)
|
||||||
req.Header.Set("Cache-Control", "no-cache")
|
req.Header.Set("Cache-Control", "no-cache")
|
||||||
req.Header.Set("Pragma", "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))
|
req.URL.RawQuery = fmt.Sprintf("r=%d", rand.Intn(999999))
|
||||||
|
|
||||||
resp, err := crs.client.Do(req)
|
resp, err := crs.client.Do(req)
|
||||||
@@ -511,7 +508,7 @@ func (crs *CookieRefreshService) attemptFetchCookie() (string, error) {
|
|||||||
|
|
||||||
// Small delay after getting login page
|
// Small delay after getting login page
|
||||||
log.Println("⏳ Waiting 1 second before processing 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
|
// Step 3: Extract CSRF token
|
||||||
log.Println("Step 3: Extracting 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!")
|
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(`<div class="blockMessage blockMessage--error[^>]*>(.*?)</div>`),
|
||||||
|
regexp.MustCompile(`data-xf-init="auto-submit"[^>]*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
|
// Delay before checking cookies
|
||||||
log.Println("⏳ Waiting 1 second for login to process...")
|
log.Println("⏳ Waiting 1 second for login to process...")
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
// Step 5: Extract cookies
|
// Step 5: Extract cookies
|
||||||
log.Println("Step 5: Extracting authentication 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 !hasXfUser && loginResp.StatusCode >= 300 && loginResp.StatusCode < 400 {
|
||||||
if loc := loginResp.Header.Get("Location"); loc != "" {
|
if loc := loginResp.Header.Get("Location"); loc != "" {
|
||||||
log.Printf("Following redirect to %s to check for xf_user...", 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
|
followURL := loc
|
||||||
if !strings.HasPrefix(loc, "http") {
|
if !strings.HasPrefix(loc, "http") {
|
||||||
followURL = fmt.Sprintf("https://%s%s", crs.domain, loc)
|
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 {
|
if err == nil {
|
||||||
followResp.Body.Close()
|
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)
|
cookies = crs.client.Jar.Cookies(cookieURL)
|
||||||
cookieStrs = []string{} // Reset
|
cookieStrs = []string{} // Reset
|
||||||
for _, c := range cookies {
|
for _, c := range cookies {
|
||||||
@@ -1894,4 +1925,4 @@ func main() {
|
|||||||
cookieService.Stop()
|
cookieService.Stop()
|
||||||
|
|
||||||
log.Println("Bridge stopped successfully")
|
log.Println("Bridge stopped successfully")
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user