C# में HttpClient से AWS WAF कैप्चा कैसे हल करें

aws waf captcha in c# - How to Pass an AWS WAF CAPTCHA in C# With HttpClient

C# में AWS WAF कैप्चा पास करने के लिए आप संरक्षित पेज को HttpClient से लाते हैं, उसके HTTP 405 जवाब से पाँच values निकालते हैं, और उन्हें CapSkip AWS WAF सॉल्वर के AmazonWafAsync को देते हैं, फिर लौटी voucher जोड़ी को aws-waf-token cookie से बदलते हैं, जिसे आपका CookieContainer उसके बाद हर रिक्वेस्ट के साथ भेजता है। CapSkip आपके अपने Windows PC पर हल करता है, इसलिए प्रति-हल कोई शुल्क नहीं लगता और नाकाम कोशिश पर कोई अतिरिक्त खर्च नहीं होता। कोड पाँच छोटे ब्लॉक और करीब 130 लाइनों का है।

आपको क्या चाहिए

  • CapSkip 3.0.0 या बाद का, जो Windows पर इंस्टॉल हो। AWS WAF का सपोर्ट उसी रिलीज़ से शुरू होता है।
  • कम से कम .NET 8 SDK और CapSkip NuGet package का वर्ज़न 1.4.0 या बाद का। वर्ज़न 1.3.0 में AmazonWafAsync नहीं है, जो मैंने package में जाँचा। नीचे सब कुछ .NET SDK 10.0.100 पर, net10.0 और net8.0 पर CapSkip 1.5.0 के साथ चला।
  • ऐसा पेज जो आपका अपना हो या जिसे ऑटोमेट करने की आपको अनुमति हो, और जो AWS WAF CAPTCHA action वाले rule से सुरक्षित हो। C# में AWS WAF कैप्चा की यह गाइड उस चैलेंज पेज को संभालती है जो HTTP 405 से जवाब देता है। Python गाइड उस पेज को ज़्यादा विस्तार से समझाती है और विजेट वाले संस्करण का लिंक देती है।
  • सॉल्वर के लिए एक पता। Local mode में 127.0.0.1 उसी PC पर है। Server mode में दूसरी मशीन का प्रोग्राम API के ज़रिए CapSkip को कॉल कर सकता है, जैसा अंत के पास वाला सेक्शन समझाता है। दोनों के बीच आप यहाँ बदलते हैं: कनेक्शन सेटिंग्स.

यह कैसे टेस्ट किया गया: असली CapSkip 1.5.0 package के साथ, तीन mock सर्वर के सामने जो मैंने खुद लिखे थे (यानी टेस्ट के लिए बनाए नकली सर्वर)। उनमें से एक CapSkip के API दस्तावेज़ के AWS WAF सेक्शन की जाँचें लागू करता है, दूसरा संरक्षित साइट है और तीसरा token host है। टेस्ट के लिए बना एक छोटा handler awswaf.com hosts को उस token host पर भेज देता था, और वह नीचे के कोड में नहीं है। CapSkip app मेरे PC पर चल रहा था और मैंने उसे सिर्फ़ अमान्य रिक्वेस्ट भेजीं, इसलिए मैंने कोई puzzle शुरू नहीं किया और कोई असली हल नहीं देखा। कोई असली AWS WAF साइट शामिल नहीं थी। इस पोस्ट में हल का कोई समय नहीं है, और मैं यह वादा नहीं कर सकता कि कोई असली साइट इस flow से बनी cookie स्वीकार करेगी।

चरण 1: प्रोजेक्ट बनाएँ और 405 पेज पढ़ें

एक console प्रोजेक्ट बनाएँ और package जोड़ें। default template implicit usings और nullable types चालू करता है, और कोड दोनों पर निर्भर है।

dotnet new console -o aws-waf-demo
cd aws-waf-demo
dotnet add package CapSkip

संरक्षित पेज HTTP 405 और header x-amzn-waf-action के मान captcha के साथ जवाब देता है। body window.gokuProps परिभाषित करती है और awswaf.com host से challenge.js और captcha.js लोड करती है। कोड को उससे पाँच values चाहिए: gokuProps.key, iv और context, और दोनों script के पते। iv और context हर response में नए होते हैं, इसलिए पुरानी जोड़ी कभी दोबारा इस्तेमाल न करें।

Program.cs की सामग्री को चरण 1 से 4 के C# ब्लॉक से बदलें, एक के बाद एक, इसी ब्लॉक से शुरू करके। चरण 2 का छोटा snippet उनमें शामिल नहीं है। Page.Read दो regular expressions से मिलान करता है, क्योंकि object एक script tag के अंदर है, JSON फ़ाइल में नहीं। जवाब पार्स होने लायक CAPTCHA पेज न हो तो यह null लौटाता है। इसमें सादा 200, खाली body वाला 405, और ऐसा 405 पेज आता है जिसमें पाँच values में से कोई एक नहीं है।

using System.Net;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using CapSkip;

record Challenge(string SiteKey, string Iv, string Context, string ChallengeScript, string CaptchaScript);

static class Page
{
    // Read the values from an AWS WAF CAPTCHA page (HTTP 405), or return null.
    public static Challenge? Read(HttpStatusCode status, string html)
    {
        if (status != HttpStatusCode.MethodNotAllowed || !html.Contains("gokuProps")) return null;

        var block = Regex.Match(html, @"gokuProps\s*=\s*\{(.*?)\}", RegexOptions.Singleline).Groups[1].Value;
        string Field(string name) =>
            Regex.Match(block, "\"" + name + "\"\\s*:\\s*\"([^\"]+)\"").Groups[1].Value;
        string Script(string name) =>
            Regex.Match(html, "<script[^>]+src=\"([^\"]+/" + name + "\\.js)\"").Groups[1].Value;

        var found = new Challenge(Field("key"), Field("iv"), Field("context"),
                                  Script("challenge"), Script("captcha"));
        var values = new[] { found.SiteKey, found.Iv, found.Context, found.ChallengeScript, found.CaptchaScript };
        return values.Any(string.IsNullOrEmpty) ? null : found;
    }
}

दूसरा ब्लॉक HttpClient वाला हिस्सा है। AWS के दस्तावेज़ के अनुसार puzzle पेज तब शामिल होता है जब रिक्वेस्ट के साथ text/html वाला Accept header हो (देखें AWS WAF दस्तावेज़). जब तक आप न जोड़ें, HttpClient कोई Accept header नहीं भेजता, इसलिए Web.Get उसे सेट करता है। उसके बिना मेरा mock खाली 405 भेजता है। मैंने यह नहीं आज़माया कि असली AWS क्या भेजता है।

static partial class Web
{
    const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
                             "(KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36";

    // The jar lives in the handler, so a cookie you add later goes out with every request to its host.
    public static HttpClient Client(CookieContainer jar) =>
        new(new HttpClientHandler { CookieContainer = jar }) { Timeout = TimeSpan.FromSeconds(30) };

    public static async Task<(HttpStatusCode Status, string Body)> Get(HttpClient http, string url)
    {
        using var request = new HttpRequestMessage(HttpMethod.Get, url);
        request.Headers.TryAddWithoutValidation("User-Agent", UserAgent);
        request.Headers.TryAddWithoutValidation("Accept", "text/html");  // HttpClient sends none
        using var response = await http.SendAsync(request);
        return (response.StatusCode, await response.Content.ReadAsStringAsync());
    }
}

चरण 2: AmazonWafAsync से हल करें

Signature AmazonWafAsync(sitekey, url, options, cancellationToken) है। सिर्फ़ sitekey और url ज़रूरी हैं, और बिना options वाला call mock रन में चला। API दस्तावेज़ कहते हैं कि जो value आप छोड़ देते हैं वह हल के समय पेज से पढ़ ली जाती है। यह कोड फिर भी पाँचों भेजता है, क्योंकि इसने पेज अभी-अभी लाया है और चरण 3 को वैसे भी challenge.js का पता चाहिए। iv और context साथ जाते हैं। एक को दूसरे के बिना भेजने पर किसी रिक्वेस्ट से पहले ही ValidationException उठता है। यही उस script पते के लिए होता है जो awswaf.com host पर https नहीं है या जो challenge.js, captcha.js या jsapi.js पर खत्म नहीं होता। मैंने दूसरा host, एक http पता और गलत फ़ाइल का नाम आज़माया, और तीनों ठुकरा दिए गए।

नतीजा एक SolveResult है। CaptchaVoucher और ExistingToken जोड़ी रखते हैं, Code कच्चा जवाब JSON string के रूप में रखता है, और CaptchaId टास्क का नंबर रखता है। call recaptchaTimeout के default 300 सेकंड तक इंतज़ार करता है, क्योंकि puzzle का जवाब देने से पहले CapSkip टोकन बनाने के लिए एक असली ब्राउज़र खोलता है। मैंने किसी हल का समय नहीं नापा।

static class Solve
{
    // Local mode. In Server mode, use your server's address instead.
    static readonly CapSkipClient Solver = new(host: "127.0.0.1", port: 8080);

    public static Task<SolveResult> Run(Challenge c, string pageUrl, CancellationToken ct) =>
        Solver.AmazonWafAsync(c.SiteKey, pageUrl, new Dictionary<string, object?>
        {
            ["iv"] = c.Iv,
            ["context"] = c.Context,
            ["challenge_script"] = c.ChallengeScript,
            ["captcha_script"] = c.CaptchaScript,
        }, ct);
}

हल को proxy से भेजने के लिए options dictionary में proxy की एक entry जोड़ें। आपकी अपनी रिक्वेस्ट भी उसी पते से निकलनी चाहिए, इसलिए अपने handler को वही proxy दें। WebProxy user:pass@ वाली string नहीं पढ़ता, इसलिए login Credentials में जाता है। CapSkip का अपना कोड उसे इसी तरह निकालता है। नीचे की पहली पंक्ति mock रन में in.php तक proxy और proxytype के रूप में पहुँची। दूसरी Web.Client के अंदर का handler बदलती है। वह compile हुई, पर मैंने proxy से कोई रिक्वेस्ट नहीं भेजी, इसलिए उसे बिना परखा हुआ मानें।

// Snippet, not part of Program.cs. The first line is checked at in.php, the second is not tested.
["proxy"] = new Proxy("HTTP", "user:[email protected]:8080"),  // add to the options in Solve.Run
var handler = new HttpClientHandler { UseProxy = true, CookieContainer = jar, Proxy =
    new WebProxy("http://1.2.3.4:8080") { Credentials = new NetworkCredential("user", "pass") } };

चरण 3: HttpClient से voucher का exchange करें

इस flow में voucher exchange आपका कोड करता है, CapSkip नहीं, इसलिए उस चरण पर AWS को आपके HttpClient का पता दिखता है। voucher endpoint challenge.js के बगल में है: वही फ़ोल्डर, पर challenge.js की जगह voucher। Web.Exchange जोड़ी को JSON के रूप में POST करता है और जवाब का token field पढ़ता है। API दस्तावेज़ का उदाहरण text/plain;charset=UTF-8 content type और एक Origin header भेजता है। मैंने CapSkip app का अपना exchange कोड पढ़ा, जो एक Referer भी जोड़ता है और उस ब्राउज़र का User-Agent भी जिसने टोकन बनाया, और null या अमान्य existing_token पर HTTP 400 को नाकाम exchange गिनता है। Web.Exchange चारों header भेजता है। in.php का जवाब वह User-Agent नहीं लौटाता, इसलिए यह कोड एक सामान्य Chrome वाला भेजता है, और मैं परख नहीं सका कि AWS को इस फ़र्क़ से फ़र्क़ पड़ता है या नहीं। SDK के दस्तावेज़ कहते हैं कि जोड़ी मिलते ही exchange कर दें। वे कोई अवधि नहीं बताते, और मैंने कोई नापी नहीं।

C# में content type पर ध्यान चाहिए। text/plain और Encoding.UTF8 वाला StringContent उसे text/plain; charset=utf-8 लिखता है, semicolon के बाद एक space के साथ और छोटे अक्षरों में utf-8, जो मैंने नापा। इसलिए method एक ByteArrayContent बनाता है और header हाथ से सेट करता है। mock ने दस्तावेज़ में लिखी वर्तनी ठीक वैसी ही दर्ज की।

static partial class Web
{
    // Trade the voucher pair for the aws-waf-token. Your code makes this call, not CapSkip.
    public static async Task<string> Exchange(HttpClient http, SolveResult result, Challenge c, string pageUrl)
    {
        // The voucher endpoint sits beside challenge.js, same folder.
        var voucherUrl = c.ChallengeScript[..c.ChallengeScript.LastIndexOf('/')] + "/voucher";
        var origin = new Uri(pageUrl).GetLeftPart(UriPartial.Authority);
        var pair = JsonSerializer.Serialize(new
        {
            captcha_voucher = result.CaptchaVoucher,
            existing_token = result.ExistingToken,
        });

        using var request = new HttpRequestMessage(HttpMethod.Post, voucherUrl)
        {
            Content = new ByteArrayContent(Encoding.UTF8.GetBytes(pair)),
        };
        // StringContent would rewrite this header, so write it exactly.
        request.Content.Headers.TryAddWithoutValidation("Content-Type", "text/plain;charset=UTF-8");
        request.Headers.TryAddWithoutValidation("Origin", origin);
        request.Headers.TryAddWithoutValidation("Referer", origin + "/");
        request.Headers.TryAddWithoutValidation("User-Agent", UserAgent);

        using var response = await http.SendAsync(request);
        response.EnsureSuccessStatusCode();
        using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
        return doc.RootElement.GetProperty("token").GetString()!;  // the aws-waf-token cookie value
    }
}

चरण 4: पूरा काम करने वाला उदाहरण

सबसे पहले PageUrl को अपने संरक्षित पेज पर बदलें। जैसा चिपकाया गया है, वह example.com को रिक्वेस्ट करता है, जिस पर कोई CAPTCHA rule नहीं है, इसलिए प्रोग्राम को हल करने को कुछ नहीं मिलता। फिर इसे dotnet run से चलाएँ। हर कोशिश पेज को दोबारा लाती है, जिससे हर बार का अपना iv और context होता है। ApiException के संदेश में error code होता है, इसलिए catch filter सिर्फ़ ERROR_CAPTCHA_UNSOLVABLE पर दोबारा कोशिश करता है। कोई भी दूसरा code हो तो retry उसी तरह नाकाम होगी, इसलिए वह सामने आ जाता है। अगर आपने CapSkip में replacement error text बदला है, तो filter उससे नहीं मिलेगा। टोकन PageUrl के host के लिए jar में जाता है, और आख़िरी रिक्वेस्ट पेज को फिर पढ़ती है। C# में AWS WAF कैप्चा का पूरा loop यही है।

static class Program
{
    const string PageUrl = "https://www.example.com/";  // change this to your protected page

    static async Task<int> Main()
    {
        var jar = new CookieContainer();
        using var http = Web.Client(jar);
        using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10));  // a budget for all solves

        for (var attempt = 1; attempt <= 3; attempt++)
        {
            var (status, html) = await Web.Get(http, PageUrl);  // fresh page, fresh iv and context
            var challenge = Page.Read(status, html);
            if (challenge is null)
            {
                Console.WriteLine($"{(int)status} {html[..Math.Min(60, html.Length)]}");
                return status == HttpStatusCode.OK ? 0 : 1;  // 200 means there was nothing to solve
            }

            SolveResult result;
            try { result = await Solve.Run(challenge, PageUrl, cts.Token); }
            catch (ApiException ex) when (ex.Message.Contains("ERROR_CAPTCHA_UNSOLVABLE"))
            {
                Console.WriteLine($"attempt {attempt}: {ex.Message}");  // the message holds the error code
                continue;  // a fresh page draws a fresh puzzle
            }
            catch (CapSkip.TimeoutException)
            {
                Console.WriteLine("CapSkip gave no answer in time");
                return 1;
            }

            var token = await Web.Exchange(http, result, challenge, PageUrl);  // never store the pair
            jar.Add(new Uri(PageUrl), new Cookie("aws-waf-token", token));

            var (code, page) = await Web.Get(http, PageUrl);
            Console.WriteLine($"{(int)code} {page[..Math.Min(60, page.Length)]}");
            return code == HttpStatusCode.OK ? 0 : 1;
        }

        Console.WriteLine("AWS WAF CAPTCHA not solved in 3 attempts");
        return 1;
    }
}

mock रन में प्रोग्राम ने इस तरह व्यवहार किया। हर पंक्ति net10.0 और net8.0 दोनों पर एक ही आउटपुट के साथ चली।

mock सेटिंगप्रोग्राम ने क्या किया
Mock हर puzzle का जवाब देता हैएक टास्क और एक exchange, फिर 200 और असली पेज
Mock पहले दो puzzle ठुकराता हैदो attempt लाइनें, फिर तीसरे टास्क के बाद 200
Mock पहले तीन puzzle ठुकराता हैतीन attempt लाइनें, यह संदेश कि CAPTCHA हल नहीं हुआ, exit code 1, और कोई voucher रिक्वेस्ट नहीं
Web.Get से Accept पंक्ति हटाई गईmock ने खाली 405 भेजा, Page.Read ने null लौटाया, और प्रोग्राम ने 405 प्रिंट किया और 1 के साथ बाहर निकला
Mock ERROR_BAD_PARAMETERS से जवाब देता हैApiException पहली कोशिश में ही सामने आया, बिना पकड़ा हुआ, और कोई retry नहीं हुई
exchange से पहले voucher जोड़ी बदली गईmock ने 400 दिया और प्रोग्राम बिना पकड़े गए HttpRequestException के साथ खत्म हुआ

cookie साइट के CAPTCHA इम्यूनिटी टाइम तक काम करती है, यानी वह अवधि जिसमें AWS WAF हल कर चुके client को puzzle छोड़ने देता है। इस अवधि के बारे में AWS WAF इम्यूनिटी टाइम दस्तावेज़ के अनुसार default 300 सेकंड है, और साइट का मालिक इसे 60 सेकंड से तीन दिन के बीच रख सकता है। पूरे रन में एक ही HttpClient और उसका jar रखें, और जब तक 405 वापस न आए तब तक वही टोकन इस्तेमाल करें।

आम errors

आप जो देखते हैंकारणफिक्स
CapSkip.ValidationExceptionSDK इसे किसी भी रिक्वेस्ट से पहले उठाता है: context के बिना भेजा गया iv, https और awswaf.com के बाहर का script पता, ऐसा url जो http या https नहीं है, या खाली sitekey। संदेश दोषी का नाम बताता हैजिस argument का नाम आए, उसे ठीक करें
ApiException: ERROR_CAPTCHA_UNSOLVABLECapSkip उस puzzle को हल नहीं कर सका। API दस्तावेज़ के अनुसार हर नई कोशिश पर अलग puzzle मिलता हैloop नए पेज के साथ दोबारा कोशिश करता है
ApiException, किसी दूसरे code के साथERROR_BAD_PARAMETERS, ERROR_PROXY_FORMAT, ERROR_TASK_NOT_SUPPORTED (API दस्तावेज़ कहते हैं कि task type समर्थित नहीं है या CapSkip में उसकी setting बंद है) या ERROR_NO_SLOT_AVAILABLE (CapSkip में तय की गई request rate limit से ऊपर गया key, जो सिर्फ़ उन keys पर लागू होती है जिनकी limit तय है)यह बिना retry के सामने आता है। संदेश पढ़ें। rate limit के लिए कुछ सेकंड रुकें और दोबारा चलाएँ
CapSkip.TimeoutExceptionइंतज़ार में कोई जवाब नहीं मिला। यह ApiException से नहीं, CapSkipError से निकलता है, इसलिए retry filter इसे नहीं पकड़ताजाँचें कि CapSkip चल रहा है और हल कर रहा है, फिर दोबारा चलाएँ
CapSkip.NetworkExceptionCapSkip चल नहीं रहा, host या port गलत है, या आपका cancellation token चल गयाCapSkip शुरू करें, कनेक्शन सेटिंग्स जाँचें, और संदेश पढ़ें
Web.Exchange से HttpRequestExceptionEnsureSuccessStatusCode को असफल जवाब दिखा। mock में बेमेल या बदली हुई जोड़ी पर 400 मिला। मैं असली वाला दोहरा नहीं सका, और बहुत देर से exchange की गई जोड़ी भी इसी तरह नाकाम हो सकती हैतुरंत exchange करें और नया टास्क सबमिट करें
कंपाइलर error CS0104सीधा TimeoutException लिखना System और CapSkip के बीच अस्पष्ट हैCapSkip.TimeoutException लिखें
cookie के साथ भी 405टोकन की अवधि खत्म हो गई, cookie किसी दूसरे host के लिए जोड़ी गई थी, या साइट टोकन को header से पढ़ती हैफिर से हल करें, फिर देखें कि पेज की अपनी रिक्वेस्ट क्या भेजती हैं

सर्वर पर CapSkip चलाना

CapSkipClient एक host और एक port लेता है, और अगर आपने API Key Validation चालू किया है तो एक apiKey भी, इसलिए solver को कहीं और ले जाना एक पंक्ति का बदलाव है: 127.0.0.1 की जगह दूसरी मशीन का पता दें। Server mode में CapSkip आपके नेटवर्क पते या पब्लिक IP पर सुनता है, जिससे किसी दूसरे PC, VPS या hosted platform पर चल रहा .NET प्रोग्राम वही API कॉल कर सकता है। CapSkip चलाने वाली मशीन Windows होनी चाहिए, यानी PC या Windows server, और वह आपकी ही रहती है और बिना किसी प्रति-हल शुल्क के। CapSkip स्थिर पब्लिक IP सुझाता है ताकि रिमोट एक्सेस स्थिर रहे। इसके बाद port किसी भी पते से कनेक्शन स्वीकार करता है, इसलिए जिन्हें अंदर नहीं आना चाहिए उनके लिए Windows Firewall का block rule जोड़ें। हल फिर उसी मशीन पर चलता है, जबकि चरण 3 का voucher exchange वहाँ चलता है जहाँ आपका प्रोग्राम चलता है। मैंने यह नहीं आज़माया कि AWS को इस फ़र्क़ से फ़र्क़ पड़ता है या नहीं। अगर पड़ता है, तो दोनों चरणों को वही proxy दें।

FAQ

CapSkip package के साथ catch (TimeoutException) compile क्यों नहीं होता?

System और CapSkip, दोनों एक TimeoutException परिभाषित करते हैं। implicit usings चालू होने और फ़ाइल के ऊपर using CapSkip होने पर छोटा नाम अस्पष्ट हो जाता है और compiler error CS0104 पर रुक जाता है। CapSkip.TimeoutException लिखें, या CapSkipError पकड़ें, जो SDK के फेंके हर error का base class है।

voucher POST के लिए StringContent क्यों इस्तेमाल न करें?

यह content type को दोबारा लिख देता है। text/plain और Encoding.UTF8 वाला StringContent text/plain; charset=utf-8 भेजता है, जबकि API दस्तावेज़ text/plain;charset=UTF-8 दिखाते हैं। हाथ से लिखे header वाला ByteArrayContent दस्तावेज़ में लिखी वर्तनी भेजता है। मैं जाँच नहीं सका कि असली AWS दोनों को अलग मानता है या नहीं, इसलिए मैंने दस्तावेज़ वाली वर्तनी की नक़ल की।

क्या cookie दूसरे hosts या subdomains पर काम करती है?

उस तरह नहीं जिस तरह उदाहरण उसे जोड़ता है। PageUrl के पते के लिए CookieContainer में जोड़ी गई cookie host-only होती है: एक probe में वह www.example.com पर गई, api.example.com या example.com पर नहीं। अगर आपकी साइट किसी दूसरे host को कॉल करती है, तो cookie उस host के लिए भी जोड़ें। API दस्तावेज़ यह भी कहते हैं कि कुछ पेज टोकन को cookie की जगह x-aws-waf-token request header में भेजते हैं, इसलिए देखें कि पेज की अपनी रिक्वेस्ट क्या भेजती हैं।

बहुत देर लगाने वाले हल को कैसे रद्द करें?

AmazonWafAsync को एक CancellationToken दें, जैसा उदाहरण दस मिनट वाले source के साथ करता है। जो token पहले से रद्द हो, वह कुछ भी सबमिट होने से पहले call को रोक देता है, पर वह OperationCanceledException के रूप में नहीं, बल्कि संदेश A task was canceled के साथ CapSkip.NetworkException के रूप में सामने आता है। वही type यह भी बताता है कि CapSkip चल नहीं रहा, इसलिए संदेश पढ़ें।

C# में AWS WAF कैप्चा को दोबारा आज़माने में कुछ खर्च नहीं होता, क्योंकि सॉल्वर आपके अपने PC पर चलता है। इसीलिए एक लोकल कैप्चा बायपास उन साइटों के लिए ठीक बैठता है जो हर कोशिश पर नया puzzle थमाती हैं। इसी flow का Python संस्करण यहाँ है: Python गाइड। SDK की बाकी जानकारी के लिए देखें: C# कैप्चा सॉल्वर पेज.