10

I am trying to overload the navigator.userAgent using a simple chrome extension. As the content scripts operate in isolated env, I tried to create a script element and write the logic into this one. This happens from background page of the extension

chrome.tabs.query({
  active:!0
}, function(tabs) {
    var x = "window.navigator.__defineGetter__('userAgent', function() {" +
            "return 'Mozilla/5.0 (Linux; Android 4.2.1; en-us; Nexus 5 Build/JOP40D)" +
            " AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile " + 
            "Safari/535.19'; });console.log(navigator.userAgent);";

    for (var i = 0;i < tabs.length;i++) {
      var code = 'var s = document.createElement("script"); s.text = "' + x +
                 '"; document.head.insertBefore(s, document.head.firstChild);' + 
                 'navigator.userAgent ="s"; console.log(navigator.userAgent);';

      // Inject into the tabs of choice - currently everything.
      chrome.tabs.executeScript(tabs[i].id, {
        code: code
      });
    }
  });

The script gets appended for the head element and I can see that the UA string as the one that is spoofed by trying navigator.userAgent in the console of the chrome and so I believe that the navigator object was overloaded.

But this seems to be not the effective way or not happening at all as the navigator object was not updated which I found out via - http://www.quirksmode.org/js/detect.html which still shows the UA for Mac.

So, what exactly am I missing here?

Srikanth Rayabhagi
  • 1,393
  • 3
  • 12
  • 23

3 Answers3

17

navigator.userAgent is a read-only property. If you want to change navigator.userAgent, then you need to either create a new object and copy the properties, or create a new object and inherit from navigator and assign a new getter/setter.

I've recently created such an extension. I'm on Linux, though I occasionally download the Chrome for Windows. The following extension changes the user agent to Windows XP on Chrome's download page:

contentscript.js

var actualCode =  '(' + function() {
    'use strict';
    var navigator = window.navigator;
    var modifiedNavigator;
    if ('userAgent' in Navigator.prototype) {
        // Chrome 43+ moved all properties from navigator to the prototype,
        // so we have to modify the prototype instead of navigator.
        modifiedNavigator = Navigator.prototype;

    } else {
        // Chrome 42- defined the property on navigator.
        modifiedNavigator = Object.create(navigator);
        Object.defineProperty(window, 'navigator', {
            value: modifiedNavigator,
            configurable: false,
            enumerable: false,
            writable: false
        });
    }
    // Pretend to be Windows XP
    Object.defineProperties(modifiedNavigator, {
        userAgent: {
            value: navigator.userAgent.replace(/\([^)]+\)/, 'Windows NT 5.1'),
            configurable: false,
            enumerable: true,
            writable: false
        },
        appVersion: {
            value: navigator.appVersion.replace(/\([^)]+\)/, 'Windows NT 5.1'),
            configurable: false,
            enumerable: true,
            writable: false
        },
        platform: {
            value: 'Win32',
            configurable: false,
            enumerable: true,
            writable: false
        },
    });
} + ')();';

var s = document.createElement('script');
s.textContent = actualCode;
document.documentElement.appendChild(s);
s.remove();

manifest.json

{
    "name": "navigator.userAgent",
    "description": "Change navigator.userAgent to Windows on Chrome's download page.",
    "version": "1",
    "manifest_version": 2,
    "content_scripts": [{
        "run_at": "document_start",
        "js": ["contentscript.js"],
        "matches": [
            "*://www.google.com/intl/*/chrome/browser/*"
        ]
    }]
}

As you can see, I'm declaring the content script instead of dynamically inserting it, to make sure that my code runs before the page is loaded. Further, I'm using one of the tricks described in this answer to change the page's navigator instead of some other navigator in the isolated content script world.

Note that this only modifies the userAgent as seen from JavaScript. If you want to modify the user agent that's sent to the server, take a look at Associate a custom user agent to a specific Google Chrome page/tab.

Community
  • 1
  • 1
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • I figured out this method a week ago, forgot to update here. But your solution is elegant with other properties of navigator which might help me for perfect emulation. One question I am still left with is, if I use a chrome message passing to confirm something with background page before injecting the script, the script is injected little slowly n in some cases the inline scripts execute before the message passing is done. Is there a work around? – Srikanth Rayabhagi May 04 '14 at 16:50
  • @SrikanthRayabhagi I have listed some work-arounds at http://crbug.com/334486. These are hacks, and you should only consider them as a last resort. – Rob W May 04 '14 at 21:53
  • I'm getting an illegal invocation error at the "userAgent: {" line. Any idea what that might be about? The only change I made was I quoted the entire thing instead of having the function be out of quotes. – Ryan Amos Nov 14 '15 at 07:18
  • @RyanAmos Chrome 43 changed the way that built-in objects were defined. I updated the answer, try again. – Rob W Nov 14 '15 at 10:14
  • 1
    See https://gist.github.com/thorsten/148812e9cc4fb6a19215ce22afd4e5a8 for code that also works on other browsers. – TomTasche Nov 15 '19 at 06:26
  • @TomTasche you are a life saver! I had been trying for the last 6 hours trying to figure out how to change stuff in navigator. This did the trick! – 010011100101 Feb 24 '20 at 20:43
  • Why doesn't this work on a TryIt page if the navigator object is overwritten e.g https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_nav_all it seems to work on https://whichbrowser.net/ when I use a useragent of Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62 and on my own page that outputs the navigator.userAgent but when I copy the code from that TryIt psge locally it doesn't work either. Is there a logical reason for this? – MonkeyMagix Jan 08 '22 at 03:47
0

#Rob W,Thanks!

Cool! But the site crashes if some navigator values are accessed. Therefore, it would be better to completely recreate the navigator.

function setupUserAgentHook(UserAgent){
if(typeof UserAgent !== 'string' && UserAgent == '')
    return false;
    function addslashes(str) { // Quote string with slashes
    return str.replace(/([\"\'])/g, "\\$1");
}
var actualCode = '(' + function(newUserAgent){
    'use strict';
    
    var navigator = Object.create(window.navigator);
    function rTMPL(o){
        return {
            value: o,
            configurable: false,
            enumerable: true,
            writable: false
        }
    }
    var ChromeV = newUserAgent.replace(/^.*Chrome\/(\d+).*$/gi,'$1');

    Object.defineProperties(navigator, {
        userAgent: rTMPL(newUserAgent),
        appVersion: rTMPL(newUserAgent),
        platform: rTMPL('Win32'),
        productSub: rTMPL('20030107'),
        language: rTMPL('en-US'),
        languages: rTMPL(['en-US', 'en']),
        userAgentData: rTMPL({"brands":[{"brand":" Not A;Brand","version":ChromeV},{"brand":"Chromium","version":ChromeV},{"brand":"Google Chrome","version":ChromeV}],"mobile":false}),

        deviceMemory: rTMPL(8), 
        hardwareConcurrency: rTMPL(8),

        maxTouchPoints: rTMPL(0),msMaxTouchPoints: rTMPL(0),
        vendor: rTMPL('Google Inc.'),appCodeName: rTMPL('Mozilla'),appName: rTMPL('Netscape'),product: rTMPL('Gecko'),
        bluetooth: rTMPL({}),clipboard: rTMPL({}),credentials: rTMPL({}),ink: rTMPL({}),keyboard: rTMPL({}),locks: rTMPL({}),mediaCapabilities: rTMPL({}),permissions: rTMPL({}),plugins: rTMPL({}),
        scheduling: rTMPL({}),storage: rTMPL({}),wakeLock: rTMPL({}),webkitPersistentStorage: rTMPL({}),webkitTemporaryStorage: rTMPL({}),windowControlsOverlay: rTMPL({}),
        onLine: rTMPL(true),pdfViewerEnabled: rTMPL(true),cookieEnabled: rTMPL(true),webdriver: rTMPL(false),doNotTrack: rTMPL(null),vendorSub: rTMPL(""),xr: rTMPL("XRSy"),

        mediaDevices: rTMPL({ondevicechange: null}),
        usb: rTMPL({onconnect: null, ondisconnect: null}),
        hid: rTMPL({onconnect: null, ondisconnect: null}),
        managed: rTMPL({onmanagedconfigurationchange: null}),
        serial: rTMPL({onconnect: null, ondisconnect: null}),
        presentation: rTMPL({defaultRequest: null, receiver: null}),
        mediaSession: rTMPL({metadata: null, playbackState: 'none'}),
        userActivation: rTMPL({hasBeenActive: true, isActive: true}),
        virtualKeyboard: rTMPL({boundingRect: DOMRect, overlaysContent: false, ongeometrychange: null}),
        connection: rTMPL({"downlink":10,"effectiveType":"4g","onchange":null,"rtt":50,"saveData":false}),
        serial: rTMPL({controller: null, ready: Promise, oncontrollerchange: null, onmessage: null, onmessageerror: null}),
        geolocation: rTMPL({getCurrentPosition: function(fs,fe,o){fe({code: 1, message: 'User denied Geolocation'})}, watchPosition: function(fs,fe,o){fe({code: 1, message: 'User denied Geolocation'})}}),
        mimeTypes: rTMPL({0: 'MimeType', 1: 'MimeType', 2: 'MimeType', 3: 'MimeType', 'application/pdf': 'MimeType', 'application/x-google-chrome-pdf': 'MimeType', 'application/x-nacl': 'MimeType', 'application/x-pnacl': 'MimeType', 'length': 4})
    });
    Object.defineProperty(window, 'navigator', {
        value: navigator,
        configurable: true,
        enumerable: true,
        writable: true
    });             
} + ')("'+addslashes(UserAgent)+'");';

document.documentElement.setAttribute('onreset', actualCode);
document.documentElement.dispatchEvent(new CustomEvent('reset'));
document.documentElement.removeAttribute('onreset');
}
setupUserAgentHook('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36');
-1

Are you trying to change the User-Agent header that gets sent in requests? You'll have to use the declarativeWebRequest or webRequest APIs.

Running content scripts in the page only occurs after the requests have been sent.

yoz
  • 902
  • 8
  • 20
  • 1
    Yes, I am using chrome.webRequest API to modify the UserAgent in the headers but still that is not enough to update the navigator.userAgent . So, I am trying to override userAgent at the point when it send request for headers. – Srikanth Rayabhagi Apr 22 '14 at 14:35