stealth: keep the Chrome 145 identity consistent across TLS, JS, and WebGL
Stealth mode forced a Linux navigator UA and platform but reported Windows in userAgentData.getHighEntropyValues and served Windows Direct3D11 WebGL renderers, a combination no real Linux Chrome ever produces and that cross-layer fingerprint checks flag. Present one consistent Windows Chrome 145 identity instead: the wreq emulation uses Platform::Windows, and the navigator surfaces read the UA and platform from shared constants so the HTTP User-Agent header, sec-ch-ua-platform, navigator.userAgent, and userAgentData all agree. userAgentData brands are now derived from the Chrome major version via Chromium's GREASE algorithm rather than a single hardcoded token, and a Linux WebGL renderer pool is added so a Linux UA can never report a Direct3D11 renderer.
This commit is contained in:
1 parent
dc780d77e4
commit
4309935abd
4 files changed
+74
-39
No files matched your search
@@ -287,9 +287,11 @@ impl Page {
|
|||||||
if self.stealth_client.is_some() {
|
if self.stealth_client.is_some() {
|
||||||
rt.set_stealth(true);
|
rt.set_stealth(true);
|
||||||
rt.set_user_agent(obscura_net::STEALTH_USER_AGENT);
|
rt.set_user_agent(obscura_net::STEALTH_USER_AGENT);
|
||||||
// Match platform to the Linux UA so navigator.platform and
|
rt.set_platform(
|
||||||
// userAgentData.platform are consistent with navigator.userAgent.
|
obscura_net::STEALTH_NAVIGATOR_PLATFORM,
|
||||||
rt.set_platform("Linux x86_64", "Linux", "");
|
obscura_net::STEALTH_UA_PLATFORM,
|
||||||
|
obscura_net::STEALTH_UA_PLATFORM_VERSION,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if let Ok(ua) = self.http_client.user_agent.try_read() {
|
if let Ok(ua) = self.http_client.user_agent.try_read() {
|
||||||
rt.set_user_agent(&ua);
|
rt.set_user_agent(&ua);
|
||||||
|
|||||||
Vendored
+52
-33
@@ -154,7 +154,9 @@ function _fpNoise(x, y, channel) {
|
|||||||
var _fpCache = null;
|
var _fpCache = null;
|
||||||
function _getFp() {
|
function _getFp() {
|
||||||
if (_fpCache) return _fpCache;
|
if (_fpCache) return _fpCache;
|
||||||
const isMac = (globalThis.__obscura_ua_platform || 'Windows') === 'macOS';
|
const _uaPlat = globalThis.__obscura_ua_platform || 'Windows';
|
||||||
|
const isMac = _uaPlat === 'macOS';
|
||||||
|
const isLinux = _uaPlat === 'Linux';
|
||||||
const gpuPool = isMac ? [
|
const gpuPool = isMac ? [
|
||||||
'ANGLE (Apple, ANGLE Metal Renderer: Apple M1, Unspecified Version)',
|
'ANGLE (Apple, ANGLE Metal Renderer: Apple M1, Unspecified Version)',
|
||||||
'ANGLE (Apple, ANGLE Metal Renderer: Apple M1 Pro, Unspecified Version)',
|
'ANGLE (Apple, ANGLE Metal Renderer: Apple M1 Pro, Unspecified Version)',
|
||||||
@@ -162,6 +164,14 @@ function _getFp() {
|
|||||||
'ANGLE (Apple, ANGLE Metal Renderer: Apple M2 Pro, Unspecified Version)',
|
'ANGLE (Apple, ANGLE Metal Renderer: Apple M2 Pro, Unspecified Version)',
|
||||||
'ANGLE (Apple, ANGLE Metal Renderer: Apple M3, Unspecified Version)',
|
'ANGLE (Apple, ANGLE Metal Renderer: Apple M3, Unspecified Version)',
|
||||||
'ANGLE (Intel Inc., ANGLE Metal Renderer: Intel(R) Iris(TM) Plus Graphics, Unspecified Version)',
|
'ANGLE (Intel Inc., ANGLE Metal Renderer: Intel(R) Iris(TM) Plus Graphics, Unspecified Version)',
|
||||||
|
] : isLinux ? [
|
||||||
|
'ANGLE (Intel, Mesa Intel(R) UHD Graphics 630 (CFL GT2), OpenGL 4.6)',
|
||||||
|
'ANGLE (Intel, Mesa Intel(R) Iris(R) Xe Graphics (TGL GT2), OpenGL 4.6)',
|
||||||
|
'ANGLE (Intel, Mesa Intel(R) UHD Graphics 770 (RPL-S), OpenGL 4.6)',
|
||||||
|
'ANGLE (AMD, AMD Radeon RX 580 (polaris10, LLVM 15.0.7, DRM 3.54, LLVM 15.0.7), OpenGL 4.6)',
|
||||||
|
'ANGLE (AMD, AMD Radeon RX 6700 XT (navi22, LLVM 16.0.6, DRM 3.54, LLVM 16.0.6), OpenGL 4.6)',
|
||||||
|
'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 OpenGL 4.6)',
|
||||||
|
'ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 OpenGL 4.6)',
|
||||||
] : [
|
] : [
|
||||||
'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
||||||
'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)',
|
||||||
@@ -180,6 +190,10 @@ function _getFp() {
|
|||||||
'Google Inc. (Apple)','Google Inc. (Apple)','Google Inc. (Apple)',
|
'Google Inc. (Apple)','Google Inc. (Apple)','Google Inc. (Apple)',
|
||||||
'Google Inc. (Apple)','Google Inc. (Apple)',
|
'Google Inc. (Apple)','Google Inc. (Apple)',
|
||||||
'Google Inc. (Intel Inc.)',
|
'Google Inc. (Intel Inc.)',
|
||||||
|
] : isLinux ? [
|
||||||
|
'Google Inc. (Intel)','Google Inc. (Intel)','Google Inc. (Intel)',
|
||||||
|
'Google Inc. (AMD)','Google Inc. (AMD)',
|
||||||
|
'Google Inc. (NVIDIA)','Google Inc. (NVIDIA)',
|
||||||
] : [
|
] : [
|
||||||
'Google Inc. (NVIDIA)','Google Inc. (NVIDIA)','Google Inc. (NVIDIA)',
|
'Google Inc. (NVIDIA)','Google Inc. (NVIDIA)','Google Inc. (NVIDIA)',
|
||||||
'Google Inc. (Intel)','Google Inc. (Intel)',
|
'Google Inc. (Intel)','Google Inc. (Intel)',
|
||||||
@@ -2612,8 +2626,35 @@ globalThis.NetworkInformation = NetworkInformation;
|
|||||||
|
|
||||||
globalThis.ContentIndex = class ContentIndex {};
|
globalThis.ContentIndex = class ContentIndex {};
|
||||||
|
|
||||||
|
function _chromeMajor() {
|
||||||
|
var m = (globalThis.__obscura_ua || '').match(/Chrome\/(\d+)/);
|
||||||
|
return m ? (m[1] | 0) : 145;
|
||||||
|
}
|
||||||
|
// Chromium derives the sec-ch-ua GREASE brand, version, and brand order
|
||||||
|
// deterministically from the Chrome major version
|
||||||
|
// (components/embedder_support/user_agent_utils.cc). Replicating it keeps
|
||||||
|
// sec-ch-ua and userAgentData exact for every profile version rather than
|
||||||
|
// hardcoding one static token.
|
||||||
|
var _GREASE_CHARS = [' ', '(', ':', '-', '.', '/', ')', ';', '=', '?', '_'];
|
||||||
|
var _GREASE_VER = ['8', '99', '24'];
|
||||||
|
var _BRAND_PERMS = [[0,1,2],[0,2,1],[1,0,2],[1,2,0],[2,0,1],[2,1,0]];
|
||||||
|
function _uaBrands() {
|
||||||
|
var seed = _chromeMajor();
|
||||||
|
var grease = {
|
||||||
|
brand: 'Not' + _GREASE_CHARS[seed % 11] + 'A' + _GREASE_CHARS[(seed + 1) % 11] + 'Brand',
|
||||||
|
version: _GREASE_VER[seed % 3],
|
||||||
|
};
|
||||||
|
var ordered = [
|
||||||
|
grease,
|
||||||
|
{brand: 'Chromium', version: String(seed)},
|
||||||
|
{brand: 'Google Chrome', version: String(seed)},
|
||||||
|
];
|
||||||
|
var p = _BRAND_PERMS[seed % 6];
|
||||||
|
return [ordered[p[0]], ordered[p[1]], ordered[p[2]]];
|
||||||
|
}
|
||||||
|
|
||||||
globalThis.navigator = {
|
globalThis.navigator = {
|
||||||
get userAgent() { return globalThis.__obscura_ua || "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"; },
|
get userAgent() { return globalThis.__obscura_ua || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"; },
|
||||||
get appVersion() { return this.userAgent.replace('Mozilla/', ''); },
|
get appVersion() { return this.userAgent.replace('Mozilla/', ''); },
|
||||||
language: "en-US", languages: ["en-US","en"], get platform() { return globalThis.__obscura_platform || "Win32"; },
|
language: "en-US", languages: ["en-US","en"], get platform() { return globalThis.__obscura_platform || "Win32"; },
|
||||||
onLine: true, cookieEnabled: true, hardwareConcurrency: 8,
|
onLine: true, cookieEnabled: true, hardwareConcurrency: 8,
|
||||||
@@ -2643,24 +2684,22 @@ globalThis.navigator = {
|
|||||||
return m;
|
return m;
|
||||||
},
|
},
|
||||||
userAgentData: {
|
userAgentData: {
|
||||||
brands: [
|
|
||||||
{brand: "Google Chrome", version: "145"},
|
|
||||||
{brand: "Chromium", version: "145"},
|
|
||||||
{brand: "Not;A=Brand", version: "24"},
|
|
||||||
],
|
|
||||||
mobile: false,
|
mobile: false,
|
||||||
|
get brands() { return _uaBrands(); },
|
||||||
get platform() { return globalThis.__obscura_ua_platform || "Windows"; },
|
get platform() { return globalThis.__obscura_ua_platform || "Windows"; },
|
||||||
getHighEntropyValues(hints) {
|
getHighEntropyValues(hints) {
|
||||||
|
var brands = _uaBrands();
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
architecture: "x86",
|
architecture: "x86",
|
||||||
bitness: "64",
|
bitness: "64",
|
||||||
brands: [{brand:"Google Chrome",version:"145"},{brand:"Chromium",version:"145"},{brand:"Not;A=Brand",version:"24"}],
|
brands: brands,
|
||||||
fullVersionList: [{brand:"Google Chrome",version:"145.0.0.0"},{brand:"Chromium",version:"145.0.0.0"},{brand:"Not;A=Brand",version:"24.0.0.0"}],
|
fullVersionList: brands.map(function(b) { return {brand: b.brand, version: b.version + ".0.0.0"}; }),
|
||||||
mobile: false,
|
mobile: false,
|
||||||
model: "",
|
model: "",
|
||||||
platform: globalThis.__obscura_ua_platform || "Windows",
|
platform: globalThis.__obscura_ua_platform || "Windows",
|
||||||
platformVersion: globalThis.__obscura_ua_platform_version || "15.0.0",
|
platformVersion: globalThis.__obscura_ua_platform_version || "15.0.0",
|
||||||
uaFullVersion: "145.0.0.0",
|
uaFullVersion: _chromeMajor() + ".0.0.0",
|
||||||
|
wow64: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
toJSON() { return {brands:this.brands,mobile:this.mobile,platform:this.platform}; },
|
toJSON() { return {brands:this.brands,mobile:this.mobile,platform:this.platform}; },
|
||||||
@@ -6722,29 +6761,9 @@ globalThis.__obscura_init = function() {
|
|||||||
};
|
};
|
||||||
globalThis.Notification.permission = "default";
|
globalThis.Notification.permission = "default";
|
||||||
|
|
||||||
if (globalThis.__obscura_stealth) {
|
// userAgentData brands and getHighEntropyValues now derive the Chrome
|
||||||
var _stealthBrands = [
|
// version from navigator.userAgent and read the platform from the page
|
||||||
{brand: "Not:A-Brand", version: "99"},
|
// globals, so every stealth surface agrees without a per-mode override.
|
||||||
{brand: "Google Chrome", version: "145"},
|
|
||||||
{brand: "Chromium", version: "145"},
|
|
||||||
];
|
|
||||||
globalThis.navigator.userAgentData.brands = _stealthBrands;
|
|
||||||
globalThis.navigator.userAgentData.getHighEntropyValues = function(hints) {
|
|
||||||
return Promise.resolve({
|
|
||||||
architecture: "x86", bitness: "64",
|
|
||||||
brands: _stealthBrands,
|
|
||||||
fullVersionList: [
|
|
||||||
{brand:"Not:A-Brand",version:"99.0.0.0"},
|
|
||||||
{brand:"Google Chrome",version:"145.0.0.0"},
|
|
||||||
{brand:"Chromium",version:"145.0.0.0"},
|
|
||||||
],
|
|
||||||
mobile: false, model: "",
|
|
||||||
platform: "Windows",
|
|
||||||
platformVersion: "10.0.0",
|
|
||||||
uaFullVersion: "145.0.0.0",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide internals (_*, obscura, Obscura). The set of keys is static at
|
// Hide internals (_*, obscura, Obscura). The set of keys is static at
|
||||||
// snapshot-build time, so we precompute it ONCE below (after this
|
// snapshot-build time, so we precompute it ONCE below (after this
|
||||||
|
|||||||
@@ -19,4 +19,7 @@ pub use encoding::{
|
|||||||
pub use robots::RobotsCache;
|
pub use robots::RobotsCache;
|
||||||
pub use blocklist::is_blocked as is_tracker_blocked;
|
pub use blocklist::is_blocked as is_tracker_blocked;
|
||||||
#[cfg(feature = "stealth")]
|
#[cfg(feature = "stealth")]
|
||||||
pub use wreq_client::{StealthHttpClient, STEALTH_USER_AGENT};
|
pub use wreq_client::{
|
||||||
|
StealthHttpClient, STEALTH_NAVIGATOR_PLATFORM, STEALTH_UA_PLATFORM,
|
||||||
|
STEALTH_UA_PLATFORM_VERSION, STEALTH_USER_AGENT,
|
||||||
|
};
|
||||||
@@ -19,7 +19,18 @@ use crate::client::{Response, ObscuraNetError};
|
|||||||
|
|
||||||
#[cfg(feature = "stealth")]
|
#[cfg(feature = "stealth")]
|
||||||
pub const STEALTH_USER_AGENT: &str =
|
pub const STEALTH_USER_AGENT: &str =
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36";
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36";
|
||||||
|
|
||||||
|
// The wreq emulation (Profile::Chrome145, Platform::Windows) sends this exact
|
||||||
|
// UA and sec-ch-ua-platform "Windows" on the wire. navigator has to report the
|
||||||
|
// same identity, otherwise the TLS/HTTP layer and the JS layer disagree and a
|
||||||
|
// site cross-checks the mismatch as a bot signal.
|
||||||
|
#[cfg(feature = "stealth")]
|
||||||
|
pub const STEALTH_NAVIGATOR_PLATFORM: &str = "Win32";
|
||||||
|
#[cfg(feature = "stealth")]
|
||||||
|
pub const STEALTH_UA_PLATFORM: &str = "Windows";
|
||||||
|
#[cfg(feature = "stealth")]
|
||||||
|
pub const STEALTH_UA_PLATFORM_VERSION: &str = "15.0.0";
|
||||||
|
|
||||||
#[cfg(feature = "stealth")]
|
#[cfg(feature = "stealth")]
|
||||||
pub struct StealthHttpClient {
|
pub struct StealthHttpClient {
|
||||||
@@ -38,7 +49,7 @@ impl StealthHttpClient {
|
|||||||
pub fn with_proxy(cookie_jar: Arc<CookieJar>, proxy_url: Option<&str>) -> Self {
|
pub fn with_proxy(cookie_jar: Arc<CookieJar>, proxy_url: Option<&str>) -> Self {
|
||||||
let emulation_opts = wreq_util::Emulation::builder()
|
let emulation_opts = wreq_util::Emulation::builder()
|
||||||
.profile(wreq_util::Profile::Chrome145)
|
.profile(wreq_util::Profile::Chrome145)
|
||||||
.platform(wreq_util::Platform::Linux)
|
.platform(wreq_util::Platform::Windows)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let mut builder = wreq::Client::builder()
|
let mut builder = wreq::Client::builder()
|
||||||
|
|||||||
Reference in new issue
Block a user