【抛砖引玉】关于绕过cloudflare turnstile这件事

最近心血来潮,用DrissionPage写了一个绕过Cloudflare Turnstile 复选框验证的模块,参考思路:https://github.com/TheFalloutOf76/CDP-bug-MouseEvent-.screenX-.screenY-patcher。
虽然实现很丑陋,但是在本地电脑上用headless模式也能正常绕过,可是一部署到服务器上就过不去了,所以想发到L站看看有没有技术大牛能看看是怎么回事。(btw. user_agent上的浏览器内核版本要用真实的,要不然会被cf检测出来。对应到debian11上的chromium,是120.0.6099.224)

turnstile_bypass.py:

import os
import tempfile
import json
import shutil
import subprocess
from sys import platform

try:
    from DrissionPage import Chromium, ChromiumOptions
except ImportError:
    subprocess.check_call(['pip', 'install', 'DrissionPage'])

MANIFEST_CONTENT = {
    "manifest_version": 3,
    "name": "Turnstile Patcher",
    "version": "0.1",
    "content_scripts": [{
        "js": ["./script.js"],
        "matches": ["<all_urls>"],
        "run_at": "document_start",
        "all_frames": True,
        "world": "MAIN"
    }]
}

SCRIPT_CONTENT = """
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
let screenX = getRandomInt(800, 1200);
let screenY = getRandomInt(400, 600);
Object.defineProperty(MouseEvent.prototype, 'screenX', { value: screenX });
Object.defineProperty(MouseEvent.prototype, 'screenY', { value: screenY });
"""

def _create_extension() -> str:
    """创建临时扩展文件"""
    temp_dir = tempfile.mkdtemp(prefix='turnstile_extension_')
    
    try:
        manifest_path = os.path.join(temp_dir, 'manifest.json')
        with open(manifest_path, 'w', encoding='utf-8') as f:
            json.dump(MANIFEST_CONTENT, f, indent=4)
        
        script_path = os.path.join(temp_dir, 'script.js')
        with open(script_path, 'w', encoding='utf-8') as f:
            f.write(SCRIPT_CONTENT.strip())
        
        return temp_dir
        
    except Exception as e:
        _cleanup_extension(temp_dir)
        raise Exception(f"创建扩展失败: {e}")

def _cleanup_extension(path: str):
    """清理临时扩展文件"""
    try:
        if os.path.exists(path):
            shutil.rmtree(path)
    except Exception as e:
        print(f"清理临时文件失败: {e}")

def get_patched_browser(options: ChromiumOptions = None,headless = True) -> Chromium:
    """
    创建一个带有 Turnstile 绕过功能的浏览器实例
    
    Args:
        options: ChromiumOptions 对象,如果为 None 则创建默认配置
        
    Returns:
        Chromium: 返回配置好的浏览器实例
    """
    platform_id = "Windows NT 10.0; Win64; x64"
    if platform == "linux" or platform == "linux2":
        platform_id = "X11; Linux x86_64"
    elif platform == "darwin":
        platform_id = "Macintosh; Intel Mac OS X 10_15_7"
    user_agent =f"Mozilla/5.0 ({platform_id}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.6943.99 Safari/537.36"

    if options is None:
        options = ChromiumOptions().auto_port()

    if headless is True:
        options.headless(True)
        options.set_user_agent(user_agent)

    options.set_argument("--no-sandbox")
    
    if "--blink-settings=imagesEnabled=false" in options._arguments:
        raise RuntimeError("To bypass Turnstile, imagesEnabled must be True")
    if "--incognito" in options._arguments:
        raise RuntimeError("Cannot bypass Turnstile in incognito mode. Please run in normal browser mode.")
    
    try:
        extension_path = _create_extension()
        options.add_extension(extension_path)
        browser = Chromium(options)
        shutil.rmtree(extension_path)
        return browser
    
    except Exception as e:
        if 'extension_path' in locals() and os.path.exists(extension_path):
            shutil.rmtree(extension_path)
        raise e

def click_turnstile_checkbox(tab) -> bool:
    """
    等待 Turnstile 加载完成并点击
    
    Args:
        tab: 由 get_patched_browser() 得到的 Chromium 的标签页对象
        
    Returns:
        bool: 是否通过 turnstile 验证
    """
    try:
        if not tab.wait.eles_loaded("@name=cf-turnstile-response"):
            raise RuntimeError("未检测到 cloudflare turnstile 组件")
        solution = tab.ele("@name=cf-turnstile-response")
        wrapper = solution.parent()
        iframe = wrapper.shadow_root.ele("tag:iframe")
        iframe_body = iframe.ele("tag:body").shadow_root
        checkbox = iframe_body.ele("tag:input",timeout=20)
        success = iframe_body.ele("@id=success")
        checkbox.click()
        return True if tab.wait.ele_displayed(success,timeout=1) else False
        
    except Exception as e:
        print(f"Turnstile 处理失败: {e}")
        return False

test.py:

import turnstile_bypass

def main():
    browser = tunstile_bypass.get_patched_browser()
    tab = browser.get_tab()
    
    tab.get("https://turnstile.zeroclover.io/")
    print(tunstile_bypass.click_turnstile_checkbox(tab))
    
    tab.ele("@type=submit").click()
    if tab.ele("Captcha success!"):
        print("Captcha success!")
    elif tab.ele("Captcha failed!"):
        print("Captcha failed!")

    browser.quit()

if __name__ == "__main__":
    main()
7 个赞

太强了,大佬

本地能过,服务器不行,多半就是IP问题了,说明会基于IP用不同策略,导致本地测试可行的在服务器不行

ip质量不同的情况下风控力度也不一样,换个服务器试试呢

我之前也遇到了,window上正常但是Linux上就过不了。换其他的插件能过

头大zsbd

佬用的啥插件能说下吗

不是给了一个docker的实例,你按照他那个来就好了

1 个赞

佬请问用的什么插件

有没有可能还是UA的问题,我放在linux docker容器里,拼出正确的UA再点击就能通过插件过掉

就是IP的问题,我详尽测试了,如果本地运行+连接服务器上的代理,就会无限验证,如果本地运行+连接本地的Dummy代理,就不会。使用不同的网络代理端点,一个订阅里不同的服务器,也会有的可以,有的不行。

同时,如果在无限验证时手动点击,就可以成功验证。

  1. 此时插件已在运行,这说明并不是检测到插件运行而拒绝。
  2. 鼠标仅摇晃不点击,不通过,说明并不是需要检测鼠标轨迹。

目前我使用的策略是使用插件发送 “Input.dispatchMouseEvent”,同时配以 turnstilePatch,猜测有某个独特特征,使这次点击被怀疑,以至于在风控高的情况下失败。

1 个赞

感觉佬这个绕过是没问题的,风控应该是IP。但是次数频繁了还是会被发现,
有个修改代码建议。
1、CF验证完成后,会生成cookie
2、建议在脚本里面加个获取cookie的方法,
3、获取cookie后,在cookie失效前(24小时一般),用cookie+UA+IP的方式请求网站爬取数据,此时基本不会被风控(qps过高除外)
4、望佬能更新,我想白嫖脚本,第3点我测过,24小时我手动更新一次cookie,爬虫正常跑

其实我感觉非风控IP下点击能过,是Cloudflare的仁慈,一定有什么我们不知道的异常地方(大概率和点击事件/加载顺序等相关),是可以检测到非法点击的,如果有大佬可以从MouseEvent对象中发现差异端倪就好了,可惜iframe+shallowroot里的event不好抓取,我不会抓。

感谢回复

其实只要加一段代码就行了,tab对象有一个cookies()方法可以获取页面的cookie:https://www.drissionpage.cn/browser_control/get_page_info#-cookies

1 个赞