自动绕过cloudflare Challenge

2,532 阅读3分钟

原文地址:alvis.org.cn/posts/34c6f…

1. 什么是 Cloudflare 验证码,为什么需要它?

Cloudflare验证码是一种防止自动请求的保护系统,可以防止网站资源被滥用。对于大多数真正的访问者来说,这种验证码不会造成任何不便,因为它不要求执行某些任务 - 验证通常在后台进行(否则,您最多需要做的就是点击带有 "确认您是人类 "字样的窗口) 

当您成功通过 Cloudflare 验证码时:

image.png

如果系统检测到自动请求的迹象,则可能会出现此消息:

image.png

Cloudflare 提供两种验证码:转门和挑战。

2. Turnstile 和 Challenge:区别是什么?

因为现在一些Challenge也会和Turnstile差不多,要想具体区分出来他们的话,我最常用的方法就是,打开控制台,搜索cvid

image.png

这样的基本就是Challenge验证码了。

当然,我在网上还找了其他两种方案,但是没有亲身试过。

  • 对网站的首次请求返回 403 代码:

image.png

  • id 为 challenge-form 的表单具有一个包含__cf_chl_f_tk=参数的 action 属性(不要误认为是转门验证码参数中的 action):

image.png

3. 在node上使用playwright实现解决方案(只弄Challenge验证码,Challenge比较复杂)

通过page.addInitScript方法,在页面加载前注入一段脚本。此脚本使用Proxy对象重写window.turnstilerender方法。当render方法被调用时,它会提取相关参数,如sitekeycData等,存储在window.params中,并保存回调函数window.turnstileCallback。同时,原render方法依然会被执行。

  await page.addInitScript(() => {
	console.log('xxxxxxxxxxxxxxxxxxxxx脚本注入')
	// 重写 window.turnstile 方法
	window.turnstile = new Proxy(window.turnstile || {}, {
	  get(target, prop) {
		console.log(`proxy拦截到了:${prop}`);
		if (prop === 'render') {
		  return function (...args) {
			const [elementId, options] = args;
			console.log('Turnstile Render Options:', JSON.stringify(options));
			window.params = {
			  type: "TurnstileTaskProxyless",
			  websiteKey: options.sitekey,
			  websiteURL: window.location.href,
			  data: options.cData,
			  pagedata: options.chlPageData,
			  action: options.action,
			  userAgent: navigator.userAgent
			};
			window.turnstileCallback = options.callback;
			return target.render?.apply(this, args);
		  };
		}
		return target[prop];
	  }
	});
  });

使用page.goto方法导航到指定的openUrl,并等待页面网络活动结束(waitUntil: 'networkidle'

await page.goto(openUrl, { waitUntil: 'networkidle' });

通过page.evaluate在页面上下文中执行一段 JavaScript 代码,等待window.params被设置。如果在 5 秒内window.params存在,则返回其值;否则返回null

const params = await page.evaluate(() =>
  new Promise((resolve) => {
    console.log('xxxxxxparams======');
    if (window.params) {
      resolve(window.params);
    } else {
      setTimeout(() => resolve(null), 5000);
    }
  })
);

如果成功获取到params,则构建一个包含clientKeytask信息的data对象,通过fetch向 2Captcha 的createTask接口发送 POST 请求,创建一个验证码识别任务,并等待响应解析为 JSON 格式数据。

if (params) {
  const data = {
    clientKey: key,
    task: {
      type: 'TurnstileTaskProxyless',
      websiteURL: params.websiteURL,
      websiteKey: params.websiteKey,
      data: params.data,
      action: params.action,
      pagedata: params.pagedata
    }
  };

  const createResult = await fetch('https://api.2captcha.com/createTask', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  const createTaskResult = await createResult.json();

若创建任务成功得到taskId,则定义一个递归函数getTaskResult来轮询 2Captcha 的getTaskResult接口,获取任务处理结果。如果任务状态为processing,则等待 5 秒后再次请求,直到获取到最终结果。

if (createTaskResult.taskId) {
  const getTaskResult = async (taskId) => {
    const taskResult = await fetch('https://api.2captcha.com/getTaskResult', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        clientKey: key,
        taskId
      })
    });
    const taskResponse = await taskResult.json();

    if (taskResponse.status === 'processing') {
      await new Promise(resolve => setTimeout(resolve, 5000));
      return await getTaskResult(taskId);
    }
    return taskResponse;
  };

  const taskRes = await getTaskResult(createTaskResult.taskId);

当获取到任务结果且包含token时,通过page.evaluate在页面上下文中执行window.turnstileCallback回调函数,并传入token,完成验证码的绕过流程。

if (taskRes?.solution?.token) {
  await page.evaluate((token) => {
    console.log(window, token);
    window.turnstileCallback(token);
  }, taskRes?.solution?.token);
}

通过上述步骤,代码利用 2Captcha 的服务实现了对 Cloudflare 挑战型验证码的绕过,为网络爬虫和自动化工具提供了绕过此类验证码的能力。然而,在实际应用中,需要注意遵守相关法律法规和网站的使用条款,避免非法的爬虫行为。