[$1250 - High Severity] Bypassing Brower Extension's Geolocation Spoofing with a Malicious Website (Location Guard & ExpressVPN)

Bypassing the spoof geolocation feature in browser extensions to disclose the physical location of a user. I share two high severity bugs. Bug 1 is a generic payload that works across multiple extensions, and bug 2 is an ExpressVPN specific payload that has been patched. This post is a case study with the Location Guard & ExpressVPN extensions, my bug bounty experience, and a few takeaways that may prove insightful for others.

Table of Contents

[Bug 1: Genaric]: Bypass Browser Extension Geolocation Spoofing with a Malicious Webpage

The current HTML5 geolocation feature of modern browsers does not solely rely on IP addresses but rather gathers data from several sources (such as local WiFi networks) to identify a relatively accurate physical location.

This bug seems to affect most if not all browser extensions in both Chromium and Firefox extensions that attempt to hook a browser’s geolocation functions. This Chromium bug describes that any webpage can effectively execute code before a browser extension is loaded; any attempt of a browser extension to spoof locations by hooking the HTML5 API can be thwarted by a malicious webpage or, potentially, a webpage hosting a malicious XSS payload. It may be worthwhile for developers and bug hunters to familiarize themselves with this behavior when evaluating browser extension features not only for geolocation spoofing but also for any attempts to hook browser APIs.

Note, that the Chromium bug description does state that other common extension features such as ad-blockers may also be affected.

For instance, » this POC: BrowserExtensionGetPhysicalLocationLeakPOC.html « will bypass the Location Guard extension which has over 300,000+ users in the chrome web store and 14k downloads on Firefox as well as the ExpressVPN extension that has over 1,000,000+ users in the Chrome store and 86,000+ users in Firefox.

Video POC: Bypassing GPS spoofing in the Location Guard and Express VPN extensions across Firefox + Google Chrome.

Generic POC Payload

This payload differs from the ExpressVPN payload (further down in the post) as it weaponizes the Chromium bug by loading an iframe and accessing its functions as a means to front-run a browser extension’s content script, it then runs a loop to repeatedly restore the state of the browser’s geolocation APIs to their original state (i.e.: it repeatedly attempts to unhook geolocation functions that are tampered with by a browser extension).

This payload should be generic enough to work for multiple extensions.

/** main JS payload to bypass spoofing **/

// create iframe
const f = document.createElement('iframe');
f.src = location;
f.hidden = true;
const fw = f.contentWindow;

// use iframe to reference original HTML5 geolocation functions
window.watchPosition_orig = fw.navigator.geolocation.watchPosition
window.getCurrentPosition_orig = fw.navigator.geolocation.getCurrentPosition

// loop, replacing geolocation functions with original functions
setInterval(() => {
    window.navigator.geolocation.watchPosition = window.watchPosition_orig
    window.navigator.geolocation.getCurrentPosition = window.getCurrentPosition_orig
}, 100)

Note that sometimes the HTML5 location will use the location of your VPN server. Sometimes the location is accurate to your real physical location, other times it is slightly off, or the Geolocation may fail completely with an error in the console. The results are relatively random depending on your location, nearby networks, device settings, browser settings, and other factors; however, it may eventually disclose your location when leaving the bypass page open for a few minutes or potentially refreshing the page. I chose to implement an infinite loop to repeatedly retrieve location information as it may change in successive calls to the location API - this may cause repeated location permissions prompts. Additionally, ExpressVPN expressed having issues with the payload in MacOS. While I have confirmed it to work with Location Guard on a Macbook Pro using Chrome, be aware that this exploit is dependent on the location settings of the device as well as the browser and quite a few other variables. Mileage may vary.


A new browser extension called sqrx offers some very interesting functionality for security minded individuals and was founded by Vivek Ramachandran, an extremely well respected figure in the cybersecurity community.

Chromium based browsers can leverage extensions such as vytal which spoof geolocation by hooking the chrome.debugger API rather than injecting a Content Script to hook the geolocation APIs.

A way to manually spoof location in Chromium-based browsers is to set location from the Sources tab of the Developer Tools (source), or disable location services. Firefox has its own method of spoofing through the built-in about:config page.

And the most secure option is to not accept location tracking from a website at all if physical security is a high priority to the user.

[Bug 2: ExpressVPN postMessage bug] Case Study: Bug Submission + Proof of Concept

For the benefit of other bug hunters or interested parties, I decided to include my experience with this bug bounty submission as it was not the most straightforward process.

I will note that I wrote my report in haste, the event listener validation can be bypassed because the script is injected into every webpage, so messages from that webpage to itself pass the check. I also note later in this post some of my reflections on what I should have done differently in my reporting.

In its near original form, below is my submission to BugCrowd which can be found here.

Overview of the Vulnerability

ExpressVPN’s browser extensions (both Firefox + Chrome + Edge + others?) have an HTML5 geolocation spoof feature. This feature can be bypassed since the gps.js content script injects an event listener that validates only that event.source == window (source: validation logic).

The vulnerable code will set the hookedObj.fakeGeo property (source) to false which controls spoofing conditional logic code flow at lines 19 and 42 within a JavaScript closure that is injected into every page the user browses: source: extension manifest.json.

Thus, it is possible for an attacker to phish a target using either an attacker-owned malicious site (user would have to approve location usage for the browser), or weaponize XSS on a site the victim user has already trusted (.e.g.: maps.google.com) and potentially leak their current physical location.

To execute this, a web page need only execute these commands

    // bypass spoofing by posting a message to update 'hookedObj' in gps.js
        let info = {
            fakeIt: false,
            coords: {
            lat: 99.9999,
            lon: 99.9999,
        window.postMessage({ method: 'updateLocation', info: info }, "*")
}, 1000)

Business Impact

The ExpressVPN Browser Extension HTML5 spoofing feature gives users a false sense of safety when allowing location settings for webpages. There may be a portion of ExpressVPN users (e.g.: investigative journalists in dangerous countries, politicians, confidential informants, undercover personnel, deployed military personnel etc.) that rely on geolocation spoofing to not give away their location for physical safety reasons - as such, a successful attacker may have enough information to pose an immediate physical threat to the ExpressVPN user and those around them.

In addition, with the combined timestamp + physical location + VPNServer IP data, an advanced threat actor could potentially correlate (with additional data - e.g.: access to ISP data for the local geographic region) the Private IP of the ExpressVPN user.

Note that the physical location feature ranges in accuracy and may or may not be accurate when spoofing is disabled.

Steps to Reproduce

  1. Download + Install + Connect ExpressVPN Extension + enable HTML5 location spoofing
  2. Use a browser to navigate to spoofed location: https://do6.us/ca20a9ef-0390-438b-b83d-1826595d22ef/ExpressVPNPhysicalLocationLeakPOC + allow location services
  3. Add parameter ?bypass=1 : https://do6.us/ca20a9ef-0390-438b-b83d-1826595d22ef/ExpressVPNPhysicalLocationLeakPOC?bypass=1

Note that the physical location should be more accurate despite the ExpressVPN spoofing setting in the Browser Extension still being enabled. The location should be the same as if you were to disable the HTML5 “Privacy & Security > Spoof your location” feature in the ExpressVPN extension.

Proof of Concept (PoC)

Screenshot: leaking IP and location

« Express VPN-specific POCs were posted here, generic POCs that work for ExpressVPN are included in this post. »

ExpressVPN’s Response

ExpressVPN planned to remediate with a new version of their Chrome Extension through a refactor, however, were unable to create a working version free of this issue - they referenced this Chromium bug as a roadblock.

This Chromium Bug sparked my interest as I weaponized it into the “generic” payload described above which also worked with the Location Guard extension.

ExpressVPN had previously investigated using the debug method the Vytal extension used but decided not to do so due to potential user concerns with the extension making use of debug privileges.

Disclaimers were added to the ExpressVPN extension on installation:

Updated ExpressVPN disclaimer for spoof location bypass (extension install)

And, in the text of the location spoofing setting:

Updated ExpressVPN disclaimer for spoof location bypass

I reported this issue related to the missing validation logic for the postMessage handler on 7 January 2023, and ExpressVPN published an updated version of the extension on 31 July 2023. The code for this build was merged into the public repo on 7 August 2023. After this update, the ExpressVPN specific POC no longer worked.

My Journey + Methodology

I happened to stumble upon this buy purely by chance. I intended to experiment with CodeQL by looking at a bug bounty target with source code and happened to randomly choose ExpressVPN as I was also exploring dumping source code of all Chrome Web Store Extensions (I may post a future blog post with my POC solution).

CodeQL Detection Failure

Interestingly, there is a built-in CodeQL detector for unprotected message handlers, however, it did not detect the vulnerable event handler because the check on event.source is bypassed in this specific instance - the script is injected into the malicious webpage (via. the browser extension) where the malicious site can post messages to itself. This an interesting scenario specific to browser extensions and may be something to think about for other contexts.

Leveraging ChatGPT to Explore Impact

ChatGPT helped me brainstorm users that may potentially be physically endangered from a physical location leak, this list includes:

  • Domestic abuse victims
  • Political activists
  • Witnesses to a crime
  • Children
  • Individuals in a witness protection program
  • Celebrities
  • Military Personnel
  • Individuals in law enforcement
  • Doctors and nurses working in war zones or other dangerous areas
  • Asylum seekers
  • Whistleblowers
  • Journalists

Some of these I identified myself, but there are several I had not considered. ChatGPT was wonderful for brainstorming potentially high-risk victims.

Internal Duplicate + P3 Severity

Unfortunately, during initial triage, the postMessage bug I reported was incorrectly triaged as an internal duplicate of the Chromium issue, even though the public codebase had not been updated for 2+ years. ExpressVPN said the public codebase had not been updated for 2 years as the root cause of the internally tracked issue was the Chromium bug. This initial incorrect triage was also the reason the issue took longer than expected to address.

Additionally, the initially triaged P3 severity was a little sad (ref: BugCrowd vulnerability priority). I was hoping for a P2 as I felt there was a subset of people that this vulnerability could lead to very serious physical safety concerns (loss of life), especially since some sensitive users are being actively targeted by ExpressVPN marketing (e.g.: ExpressVPN offers free service to Journalists in Ukraine) - a detail I left out of my original report, so perhaps that was on me.

Note: At the time of this blog post, we are 1+ year(s) into the Russian invasion of Ukraine, a situation where journalists may end up in active war zones. It is not farfetched to think Russian hackers may target journalists as a means of exposing Ukrainian troops, bunkers, or otherwise interesting locations to 💣bomb/💥target.

Free Express VPN for Journalists under oppressive circumstances

After writing a draft blog post with the above details, ExpressVPN realized that my initial report from 7 January 2023 about the unique postMessage validation issue was incorrectly triaged as a duplicate of the Chromium issue. They removed the internal duplicate marking, upgraded the severity to P2 - High Severity as it was originally triaged as a duplicate of the Chromium bug, and rewarded me the maximum payout for a P2 issue. I write these details to encourage other bug hunters to respectfully speak up for themselves if they feel a finding was underappreciated or misclassified. I felt rushed when submitting my original report due to self-imposed time constraints (trying to avoid a duplicate). I think it is worthwhile to spend some extra time to ensure the originally submitted report is of high quality and contains all the details/context that will help triagers accurately assess impact.

State Variables as Sinks

I have been doing some web3 auditing in Solidity where state changes can lead to disastrous consequences. This has made me re-frame my perception of sinks, especially after this ExpressVPN finding in a web2 context. The root cause of the ExpressVPN-specific vulnerability outlined in this post stems from updating a state variable (boolean fakeIt - used to disable the spoofing feature). It is useful for bug hunters, code auditors, and developers, in general, to keep in mind the potential attack vectors that may stem from unexpected state changes - it was nice to find a web2 state change issue.


This was a fun bug and journey that I hope the reader has found worth their time. Being new to bug bounty, my report was rushed and in the future, I’ll take my time to ensure the impact is more clearly highlighted.

Resolution Timeline

  • 2023-01-07: Submitted to ExpressVPN on BugCrowd
  • 2023-08-14: Closed

ExpressVPN’s Parting Words

Below is a statement ExpressVPN volunteered to include in this post, I greatly appreciate their willingness to work with me through this process and offer very prompt feedback on the technical accuracy and verbiage included in this post. They have been an excellent partner to work with!

“ExpressVPN welcomes the opportunity to collaborate with Alec on this research or any future work. We believe in continued collaboration with researchers and the security community to ensure as many highly skilled security professionals review our products. Throughout the process, Alec handled all discussion points professionally and respectfully, and we applaud his efforts to continue improving the security of our products and ensuring that the internet is a safer place for all.” - ExpressVPN