Skip to content

Commit

Permalink
Navigator platform and vendor leaking fixed (#166)
Browse files Browse the repository at this point in the history
  • Loading branch information
tarampampam authored Oct 28, 2021
1 parent 4ec6a2c commit 71630ac
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 153 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ The format is based on [Keep a Changelog][keepachangelog] and this project adher
### Fixed

- Aggressive User-Agent detection (now even the inline scripts cannot detect the real User-Agent; thanks to [@neroux](https://github.com/neroux) for the idea) [#26], [#36]
- Navigator `platform` and `vendor` leaking [#7], [#144]

[#144]:https://github.com/tarampampam/random-user-agent/issues/144
[#7]:https://github.com/tarampampam/random-user-agent/issues/7
[#26]:https://github.com/tarampampam/random-user-agent/issues/26
[#36]:https://github.com/tarampampam/random-user-agent/issues/36

Expand Down Expand Up @@ -358,3 +361,6 @@ The format is based on [Keep a Changelog][keepachangelog] and this project adher
### Changed

- First release on Github

[keepachangelog]:https://keepachangelog.com/en/1.0.0/
[semver]:https://semver.org/spec/v2.0.0.html
64 changes: 48 additions & 16 deletions src/content-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,37 +32,69 @@ new Promise((resolve: (p: Payload) => void, reject: (e: Error) => void) => {
.then((resp): void => { // <-- the promise is the main problem for hiding from inline scripts detection
const applicable = (resp[0] as ApplicableToURIResponse).payload.applicable
const settings = (resp[1] as GetSettingsResponse).payload
const useragent = (resp[2] as GetUseragentResponse).payload.useragent
const uaInfo = (resp[2] as GetUseragentResponse).payload.info

if (applicable && settings.jsProtection.enabled && typeof useragent === 'string') {
if (applicable && settings.jsProtection.enabled && uaInfo !== undefined) {
return resolve({
useragent: useragent,
uaInfo: uaInfo,
})
}
})
.catch(reject)
})
.then((p: Payload): string => '(' + function (p: Payload): void {
// allows to overload object property with a getter function (without potential exceptions)
const overloadPropertyWithGetter = (object: object, property: string, value: any): void => {
if (typeof object === 'object') {
if (Object.getOwnPropertyDescriptor(object, property) === undefined) {
Object.defineProperty(object, property, {get: (): any => value})
// makes required navigator object modifications
const patchNavigator = (navigator: Navigator): void => {
// allows to overload object property with a getter function (without potential exceptions)
const overloadPropertyWithGetter = (object: object, property: string, value: string): void => {
if (typeof object === 'object') {
if (Object.getOwnPropertyDescriptor(object, property) === undefined) {
Object.defineProperty(object, property, {get: (): string => value})
}
}
}
}

// makes required navigator object modifications
const patchNavigator = (navigator: Navigator): void => {
if (typeof navigator === 'object') {
overloadPropertyWithGetter(navigator, 'userAgent', p.useragent)
overloadPropertyWithGetter(navigator, 'userAgent', p.uaInfo.useragent)

// app version should not contain "Mozilla/" prefix
overloadPropertyWithGetter(navigator, 'appVersion', p.useragent.replace(/^Mozilla\//i, ''))
overloadPropertyWithGetter(navigator, 'appVersion', p.uaInfo.useragent.replace(/^Mozilla\//i, ''))

// patch the platform property (based on os type)
switch (p.uaInfo.osType) { // fixes <https://github.com/tarampampam/random-user-agent/issues/7>
case 'windows':
overloadPropertyWithGetter(navigator, 'platform', 'Win32')
break

case 'linux':
overloadPropertyWithGetter(navigator, 'platform', 'Linux x86_64')
break

case 'android':
overloadPropertyWithGetter(navigator, 'platform', 'Linux armv8l')
break

case 'macOS':
overloadPropertyWithGetter(navigator, 'platform', 'MacIntel')
break

case 'iOS':
overloadPropertyWithGetter(navigator, 'platform', 'iPhone')
break
}

switch (p.uaInfo.engine) {
case 'blink':
overloadPropertyWithGetter(navigator, 'vendor', 'Google Inc.')
break

case 'gecko': // firefox always with an empty vendor
overloadPropertyWithGetter(navigator, 'vendor', '')
break

// firefox always with an empty vendor
if (p.useragent.toLowerCase().includes('firefox\/')) {
overloadPropertyWithGetter(navigator, 'vendor', '')
case 'webkit':
overloadPropertyWithGetter(navigator, 'vendor', 'Apple Computer Inc.')
break
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/before-send-headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ export default class BeforeSendHeaders {
chrome.webRequest.onBeforeSendHeaders.addListener(
(details: WebRequestHeadersDetails): BlockingResponse | void => {
if (this.settings.get().enabled && this.filterService.applicableToURI(details.url)) {
const useragent = this.useragent.get().useragent
const useragent = this.useragent.get().info

if (details.requestHeaders && typeof useragent === 'string') {
if (details.requestHeaders && useragent !== undefined) {
for (let i = 0; i < details.requestHeaders.length; i++) {
if (details.requestHeaders[i].name === 'User-Agent' && details.requestHeaders[i].value) {
details.requestHeaders[i].value = useragent
details.requestHeaders[i].value = useragent.useragent

break
}
Expand Down
9 changes: 5 additions & 4 deletions src/hooks/headers-received.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import Useragent from '../useragent/useragent'
import FilterService from '../services/filter-service'
import BlockingResponse = chrome.webRequest.BlockingResponse
import WebResponseHeadersDetails = chrome.webRequest.WebResponseHeadersDetails
import UseragentInfo from '../useragent/useragent-info'

declare var __UNIQUE_RUA_COOKIE_NAME__: string // see the webpack config, section "plugins" (webpack.DefinePlugin)
export const CookieName: string = __UNIQUE_RUA_COOKIE_NAME__

export interface Payload {
useragent: string
uaInfo: UseragentInfo
}

export function encode(payload: Payload): string {
Expand Down Expand Up @@ -56,14 +57,14 @@ export default class HeadersReceived {
const settings = this.settings.get()

if (settings.enabled && settings.jsProtection.enabled && this.filterService.applicableToURI(details.url)) {
const useragent = this.useragent.get().useragent
const useragent = this.useragent.get().info

if (details.responseHeaders && typeof useragent === 'string') {
if (details.responseHeaders && useragent !== undefined) {
const date = new Date()
date.setTime(date.getTime() + 60 * 1000) // +60 seconds

const payload: Payload = {
useragent: useragent,
uaInfo: useragent,
}

details.responseHeaders.push({
Expand Down
5 changes: 3 additions & 2 deletions src/messaging/handlers/renew-useragent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Handler, HandlerRequest, HandlerResponse} from '../handlers'
import UseragentService from '../../services/useragent-service'
import UseragentInfo from '../../useragent/useragent-info'

const name: string = 'refresh-useragent'

Expand All @@ -11,8 +12,8 @@ export interface RenewUseragentRequest extends HandlerRequest {

export interface RenewUseragentResponse extends HandlerResponse {
payload: {
previous: string | undefined
new: string
previous: UseragentInfo | undefined
new: UseragentInfo
}
}

Expand Down
19 changes: 13 additions & 6 deletions src/services/useragent-service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Settings from '../settings/settings'
import {UseragentGenerator} from '../useragent/generator'
import Useragent from '../useragent/useragent'
import UseragentInfo from '../useragent/useragent-info'

export default class UseragentService {
private readonly settings: Settings
Expand All @@ -16,10 +17,10 @@ export default class UseragentService {
// Renew the useragent (generate a new and set them into the settings)
renew(): {
source: 'custom_agents_list' | 'generator'
previous: string | undefined
new: string
previous: UseragentInfo | undefined
new: UseragentInfo
} {
const previous = this.useragent.get().useragent
const previous = this.useragent.get().info

if (this.settings.get().customUseragent.enabled) {
const list: string[] = this.settings.get().customUseragent.list
Expand All @@ -28,20 +29,26 @@ export default class UseragentService {
const random: string = list[Math.floor(Math.random() * list.length)]

if (random.trim().length > 0) {
this.useragent.update({useragent: random})
const custom: UseragentInfo = {
useragent: random,
engine: 'unknown', // TODO probably detect this properties here?
osType: 'unknown',
}

this.useragent.update({info: custom})

return {
source: 'custom_agents_list',
previous: previous,
new: random,
new: custom,
}
}
}
}

const generated = this.generator.generate(this.settings.get().generator.types)

this.useragent.update({useragent: generated})
this.useragent.update({info: generated})

return {
source: 'generator',
Expand Down
Loading

0 comments on commit 71630ac

Please sign in to comment.