Is UltraSurf Security, Privacy & Freedom VPN safe?
UltraSurf is high risk. UltraSurf sends a copy of your browsing data (URL, referrer, method, status, tab, UUID) to a proxy at 10.11.0.2:7000/_test_ over plain HTTP, same key as analytics. 14 POSTs captured in a 5-nav test; bodies matched visited pages.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Browsing Telemetry Posted Over Cleartext HTTP to Proxy Endpoint 10.11.0.2:7000
UltraSurf sends a copy of your browsing data (URL, referrer, method, status, tab, UUID) to a proxy at 10.11.0.2:7000/_test_ over plain HTTP, same key as analytics. 14 POSTs captured in a 5-nav test; bodies matched visited pages.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You visit any web page while UltraSurf VPN is switched on.
UltraSurf POSTs an encrypted JSON record of the navigation, URL, referrer, method, status, tab ID, and your UUID, to http://10.11.0.2:7000/_test_ over plain HTTP.
This happens on every navigation, in addition to the separate analytics POST to analytics.ultrasurfing.com (claim 4180). The 10.11.0.2 endpoint is a private RFC1918 address, the proxy that the VPN tunnels through intercepts it before it reaches the real destination.
- Your device ID ("Id")a2595b8f-fc6b-4fba-bad9-a5bdb5fc9501
The same persistent UUID from chrome.storage.sync, ties this navigation back to you across sessions, devices, and reinstalls.
- Target URL ("Url")https://canary.example.com/CANARY_BIRD_12345
The full address of the page you just navigated to, including any query parameters.
- Referrer URL ("Ref")https://news.ycombinator.com/
The page you came from, lets the receiver reconstruct your browsing path.
- HTTP method ("Method")GET
How the page was loaded (GET, POST, etc).
- HTTP status ("Code")200
The response status the browser received for this navigation.
- Tab ID ("TabId")428
Which browser tab the navigation happened in, lets the receiver group your activity per-tab.
The body is AES-GCM encrypted with the same hardcoded key ("8JCys9wTIqVO6gZu") used for the analytics endpoint, so anyone with the extension can decrypt it. The transport itself, however, is plain HTTP, so anyone who can observe the network path before the proxy intercepts the request sees the URL, the request method, and the encrypted body in cleartext.
[ { "Id": "a2595b8f-fc6b-4fba-bad9-a5bdb5fc9501", "Ref": "https://news.ycombinator.com/", "Url": "https://canary.example.com/CANARY_BIRD_12345", "Method": "GET", "Code": 200, "TabId": 428 }]The code that POSTs your browsing data to 10.11.0.2:7000.
Navigation handler — annotated
// app.js — fires on every completed main_frame navigation while VPN is onthis.handlerOnCompletedWebRequest = async function(event) { if (!enabled) return; // STEP 1: Build a record of this navigation, AES-GCM encrypt it, // then ship it to the proxy verify endpoint at 10.11.0.2:7000 over PLAIN HTTP. verify('web', -1, JSON.stringify( await this.prepareRequest([{ Id: uuid, // your persistent UUID Ref: prevUrlByTab[event.tabId] || event.initiator, // previous URL Url: event.url, // current URL Method: event.method, // HTTP method Code: event.statusCode, // HTTP response status TabId: event.tabId, // browser tab ID }]) )); // STEP 2 (claim 4180): also POST a copy to https://analytics.ultrasurfing.com/process // ... (see claim 4180)};verify() — annotated (note the http:// scheme)
// assets/js/background/js/verify.jsexport default async function verify(tag, timeout, data) { // ... metadata gathering (uid, pops, active tab, window state) ... fetch( // NOTE: 'http://' — plain HTTP, NOT HTTPS. // 10.11.0.2 is a private RFC1918 address: the UltraSurf VPN proxy // intercepts this request before it reaches the public internet. 'http://10.11.0.2:7000/_test_' + '?tag=' + tag + timeout // e.g. 'web-1' + '&last=' + last + '&timeout=' + timeout + '&pops0=' + pops0 + '&lastV=' + lastV + '&lastVTag=' + lastVerifyTag + '&ver=' + chrome.runtime.getManifest().version + '&pops=' + pops + '&active=' + active + '&win=' + winstate + '&uid=' + uid, // 8-char random per-session ID { method: 'POST', body: data, // AES-GCM-encrypted navigation record } ); // ...response handling (server can reply with a URL to open in a new tab)...}200 OK, DA agent observed 14 POSTs during the 5-navigation test, bodies 258-406 bytes. Server can also respond with a URL string in the body, which the extension then opens in a new browser tab (verify.js:139-158).
- Content-Type
- text/plain;charset=UTF-8
{ "eventType": 1, "request": { "enRequest": "\"7Lk2Bp9Vc4XdN3oRfKaWzMqJsTuP1eGyHjAvCfBmDoNuRrTpEy6sLzQ8wKxJVHfGmzC9YuI3LqWvAaP4dFOgN2RyEhZxBcLjMnUkPwVoTeS8gIa0YfDuJsRkBpHzMcVoXqLpEjBfNyTwOpRsKuI4xGdCmHvUaWb6sFqLpJxNzKvUhXcNo1tEpDyHqL8sMfUcJaPoVwBnG2RjT5kQXdLzCp9Y\"" }}- 10.11.0.2:7000
Private RFC1918 address, intercepted by the UltraSurf VPN proxy (Ultrasurf Inc), which forwards the record. Also does remote tab-open: a response over 10 chars opens a new tab.
UltraSurf verify server can open returned URLs in new tabs
When UltraSurf is enabled, it sends check-ins to `10.11.0.2:7000` with tab state and an encrypted body.
Dynamic analysis observed the server respond `https://ultrasurfing.com`; the extension opened a new tab to that URL shortly after.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
You browse with UltraSurf enabled.
The extension runs verify check-ins as part of its proxy connection flow and scheduled tracking.
The extension posts a verify request and opens a returned web address in a new tab.
Dynamic analysis observed `https://ultrasurfing.com` returned by the verify server and loaded in a new tab shortly afterward.
200 OK with response body `https://ultrasurfing.com`; the browser then made page-level requests to ultrasurfing.com.
- Verify tagtag=web-1, lastVTag=web
Shows which extension check-in path produced the request.
- Pop-up counterspops=1, last=259213, pops0=259214
Shows how recently tabs were opened and how many tab openings are already recorded.
- Tab stateactive=true, win=normal
Shows whether the tracked tab was active and what browser-window state was reported.
- Extension user IDuid=tbcKMG8k
Lets the service associate repeated verify requests with the same browser profile.
- Encrypted telemetry body406-byte AES-GCM encrypted body
Carries navigation telemetry in a form that is not readable from the request URL alone.
The verify response becomes a browser tab URL
Base verify endpoint
src/core/config.jsexport const VERIFY_BASE_URL = "http://10.11.0.2:7000/_test_";Query-string construction
src/features/verify.jsfunction buildVerifyUrl(tag, timeout, state, active, winstate) { const params = new URLSearchParams({ tag: `${tag}${timeout}`, last: String(secondsSince(state.lastPopTime || 0)), timeout: String(timeout), pops0: String(secondsSince(state.popsResetTime || 0)), lastV: String(secondsSince(state.lastVerifyTime || 0)), lastVTag: state.lastVerifyTag || "init", ver: chrome.runtime.getManifest().version, pops: String(state.pops || 0), active: String(active), win: String(winstate), uid: state.uid || "" }); return `${VERIFY_BASE_URL}?${params.toString()}`;}New-tab opener
src/features/verify.jsasync function openLanding(link, state, trackedTabId) { if (trackedTabId > 0) { try { await chrome.tabs.remove(trackedTabId); } catch {} } const tab = await chrome.tabs.create({ url: link }); await patchState({ tabid: tab.id, pops: Number(state.pops || 0) + 1, lastPopTime: now() });}Verify request and response branch
src/features/verify.jsexport async function verify(tag, timeout = 0, data = "") { const skipLock = !String(tag).includes("web") && timeout <= 0; if (skipLock) { if (verifying > 0) { log(`[VERIFY] Skip duplicate verify tag=${tag} timeout=${timeout}`); return; } verifying += 1; } const state = await getState(); if (!state.enabled) { verifying = 0; log(`[VERIFY] Skip because extension is disabled. tag=${tag} timeout=${timeout}`); return; } await patchState({ lastVerifyTime: now(), lastVerifyTag: tag }); const { tabid, active } = await getTrackedTabInfo(state.tabid); const winstate = await getWindowState(); const verifyUrl = buildVerifyUrl(tag, timeout, state, active, winstate); const cycleInfo = getProxyCycleInfo(); const attemptNumber = cycleInfo.currentAttemptIndex + 1; const fetchTimeoutMs = getAttemptFetchTimeoutMs(attemptNumber); const startedAt = now(); log( `[VERIFY] Start tag=${tag} timeout=${timeout} fetchTimeout=${fetchTimeoutMs} active=${active} win=${winstate} cycle=${cycleInfo.cycleId} attempt=${attemptNumber}/${cycleInfo.totalAttempts}` ); try { const response = await fetchWithTimeout( verifyUrl, { method: "POST", body: data }, fetchTimeoutMs ); const elapsed = now() - startedAt; if (response.status !== 200) { log(`[VERIFY] Non-200 response status=${response.status} tag=${tag} timeout=${timeout} elapsed=${elapsed}ms`); const handled = await handleNon200Status(response.status, tabid, state, tag); verifying = 0; if (handled) return; throw new Error(String(response.status)); } const link = await response.text(); await setConnectedFlow(tag); await incrementSuccessfulConnectCount(); log(`[VERIFY] SUCCESS tag=${tag} timeout=${timeout} elapsed=${elapsed}ms linkLen=${link.length}`); if (link.length > 10) { await openLanding(link, state, tabid); } else if (link.length > 0) { await resetPopWindow(); } verifying = 0; } catch (error) { const elapsed = now() - startedAt; const msg = String(error?.message || error || "unknown error"); log(`[VERIFY] FAILED tag=${tag} timeout=${timeout} elapsed=${elapsed}ms error=${msg}`); if (timeout < 0) { verifying = 0; log(`[VERIFY] Negative timeout, stop retrying. tag=${tag}`); return; } const current = await getState(["enabled"]); if (!current.enabled) { verifying = 0; log(`[VERIFY] Extension disabled after failure, stop retrying. tag=${tag}`); return; } let rotated = false; try { rotated = await rotateProxy(); } catch (rotateError) { log(`[VERIFY] rotateProxy threw error=${String(rotateError?.message || rotateError || "unknown rotate error")}`); } const refreshedCycleInfo = getProxyCycleInfo(); const cycleElapsed = refreshedCycleInfo.startedAt > 0 ? now() - refreshedCycleInfo.startedAt : 0; if (!rotated) { await patchState({ enabled: false }); await disableProxy(); await markVerifyFailureState(); verifying = 0; log(`[VERIFY] ALL ATTEMPTS FAILED (A + B) tag=${tag} cycle=${refreshedCycleInfo.cycleId} cycleElapsed=${cycleElapsed}ms proxyCleared=true enabled=false`); return; } log( `[VERIFY] Scheduling retry tag=${tag} delay=${RETRY_DELAY_MS}ms nextTimeout=${getAttemptFetchTimeoutMs(refreshedCycleInfo.currentAttemptIndex + 2)} rotated=${rotated} cycleElapsed=${cycleElapsed}ms` ); setTimeout(() => { verify(tag, fetchTimeoutMs, msg); }, RETRY_DELAY_MS); }}Every 5 minutes
After the connection flow, UltraSurf repeats verify check-ins every five minutes while the extension remains enabled.
- 10.11.0.2
Private verify endpoint that receives POST check-ins and returned the URL opened by the extension.
- ultrasurfing.com
Returned URL loaded in the new tab during the confirmed observation.
Safe Browsing sends every URL + referrer + persistent UUID to UltraSurf servers
Enabling Safe Browsing sends every page's URL, referrer, and a permanent device ID to safe.ultrasurfing.com per navigation.
A planted ID appeared in six POSTs across Wikipedia, Amazon, Reddit, ChatGPT, linking navigations to your device.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You navigate to any web page while Safe Browsing is active.
The extension POSTs the page address, the previous address, and your permanent device ID to safe.ultrasurfing.com.
This fires on every navigation across all tabs, URL changes, page refreshes, and new tabs opened from links.
JSON response with an 'action' field; value 'allow' permits navigation, 'block' redirects tab to extension's warning.html page.
- Content-Type
- application/json
{ "url": "https://en.wikipedia.org/wiki/Main_Page", "referrer": "https://www.google.com/search?q=wikipedia", "userId": "a3f4e1b2-9c7d-4e2f-8a1b-0d3c5e7f9a2b"}- The page you are visitinghttps://en.wikipedia.org/wiki/Main_Page
The full address of the page, stripped of the URL fragment (#hash) but including any path, query parameters, and search terms.
- The page you came fromhttps://www.google.com/search?q=wikipedia
The previous page on this tab, or the opener tab's URL if you clicked a link to open a new tab. Builds a chain of your browsing activity.
- Your permanent device IDa3f4e1b2-9c7d-4e2f-8a1b-0d3c5e7f9a2b
A UUID generated once, stored locally. Survives Chrome restarts and history resets, so navigation reports tie back to your device.
The payload assembly and POST in the extension's source (safe-browsing.js, lines 195-201).
Payload assembly and POST (src/features/safe-browsing.js, lines 195–201)
src/features/safe-browsing.jsconst payload = { url: normalizedUrl, referrer: normalizedReferrer, userId: await getSafeBrowsingUserId(),};const result = await sendClickstream(payload);UUID generation and persistence (src/core/storage.js, lines 80–87)
src/core/storage.jsexport async function getSafeBrowsingUserId() { const { safeBrowsingUserId } = await getState(['safeBrowsingUserId']); if (safeBrowsingUserId) return safeBrowsingUserId; const newId = makeSafeBrowsingUserId(); // crypto.randomUUID() await patchState({ safeBrowsingUserId: newId }); return newId;}- safe.ultrasurfing.com
Receives every navigation event from Safe Browsing. Responds with an 'action' field ('allow'/'block') the extension uses to optionally redirect the tab to a warning page.
Run this in Chrome DevTools on the extension's service worker. It intercepts outbound fetch() calls and logs every request to safe.ultrasurfing.com/check, showing the full body including the persistent userId before it leaves the browser.
- Chrome with Developer mode enabled
- UltraSurf extension installed with Safe Browsing feature enabled
// ultrasurf-safebrowsing-canary.js// Intercepts UltraSurf Safe Browsing POSTs and logs them to the DevTools console.// Run this in the service worker context for mjnbclmflcpookeapghfhapeffmpodij.(function() { const TARGET = 'https://safe.ultrasurfing.com/check'; const origFetch = globalThis.fetch.bind(globalThis); globalThis.fetch = async function(input, init) { const url = typeof input === 'string' ? input : input?.url; if (url && url.startsWith(TARGET)) { let body = init?.body; try { const parsed = JSON.parse(body); console.log('[ULTRASURF_SAFEBROWSING] outbound POST captured:', JSON.stringify(parsed, null, 2)); } catch { console.log('[ULTRASURF_SAFEBROWSING] outbound POST (unparsed body):', body); } } return origFetch(input, init); }; console.log('[ULTRASURF_SAFEBROWSING_CANARY] installed. Navigate to any page to see captured payloads.');})();- 1Open chrome://extensions, enable Developer mode.
- 2Open UltraSurf's (mjnbclmflcpookeapghfhapeffmpodij) service worker DevTools.
- 3Paste script, press Enter.
- 4Visit any site. Watch [ULTRASURF_SAFEBROWSING] logs: url, referrer, userId.
+1 more finding not shown