PHP’de Senkron Bir İsteğin İçinde ALTCHA Nasıl Çözülür

solve altcha in php - How to Solve ALTCHA in PHP on a Synchronous Request

PHP’de ALTCHA çözmek için tek bir çağrı yeterlidir ve tarayıcı gerekmez. ALTCHA tanımaya değil iş kanıtına dayanır: site bir challenge yayınlar ve istemcinin, onu karşılayan sayacı bulana kadar hash alması gerekir. Bakılacak bir şey yoktur, dolayısıyla işin içinde WebDriver veya headless tarayıcı bulunmaz; cevap tahmin edilmez, hesaplanır. CapSkip bu türü 1.2.6 sürümünde ekledi ve PHP paketi onu tek bir metot olarak sunuyor. PHP, dört SDK arasında hiç eşzamanlılık hikâyesi olmayan tek pakettir ve bu kılavuzu şekillendiren de budur: çağrı çalışırken isteğinizi bloke eder, bu yüzden doğru yapılması gereken şey, PHP’nin kendi sınırlarının isteği çözücü yanıtlamadan kesmemesini sağlamaktır.

Neye ihtiyacınız var

  • Bir Windows makinesinde çalışan CapSkip 1.2.6 veya üzeri. ALTCHA desteği bu sürümle geldi.
  • curl ve json eklentileriyle PHP 8.0 veya üzeri; bunlar çoğu kurulumda hazır gelir. Paketin başka çalışma zamanı bağımlılığı yoktur, dolayısıyla düz bir betiğe de Laravel’e de Symfony’ye de aynı şekilde girer.
  • Widget’ın bulunduğu sayfanın URL’si ve widget’ın challenge’ını istediği endpoint.
  • Çözücü için bir adres. Local mode yalnızca o cihaza yanıt vermek üzere 127.0.0.1 üzerinden çalışır; Server mode ise başka bir makinenin erişebilmesi için ağ adresinizi veya genel IP’nizi dinler. Hangisinin geçerli olduğunu 4. adım anlatıyor ve ikisi de şurada yer alıyor: bağlantı ayarları.
# composer require capskip/capskip
composer require capskip/capskip

1. Adım: çözme çağrısı ve challenge nereden geliyor

Tek metot, iki argüman: sayfa URL’si ve ardından challenge’ı taşıyan bir options dizisi. Endpoint’i verin, CapSkip challenge’ı kendisi çeksin.

// 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 it

Dönen dizideki iki anahtar yalnızca ALTCHA’ya aittir. token, formun istediği base64 yüktür; number ise challenge’ı çözen sayaçtır. code anahtarı token ile aynı dizeyi taşır, dolayısıyla ikisi de işe yarar; ancak token, gideceği alanın adını taşıdığı için çağrı yerinde daha iyi okunur. GeeTest anahtarları ve Turnstile user agent’ı burada bulunmaz.

number değerini loglamaya değer. Yükleri farklı olsa da her iki ALTCHA kuşağı için de raporlanır: eski bir token sayacı en üst düzeyde taşır, iş kanıtı v2 token’ı ise taşımaz ve onu bir solution nesnesinin içinde tutar. CapSkip bunu sunucunun kendi yanıtındaki solution nesnesinden okur, böylece iki kuşak da aynı şekilde raporlanır.

Widget’ın istediği endpoint’i bulun

DevTools’u açın, Network sekmesine geçin ve widget’ın bulunduğu sayfayı yeniden yükleyin. Widget, challenge’ı için genellikle içinde altcha geçen bir yola tek bir istek yapar. Aktardığınız şey o istek URL’sidir; döndürdüğü JSON ise challenge belgesidir ve onun yerine bunu da aktarabilirsiniz.

Challenge’ı adlandıran özniteliği tahmin etmeye çalışmayın, çünkü bu öznitelik widget nesilleri arasında değişti. Sayfa kaynağını okuyun.

Widget nesliChallenge’ı adlandıran öznitelik
v1 ve v2Bir endpoint için challengeurl, satır içi challenge için ayrı bir challengejson özniteliği
v3 ve sonrasıchallenge; aynı öznitelik hem bir URL hem de challenge verisini kabul eder

Üç görünüm stili, yani native, checkbox ve switch, tamamen görseldir. Aynı yükü gönderirler ve fark hiçbir zaman çözücüye ulaşmaz, dolayısıyla hangisine baktığınızı çözmeniz gerekmez. ALTCHA, öznitelikleri şurada belgeliyor: kendi entegrasyon kılavuzu.

Bunun yerine challenge belgesini vermek

Kodunuz challenge’ı zaten çektiyse belgeyi aktarın; o zaman hiç ağ isteği yapılmaz. Challenge bir endpoint’ten değil sayfaya gömülü olarak geldiğinde ya da onu çekmek betiğinizde olup çözücüde olmayan çerezleri gerektirdiğinde kullanılacak yol budur.

// 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,
    ],
]);

O seçenek, sizin için serileştirilen bir dizi alır; elinizde zaten varsa bir JSON dizesi de alabilir. Hem endpoint’i hem belgeyi göndermek serbesttir ve satır içi belge kazanır, çünkü çekmek yalnızca az önce verdiğinizi yeniden almak olurdu. Yine de iki yol yük altında farklı davranır. Süresi çoktan dolmuş satır içi bir challenge, boşuna hash alınmak yerine anında reddedilir; bir endpoint ise iş kuyrukta beklerken ilki öldüyse çözücünün taze bir challenge çekmesine izin verir.

Çözücünün kapsadığı algoritmalar

Aynı metot her iki nesli de karşılar. Legacy şema SHA-1, SHA-256, SHA-384 ve SHA-512 ile desteklenir; proof-of-work v2 ise PBKDF2 ve iteratif SHA ile desteklenir. PBKDF2, ALTCHA’nın kendisinin önerdiği varsayılandır, dolayısıyla bu kapsam yayındaki sitelerin büyük çoğunluğunu içerir.

Argon2id ve scrypt istisnadır ve denenmek yerine reddedilir: bunlardan birini kullanan bir görev, saniyenin yaklaşık üçte biri içinde ERROR_CAPTCHA_UNSOLVABLE ile geri döner ve asla yeniden denenmez. Bu bilinçli bir tercih. Bellek yoğun bir fonksiyon, yeniden denemenin düzelteceği bir şey değildir; meşgul görünmektense hemen başarısız olmak daha iyidir. ALTCHA’da bu sonuç, okunamayan bir görseli değil algoritmayı işaret eder ve bu hata kodu ayrıca şurada açıklanıyor: kendine ait bir rehber.

2. Adım: çözümü yürütme sınırınızın içinde tutun

Bu, PHP’ye özgü kısım ve kafa karıştırıcı arızayı doğuran da bu. Çağrı bloke eder. PHP’nin burada arka plan görevi yoktur ve paketteki async istemci yalnızca diğer SDK’larla eşitlik için tutulan bir takma addır, dolayısıyla çözücü çalışırken isteğiniz orada bekler. Artık birbirine karşı işleyen iki saat vardır ve bunlar çok farklı biçimlerde başarısız olur.

Sorunsuz bir ALTCHA çözümü milisaniyeler sürer, dolayısıyla normal işleyişte iki saat de önemli değildir. Önemli oldukları yer kötü yoldur: çözücü bir reCAPTCHA iş kuyruğunun ardında meşguldür ve çağrı bekler. İstemcinin ALTCHA için kendi tavanı 120 saniyelik varsayılan yoklama zaman aşımıdır ve ALTCHA, bir tarayıcı oturumu değil CPU işi olduğu için daha uzun olan reCAPTCHA’nınki yerine bunu kullanır.

Yapıcı seçeneğiVarsayılanNeyi kapsar
defaultTimeout120 saniyeALTCHA ve görsel CAPTCHA yoklaması
recaptchaTimeout300 saniyereCAPTCHA, Turnstile ve GeeTest yoklaması
pollingIntervalEn fazla 5 saniyeYoklama 0,25 saniyeden başlar ve kademeli olarak bu değere kadar çıkar

Buna karşılık PHP’nin kendi max_execution_time ayarı, bir web isteğinde varsayılan olarak 30 saniyedir; komut satırında ise sıfırdır, yani sınır yoktur. Bir çözüm sırasında devreye girip girmemesi platforma bağlıdır ve insanları şaşırtan da budur. Unix benzeri sistemlerde betiğin bir soket üzerinde beklerken geçirdiği süreyi saymaz, dolayısıyla uzun bir çözücü beklemesi ona hiç takılmadan geçebilir. Windows’ta aynı ayar gerçek zaman olarak ölçülür, dolayısıyla devreye girer. Her iki durumda da onun üstünde, bunu umursamayan başka tavanlar vardır: PHP-FPM’in request_terminate_timeout ayarı ve öndeki web sunucusunun kendi okuma zaman aşımı.

Asıl önemli fark, her biri kazandığında elinize ne geçtiğidir. Önce SDK’nın zaman aşımına ulaşılırsa bir TimeoutException alırsınız; catch bloğunuz bunu karşılar ve makul bir yanıta çevirir. Diğer ikisinden biri kazanırsa betik doğrudan öldürülür ve hiçbir catch bloğu çalışmaz. Bu ikisi de aynı şey değildir: PHP’nin kendi sınırı, isteği hata logunuza düşen ve kapanış fonksiyonlarınızı yine de çalıştıran ölümcül bir hatayla bitirir; worker’ı öldüren bir süreç yöneticisi veya web sunucusu ise hiçbir şey çalıştırmaz ve ziyaretçiye içinde işe yarar hiçbir şey olmayan bir 502 veya 504 bırakır. Bu yüzden istemcinin tavanını, isteği kesecek olan her neyse onun altında kalacak şekilde bilinçli olarak ayarlayın.

// 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
]);

Normalde milisaniyeler içinde biten bir tür için yirmi saniye cömerttir ve varsayılan 30 saniyelik web sınırının altında, isteğin geri kalanının çalışması için yer bırakır. Yürütme sınırının olmadığı komut satırında varsayılanı olduğu gibi bırakın. Bir çözüm düzenli olarak bu sayılara yaklaşıyorsa sorun zaman aşımı değildir; çözücü erişilebilir değildir ya da doludur ve tavanı yükseltmek yalnızca isteğin aynı şeyi söylemeden önce daha uzun süre asılı kalmasına yol açar.

3. adım: token’ı süresi geçmeden, hiç değiştirmeden geri gönderin

Widget, payload’ını altcha adlı bir form alanında gönderir; dolayısıyla token’ınız da oraya gider. Sessiz sedasız bozulan adım budur.

// 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, alanları sunucunun kendi HMAC’ı ile imzalanmış bir JSON belgesinin base64 hâlidir. Her değişiklik onu geçersiz kılar, bu yüzden düzenleme gibi görünen her şey gönderimi bozar: boşlukları kırpmak, çözüp yeniden kodlamak veya JSON’u anahtarlar farklı sırada olacak şekilde yeniden kurmak. Bir framework içindeki iyi niyetli girdi filtrelerine dikkat edin; giden form verisine uygulanan bir temizleyici bir karakteri seve seve söker ve elinizde artık imzasına uymayan bir yük bırakır. Bazı entegrasyonlar yükü bir form alanı yerine JSON gövde alanından okur, bu yüzden sayfanın kendi gönderiminin ne yolladığına bakın ve aynısını yapın.

Bu adımın başarısız olmasının diğer yolu zamanlamadır. Challenge pencereleri kısadır ve bazı siteler onları iki dakika içinde kapatır. Süresi dolduğunda site, cevabı tam olarak yanlış bir cevaba benzeyen çıplak bir doğrulama hatasıyla reddeder ve yanıtta ikisinden hangisinin yaşandığını söyleyen hiçbir şey bulunmaz. Bundan üç alışkanlık korur: challenge’ı uzun bir akışın başında değil, çözmeden hemen önce çekin; token’ı onu çözen aynı istekte gönderin; ve bir kişi formu doldururken asla oturumda token tutmayın.

4. adım: çözücünün nerede çalıştığı ve bunun hangi bağlantı modunu gerektirdiği

Yukarıdaki örnekler 127.0.0.1 kullanıyor, çünkü PHP ve çözücü aynı makineyi paylaştığında doğrusu budur. Kod başka bir yerde çalışır çalışmaz, örneğin bir container’da, bir web barındırmada, bir VPS’te veya bir CI runner’ında, loopback artık çözücüyü işaret etmez ve ilk çözüm bir NetworkException fırlatır.

CapSkip’i Server mode’a alın; o zaman bunun yerine ağ adresinizi veya genel IP’nizi dinler, böylece bunların hepsi aynı HTTP API üzerinden ona erişebilir. Yol internet üzerinden geçiyorsa sabit bir genel IP önerilir; yanında yalnızca beklediğiniz adreslere izin veren bir güvenlik duvarı kuralıyla. Server mode yalnızca çözücünün nerede dinlediğini değiştirir, başka hiçbir şeyi: donanım yine sizindir ve yine sayaçsızdır. Host ve portu ortamdan okuyun ki tek bir dağıtım her iki yerde de çalışsın. İstemci CAPSKIP_HOST ve CAPSKIP_PORT değerlerini kendi başına okumaz; bu yüzden, aşağıdaki tam örnekte olduğu gibi, bunları yapıcıya geçirin.

PHP nerede çalışıyorHangi bağlantı modu
CapSkip makinesinde, yerel bir geliştirme sunucusunda veya bir CLI betiğindeLocal modu. 127.0.0.1 gerçekten doğru
Aynı ağdaki başka bir makinedeServer modu, o makinenin özel adresinde
Paylaşımlı barındırmada, bir VPS’te veya bir container platformundaStatik bir genel IP ve bir güvenlik duvarı kuralı ile Server modu

Proxy’lerle ilgili ALTCHA’ya özgü bir not. Burada proxy desteklenir, ancak yalnızca challenge çekme işlemi için kullanılır. Yönlendirilecek bir tarayıcı oturumu olmadığından, iş kanıtının kendisine hiçbir etkisi yoktur.

Tam çalışan örnek

// 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';
}

Dört istisnanın hepsi ortak bir temel sınıftan türer, dolayısıyla onların yerine o sınıfı yakalamak SDK’nın üretebileceği her arızayı tek bir blokta karşılar. Yanıt farklıysa yukarıdaki gibi belirli olanları, farklı değilse temel sınıfı yakalayın.

Diğer türler aynı biçimde, yalnızca farklı bir metotla çalışır. reCAPTCHA çağrısı bir sitekey ve bir sayfa URL’si alır; Turnstile de aynı şekilde çalışır; GeeTest sayfa URL’sinin yanında bir gt değeri ve bir challenge alır; görsel çözme ise bir dosya yolu, bir URL veya base64 alır. Metotların tam listesi şurada: PHP CAPTCHA çözücü sayfası.

Turnstile, tam bir challenge sayfası olarak geldiğinde sitekey’den fazlasına ihtiyaç duyan tek türdür. Ek değerleri şurada ele alınıyor: PHP Turnstile kılavuzu.

Sık görülen hatalar ve anlamları

GördüğünüzNedenDüzeltme
Hiçbir şey loglanmadan gelen bir 502 veya 504 ve çalışmayan catch bloğuSüreç yöneticisi veya web sunucusu, SDK pes etmeden önce isteği öldürdüdefaultTimeout değerini FPM sonlandırma zaman aşımının ve öndeki web sunucusunun okuma zaman aşımının altına ayarlayın
PHP logunda azami yürütme süresine dair ölümcül bir hata ve çalışmayan catch bloğuİsteği önce PHP’nin kendi sınırı bitirdidefaultTimeout değerini max_execution_time değerinin altına ayarlayın
Beklediği saniye sayısını bildiren bir TimeoutExceptionÇözücü, SDK’nın belirlediği tavan içinde yanıt vermediÇözücünün çalıştığını ve dolu olmadığını kontrol edin. Tavanı yükseltmek aynı cevabı yalnızca geciktirir
Siteden gelen yalın bir doğrulama hatası, token ise sorunsuz görünüyorForm gönderilmeden önce challenge’ın süresi dolduÇekme, çözme ve gönderme işlemlerini aynı istekte yapın
Yaklaşık saniyenin üçte biri içinde, bir ApiException içinde ERROR_CAPTCHA_UNSOLVABLEChallenge, Argon2id veya scrypt kullanıyorYeniden denenecek bir şey yok. Bu ikisi tasarım gereği reddedilir
Çağrıda bir ValidationExceptionİki challenge seçeneğinden hiçbiri verilmedi ya da ALTCHA’nın kabul etmediği bir seçenek verildiChallenge endpoint’ini ya da challenge belgesini verin ve geri kalan her şeyi çıkarın
İlk çözümde bir NetworkExceptionCapSkip çalışmıyor ya da host ve port yanlışCapSkip’i başlatın, sonra Local mode’a mı Server mode’a mı ait olduğuna karar verin
token anahtarı dizide yokO anahtar yalnızca ALTCHA için doldurulurALTCHA metodunu çağırın. Bir ALTCHA sonucunda code anahtarı aynı dizeyi tutar
Loglarınızda çözüldüğü görünen bir token’ı form reddediyorBir şey payload’ı yeniden kodladı, kırptı veya sırasını değiştirdiDizeyi hiç dokunmadan, olduğu gibi geçirin

FAQ

PHP’de ALTCHA çözmek için tarayıcı gerekir mi?

Hayır ve onu PHP için iyi bir seçenek yapan da bu. ALTCHA bakılacak bir şey değil, bir hash problemi verir; dolayısıyla iş tamamen CPU işidir ve milisaniyeler içinde biter. Kurulacak bir WebDriver ve web sunucunuzun yanında ayakta tutulacak bir Chromium yoktur; tarayıcıya dayalı CAPTCHA türlerini PHP’den zahmetli kılan da tam olarak bu kısımdır. curl kullanan düz bir betik yeterlidir.

Paylaşımlı barındırmadaki veya bir VPS’teki PHP çözücüye erişebilir mi?

Evet. Bağlantı ayarlarından CapSkip’i Server mode’a alın ki loopback yerine bir ağ adresini dinlesin, sonra host ortam değişkenini o adrese yöneltin. Paylaşımlı barındırma, bir VPS, bir container platformu ve bir CI runner; hepsi aynı şekilde, aynı HTTP API üzerinden bağlanır. Yol internetten geçiyorsa sabit bir genel IP kullanın ve bir güvenlik duvarı kuralıyla kısıtlayın. Bunların hepsinde çözücü sizin sahip olduğunuz donanımda kalır, dolayısıyla lisans veya çözüm sayısı hakkında hiçbir şey değişmez.

PHP’de aynı anda birden fazla ALTCHA challenge’ı çözebilir miyim?

Tek bir betikten hayır. PHP istemcisi senkrondur ve paketteki async adı, ikinci bir uygulama değil, dört SDK aynı okunsun diye tutulan bir takma addır; dolayısıyla çağrılar birbiri ardına çalışır. Buradaki eşzamanlılık, birden fazla worker süreci çalıştırmak demektir ve PHP bunu genel olarak böyle yapar. Bir çözüm milisaniyelik bir hash işi olduğu için bu tür açısından nadiren önemlidir, ama etrafında toplu bir çalışma planlamadan önce bilmekte fayda var.

Web isteği sırasında mı yoksa kuyruğa alınmış bir işte mi çözmeliyim?

Gönderim aynı istekte gerçekleşiyorsa, ki olağan durum budur, istek içinde çözün; çünkü challenge penceresi kısadır ve kuyruğa alınmış bir iş hiçbir fayda sağlamadan gecikme ekler. Çevredeki iş zaten asenkronsa, örneğin çok sayıda sayfayı gezen bir kazıyıcıysa, çözümü bir worker’a taşıyın. Yapmamanız gereken şey ikisini ayırmaktır: bir istekte challenge çekip onu daha sonraki bir işte çözmek, bir siteye süresi dolmuş bir cevap verme olasılığı en yüksek düzendir.

Kısa özet

Challenge endpoint’ini widget’tan okuyun, sayfa URL’siyle birlikte tek ALTCHA metoduna aktarın ve token’ı hiç dokunmadan altcha adlı alana geri gönderin. İstemcinin varsayılan zaman aşımını, isteği önce öldürecek olan her neyse onun altına ayarlayın; çünkü yakalayabileceğiniz bir TimeoutException, süreç yöneticisinin sonlandırdığı bir istekten çok daha değerlidir. Fetch’i, çözümü ve gönderimi aynı istekte tutun; çünkü challenge penceresi iki dakika içinde kapanabilir ve süresi dolmuş bir challenge tam olarak yanlış bir cevaba benzer. PHP çözücüyle aynı makineyi paylaşmayı bıraktığı anda Server mode’a geçin.

Yeniden denemeyi nasıl tasarladığınızı değiştiren son bir nokta. Bu captcha atlatma iş kanıtını zaten sahip olduğunuz bir makinede hesapladığı için, süresi geçmiş bir challenge’ı yeniden denemek size kendi CPU’nuzdan yalnızca birkaç milisaniyeye mal olur, başka hiçbir şeye mal olmaz; bu yüzden bayat bir challenge’ı kurtarmaya çalışmak yerine yeni bir challenge çekmeyi rahatça göze alabilirsiniz.