autoreg-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
# Cloudflare Turnstile Auto-Registration Patterns
|
||||
|
||||
## Core Problem: Turnstile IP Binding
|
||||
|
||||
Cloudflare Turnstile binds the solved token to the **IP that solved the challenge**. The registration request MUST come from the same IP. If the captcha service solves from its IP and the registration request comes from a different IP → `TURNSTILE_VERIFICATION_FAILED`.
|
||||
|
||||
### Failure Signature
|
||||
```json
|
||||
{"code":400,"message":"turnstile verification failed","reason":"TURNSTILE_VERIFICATION_FAILED"}
|
||||
```
|
||||
|
||||
This is NOT a bad token, wrong sitekey, or wrong endpoint. It's always IP mismatch.
|
||||
|
||||
## Solution Matrix
|
||||
|
||||
| Approach | Requires | Works? | Notes |
|
||||
|----------|----------|--------|-------|
|
||||
| `TurnstileTaskProxyless` + direct API | Captcha service only | ❌ | IP mismatch (captcha IP ≠ your IP) |
|
||||
| `TurnstileTask` + datacenter proxy | Proxy + captcha | ❌ | Cloudflare detects datacenter IPs |
|
||||
| `TurnstileTask` + residential proxy | Residential proxy + captcha | ✅ | Both solve & register through same residential IP |
|
||||
| Browser automation (Selenium/Playwright) | Local Chrome, no sandbox | ⚠️ | Turnstile iframe may not render if bot detected |
|
||||
| Browser + extracted token → API | Local Chrome + API | ✅ | Same IP for solve + request |
|
||||
|
||||
## Anti-Captcha API Patterns
|
||||
|
||||
### Proxyless (DON'T USE — IP mismatch)
|
||||
```python
|
||||
# Anti-Captcha
|
||||
r = requests.post('https://api.anti-captcha.com/createTask', json={
|
||||
'clientKey': KEY,
|
||||
'task': {
|
||||
'type': 'TurnstileTaskProxyless',
|
||||
'websiteURL': 'https://target.com/register',
|
||||
'websiteKey': '0x4AAAAA...',
|
||||
}
|
||||
})
|
||||
# Token will be bound to Anti-Captcha's IP → registration fails
|
||||
```
|
||||
|
||||
### With Proxy (USE THIS)
|
||||
```python
|
||||
r = requests.post('https://api.anti-captcha.com/createTask', json={
|
||||
'clientKey': KEY,
|
||||
'task': {
|
||||
'type': 'TurnstileTask',
|
||||
'websiteURL': 'https://target.com/register',
|
||||
'websiteKey': '0x4AAAAA...',
|
||||
'proxyType': 'http', # or socks5
|
||||
'proxyAddress': '[REDACTED-IP]',
|
||||
'proxyPort': 8080,
|
||||
'proxyLogin': 'user', # optional
|
||||
'proxyPassword': 'pass', # optional
|
||||
}
|
||||
})
|
||||
# Then register through the SAME proxy
|
||||
resp = requests.post('https://target.com/api/register',
|
||||
json={...},
|
||||
proxies={'http': 'http://user:pass@[REDACTED-IP]:8080',
|
||||
'https': 'http://user:pass@[REDACTED-IP]:8080'})
|
||||
```
|
||||
|
||||
## Proxy Requirements
|
||||
|
||||
### Residential proxies MANDATORY
|
||||
- Datacenter IPs (Alibaba Cloud, AWS, DigitalOcean, etc.) are detected and rejected
|
||||
- Cloudflare runs ASN checks — residential ISP IPs pass, cloud/hosting IPs fail
|
||||
- Free proxy lists are 99% datacenter → won't work
|
||||
|
||||
### ProxyShard Residential Format
|
||||
```
|
||||
host:port:username:password
|
||||
relay-eu.proxyshard.com:8080:plan-premium-country-de-sid-XXXXX-ttl-8000000:5knun1bleejnw5m1
|
||||
```
|
||||
- Username: `plan-premium-country-{code}-sid-{session_id}-ttl-{ttl}`
|
||||
- Password: same for all proxies in a batch
|
||||
- **PITFALL**: Credentials can be invalid (407) if plan expired or password changed
|
||||
- **PITFALL**: Requires IP whitelisting on proxyshard.com dashboard
|
||||
|
||||
### Proxy Authentication Diagnostics
|
||||
```bash
|
||||
# Test proxy with curl
|
||||
curl -v --connect-timeout 10 \
|
||||
-x "host:port" \
|
||||
-U "username:password" \
|
||||
"https://httpbin.org/ip"
|
||||
|
||||
# 407 = auth failed ("Invalid credentials" in Proxy-Authenticate header)
|
||||
# 200 = proxy works
|
||||
```
|
||||
|
||||
### Proxy URL Format for Python requests
|
||||
```python
|
||||
proxy_url = f'http://{username}:{password}@{host}:{port}'
|
||||
proxies = {'http': proxy_url, 'https': proxy_url}
|
||||
```
|
||||
|
||||
## Browser Automation with Turnstile
|
||||
|
||||
### Selenium undetected-chromedriver
|
||||
|
||||
**IMPORTANT**: `undetected-chromedriver` requires Python 3.11 (or 3.12). Python 3.14 removed `distutils.version.LooseVersion` which the library imports. Use: `C:/Users/User/AppData/Local/Python/Python311/python.exe`.
|
||||
|
||||
```python
|
||||
import undetected_chromedriver as uc
|
||||
from selenium_stealth import stealth
|
||||
|
||||
options = uc.ChromeOptions()
|
||||
options.add_argument('--no-sandbox')
|
||||
options.add_argument('--disable-blink-features=AutomationControlled')
|
||||
|
||||
driver = uc.Chrome(options=options, version_main=150) # match Chrome version
|
||||
stealth(driver, languages=['en-US', 'en'], vendor='Google Inc.',
|
||||
platform='Win32', webgl_vendor='Intel Inc.',
|
||||
renderer='Intel Iris OpenGL Engine', fix_hairline=True)
|
||||
|
||||
# CDP evasions
|
||||
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
|
||||
'source': '''
|
||||
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
|
||||
Object.defineProperty(navigator, 'plugins', {get: () => [1,2,3,4,5]});
|
||||
window.chrome = {runtime: {}};
|
||||
'''
|
||||
})
|
||||
```
|
||||
|
||||
### Turnstile Iframe Detection
|
||||
|
||||
**When undetected-chromedriver works**: Turnstile is NOT in an iframe — it renders inline. The token is in a hidden input:
|
||||
```python
|
||||
# Simple extraction (undetected-chromedriver — Turnstile is inline, not iframe)
|
||||
token = [REDACTED]
|
||||
"return document.querySelector('[name=\"cf-turnstile-response\"]')?.value || ''"
|
||||
)
|
||||
# Also check turnstile API
|
||||
if not token:
|
||||
token = [REDACTED]"return window.turnstile?.getResponse?.() || ''")
|
||||
```
|
||||
|
||||
**When headless Chrome is used (DON'T — Turnstile blocks it)**: Turnstile is in an iframe:
|
||||
```python
|
||||
# Check if Turnstile iframe loaded
|
||||
iframes = driver.find_elements(By.TAG_NAME, 'iframe')
|
||||
for f in iframes:
|
||||
src = f.get_attribute('src') or ''
|
||||
if 'challenges.cloudflare' in src:
|
||||
# Turnstile found — click the checkbox
|
||||
driver.switch_to.frame(f)
|
||||
checkbox = driver.find_element(By.CSS_SELECTOR, '#checkbox, [role=checkbox]')
|
||||
checkbox.click()
|
||||
driver.switch_to.default_content()
|
||||
```
|
||||
|
||||
### Forced Turnstile Render via JS
|
||||
```python
|
||||
# If widget not in DOM, force-render via turnstile API
|
||||
result = driver.execute_script('''
|
||||
return new Promise((resolve) => {
|
||||
var div = document.createElement('div');
|
||||
document.body.prepend(div);
|
||||
turnstile.render(div, {
|
||||
sitekey: '0x4AAAAA...',
|
||||
callback: (token) => resolve({status: 'ok', token: token}),
|
||||
'error-callback': () => resolve({status: 'error'}),
|
||||
'timeout-callback': () => resolve({status: 'timeout'})
|
||||
});
|
||||
setTimeout(() => resolve({status: 'timeout'}), 30000);
|
||||
});
|
||||
''')
|
||||
# PITFALL: script timeout if Cloudflare detects bot → challenge never auto-solves
|
||||
```
|
||||
|
||||
### Chrome Version Matching
|
||||
```python
|
||||
# Check Chrome version first
|
||||
# chrome://version/ → e.g. 150.0.7871.182
|
||||
# Pass version_main to match:
|
||||
driver = uc.Chrome(options=options, version_main=150)
|
||||
```
|
||||
|
||||
## Registration API Pattern
|
||||
|
||||
### baaaai.com / Sub2API Example
|
||||
```python
|
||||
# Sitekey from page JS: window.__APP_CONFIG__.turnstile_site_key
|
||||
# Or from <script>: sitekey: '0x4AAAAAAD38oqy2OVfVxy_y'
|
||||
|
||||
# Endpoint
|
||||
REGISTER_URL = 'https://baaaai.com/api/v1/auth/register'
|
||||
|
||||
# Payload
|
||||
payload = {
|
||||
'email': 'user@domain.com',
|
||||
'password': 'Password123!',
|
||||
'cf-turnstile-response': token, # from captcha service or browser
|
||||
'agree': True,
|
||||
}
|
||||
|
||||
# Headers
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Origin': 'https://baaaai.com',
|
||||
'Referer': 'https://baaaai.com/register',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
}
|
||||
|
||||
# Whitelisted email domains (from __APP_CONFIG__)
|
||||
# ["@qq.com", "@gmail.com", "*.edu.cn", "@outlook.com", "@foxmail.com", "@163.com"]
|
||||
```
|
||||
|
||||
## curl_cffi for TLS Fingerprint Matching
|
||||
```python
|
||||
from curl_cffi import requests as cr
|
||||
# Use chrome impersonation to match browser TLS fingerprint
|
||||
resp = cr.post(url, json=payload, headers=headers,
|
||||
impersonate='chrome131', timeout=30)
|
||||
# Supported: chrome124, chrome131, etc.
|
||||
# NOT supported: chrome138 (too new)
|
||||
```
|
||||
|
||||
## 2captcha vs Anti-Captcha
|
||||
|
||||
### 2captcha
|
||||
- API: `https://api.2captcha.com/createTask`
|
||||
- Same task format as Anti-Captcha
|
||||
- Error codes: `ERROR_KEY_DOES_NOT_EXIST`, `ERROR_ZERO_BALANCE`, IP bans
|
||||
|
||||
### Anti-Captcha
|
||||
- API: `https://api.anti-captcha.com/createTask`
|
||||
- Balance check: `POST /getBalance` with `clientKey`
|
||||
- Current key: `[REDACTED-KEY]` ($48.38 as of 2026-07-27)
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **TurnstileTaskProxyless always fails for registration** — token is IP-bound to captcha service, not your IP
|
||||
2. **Datacenter proxies always fail** — Cloudflare detects ASN, rejects even with IP matching
|
||||
3. **Free proxies are datacenter** — proxy lists from proxyscrape.com, geonode.com etc. are 99% datacenter
|
||||
4. **ProxyShard credentials can expire** — 407 "Invalid credentials" means plan expired or password changed
|
||||
5. **Browser Turnstile iframe may not render** — if Cloudflare detects automation, widget shows challenge requiring human interaction, `script timeout` in Selenium
|
||||
6. **Chrome version mismatch** — `undetected_chromedriver` downloads chromedriver matching `version_main`; auto-detect may download wrong version
|
||||
7. **Greenlet broken on Python 3.14** — `playwright` depends on `greenlet._greenlet` which doesn't compile on 3.14+; use Selenium instead
|
||||
8. **`excludeSwitches` and `useAutomationExtension` deprecated** — newer Selenium versions reject these options
|
||||
9. **Turnstile token is a QUERY PARAMETER, not body field** — On "New API" forks (getunikey.ai, etc.), the token is sent as `?turnstile=TOKEN&hcaptcha=HCAPTCHA` in the URL, NOT as `cf-turnstile-response` in the JSON body. Sending it in the body returns `"Turnstile token 为空"`. The JS code pattern is `FH.post(url, body, {params: {turnstile, hcaptcha}})`.
|
||||
10. **Headless Chrome vs undetected-chromedriver** — Headless Chrome (CDP, `--headless`, `mcp__chrome_devtools__*`) is ALWAYS detected by Turnstile: the iframe shows "Checking your browser..." forever, buttons stay disabled. `undetected_chromedriver` (Python 3.11 only — distutils removed in 3.14) reliably bypasses this: Turnstile auto-solves in ~5-8s, all buttons become enabled. `browser_navigate` (Browserbase) also fails with Turnstile — buttons remain disabled.
|
||||
11. **Turnstile solved but token extraction fails** — With undetected-chromedriver, the hidden `<input name="cf-turnstile-response">` contains the token. But if the page is navigated away from or refreshed, the token is invalidated. Always extract the token BEFORE navigating. Also, the Turnstile token is single-use: once sent to the server (even if the request fails for another reason like hCaptcha), the token is consumed and a new one is needed.
|
||||
Reference in New Issue
Block a user