kpsdk-solver icon indicating copy to clipboard operation
kpsdk-solver copied to clipboard

Browser Automation Detected on Linux Machines

Open 0x6a69616e opened this issue 11 months ago • 7 comments

I've been notified about a persistent problem affecting several users, and I'd like to address it myself since there haven't been any related issues submitted to this repository. Correct me if I'm mistaken, but it appears that kpsdk-solver is easily being detected for browser automation on Linux-based systems.

At the moment, the easiest workaround is to run the solver on a Windows-based machine. However, despite this, I still plan on putting efforts into patching this issue in an attempt to maintain some cross-platform functionality.

0x6a69616e avatar Mar 06 '24 22:03 0x6a69616e

I have a Linux machine where Kasada appears to function smoothly. Transferring certain window.navigator entries and the user agent from it to a solver on a Windows system seems to work fine.

const context = await browser.newContext({
  userAgent: 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'
});

await context.addInitScript(() => {
  Object.entries({
    appCodeName: 'Mozilla',
    appName: 'Netscape',
    appVersion: '5.0 (X11)',
    buildID: '20181001000000',
    doNotTrack: "1",
    oscpu: 'Linux x86_64',
    platform: 'Linux x86_64',
    product: 'Gecko',
    productSub: '20100101'
  }).map(([key, value]) => Object.defineProperty(navigator, key, {
    get: () => value
  }));
});

Removing the oscpu and platform fields results in no presence of reinterrogationTimeoutDuration in the obtained SDK message, which indicates detection of an automated browser.

This is not a foolproof solution.

0x6a69616e avatar Mar 12 '24 00:03 0x6a69616e

Hi, is there a Docker image that will work stably on Linux?

niriter avatar Apr 10 '24 10:04 niriter

No, not exactly. 😅

0x6a69616e avatar Apr 11 '24 13:04 0x6a69616e

I have a Linux machine where Kasada appears to function smoothly. Transferring certain window.navigator entries and the user agent from it to a solver on a Windows system seems to work fine.

const context = await browser.newContext({
  userAgent: 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'
});

await context.addInitScript(() => {
  Object.entries({
    appCodeName: 'Mozilla',
    appName: 'Netscape',
    appVersion: '5.0 (X11)',
    buildID: '20181001000000',
    doNotTrack: "1",
    oscpu: 'Linux x86_64',
    platform: 'Linux x86_64',
    product: 'Gecko',
    productSub: '20100101'
  }).map(([key, value]) => Object.defineProperty(navigator, key, {
    get: () => value
  }));
});

Removing the oscpu and platform fields results in no presence of reinterrogationTimeoutDuration in the obtained SDK message, which indicates detection of an automated browser.

This is not a foolproof solution.

Just FYI these values are working for me inside docker with TTV, despite the image being based on Ubuntu:

const context = await browser.newContext({
            userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0'
        });

        try {
            await context.addInitScript(() => {
                Object.entries({
                    appCodeName: 'Mozilla',
                    appName: 'Netscape',
                    appVersion: "5.0 (Windows)",
                    buildID: '20181001000000',
                    doNotTrack: "1",
                    oscpu: "Windows NT 10.0; Win64; x64",
                    platform: 'Win32',
                    product: 'Gecko',
                    productSub: '20100101'
                }).map(([key, value]) => Object.defineProperty(navigator, key, {
                    get: () => value
                }));
            });

pydlv avatar May 06 '24 05:05 pydlv

hi, I requested the api through the headers I got and got a 500 error response, is this because of linux?

winterfell2021 avatar Jun 12 '24 10:06 winterfell2021

hi, I requested the api through the headers I got and got a 500 error response, is this because of linux?

It's not entirely clear whether Linux plays a major role in causing the 500 error response. Could you specify which service you're using kpsdk-solver against?

0x6a69616e avatar Jun 12 '24 13:06 0x6a69616e

hi, I requested the api through the headers I got and got a 500 error response, is this because of linux?

It's not entirely clear whether Linux plays a major role in causing the 500 error response. Could you specify which service you're using kpsdk-solver against?

I try to against vercel on debian12, code:

const playwright = require('playwright');
const Solver = require('kpsdk-solver');
const request_api = require('request');
let config = {
    kasada: [{
        domain: 'xxx',
        method: 'POST',
        path: '/api/xxx',
        protocol: 'https:'
    }],
    'sdk-script': {
        url: 'https://xxx/149e9513-01fa-4fb0-aad4-566afd725d1b/2d206a39-8ed7-437e-a3be-862e0f06eea3/p.js'
    },
    url: 'https://xxx'
}
const solver = new Solver(config);

(async () => {
    const browser = await playwright.firefox.launch({ headless: true });
    const context = await browser.newContext({
        userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0'
    });

    await context.addInitScript(() => {
        Object.entries({
            appCodeName: 'Mozilla',
            appName: 'Netscape',
            appVersion: "5.0 (Windows)",
            buildID: '20181001000000',
            doNotTrack: "1",
            oscpu: "Windows NT 10.0; Win64; x64",
            platform: 'Win32',
            product: 'Gecko',
            productSub: '20100101'
        }).map(([key, value]) => Object.defineProperty(navigator, key, {
            get: () => value
        }));
    });

    const page = await solver.create(context, page => {
        // optional, page callback; access the page instance before the solver uses it
        console.log(page.url()); // should return about:blank or smthn
    });

    // retrieve the SDK messages
    console.log(page.solver.messages); // KPSDK:DONE:...

    // make a modifiable fetch request
    var options = {
        'method': 'POST',
        'url': 'https://xxxx',
        body: JSON.stringify({
            // actual body from network
        })

    };
    const { route, request } = await page.solver.fetch('xxxx',
        options
    );
    let headers = request.headers();
    options.headers = headers;
    request_api(options, function (error, response) {
        if (error) throw new Error(error);
        console.log(response.body);
    });
    await route.abort(); // abort unless same-page client token regeneration should be used

    await page.close();
    await context.close();
    await browser.close();
})();

winterfell2021 avatar Jun 13 '24 02:06 winterfell2021