DevOps / Backend Developer
عبور از Captcha و حل خودکار آن
Captcha ها معمولا برای مسدود کردن افراد خراب کاری به کار می رود که قصد کلاهبرداری یا دستکاری داده ها را دارند با در کمپین های تبلیغاتی مختلف میلیون ها حمله انجام می دهند. دلایل زیادی وجود داره که بخواهید یک سایت رو از طریق برنامه ای که نوشتید کنترل کنید و مسلما Captcha نمیتونه جلوی شما رو بگیره.
اکنون بیش از چند سال است که با Captcha ها سر و کار دارید. آن همه خط و کلمه و عدد که راه شما را سد می کنند. به هنگام ورود ، ثبت نام یا ارسال نظر در هر جایی!
Captcha مخفف عبارت Completely Automated Public Turing tests to tell Computers and Humans Apart یا معدل فارسی ( تست های تورینگ عمومی کاملا خودکار جهت تشخیص رایانه ها و انسان ها ) است و به گونه ای طراحی شده اند تا مانند دروازه ای خاص ، انسان ها را از خود عبور دهند و جلوی ربات ها را بگیرند. البته خطوط و بی معنا و کلمات ناخوانای سنتی این روزها کمتر رایج هستند و جای خودشون رو به reCaptcha گوگل دادند. این کپچا به هنگامی که ضریب انسانیت شما را بالا در نظر بگیرد به صورت خودکار تیک سبزی نشان داده و اجازه عبور می دهد.
اگر امتیازی بالاتر از آستانه در نظر گرفته شده کسب نکنید ، کپچا شما رو به یک چالش تصویری جذاب دعوت میکنه و پازلی در اختیار شما میذاره که واقعا حل اون آزاردهنده تر از رمزگشایی کپچاهای سنتی !!!
کپچا برای انسان آزاردهنده است ، ولی اگر حداقل کارشون رو انجام بدن و ما رو با پازل های مختلف درگیر نکنن میشه اون ها رو تحمل کرد ( ایده ای که منجر به انتشار نسخه سوم reCaptcha یا Invisible Captcha شد ). اما راه ساده تر از اثبات انسان بودن ، حل خودکار کپچاها است.
اینجا است که به سراغ 2Captcha می رویم.
2Captcha چطور کار می کنه ؟
این ابزار گونه های مختلفی از کپچا را حل می کند که این روند قالبا در دو مرحله انجام می شود. ابتدا داده های لازم برای حل کپچا را ارسال کرده و یک Request ID تحویل می گیرد. در صورتی که کپچا به صورت عکس باشد این داده ها شامل رشته Base64 آن عکس است.
در مرحله بعد درخواست خودتون رو ثبت می کنید و منتظر جواب کپچا میمونید !
تا اینجای کار همه چیز ساده است. اما وقتی به سراغ Google reCaptcha میرید داستان کمی فرق داره. شما همچنان درگیر همون 2 مرحله قبل هستید فقط داده های متفاوتی رو ارسال می کنید. در این مورد باید Site Key مربوط به reCaptcha رو ارسال کنید. این مقدار به صورت پیشفرض در بدنه HTML سایت قرار می گیره.
پاسخی که از کپچا دریافت میشه یه Token که باید در کنار باقی فیلدهای فرم با شناسه g-recaptcha-response
ارسال بشه. برای مثال به تصویر زیر نگاه کنید. ( نسخه دمو reCaptcha v2 که خود گوگل منتشر کرده ). فیلد توکن با استایل display: none
در فرم قرار میگیره تا در نهایت Submit بشه. این که فیلد متنی در اختیار شما است یعنی به راحتی میتونید مقدار اون رو ویرایش کنید تا آزمایش های خودتون رو انجام بدید. این گزینه کمک بزرگی به کاهش زمان Bypass میکنه.
برای کپچاهای مبتنی بر تصویر ، نتیجه تقریبا در لحظه حاصل میشه ولی برای reCaptcha نسخه 2 چیزی حدود 15 تا 30 ثانیه طول میکشه ( به Timeout کپچا نمیرسه خیالتون راحت ? )
خودکار سازی روند Bypass
قبل از اینکه خودتون رو درگیر کپچا کنید ، باید ابزارهای خودتون رو انتخاب کنید. مسلما نمیشه درخواست ها رو دستی به سرور ارسال کرده و نتایج رو دریافت و در فرم قرار بدید. در ضمن کل روند Bypass کردن برای تست و آزمایش و رد کردن کپچاها در شبیه ساز هاست ( کارهای دیگه ای هم میشه کرد ولی بیاید خوش بینانه فکر کنیم و کلاه سفید باشیم ). ?
در اولین مرحله به یک مرورگر نیاز داریم چون که با وب سایت و برنامه تحت وب سر و کار داریم. به شخصه روزمره از Firefox استفاده می کنم و تست ها و اتوماسیون ها رو با Google Chrome انجام میدم. فایرفاکس هم جواب میده و برای اتوماسیون ، کروم بهتر عمل میکنه.
در مرحله بعد کتابخانه ای برای اتوماسیون خودمون نیاز داریم. پیشنهاد من Puppeteer. با استفاده از این کتابخانه شما حتی نیاز به نصب گوگل کروم هم ندارید چون به صورت پیشفرض به همراه خودش نسخه ای از Chromium رو نصب می کنه. البته در صورت نیاز می تونید از نسخه ی کروم که خودتون جدا نصب کردید هم استفاده کنید ، مشکلی نیست.
با دستور زیر Puppeteer رو نصب کنید :
npm i puppeteer
# or
yarn add puppeteer
با اجرای این دستور آخرین نسخه puppeteer به همراه مرورگر مربوطه نصب میشه. ( حجمی در حدود 200 مگابایت ). در صورتی که نیاز به دانلود و نصب مجزای مرورگر ندارید از متغییر PUPPETEER_SKIP_CHROMIUM_DOWNLOAD
استفاده کنید. یا خیلی ساده می تونید از puppeteer-core
استفاده کنید. این نسخه مرورگری رو به همراه خودش نصب نمیکنه و فقط فایل های اصلی کتابخانه رو در اختیار شما قرار میده.
npm i puppeteer-core
# or
yarn add puppeteer-core
برای مقایسه بهتر نسخه معمولی و Core به این آدرس مراجعه کنید.
تست و شروع به کار
بعد از اینکه مطمئن شدید همه چیز نصب شده و آماده به کاره وقتشه تا یه تست کوچیک انجام بدیم. برای مثال یک وب سایت رو باز کنیم. بریم سراغ وب سایت Dice :
const puppeteer = require("puppeteer-core");
(async () => {
let launchOptions = {
headless: false,
executablePath:
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
defaultViewport: null,
};
const browser = await puppeteer.launch(launchOptions);
const page = await browser.newPage();
await page.goto("https://www.dice.com/register");
})();
در اینجا ما سه گزینه را تنظیم می کنیم :
- ویژگی
headless
رو غیرفعال می کنیم تا مرورگر باز بشه و خودمون همه چیز رو مشاهده کنیم - با استفاده از گزینه
executablePath
می تونیم آدرس فایل اجرایی مرورگر رو به برنامه بدیم. برای مثال اینجا از نسخه ویندوز استفاده شده. - با استفاده از
defaultViewport
اندازه های پیشفرض صفحه رو کامل غیرفعال می کنیم تا مشکلی با وب سایت های مختلف نداشته باشیم. برای مثال اگه مرورگر رو Maximize باز کنید ، وب سایت به طور کامل کل صفحه رو پر نمیکنه.
تا اینجای کار که ساده بود! حالا بریم سراغ مراحل بعدی کار تا کم کم اتوماسیون خودمون رو راه بندازیم. ابتدا باید فیلد های خودمون رو در صفحه شناسایی کنیم. تمام مرورگر ها ابزار Inspect/devtools دارن. این ابزار با کلید F12 باز میشه و سپس باید المان های مختلف رو پیدا کنیم. ترکیب Ctrl+Shift+C
در ویندوز و Shift+C+⌘
در مک. اینجا در وب سایت Dice ما 5 فیلد داریم که باید پر بشه.
- First Name = #fname
- Last Name = #lname
- Email = #email
- New Password = #password
- Retype Password = #passwordConfirmation
حالا باید داده های خودمون رو در فیلدهای بالا وارد کنیم. این کار هم به سادگی انجام میشه و تابعی به اسم type
برای اون در اختیار داریم :
await page.type("#fname", "FirstName");
await page.type("#lname", "LastName");
await page.type("#email", "mail@domain.com");
await page.type("#password", "StrongP@ssw0rd#");
await page.type("#passwordConfirmation", "StrongP@ssw0rd#");
شبیه سازی کلیک روی دکمه Register هم به سادگی مراحل قبل بوده و با تابع click
انجام میشه :
await page.click("#people button[type=submit]");
حالا با اجرای کد ، ثبت نام به صورت خودکار انجام میشه ولی خب مسلما کار نمیکنه چون Captcha رو رد نکردیم !
راه اندازی 2Captcha
ابتدا در وب سایتشون ثبت نام کرده و کلید API خودتون رو دریافت کنید.
روند کار API به این صورته که داده های کپچا رو به 2Captcha ارسال می کنید و نتایج رو دریافت می کنید. ولی اینجا با reCaptcha طرف هستیم و در نتیجه باید تغییراتی را هم داشته باشیم
- به reCaptcha SiteKey نیاز داریم که اول پست توضیح دادم. این مقدار در کدهای جاوا اسکریپت قرار داره که با جستجو در devtools می تونید اون رو پیدا کنید.
- باید
method
رویuserrecaptcha
قرار داده بشه - آدرس صفحه نیز باید ارسال بشه
const formData = {
method: "userrecaptcha",
key: apiKey,
googlekey: "6LcleDIUAAAAANqkex-vX88sMHw8FXuJQ3A4JKK9",
pageurl: "https://www.dice.com/register",
json: 1,
};
const response = await request.post("http://2captcha.com/in.php", {
form: formData,
});
const requestId = JSON.parse(response).request;
بعد از اینکه درخواست خودتون رو ارسال کردید و Request ID
دریافت شد ، باید از اون برای دریافت نتیجه استفاده کنید و به چنین آدرسی درخواست بفرستید :
http://2captcha.com/res.php?key=${apiKey}&action=get&id=${reqId}
در صورتی که کپچای شما حاضر نباشد جواب CAPTCHA_NOT_READY
دریافت خواهید کرد که یعنی باید چند ثانیه دیگه مجدد تست کنید. وقتی که کپچا حاضر باشه پاسخ شما داده میشه که برای کپچا های عادی ، جواب اون و برای reCaptcha مقدار مورد نظر جهت قرار دادن در فرم برای Submit خواهد بود.
برای reCaptcha v2 این فرایند 15 الی 30 ثانیه ( شایدم بیشتر ) طول بکشه. در ادامه نمونه کد مربوطه رو مشاهده می کنید :
async function pollForRequestResults(
key,
id,
retries = 30,
interval = 1500,
delay = 15000
) {
await timeout(delay);
return poll({
taskFn: requestCaptchaResults(key, id),
interval,
retries,
});
}
function requestCaptchaResults(apiKey, requestId) {
const url = `http://2captcha.com/res.php?key=${apiKey}&action=get&id=${requestId}&json=1`;
return async function () {
return new Promise(async function (resolve, reject) {
const rawResponse = await request.get(url);
const resp = JSON.parse(rawResponse);
if (resp.status === 0) return reject(resp.request);
resolve(resp.request);
});
};
}
const timeout = (millis) =>
new Promise((resolve) => setTimeout(resolve, millis));
در اینجا طی یک بازه زمانی تعیین شده بررسی می کنیم که آیا کپچای ما حل شده یا نه. تا در سریع ترین زمان ممکن و قبل از رسیدن به Timeout فرم خودمون رو ثبت کنیم.
بعد از این که پاسخ دریافت شد ، باید اون رو در فیلد g-recaptcha-response
که بالاتر توضیح دادم قرار بدید. این کار هم که با تابع type
انجام میشه ولی به همین سادگی هم نیست چون این فیلد در حال عادی مخفی بوده و این تابع جواب نمیده. حالا دو تا راه داریم :
- فیلد رو Visible کنید و سپس از تابع type استفاده کنید ❌
- با استفاده از جاوا اسکریپت مقدار رو در فیلد قرار بدیم ✔
مورد دوم ساده تره ! و خب مسلما تابع آماده ای هم براش هست. اینجا می تونیم از تابع evaluate
استفاده کنیم :
const response = await pollForRequestResults(apiKey, requestId);
const js = `document.getElementById("g-recaptcha-response")="${response}"`;
await page.evaluate(js);
وقتی توکن به فرم اضافه شد ، همه چیز آماده است تا ثبت نام خودمون رو انجام بدیم. به همین سادگی ! فقط یکم تمیزکاری نیاز دارید.
نکته : جهت سرگرمی بیشتر و اینکه همه چیز رو مرحله به مرحله مشاهده کنید می تونید از متغییر slowMo
استفاده کنید ?
پروژه و کد کامل این پست رو در گیت هاب می تونید مشاهده کنید :
مطلبی دیگر از این انتشارات
Clubhouse و مشکلات جدیدش
مطلبی دیگر از این انتشارات
بومی سازی سیستم مانیتورینگ - بخش اول
مطلبی دیگر از این انتشارات
مانیتورینگ 200 هزار درخواست DNS در ثانیه