So lösen Sie ALTCHA in PHP in einer synchronen Anfrage

Um ALTCHA in PHP zu lösen, brauchen Sie einen Aufruf und keinen Browser. ALTCHA ist Proof of Work und keine Erkennung: Die Website gibt eine Challenge heraus, und der Client muss so lange hashen, bis er den Zähler findet, der sie erfüllt. Es gibt nichts anzuschauen, also sind kein WebDriver und kein Headless-Browser beteiligt, und die Antwort wird berechnet und nicht geraten. CapSkip hat den Typ in Version 1.2.6 hinzugefügt, und das PHP-Paket stellt ihn als eine einzige Methode bereit. PHP ist das einzige der vier SDKs ganz ohne Nebenläufigkeit, und genau das prägt diese Anleitung: Der Aufruf blockiert Ihre Anfrage, solange er läuft, entscheidend ist also, dafür zu sorgen, dass die Grenzen von PHP selbst die Anfrage nicht abschneiden, bevor der Solver antwortet.
Was Sie brauchen
- CapSkip 1.2.6 oder neuer auf einem Windows-Rechner. Die ALTCHA-Unterstützung kam mit diesem Release.
- PHP 8.0 oder neuer mit den Erweiterungen curl und json, die in den meisten Installationen enthalten sind. Das Paket hat keine weiteren Laufzeitabhängigkeiten, es passt also gleichermaßen in ein einfaches Skript, in Laravel oder in Symfony.
- Die URL der Seite, auf der das Widget sitzt, sowie der Endpunkt, von dem das Widget seine Challenge abruft.
- Eine Adresse für den Solver. Der Local-Modus antwortet auf 127.0.0.1 nur für dieses Gerät; der Server-Modus lauscht auf Ihrer Netzwerkadresse oder öffentlichen IP, damit ein anderer Rechner ihn erreichen kann. Schritt 4 erklärt, welcher davon gilt, und beide finden Sie unter Verbindungseinstellungen.
# composer require capskip/capskip composer require capskip/capskip
Schritt 1: Der Aufruf zum Lösen und woher die Challenge kommt
Eine Methode, zwei Argumente: die Seiten-URL, dann ein Options-Array, das die Challenge enthält. Geben Sie ihr den Endpunkt, und CapSkip ruft die Challenge selbst ab.
// 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 itZwei Schlüssel des zurückgegebenen Arrays gibt es nur bei ALTCHA. token ist die base64-Payload, die das Formular erwartet, und number ist der Zähler, der die Challenge gelöst hat. Der Schlüssel code enthält dieselbe Zeichenfolge wie token, beides funktioniert also, aber token ist nach dem Feld benannt, in das es gehört, und liest sich an der Aufrufstelle besser. Die GeeTest-Schlüssel und der Turnstile-User-Agent fehlen hier.
Es lohnt sich, number zu loggen. Der Wert wird für beide ALTCHA-Generationen gemeldet, obwohl sich ihre Payloads unterscheiden: Ein Legacy-Token trägt den Zähler auf oberster Ebene, ein Proof-of-Work-v2-Token dagegen nicht, es hält ihn stattdessen in einem solution-Objekt. CapSkip liest ihn in der Antwort des Servers selbst aus dem solution-Objekt aus, sodass beide Generationen auf dieselbe Weise gemeldet werden.
Den Endpunkt finden, den das Widget abruft
Öffnen Sie die DevTools, wechseln Sie auf den Tab Network und laden Sie die Seite neu, auf der das Widget sitzt. Das Widget stellt eine Anfrage für seine Challenge, meist an einen Pfad, der altcha enthält. Diese Anfrage-URL ist das, was Sie übergeben, und das JSON, das sie zurückgibt, ist das Challenge-Dokument, das Sie stattdessen übergeben können.
Raten Sie nicht, welches Attribut sie benennt, denn das hat sich zwischen den Widget-Generationen geändert. Lesen Sie den Seitenquelltext.
| Widget-Generation | Attribut, das die Challenge benennt |
|---|---|
| v1 und v2 | challengeurl für einen Endpunkt, mit einem separaten Attribut challengejson für eine Inline-Challenge |
| v3 und neuer | challenge, und dasselbe Attribut nimmt entweder eine URL oder die Challenge-Daten |
Die drei Darstellungsvarianten native, checkbox und switch sind rein optisch. Alle senden dieselbe Payload, und der Unterschied erreicht den Solver nie, Sie müssen also nicht herausfinden, welche davon Sie vor sich haben. Für die Attribute selbst pflegt ALTCHA die eigene Integrationsanleitung.
Stattdessen das Challenge-Dokument übergeben
Wenn Ihr Code die Challenge schon abgerufen hat, übergeben Sie das Dokument, und es findet überhaupt keine Netzwerkanfrage statt. Das ist der Weg, den Sie nehmen, wenn die Challenge eingebettet in der Seite ankommt und nicht von einem Endpunkt, oder wenn ihr Abruf Cookies braucht, die Ihr Skript hat und der Solver nicht.
// 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,
],
]);Diese Option nimmt ein Array, das für Sie serialisiert wird, oder einen JSON-String, wenn Sie schon einen haben. Beides zu senden, den Endpunkt und das Dokument, ist erlaubt, und das Inline-Dokument gewinnt, weil ein Abruf nur noch einmal holen würde, was Sie gerade geliefert haben. Unter Last verhalten sich die beiden Wege allerdings unterschiedlich. Eine Inline-Challenge, die bereits abgelaufen ist, wird sofort abgelehnt, statt sinnlos gehasht zu werden, während ein Endpunkt dem Solver erlaubt, eine frische Challenge zu holen, falls die erste gestorben ist, während der Auftrag in der Warteschlange lag.
Welche Algorithmen der Solver abdeckt
Dieselbe Methode bedient beide Generationen. Das Legacy-Verfahren ist mit SHA-1, SHA-256, SHA-384 und SHA-512 abgedeckt, Proof of Work v2 mit PBKDF2 und iterativem SHA. PBKDF2 ist der Standard, den ALTCHA selbst empfiehlt, damit ist also die große Mehrheit der Live-Websites abgedeckt.
Argon2id und scrypt sind die Ausnahmen, und sie werden abgelehnt statt versucht: Ein Task, der eines von beiden nutzt, kommt nach etwa einer Drittelsekunde mit ERROR_CAPTCHA_UNSOLVABLE zurück und wird nie wiederholt. Das ist so gewollt. Eine speicherharte Funktion lässt sich durch einen erneuten Versuch nicht beheben, deshalb ist sofortiges Scheitern besser, als beschäftigt zu wirken. Bei ALTCHA deutet dieses Ergebnis auf den Algorithmus und nicht auf ein unlesbares Bild, und der Fehlercode hat einen eigenen Leitfaden.
Schritt 2: Den Lösungsvorgang innerhalb Ihres Ausführungslimits halten
Das ist der PHP-spezifische Teil, und er ist derjenige, der einen verwirrenden Fehler erzeugt. Der Aufruf blockiert. PHP hat hier keine Hintergrundaufgabe, und der async-Client im Paket ist nur ein Alias, der aus Gleichklang mit den anderen SDKs geblieben ist, während der Solver arbeitet, sitzt Ihre Anfrage also da. Jetzt laufen zwei Uhren gegeneinander, und sie scheitern sehr unterschiedlich.
Ein erfolgreicher ALTCHA-Lösungsvorgang dauert Millisekunden, im Normalbetrieb spielt also keine der beiden Uhren eine Rolle. Sie spielen auf dem schlechten Weg eine Rolle: Der Solver ist hinter einer Warteschlange von reCAPTCHA-Aufträgen beschäftigt, und der Aufruf wartet. Die Obergrenze des Clients für ALTCHA ist das Standard-Polling-Timeout von 120 Sekunden, und ALTCHA nutzt dieses und nicht das längere für reCAPTCHA, weil es CPU-Arbeit ist und keine Browser-Sitzung.
| Konstruktor-Option | Standard | Was sie abdeckt |
|---|---|---|
| defaultTimeout | 120 Sekunden | Polling für ALTCHA und Bild-Captchas |
| recaptchaTimeout | 300 Sekunden | Polling für reCAPTCHA, Turnstile und GeeTest |
| pollingInterval | Maximal 5 Sekunden | Das Polling beginnt bei 0,25 Sekunden und steigt per Backoff bis auf diesen Wert |
Dagegen liegt max_execution_time von PHP selbst bei einer Web-Anfrage standardmäßig bei 30 Sekunden und auf der Kommandozeile bei null, also ohne Limit. Ob es während eines Lösungsvorgangs greift, hängt von der Plattform ab, und genau das überrascht viele. Auf Unix-artigen Systemen zählt es die Zeit nicht mit, die das Skript an einem Socket wartet, eine lange Wartezeit auf den Solver kann also vollständig daran vorbeilaufen. Unter Windows wird dieselbe Einstellung als reale Zeit gemessen, dort greift sie also. So oder so liegen darüber weitere Obergrenzen, denen das egal ist: PHP-FPM hat request_terminate_timeout, und der Webserver davor hat ein eigenes Lese-Timeout.
Der Unterschied, auf den es ankommt, ist das, was Sie bekommen, je nachdem, welche Uhr gewinnt. Wird das Timeout des SDK zuerst erreicht, bekommen Sie eine TimeoutException, die Ihr catch-Block behandelt und in eine sinnvolle Antwort verwandelt. Gewinnt eine der beiden anderen, wird das Skript rundweg beendet, und kein catch-Block läuft. Auch diese beiden sind nicht dasselbe: Das Limit von PHP selbst beendet die Anfrage mit einem fatalen Fehler, der in Ihrem Error-Log landet und der Ihre Shutdown-Funktionen trotzdem noch ausführt, während ein Prozessmanager oder Webserver, der den Worker abwürgt, gar nichts ausführt und dem Besucher einen 502 oder 504 ohne jeden brauchbaren Inhalt hinterlässt. Setzen Sie die Obergrenze des Clients also bewusst, und zwar unter das, was die Anfrage abschneiden wird.
// 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
]);Zwanzig Sekunden sind großzügig für einen Typ, der normalerweise in Millisekunden fertig ist, und sie lassen unter einem Standard-Weblimit von 30 Sekunden Raum für den Rest der Anfrage. Auf der Kommandozeile, wo es kein Ausführungslimit gibt, lassen Sie den Standardwert unangetastet. Kommt ein Lösungsvorgang regelmäßig in die Nähe einer dieser Zahlen, ist nicht das Timeout das Problem, sondern dass der Solver nicht erreichbar oder ausgelastet ist, und die Obergrenze anzuheben lässt die Anfrage nur länger hängen, bevor sie es Ihnen sagt.
Schritt 3: Den Token unverändert zurücksenden, bevor er abläuft
Das Widget sendet seine Payload in einem Formularfeld namens altcha, dort gehört also Ihr Token hin. Das ist der Schritt, der unauffällig kaputtgeht.
// 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);Der Token ist base64 eines JSON-Dokuments, dessen Felder von der HMAC-Signatur des Servers abgedeckt werden. Jede Änderung macht ihn ungültig, alles, was nach Aufräumen aussieht, zerstört also das Absenden: Leerzeichen abschneiden, ihn dekodieren und neu kodieren oder das JSON mit den Schlüsseln in anderer Reihenfolge neu aufbauen. Achten Sie auf gut gemeinte Eingabefilter in einem Framework, denn ein Sanitizer, der auf ausgehende Formulardaten angewendet wird, schneidet bereitwillig ein Zeichen weg und hinterlässt Ihnen eine Payload, die nicht mehr zu ihrer Signatur passt. Manche Integrationen lesen die Payload aus einem JSON-Body-Feld statt aus einem Formularfeld, prüfen Sie also, was das Absenden der Seite selbst sendet, und machen Sie es genauso.
Die andere Art, wie dieser Schritt scheitert, ist das Timing. Challenge-Fenster sind kurz, und manche Websites schließen sie innerhalb von zwei Minuten. Wenn eines abläuft, weist die Website die Antwort mit einem nackten Verifizierungsfehler ab, der genauso aussieht wie eine falsche Antwort, und in der Antwort steht nichts, was Ihnen sagt, welcher der beiden Fälle eingetreten ist. Drei Gewohnheiten verhindern das: Rufen Sie die Challenge unmittelbar vor dem Lösen ab und nicht am Anfang eines langen Durchlaufs, senden Sie den Token in derselben Anfrage ab, die ihn gelöst hat, und halten Sie niemals einen Token in einer Session, während ein Mensch ein Formular ausfüllt.
Schritt 4: Wo der Solver läuft und welchen Verbindungsmodus das erfordert
Die Beispiele oben verwenden 127.0.0.1, weil das richtig ist, wenn PHP und der Solver auf demselben Rechner liegen. Sobald der Code woanders läuft, etwa in einem Container, bei einem Webhoster, auf einem VPS oder auf einem CI-Runner, zeigt Loopback nicht mehr auf den Solver, und der erste Lösungsversuch wirft eine NetworkException.
Schalten Sie CapSkip in den Server-Modus, dann lauscht er stattdessen auf Ihrer Netzwerkadresse oder öffentlichen IP, sodass jede dieser Umgebungen ihn über dieselbe HTTP-API erreichen kann. Eine statische öffentliche IP ist empfehlenswert, wenn der Weg über das Internet geht, zusammen mit einer Firewall-Regel, die nur die erwarteten Adressen zulässt. Der Server-Modus ändert nur, wo der Solver lauscht, und sonst nichts: Es bleibt Ihre Hardware, und es bleibt ohne Abrechnung pro Lösung. Lesen Sie Host und Port aus der Umgebung, damit ein Deployment an beiden Orten funktioniert. Der Client liest weder CAPSKIP_HOST noch CAPSKIP_PORT von sich aus, übergeben Sie beide Werte also an den Konstruktor, wie es das vollständige Beispiel unten tut.
| Wo PHP läuft | Welcher Verbindungsmodus |
|---|---|
| Auf dem CapSkip-Rechner, in einem lokalen Dev-Server oder einem CLI-Skript | Local-Modus. 127.0.0.1 ist hier wirklich richtig |
| Auf einem anderen Rechner im selben Netzwerk | Server-Modus, auf der privaten Adresse dieses Rechners |
| Auf Shared Hosting, einem VPS oder einer Container-Plattform | Server-Modus mit einer statischen öffentlichen IP und einer Firewallregel |
Ein ALTCHA-spezifischer Hinweis zu Proxys. Ein Proxy wird hier unterstützt, aber nur für den Abruf der Challenge verwendet. Es gibt keine Browser-Sitzung, die geroutet werden müsste, er hat also keinen Einfluss auf den Proof of Work selbst.
Vollständiges lauffähiges Beispiel
// 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';
}Alle vier Exceptions erben von einer gemeinsamen Basisklasse, wer stattdessen diese fängt, behandelt also jeden Fehler, den das SDK auslösen kann, in einem einzigen Block. Fangen Sie die spezifischen ab, wenn die Antwort sich unterscheidet, so wie oben, und die Basisklasse, wenn nicht.
Die anderen Typen haben dieselbe Form mit einer anderen Methode. Der reCAPTCHA-Aufruf nimmt einen sitekey und eine Seiten-URL, Turnstile funktioniert genauso, GeeTest nimmt neben der Seiten-URL einen gt-Wert und eine Challenge, und das Lösen von Bildern nimmt einen Dateipfad, eine URL oder base64. Für die vollständige Methodenliste siehe die PHP-Captcha-Solver-Seite.
Turnstile ist der eine Typ, der mehr als einen sitekey braucht, wenn er als vollständige Challenge-Seite ankommt. Seine zusätzlichen Werte behandelt die PHP-Turnstile-Anleitung.
Häufige Fehler und was sie bedeuten
| Was Sie sehen | Ursache | Beheben |
|---|---|---|
| Ein 502 oder 504 ohne Logeintrag, und kein catch-Block ist gelaufen | Der Prozessmanager oder der Webserver hat die Anfrage beendet, bevor das SDK aufgegeben hat | Setzen Sie defaultTimeout unter das Terminate-Timeout von FPM und das Lese-Timeout des Webservers davor |
| Ein fataler Fehler wegen der maximalen Ausführungszeit im PHP-Log, und kein catch-Block ist gelaufen | Das Limit von PHP selbst hat die Anfrage zuerst beendet | Setzen Sie defaultTimeout unter max_execution_time |
| Eine TimeoutException, die die Sekunden nennt, die sie gewartet hat | Der Solver hat nicht innerhalb der Obergrenze des Clients geantwortet | Prüfen Sie, ob der Solver läuft und nicht ausgelastet ist. Die Obergrenze anzuheben verzögert nur dieselbe Antwort |
| Ein nackter Verifizierungsfehler von der Website, bei einem Token, der in Ordnung aussieht | Die Challenge ist abgelaufen, bevor das Formular abgesendet wurde | Abrufen, lösen und absenden in derselben Anfrage |
| ERROR_CAPTCHA_UNSOLVABLE in einer ApiException, nach etwa einer Drittelsekunde | Die Challenge verwendet Argon2id oder scrypt | Nichts zu wiederholen. Diese beiden werden bewusst abgelehnt |
| Eine ValidationException beim Aufruf | Keine der beiden Challenge-Optionen wurde übergeben, oder es wurde eine Option übergeben, die ALTCHA nicht annimmt | Übergeben Sie den Challenge-Endpunkt oder das Challenge-Dokument und lassen Sie alles andere weg |
| Eine NetworkException beim ersten Lösen | CapSkip läuft nicht, oder Host und Port sind falsch | Starten Sie CapSkip und entscheiden Sie dann, ob es in den Local-Modus oder den Server-Modus gehört |
| Der Schlüssel token fehlt im Array | Dieser Schlüssel wird nur bei ALTCHA gefüllt | Rufen Sie die ALTCHA-Methode auf. Bei einem ALTCHA-Ergebnis enthält der Schlüssel code dieselbe Zeichenfolge |
| Das Formular weist einen Token ab, den Ihre Logs als gelöst ausweisen | Irgendetwas hat die Payload neu kodiert, beschnitten oder umsortiert | Geben Sie die Zeichenfolge unverändert direkt weiter |
FAQ
Braucht man zum Lösen von ALTCHA in PHP einen Browser?
Nein, und genau deshalb passt es so gut zu PHP. ALTCHA gibt ein Hashing-Problem heraus und nicht etwas zum Anschauen, die Arbeit ist also reine CPU-Arbeit und in Millisekunden fertig. Es gibt keinen WebDriver zu installieren und kein Chromium, das neben Ihrem Webserver am Leben gehalten werden muss, und genau dieser Teil macht browsergesteuerte Captcha-Typen aus PHP heraus unhandlich. Ein einfaches Skript mit curl genügt.
Kann PHP auf Shared Hosting oder einem VPS den Solver erreichen?
Ja. Schalten Sie CapSkip in den Verbindungseinstellungen in den Server-Modus, damit er auf einer Netzwerkadresse statt auf Loopback lauscht, und richten Sie dann die Host-Umgebungsvariable auf diese Adresse. Shared Hosting, ein VPS, eine Container-Plattform und ein CI-Runner verbinden sich alle auf dieselbe Weise, über dieselbe HTTP-API. Verwenden Sie eine statische öffentliche IP, wenn der Weg über das Internet führt, und beschränken Sie sie mit einer Firewall-Regel. Der Solver bleibt in jedem dieser Fälle auf Hardware, die Ihnen gehört, an der Lizenz und der Zahl der Lösungen ändert sich also nichts.
Kann ich in PHP mehrere ALTCHA-Challenges auf einmal lösen?
Nicht aus einem Skript heraus. Der PHP-Client ist synchron, und der async-Name im Paket ist ein Alias, damit sich die vier SDKs gleich lesen, und keine zweite Implementierung, die Aufrufe laufen also nacheinander. Nebenläufigkeit heißt hier, mehrere Worker-Prozesse zu betreiben, so macht PHP das generell. Für diesen Typ spielt es selten eine Rolle, denn ein Lösungsvorgang sind Millisekunden Hashing, aber es ist gut zu wissen, bevor Sie einen Massenlauf darum herum planen.
Soll ich während der Web-Anfrage lösen oder in einem Job aus der Warteschlange?
Lösen Sie in der Anfrage, wenn das Absenden in derselben Anfrage passiert, was der übliche Fall ist, denn das Challenge-Fenster ist kurz, und ein Job in der Warteschlange bringt Verzögerung ohne Nutzen. Verlagern Sie es in einen Worker, wenn die umgebende Arbeit ohnehin asynchron ist, etwa bei einem Scraper, der viele Seiten abläuft. Was Sie nicht tun dürfen, ist beides zu trennen: eine Challenge in einer Anfrage abzurufen und sie in einem späteren Job zu lösen, ist die Anordnung, die einer Website am ehesten eine abgelaufene Antwort liefert.
Die Kurzfassung
Lesen Sie den Challenge-Endpunkt vom Widget ab, übergeben Sie ihn zusammen mit der Seiten-URL an die eine ALTCHA-Methode und senden Sie den Token unverändert in das Feld namens altcha zurück. Setzen Sie das Standard-Timeout des Clients unter das, was die Anfrage zuerst beendet, denn eine TimeoutException, die Sie fangen können, ist sehr viel mehr wert als eine Anfrage, die der Prozessmanager abwürgt. Halten Sie Abruf, Lösen und Absenden in derselben Anfrage, denn das Challenge-Fenster kann sich innerhalb von zwei Minuten schließen, und eine abgelaufene Challenge sieht genauso aus wie eine falsche Antwort. Wechseln Sie in den Server-Modus, sobald PHP nicht mehr auf demselben Rechner wie der Solver liegt.
- Was die Challenge ist und wie der Typ funktioniert: die ALTCHA-Solver-Seite.
- Alle anderen Methoden, die das PHP-Paket bereitstellt: die PHP-Solver-Seite.
Noch eine letzte Sache, die verändert, wie Sie Wiederholungen gestalten. Weil dieser Weg zur Captcha-Umgehung den Proof of Work auf einem Rechner berechnet, der Ihnen bereits gehört, kostet der erneute Versuch bei einer abgelaufenen Challenge nur ein paar Millisekunden eigener CPU-Zeit und sonst nichts, Sie können sich also erlauben, eine frische Challenge zu holen, statt eine veraltete weiterzuschleppen.
