Cara Memecahkan ALTCHA di PHP di Dalam Request Sinkron

Untuk memecahkan ALTCHA di PHP Anda hanya perlu satu pemanggilan dan tanpa browser. ALTCHA adalah proof of work, bukan pengenalan gambar: situs menerbitkan sebuah challenge dan klien harus melakukan hashing sampai menemukan counter yang memenuhinya. Tidak ada yang perlu dilihat, jadi tidak ada WebDriver dan tidak ada headless browser yang terlibat, serta jawabannya dihitung, bukan ditebak. CapSkip menambahkan tipe ini pada versi 1.2.6 dan paket PHP menyediakannya sebagai satu metode tunggal. PHP adalah satu-satunya dari keempat SDK yang sama sekali tidak punya cerita konkurensi, dan itulah yang membentuk panduan ini: pemanggilannya memblokir request Anda selama berjalan, jadi yang harus dipastikan adalah batas milik PHP sendiri tidak memutus request sebelum solver menjawab.
Apa yang Anda butuhkan
- CapSkip 1.2.6 atau lebih baru yang berjalan di komputer Windows. Dukungan ALTCHA hadir pada rilis tersebut.
- PHP 8.0 atau yang lebih baru dengan ekstensi curl dan json, yang sudah tersedia di sebagian besar instalasi. Paket ini tidak punya dependensi runtime lain, jadi ia bisa langsung dipakai di skrip biasa, di Laravel, maupun di Symfony.
- URL halaman tempat widget berada, dan endpoint yang diminta widget itu untuk mengambil challenge-nya.
- Alamat untuk solver. Local mode merespons di 127.0.0.1 hanya untuk perangkat itu saja; Server mode mendengarkan di alamat jaringan atau IP publik Anda sehingga mesin lain bisa menjangkaunya. Langkah 4 membahas mana yang berlaku, dan keduanya berada di bawah pengaturan koneksi.
# composer require capskip/capskip composer require capskip/capskip
Langkah 1: pemanggilan solve, dan dari mana challenge berasal
Satu metode, dua argumen: URL halaman, lalu array options yang membawa challenge. Berikan endpoint-nya dan CapSkip akan mengambil sendiri challenge tersebut.
// composer require capskip/capskip
require 'vendor/autoload.php';
use CapSkip\CapSkip;
$solver = new CapSkip(['host' => '127.0.0.1', 'port' => 8080]);
// CapSkip fetches the challenge, then hashes until the counter fits.
$result = $solver->altcha('https://example.com/signup', [
'challenge_url' => 'https://example.com/altcha/challenge',
]);
echo $result['token']; // base64 payload for the form field
echo $result['number']; // the counter that satisfied itDua key pada array yang dikembalikan hanya dimiliki ALTCHA. token adalah payload base64 yang diminta formulir, dan number adalah counter yang memecahkan challenge tersebut. Key code membawa string yang sama dengan token, jadi keduanya sama-sama bisa dipakai, tetapi nama token sesuai dengan field tujuannya dan lebih enak dibaca di titik pemanggilan. Key GeeTest dan user agent Turnstile tidak ada di sini.
number layak dicatat di log. Nilai ini dilaporkan untuk kedua generasi ALTCHA meskipun payload keduanya berbeda: token generasi lama membawa counter di level teratas, sedangkan token proof of work v2 tidak, melainkan menyimpannya di dalam objek solution. CapSkip membacanya dari objek solution di dalam balasan milik server itu sendiri, sehingga kedua generasi dilaporkan dengan cara yang sama.
Temukan endpoint yang diminta widget
Buka DevTools, masuk ke tab Network, lalu muat ulang halaman tempat widget berada. Widget membuat satu request untuk mengambil challenge-nya, biasanya ke path yang mengandung altcha. URL request itulah yang Anda berikan, dan JSON yang dikembalikannya adalah dokumen challenge, yang bisa Anda berikan sebagai gantinya.
Jangan menebak atribut yang menamainya, karena atribut itu berubah antar generasi widget. Baca kode sumber halaman.
| Generasi widget | Atribut yang menamai challenge |
|---|---|
| v1 dan v2 | challengeurl untuk endpoint, dengan atribut challengejson terpisah untuk challenge inline |
| v3 dan setelahnya | challenge, dan atribut yang sama itu menerima URL atau data challenge |
Ketiga gaya tampilannya, native, checkbox, dan switch, murni bersifat visual. Semuanya mengirim payload yang sama dan perbedaannya tidak pernah sampai ke solver, jadi Anda tidak perlu menebak mana yang sedang Anda hadapi. ALTCHA mendokumentasikan atributnya di panduan integrasinya sendiri.
Memberikan dokumen challenge sebagai gantinya
Jika kode Anda sudah mengambil challenge, berikan dokumennya dan tidak ada request jaringan yang terjadi sama sekali. Jalur inilah yang dipakai ketika challenge datang tertanam di dalam halaman alih-alih dari sebuah endpoint, atau ketika mengambilnya membutuhkan cookie yang dimiliki skrip Anda tetapi tidak dimiliki solver.
// No fetch happens: the document is already here.
$result = $solver->altcha('https://example.com/signup', [
'challenge_json' => [
'algorithm' => 'SHA-256',
'challenge' => 'YOUR_CHALLENGE_HASH',
'salt' => 'YOUR_SALT',
'signature' => 'YOUR_SIGNATURE',
'maxnumber' => 1000000,
],
]);Option itu menerima sebuah array, yang akan diserialisasi untuk Anda, atau sebuah string JSON jika Anda sudah memilikinya. Mengirim endpoint dan dokumen sekaligus diperbolehkan dan dokumen inline yang menang, karena pengambilan hanya akan mengulang apa yang baru saja Anda berikan. Namun kedua jalur ini berperilaku berbeda saat beban tinggi. Challenge inline yang sudah kedaluwarsa langsung ditolak alih-alih di-hash sia-sia, sedangkan endpoint memungkinkan solver mengambil challenge baru jika challenge pertama sudah mati selagi job menunggu di queue.
Algoritma apa saja yang dicakup pemecah CAPTCHA
Metode yang sama menangani kedua generasi. Skema legacy dicakup dengan SHA-1, SHA-256, SHA-384 dan SHA-512, dan proof-of-work v2 dicakup dengan PBKDF2 dan SHA iteratif. PBKDF2 adalah default yang direkomendasikan ALTCHA sendiri, jadi itu mencakup sebagian besar situs yang aktif.
Argon2id dan scrypt adalah pengecualian, dan keduanya ditolak alih-alih dicoba: task yang memakai salah satunya kembali dalam sekitar sepertiga detik dengan ERROR_CAPTCHA_UNSOLVABLE dan tidak pernah dicoba ulang. Itu memang disengaja. Fungsi yang berat di memori bukan masalah yang bisa diperbaiki oleh percobaan ulang, jadi gagal seketika lebih baik daripada terlihat sibuk. Untuk ALTCHA, hasil itu menunjuk pada algoritmanya, bukan pada gambar yang tidak terbaca, dan kode error itu memiliki panduan tersendiri.
Langkah 2: jaga proses pemecahan tetap di dalam batas eksekusi Anda
Ini bagian yang khas PHP, dan inilah yang menghasilkan kegagalan membingungkan. Pemanggilannya memblokir. PHP tidak punya background task di sini dan klien async di dalam paket hanyalah alias yang dipertahankan agar setara dengan SDK lain, jadi selama solver bekerja request Anda hanya menunggu di tempat. Kini ada dua jam yang berjalan saling berlawanan, dan keduanya gagal dengan cara yang sangat berbeda.
Pemecahan ALTCHA yang mulus memakan waktu milidetik, jadi dalam operasi normal kedua jam itu tidak penting. Keduanya baru penting di jalur buruk: solver sibuk di balik antrean job reCAPTCHA, dan pemanggilannya menunggu. Batas atas milik klien untuk ALTCHA adalah polling timeout default sebesar 120 detik, dan ALTCHA memakai timeout itu alih-alih timeout reCAPTCHA yang lebih panjang karena ia adalah pekerjaan CPU dan bukan sesi browser.
| Opsi constructor | Bawaan | Apa yang dicakupnya |
|---|---|---|
| defaultTimeout | 120 detik | Polling ALTCHA dan CAPTCHA gambar |
| recaptchaTimeout | 300 detik | Polling reCAPTCHA, Turnstile dan GeeTest |
| pollingInterval | Maksimum 5 detik | Polling dimulai pada 0,25 detik dan melambat hingga nilai ini |
Di sisi lain, max_execution_time milik PHP sendiri secara default bernilai 30 detik pada request web dan nol, yang berarti tanpa batas, pada command line. Apakah ia aktif selama pemecahan tergantung pada platformnya, dan itulah yang membuat banyak orang terkecoh. Pada sistem mirip Unix, waktu yang dihabiskan skrip untuk menunggu socket tidak ikut dihitung, sehingga penantian solver yang lama bisa lolos begitu saja. Di Windows pengaturan yang sama diukur sebagai waktu nyata, jadi ia aktif. Bagaimanapun juga, ada batas lain di atasnya yang tidak peduli: PHP-FPM punya request_terminate_timeout, dan web server di depannya punya read timeout sendiri.
Perbedaan yang penting adalah apa yang Anda dapat ketika masing-masing menang. Jika timeout milik SDK tercapai lebih dulu, Anda mendapat TimeoutException, yang ditangani blok catch Anda dan diubah menjadi respons yang masuk akal. Jika salah satu dari dua batas lainnya yang menang, skripnya dimatikan begitu saja dan tidak ada blok catch yang berjalan. Keduanya pun tidak sama: batas milik PHP sendiri mengakhiri request dengan fatal error yang tercatat di error log Anda dan tetap menjalankan shutdown function Anda, sedangkan process manager atau web server yang mematikan worker tidak menjalankan apa pun dan hanya meninggalkan 502 atau 504 tanpa informasi berguna apa pun bagi pengunjung. Jadi tetapkan batas atas klien secara sengaja, di bawah apa pun yang akan memutus request itu.
// Keep the client's ceiling under whatever kills the request.
$solver = new CapSkip([
'host' => '127.0.0.1',
'port' => 8080,
'defaultTimeout' => 20, // ALTCHA and image CAPTCHA polling
]);Dua puluh detik sudah sangat longgar untuk tipe yang biasanya selesai dalam milidetik, dan itu menyisakan ruang di bawah batas web default 30 detik agar sisa request tetap bisa berjalan. Di command line, yang tidak punya batas eksekusi, biarkan nilai default apa adanya. Jika sebuah pemecahan sering mendekati salah satu angka ini, masalahnya bukan pada timeout, melainkan solver tidak terjangkau atau sudah jenuh, dan menaikkan batas atas hanya membuat request menggantung lebih lama sebelum mengatakannya.
Langkah 3: kirim token kembali tanpa perubahan, sebelum kedaluwarsa
Widget mengirim payload-nya di field form bernama altcha, jadi di situlah token Anda ditempatkan. Inilah langkah yang gagal tanpa memberi tanda.
// Send it exactly as it came back: no trimming,
// no re-encoding, no reordering.
$body = http_build_query([
'email' => '[email protected]',
'altcha' => $result['token'],
]);
$ch = curl_init('https://example.com/signup');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);token adalah base64 dari sebuah dokumen JSON yang seluruh field-nya tercakup dalam tanda tangan HMAC milik server itu sendiri. Modifikasi apa pun membuatnya tidak valid, jadi apa pun yang terlihat seperti merapikan justru akan merusak pengiriman: memangkas spasi, melakukan decode lalu encode ulang, atau menyusun ulang JSON dengan urutan key yang berbeda. Waspadai filter input yang bermaksud baik di dalam framework, sebab sanitizer yang diterapkan pada data formulir keluar akan dengan senang hati membuang satu karakter dan meninggalkan Anda dengan payload yang tidak lagi cocok dengan tanda tangannya. Sebagian integrasi membaca payload dari field body JSON alih-alih field formulir, jadi periksa apa yang dikirim oleh proses submit milik halaman itu sendiri dan tiru persis.
Cara lain langkah ini gagal adalah soal waktu. Jendela challenge itu pendek dan sebagian situs menutupnya dalam dua menit. Ketika satu challenge kedaluwarsa, situs menolak jawabannya dengan kegagalan verifikasi polos yang terlihat persis seperti jawaban salah, dan tidak ada apa pun dalam respons yang memberi tahu Anda mana dari keduanya yang terjadi. Tiga kebiasaan ini menghindarinya: ambil challenge tepat sebelum memecahkannya, bukan di awal proses yang panjang; kirim token dalam request yang sama dengan yang memecahkannya; dan jangan pernah menyimpan token di dalam session selagi seseorang mengisi formulir.
Langkah 4: di mana pemecah CAPTCHA berjalan, dan mode koneksi apa yang dibutuhkan
Contoh di atas memakai 127.0.0.1 karena itulah yang tepat ketika PHP dan solver berada di satu mesin. Begitu kodenya berjalan di tempat lain, misalnya di container, web host, VPS, atau CI runner, loopback tidak lagi menunjuk ke solver, dan pemecahan pertama akan melempar NetworkException.
Alihkan CapSkip ke Server mode dan ia akan mendengarkan di alamat jaringan atau IP publik Anda, sehingga semua tempat tadi bisa menjangkaunya lewat HTTP API yang sama. IP publik statis disarankan bila jalurnya melewati internet, disertai aturan firewall yang hanya mengizinkan alamat yang Anda harapkan. Server mode hanya mengubah di mana solver mendengarkan dan tidak lebih dari itu: perangkat kerasnya tetap milik Anda, dan tetap tanpa kuota. Baca host dan port dari environment supaya satu deployment bekerja di kedua tempat. Klien tidak membaca CAPSKIP_HOST atau CAPSKIP_PORT dengan sendirinya, jadi teruskan keduanya ke konstruktor, seperti yang dilakukan contoh lengkap di bawah.
| Tempat PHP berjalan | Mode koneksi yang mana |
|---|---|
| Di mesin CapSkip, di dev server lokal atau skrip CLI | Mode Local. 127.0.0.1 memang benar |
| Di mesin lain pada jaringan yang sama | Mode Server, di alamat privat mesin tersebut |
| Di shared hosting, VPS, atau platform container | Pakai mode Server dengan IP publik statis dan sebuah aturan firewall |
Satu catatan khusus ALTCHA soal proxy. Proxy didukung di sini, tetapi hanya dipakai untuk pengambilan challenge. Tidak ada sesi browser yang perlu dirutekan, jadi proxy tidak berpengaruh pada proof of work itu sendiri.
Contoh lengkap yang berfungsi
// composer require capskip/capskip
require 'vendor/autoload.php';
use CapSkip\CapSkip;
use CapSkip\Exceptions\ApiException;
use CapSkip\Exceptions\NetworkException;
use CapSkip\Exceptions\TimeoutException;
$solver = new CapSkip([
'host' => getenv('CAPSKIP_HOST') ?: '127.0.0.1',
'port' => (int) (getenv('CAPSKIP_PORT') ?: 8080),
'defaultTimeout' => 20,
]);
try {
// Fetch, solve and submit inside the one request.
$result = $solver->altcha('https://example.com/signup', [
'challenge_url' => 'https://example.com/altcha/challenge',
]);
$body = http_build_query([
'email' => '[email protected]',
'altcha' => $result['token'],
]);
// POST $body to the form here, while the challenge is still fresh.
echo 'solved at counter ' . $result['number'];
} catch (ApiException $e) {
// ERROR_CAPTCHA_UNSOLVABLE here means Argon2id or scrypt.
echo 'refused: ' . $e->getMessage();
} catch (TimeoutException $e) {
echo 'gave up waiting, before anything could kill the request';
} catch (NetworkException $e) {
echo 'solver unreachable: check the host and the connection mode';
}Keempat exception tersebut merupakan turunan dari satu base class yang sama, jadi menangkap base class itu saja sudah menangani setiap kegagalan yang bisa dilempar SDK dalam satu blok. Tangkap exception spesifiknya ketika responsnya berbeda, seperti di atas, dan tangkap base class-nya ketika responsnya sama.
Tipe lain berbentuk sama dengan metode yang berbeda. Pemanggilan reCAPTCHA menerima sitekey dan URL halaman, Turnstile bekerja dengan cara yang sama, GeeTest menerima nilai gt dan sebuah challenge di samping URL halaman, dan pemecahan gambar menerima path file, URL, atau base64. Daftar metode lengkapnya ada di halaman pemecah CAPTCHA PHP.
Turnstile adalah satu-satunya tipe yang butuh lebih dari sekadar sitekey ketika ia muncul sebagai halaman challenge penuh. Nilai tambahannya dibahas di panduan Turnstile PHP.
Kesalahan umum dan artinya
| Apa yang Anda lihat | Penyebab | Perbaiki |
|---|---|---|
| 502 atau 504 tanpa apa pun di log, dan tidak ada blok catch yang berjalan | Process manager atau web server mematikan request sebelum SDK menyerah | Setel defaultTimeout di bawah terminate timeout FPM dan read timeout upstream |
| Error fatal maximum-execution-time di log PHP, dan tidak ada blok catch yang berjalan | Batas milik PHP sendiri yang lebih dulu mengakhiri request | Setel defaultTimeout di bawah max_execution_time |
| TimeoutException yang menyebutkan berapa detik ia menunggu | Solver tidak menjawab di dalam batas atas milik klien | Pastikan solver berjalan dan tidak jenuh. Menaikkan batas atas hanya menunda jawaban yang sama |
| Kegagalan verifikasi biasa dari situs, dengan token yang tampak baik-baik saja | Challenge kedaluwarsa sebelum form dikirim | Ambil, pecahkan, dan kirim dalam request yang sama |
| ERROR_CAPTCHA_UNSOLVABLE di dalam ApiException, dalam sekitar sepertiga detik | Challenge memakai Argon2id atau scrypt | Tidak ada yang perlu dicoba ulang. Keduanya ditolak secara desain |
| ValidationException pada pemanggilan | Tidak ada opsi challenge yang diberikan, atau ada opsi yang diberikan tetapi tidak diterima ALTCHA | Berikan endpoint challenge atau dokumen challenge, dan buang yang lainnya |
| NetworkException pada pemecahan pertama | CapSkip tidak berjalan, atau host dan port-nya salah | Jalankan CapSkip, lalu putuskan apakah ia sebaiknya berada di Local mode atau Server mode |
| Key token tidak ada di dalam array | Key itu hanya terisi untuk ALTCHA | Panggil metode ALTCHA. Pada hasil ALTCHA, key code berisi string yang sama |
| Form menolak token yang menurut log Anda berhasil dipecahkan | Ada sesuatu yang mengodekan ulang, memangkas atau mengubah urutan payload | Teruskan string itu apa adanya, tanpa disentuh |
FAQ
Apakah memecahkan ALTCHA di PHP butuh browser?
Tidak, dan justru itu yang membuatnya cocok untuk PHP. ALTCHA memberikan soal hashing, bukan sesuatu untuk dilihat, jadi pekerjaannya murni CPU dan selesai dalam hitungan milidetik. Tidak ada WebDriver yang perlu diinstal dan tidak ada Chromium yang harus terus hidup di samping web server Anda, dan justru bagian itulah yang membuat tipe CAPTCHA berbasis browser merepotkan dari sisi PHP. Skrip biasa dengan curl sudah cukup.
Bisakah PHP di shared hosting atau VPS menjangkau solver?
Bisa. Alihkan CapSkip ke Server mode di pengaturan koneksi supaya ia mendengarkan di alamat jaringan alih-alih loopback, lalu arahkan environment variable host ke alamat itu. Shared hosting, VPS, platform container, dan CI runner semuanya terhubung dengan cara yang sama, lewat HTTP API yang sama. Pakai IP publik statis jika jalurnya melintasi internet, dan batasi dengan aturan firewall. Solver tetap berada di perangkat keras milik Anda dalam setiap kasus tersebut, jadi tidak ada yang berubah pada lisensi maupun jumlah pemecahan.
Bisakah saya memecahkan beberapa challenge ALTCHA sekaligus di PHP?
Tidak dari satu skrip. Klien PHP bersifat sinkron dan nama async di dalam paket hanyalah alias yang dipertahankan agar keempat SDK terbaca serupa, bukan implementasi kedua, jadi pemanggilan berjalan satu demi satu. Konkurensi di sini berarti menjalankan beberapa proses worker, dan memang begitulah cara PHP melakukannya secara umum. Hal ini jarang penting untuk tipe ini, karena satu pemecahan hanya butuh hashing beberapa milidetik, tetapi tetap perlu Anda ketahui sebelum merencanakan proses massal di sekitarnya.
Sebaiknya saya memecahkan saat request web atau di dalam job queue?
Pecahkan di dalam request ketika pengirimannya terjadi pada request yang sama, dan itulah kasus yang biasa, karena jendela challenge itu pendek dan job queue hanya menambah penundaan tanpa manfaat. Pindahkan ke worker ketika pekerjaan di sekitarnya memang sudah asinkron, misalnya scraper yang menelusuri banyak halaman. Yang tidak boleh Anda lakukan adalah memisahkan keduanya: mengambil challenge di satu request lalu memecahkannya di job berikutnya adalah susunan yang paling mungkin menyerahkan jawaban kedaluwarsa ke sebuah situs.
Versi singkatnya
Baca endpoint challenge dari widget, berikan ke satu metode ALTCHA bersama URL halaman, lalu kirim token kembali ke field bernama altcha tanpa menyentuhnya sedikit pun. Setel default timeout klien di bawah apa pun yang akan mematikan request lebih dulu, karena TimeoutException yang bisa Anda tangkap jauh lebih berharga daripada request yang dihentikan process manager. Jaga agar fetch, pemecahan, dan pengiriman berada dalam request yang sama, sebab jendela challenge bisa tertutup dalam dua menit dan challenge yang kedaluwarsa terlihat persis seperti jawaban salah. Alihkan ke Server mode begitu PHP tidak lagi berbagi mesin dengan solver.
- Apa itu challenge dan bagaimana tipe ini bekerja: halaman pemecah ALTCHA.
- Semua metode lain yang disediakan paket PHP: halaman solver PHP.
Satu hal terakhir yang mengubah cara Anda merancang percobaan ulang. Karena rute ini menuju bypass captcha menghitung proof of work di mesin yang sudah Anda miliki, mencoba ulang challenge yang kedaluwarsa hanya memakan beberapa milidetik CPU Anda sendiri dan tidak lebih, jadi Anda leluasa mengambil challenge baru daripada merawat challenge yang sudah basi.
