@ctrl/cloudflare
Advanced tools
Comparing version 3.1.0 to 3.1.1
@@ -1,19 +0,1 @@ | ||
/// <reference types="node" /> | ||
import { Response } from 'got'; | ||
export declare function isCloudflareChallenge(statusCode: number, headers: any, body: string): boolean; | ||
export declare function isCloudflareCaptcha(body: string): boolean; | ||
export declare function solveChallenge(body: string, domain: string): { | ||
ms: number; | ||
answer: number; | ||
}; | ||
export declare function jschlValue(body: string): string; | ||
export declare function passValue(body: string): string; | ||
export declare function sValue(body: string): string; | ||
export declare function getRValue(body: string): object; | ||
export declare function getMethod(body: string): string; | ||
/** | ||
* sets headers.accept and headers.user-agent if not exist | ||
* @param headers existing headers | ||
*/ | ||
export declare function setupHeaders(headers?: any): any; | ||
export declare function catchCloudflare<T extends Buffer | string | object>(error: any, options: any, attempts?: number): Promise<Response<T>>; | ||
export { catchCloudflare, CaptchaError, CloudflareMaxAttemptsError } from './catchCloudflare'; |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const crypto_1 = __importDefault(require("crypto")); | ||
const delay_1 = __importDefault(require("delay")); | ||
const got_1 = __importDefault(require("got")); | ||
const https_1 = __importDefault(require("https")); | ||
const vm_1 = __importDefault(require("vm")); | ||
const url_1 = require("url"); | ||
const BUG_REPORT = `\ | ||
Cloudflare may have changed their technique, or there may be a bug in the script. | ||
Please read https://github.com/typectrl/cloudflare#updates, then file a \ | ||
bug report at https://github.com/typectrl/cloudflare/issues"\ | ||
`; | ||
function isCloudflareChallenge(statusCode, headers, body) { | ||
const { server } = headers; | ||
return (statusCode === 503 && | ||
server.startsWith('cloudflare') && | ||
body.includes('jschl_vc') && | ||
body.includes('jschl_answer')); | ||
} | ||
exports.isCloudflareChallenge = isCloudflareChallenge; | ||
function isCloudflareCaptcha(body) { | ||
if (body.includes('why_captcha') || /cdn-cgi\/l\/chk_captcha/i.test(body)) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
exports.isCloudflareCaptcha = isCloudflareCaptcha; | ||
function solveChallenge(body, domain) { | ||
// regex without selecting setTimeout delay ms for reference | ||
// const timeoutReg = /setTimeout\(function\(\){\s+(var s,t,o,p,b,r,e,a,k,i,n,g,f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n/; | ||
const timeoutReg = /setTimeout\(\s*function\s*\(\){\s*(var\s+(?:[a-z]\s*,\s*)(?:[a-z]\s*,\s*)+(?:[a-z]\s*,\s*).+?\r?\n[\s\S]+?a\.value\s*=.+?)\r?\n[\s\S]+?,\s*(\d{4,5})\);\s*(?:\/\*eoc\*\/)?/; | ||
const timeout = timeoutReg.exec(body); | ||
let js = timeout && timeout.length ? timeout[1] : ''; | ||
js = js.replace(/a\.value = (.+\.toFixed\(10\);).+", r"\1/i, '$1'); | ||
js = js.replace(/(e\s=\sfunction\(s\)\s{[\s\S]*};)/g, ''); | ||
js = js.replace(/\s{3,}[a-z](?: = |\.).+/g, ''); | ||
js = js.replace(/'; \d+'/g, ''); | ||
// Strip characters that could be used to exit the string context | ||
// These characters are not currently used in Cloudflare's arithmetic snippet | ||
js = js.replace(/[\n\\']/, ''); | ||
js = js.replace('; 121', ''); | ||
if (!js.includes('toFixed')) { | ||
throw new Error(`Error parsing Cloudflare IUAM Javascript challenge. ${BUG_REPORT}`); | ||
} | ||
// get setTimeout length that cloudflare has mandated | ||
const ms = timeout && timeout.length > 1 ? Number(timeout[2]) : 6000; | ||
// 2019-03-20: Cloudflare sometimes stores part of the challenge in a div which is later | ||
// added using document.getElementById(x).innerHTML, so it is necessary to simulate that | ||
// method and value. | ||
let k = ''; | ||
let val = ''; | ||
const kReg = /k\s+=\s+'([^']+)';/g; | ||
try { | ||
// Find the id of the div in the javascript code. | ||
const kResult = kReg.exec(body); | ||
k = kResult && kResult.length ? kResult[1] : ''; | ||
const valReg = new RegExp(`<div(.*)id="${k}"(.*)>(.*)</div>`, 'g'); | ||
const valResult = valReg.exec(body); | ||
val = valResult && valResult.length ? valResult[3] : ''; | ||
} | ||
catch (_a) { | ||
// If not available, either the code has been modified again, or the old style challenge is used. | ||
// ignore errors | ||
} | ||
const dom = `var t = "${domain}";`; | ||
const a = 'var a = {};'; | ||
const o = 'var o = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";'; | ||
const e = `var e = function(s) {{ | ||
s += "==".slice(2 - (s.length & 3)); | ||
var bm, r = "", r1, r2, i = 0; | ||
for (; i < s.length;) {{ | ||
bm = o.indexOf(s.charAt(i++)) << 18 | o.indexOf(s.charAt(i++)) << 12 | (r1 = o.indexOf(s.charAt(i++))) << 6 | (r2 = o.indexOf(s.charAt(i++))); | ||
r += r1 === 64 ? g(bm >> 16 & 255) : r2 === 64 ? g(bm >> 16 & 255, bm >> 8 & 255) : g(bm >> 16 & 255, bm >> 8 & 255, bm & 255); | ||
}} | ||
return r; | ||
}};`; | ||
const g = 'var g = String.fromCharCode;'; | ||
const document = `var document= {getElementById: function(x) { return {innerHTML:"${val}"};}};`; | ||
const atob = 'var atob = function(str) {return Buffer.from(str, "base64").toString("binary");};'; | ||
const jsx = a + o + e + dom + atob + g + document + js; | ||
const str = vm_1.default.runInNewContext(jsx, { Buffer, g: String.fromCharCode }, { timeout: 5000 }); | ||
// must eval javascript - potentially very unsafe | ||
const answer = Number(str); | ||
try { | ||
return { ms, answer }; | ||
} | ||
catch (err) { | ||
throw new Error(`Error occurred during evaluation: ${err.message}`); | ||
} | ||
} | ||
exports.solveChallenge = solveChallenge; | ||
function jschlValue(body) { | ||
const lineReg = /^.*name="jschl_vc".*$/gm; | ||
const line = lineReg.exec(body); | ||
if (!line) { | ||
throw new Error('Failed to find jschl_vc'); | ||
} | ||
const valueReg = /\svalue="(\w+)"/g; | ||
const result = valueReg.exec(line[0]); | ||
if (!result || result.length === 0) { | ||
throw new Error('Failed to parse value from jschl'); | ||
} | ||
return result[1]; | ||
} | ||
exports.jschlValue = jschlValue; | ||
function passValue(body) { | ||
const lineReg = /^.*name="pass".*$/gm; | ||
const line = lineReg.exec(body); | ||
if (!line) { | ||
throw new Error('Failed to find pass field'); | ||
} | ||
const valueReg = /\svalue="(.+?)"/g; | ||
const result = valueReg.exec(line[0]); | ||
if (!result || result.length === 0) { | ||
throw new Error('Failed to parse value from pass field'); | ||
} | ||
return result[1]; | ||
} | ||
exports.passValue = passValue; | ||
function sValue(body) { | ||
const sReg = /name="s"\svalue="([^"]+)/g; | ||
const s = sReg.exec(body); | ||
return s && s.length ? s[1] : ''; | ||
} | ||
exports.sValue = sValue; | ||
function getRValue(body) { | ||
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec | ||
const match = body.match(/name="(.+?)" value="(.+?)"/); | ||
if (match) { | ||
const hiddenInputName = match[1]; | ||
return { [hiddenInputName]: match[2] }; | ||
} | ||
return {}; | ||
} | ||
exports.getRValue = getRValue; | ||
function getMethod(body) { | ||
const methodReg = /method="(.+?)"/g; | ||
const s = methodReg.exec(body); | ||
return s && s.length ? s[1] : 'GET'; | ||
} | ||
exports.getMethod = getMethod; | ||
/** | ||
* sets headers.accept and headers.user-agent if not exist | ||
* @param headers existing headers | ||
*/ | ||
function setupHeaders(headers = {}) { | ||
return { | ||
accept: headers.accept || 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', | ||
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36', | ||
'upgrade-insecure-requests': '1', | ||
'accept-language': 'en-US,en;q=0.9', | ||
connection: 'keep-alive', | ||
'Cache-Control': 'max-age=0', | ||
}; | ||
} | ||
exports.setupHeaders = setupHeaders; | ||
class CaptchaError extends Error { | ||
constructor(message) { | ||
super(message); // 'Error' breaks prototype chain here | ||
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain | ||
} | ||
} | ||
class CloudflareMaxAttemptsError extends Error { | ||
constructor(message) { | ||
super(message); // 'Error' breaks prototype chain here | ||
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain | ||
} | ||
} | ||
async function catchCloudflare(error, options, attempts = 1) { | ||
const config = { ...options }; | ||
// must have body present | ||
if (!error.response || !error.response.body) { | ||
throw error; | ||
} | ||
// must have cookiejar | ||
if (!config.cookieJar) { | ||
throw new Error('options.cookieJar required'); | ||
} | ||
const { body } = error.response; | ||
// recaptcha captcha encountered | ||
if (isCloudflareCaptcha(body)) { | ||
throw new CaptchaError('Cloudflare captcha encountered'); | ||
} | ||
// error is not cloudflare related - rethrow | ||
if (!isCloudflareChallenge(error.response.statusCode, error.response.headers, body)) { | ||
throw error; | ||
} | ||
// max attempt error | ||
if (attempts === 4) { | ||
throw new CloudflareMaxAttemptsError(`Unable to bypass cloudflare attempts: ${attempts}`); | ||
} | ||
const newHeaders = setupHeaders(config.headers); | ||
config.headers = { ...config.headers, ...newHeaders } || newHeaders; | ||
// console.log(error.options); | ||
config.headers.referer = `${error.options.url.href}${error.options.pathname || ''}`; | ||
config.headers['cache-control'] = config.headers['cache-control'] || 'private'; | ||
const retry = { | ||
statusCodes: [408, 413, 429, 500, 502, 504], | ||
limit: 0, | ||
}; | ||
config.retry = retry; | ||
// get form field values | ||
const jschlVc = jschlValue(body); | ||
const pass = passValue(body); | ||
const s = sValue(body); | ||
const rValue = getRValue(body); | ||
const method = getMethod(body); | ||
// solve js challenge | ||
const challenge = solveChallenge(body, error.options.url.hostname); | ||
// defaults to 6 seconds or ms found in html | ||
await delay_1.default(challenge.ms); | ||
// make request with answer | ||
config.prefixUrl = `${error.options.url.protocol}//${error.options.url.hostname}/`; | ||
if (config.url.startsWith(config.prefixUrl)) { | ||
config.url = config.url.replace(config.prefixUrl, ''); | ||
} | ||
const payload = { | ||
...rValue, | ||
jschl_vc: jschlVc, | ||
pass, | ||
jschl_answer: challenge.answer, | ||
}; | ||
if (method.toUpperCase() === 'GET') { | ||
config.method = 'GET'; | ||
config.url = 'cdn-cgi/l/chk_jschl'; | ||
payload.s = s; | ||
config.searchParams = new url_1.URLSearchParams(payload).toString().replace(/&/g, '&'); | ||
} | ||
else { | ||
config.method = 'POST'; | ||
const queryMatch = body.match(/id="challenge-form" action="(.+?)" method="(.+?)"/); | ||
config.searchParams = queryMatch[1].replace('/?__cf_chl_jschl_tk__', '__cf_chl_jschl_tk__').replace(/&/g, '&'); | ||
const params = new url_1.URLSearchParams(); | ||
for (const entry of Object.entries(payload)) { | ||
params.append(entry[0], entry[1]); | ||
} | ||
config.body = params.toString(); | ||
config.headers = { | ||
...config.headers, | ||
'content-type': 'application/x-www-form-urlencoded', | ||
}; | ||
config.followRedirect = true; | ||
} | ||
if (!config.agent) { | ||
config.agent = { | ||
https: new https_1.default.Agent({ | ||
ciphers: crypto_1.default.constants.defaultCipherList + ':!ECDHE+SHA:!AES128-SHA', | ||
}), | ||
}; | ||
} | ||
try { | ||
return await got_1.default(config); | ||
} | ||
catch (err) { | ||
// eslint-disable-next-line no-return-await | ||
return await catchCloudflare(err, config, attempts + 1); | ||
} | ||
} | ||
exports.catchCloudflare = catchCloudflare; | ||
var catchCloudflare_1 = require("./catchCloudflare"); | ||
exports.catchCloudflare = catchCloudflare_1.catchCloudflare; | ||
exports.CaptchaError = catchCloudflare_1.CaptchaError; | ||
exports.CloudflareMaxAttemptsError = catchCloudflare_1.CloudflareMaxAttemptsError; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@ctrl/cloudflare", | ||
"version": "3.1.0", | ||
"version": "3.1.1", | ||
"description": "Bypass Cloudflare's anti-bot page also known as I'm Under Attack Mode", | ||
@@ -59,3 +59,3 @@ "author": "Scott Cooper <scttcper@gmail.com>", | ||
"eslint-plugin-import": "2.20.2", | ||
"jest": "25.5.2", | ||
"jest": "25.5.3", | ||
"jest-junit": "10.0.0", | ||
@@ -62,0 +62,0 @@ "nock": "12.0.3", |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
37541
9
310
2