Investigating the Puppeteer mode of Open Bullet 2 (credential stuffing tool)

This is the third article of the series about Open Bullet 2. In the previous article, we studied the Http request mode of Open Bullet. It offers a set of features that enable attackers to make GET/POST HTTP requests, including POST login requests automatically.

In this article, we focus on the Puppeteer mode of Open Bullet 2. We create a simple bot based on it to study its fingerprint and see how it can be detected. In particular, we show that Open Bullet 2 relies on PuppeteerExtraSharp, but it can still be detected because of inconsistencies, such as device signals that are different in a web worker compared to the main JS execution context

Creating a Puppeteer-based bot in Open Bullet 2

First, we need to set the Chrome path for Puppeteer in RuriLib Settings panel, cf the screenshot below.

Then, we create a new configuration and edit it using the Stacker panel. The configuration looks as follows:

Our Puppeteer-based bot does the following:

  1. Create a browser instance using the Puppeteer function block;
  1. Navigate to https://deviceandbrowserinfo.com/are_you_a_bot;
  1. We wait 2s;
  1. Take a screenshot of the page;
  1. Get the value of window.fingerprint using the Execute JS block;
  1. Close the browser instance.

In the screenshot from the bot detection test page, we see that by default, Open Bullet 2 used with Puppeteer is properly detected as a bot.

The 2 tests that indicate the presence of a bot are:

  1. hasInconsistentGPUFeatures : this signal is equal to true whenever a recent browser that should support webGPU features doesn’t support it. Note that there might still be legitimate use cases where this could happen, even though it’s suspicious.
  1. hasInconsistentWorkerValues : this detection test verifies the values of JS signals, such as navigator.platform , navigator.userAgent , collected in a web worker, and compares them with the values collected in the main JS execution context. If the values are different, this may indicate that a bot tried to override/forge JS values.

The signals collected in the web worker look as follows:

"workerData": {
    "webGLVendor": "Google Inc. (Google)",
    "webGLRenderer": "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.99 Safari/537.36",
    "languages": [
      "en-US"
    ],
    "platform": "MacIntel",
    "hardwareConcurrency": 4,
  }

The same signals collected in the main JS execution context look as follows:

{
		"webGLVendor": "Intel Inc.",
		"webGLRenderer": "Intel Iris OpenGL Engine",
		"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.99 Safari/537.36",
	  "languages": [
	    "en-US",
	    "en"
	  ]
	  "platform": "Mac OS X",
		"hardwareConcurrency": 4,
}

We notice differences in the WebGL attributes, the languages, and the platform. In particular, we notice that the WebGL renderer is equal to "ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)" in the web worker, which is often a sign of bot traffic.

Why are the values different in the web worker?

If we use a normal Chrome instrumented with Puppeteer, we won’t notice any differences between the values collected in the main JS execution context and the values collected in the web worker.

The reason there are differences in the web worker is that under the hood, Open Bullet 2 doesn’t rely on Puppeteer, but on PuppeteerExtraSharp instead.

PuppeteerExtraSharp is a .NET port of the popular Puppeteer extra stealth library. It is a modified version of the Puppeteer library that aims to make bots more difficult to detect by forging their fingerprints. Moreover, it also provides features, such as automatic CAPTCHA solving through CAPTCHA farm integrations.

By default, PuppeteerExtraSharp applies a set of evasion techniques that are supposed to make bots less detectable. Note that I wrote “suppose” since it’s probably more counterproductive since the release of the latest/new headless Chrome.

One of these evasion techniques focuses on the WebGL renderer and vendor attributes. By default, it sets the value of the WebGL vendor to "Intel Inc." and the value of the WebGL renderer to "Intel Iris OpenGL Engine", which are the values we observed in the main JS execution context.

However, Puppeteer extra stealth (and PuppeteerExtraSharp) only overrides these values in the main JS execution context, not in web workers, which makes it easy to detect a discrepancy. Note that the library provides an option to specify the values of the WebGL vendor and renderer, but this is not exposed in Open Bullet 2.

We observe the same issue with the navigator.languages attribute, where the attribute is set to ["en-US", "en"] even though it’s equal to ["en-US"] in the main JS execution context.

What about CDP detection?

If you read my recent article about Puppeteer detection, you may have noticed a CDP (Chrome DevTools Protocol) detection test. The test looks as follows and can be used to detect Chrome/Chromium-based browsers instrumented using CDP, which is the technology used by frameworks like Puppeteer, Playwright, and Selenium.

var detected = false;
var e = new Error();
Object.defineProperty(e, 'stack', {
 get() {
     detected = true;
 }
});
console.log(e);

However, here, we notice that the CDP detection test is equal to false, which means that it wasn’t able to detect any form of CDP automation. Interestingly, if we run the default configuration of Puppeteer extra stealth in NodeJS, the CDP automation is working as expected:

As explained in my DataDome blog post, the detection test aims to detect side effects introduced by the CDP Runtime.enable command. Under the hood, Puppeteer extra sharp uses Puppeteer Sharp, a .NET port of Puppeteer. If we search for the Runtime.enable command in the Puppeteer Sharp codebase, we notice 3 occurrences:

In particular, we notice that in the CdpWebWorker.cs file, Puppeteer Sharps sends a Runtime.enable command. Based on this new information, I updated the fingerprinting script and the bot detection test code to execute the CDP detection check in a web worker. After this improvement, we detect that it properly detects CDP automation in the worker when Open Bullet 2 is used with Puppeteer, cf screenshot below.

Conclusion

In this third blog post of our series about Open Bullet 2, we analyzed the Puppeteer mode of Open Bullet 2. We created a simple bot based on it to study its fingerprint and see how it can be detected. We showed that Open Bullet 2 relies on PuppeteerExtraSharp, but it can still be detected because of inconsistencies, such as device signals that are different in a web worker compared to the main JS execution context.

In the next blog posts of this series, we will continue to investigate Open Bullet 2, in particular the features that enable attackers to make login attempts or to avoid bot detection techniques.

Other recommended articles

Investigating the Selenium Chrome mode of Open Bullet 2

Fourth article of a series about Open Bullet 2, a credential stuffing tool. We analyze the the Selenium Chrome mode to better understand how it works, its browser fingerprint, and how it can be detected.

Read more

Published on: 05-09-2024

Privacy leak: detecting anti-canvas fingerprinting browser extensions

In this article, we present 2 approaches that can be used to detect anti-canvas fingerprinting countermeasures and we discuss the potential consequences in terms of privacy for their users.

Read more

Published on: 29-06-2024

Fraud detection: how to detect if a user lied about its OS and infer its real OS?

In this article, we explain how we explain how you can detect that a user lied about the real nature of its OS by modifying its user agent. We provide different techniques that enable you to retrieve the real nature of the OS using JavaScript APIs such as WebGL and getHighEntropyValues.

Read more

Published on: 11-06-2024