Is INSTAVPN - Инстаграм без блокировки safe?
INSTAVPN fetches remote configuration that controls its proxy servers and checkout API domain.
INSTAVPN requests a remote configuration file that can replace the proxy server list stored by the extension and change the checkout API host it contacts. When a user enables the VPN, the extension applies the selected proxy through Chrome proxy settings and supplies embedded proxy credentials for authentication challenges.
Who publishes itsherechevskiy - 15 other listings from the same operator, 6 of them carrying a finding
sherechevskiy - 15 other listings from the same operator, 6 of them carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
15 other listings published from this account, 283k+ users between them. 6 of them carry a finding.
Shared hosts - 3 hostnames
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Unsigned remote config swaps INSTAVPN's proxy servers and payment domain
Opening the INSTAVPN popup fetches an unsigned config file that supplies the proxy servers you connect through and can replace the hardcoded payment domain.
Nothing verifies where the file came from.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
You open the INSTAVPN popup to connect to a proxy.
This happens every time the popup loads, before you click anything.
The extension fetches an unsigned remote file and lets it set the proxy list and payment domain it uses.
Nothing checks that the file came from the vendor before the extension trusts its contents.
200 OK, roughly 4.6 KB body, no signature or integrity header. Decoding the body (see below) revealed a payment domain override and 65 proxy records.
- Cache-Control
- no-cache
The response is not plain JSON, every letter is shifted 3 places before the extension reverses the shift and parses it. Reproduces that shift on the confirmed payment domain and proxy record (illustrative, password redacted).
{ "u": "marketpays.shop", "s": [ "MTk2LjE2LjUuMTQxOjkxNDg6NG94ZU4yOjxyZWRhY3RlZD58MjAyNi0xMC0yN3xnYnxybWQ=" ]}The decode-and-trust path: no verification between fetch and use
What each name means
js/conf.js (annotated)// Fallback checkout domain used until the remote config overrides itvar defaultCheckoutDomain = "https://api.hhos.ru";// Three places the remote config can come from, tried in ordervar remoteConfigUrls = { githubPages: "https://s-extension.github.io/DataExst/data.json", blogspotFallback: "https://dtxtension.blogspot.com/p/kronshtat.html", googleDocFallback: "https://docs.google.com/document/d/1X5wD-cqXbTdEMNu5CIRSWy20G7u7LwKVdoGu8ikzCj0/edit"};Fetch the first config URL that responds
js/bg.js (annotated)async function fetchRemoteConfig() { const urls = [remoteConfigUrls.githubPages, remoteConfigUrls.blogspotFallback, remoteConfigUrls.googleDocFallback]; for (const url of urls) { try { return await fetchWithTimeout(url); // no signature check on the response } catch (error) { console.warn("config fetch failed", error.message); } } throw new Error("No data");}Reverse the Caesar shift, then trust the JSON unconditionally
popup/main.js (annotated)function caesarDecode(text, shift) { return text.split('').map((char) => { if (/[a-z]/i.test(char)) { const code = char.charCodeAt(0); const offset = code >= 65 && code <= 90 ? 65 : 97; return String.fromCharCode(((code - offset - shift + 26) % 26) + offset); } return char; }).join('');}function applyRemoteConfig(rawResponse) { const decoded = caesarDecode(rawResponse.trim(), 3); const config = JSON.parse(decoded); // no schema or signature check if (config.u) { checkoutDomain = "https://" + config.u; // silently replaces the payment domain chrome.storage.local.set({ apiurl: config.u }); } if (config.s) { chrome.storage.local.set({ servers: JSON.stringify(config.s) }); // proxy list }}- Proxy address196.16.5.141:9148
The host and port the extension will route your traffic through if you pick this server.
- Proxy login4oxeN2
Username the extension sends when Chrome's proxy authentication dialog appears.
- Proxy password<redacted>
Password sent alongside the login. Shown redacted here; shipped in plaintext inside the base64 record.
- Expiry date2026-10-27
Date after which the vendor expects this proxy record to stop working.
- Country codegb
Country the proxy exit node is presented as being located in.
- s-extension.github.io
Primary source of the unsigned proxy/payment config, fetched on every popup open.
- dtxtension.blogspot.com
Fallback config source read from a Blogger post body if GitHub Pages fails.
- docs.google.com
Second fallback config source, parsed out of a public Google Doc's internal JSON.
- marketpays.shop
Payment API domain the remote config can substitute for the hardcoded default.
- api.hhos.ru
Hardcoded default payment API domain, used until the remote config overrides it.
Reverses the extension's own Caesar shift on a saved config response and lists the payment domain override and each decoded proxy record.
- Node.js 18+
const fs = require('fs');function caesarDecode(text, shift) { return text.split('').map((char) => { if (/[a-z]/i.test(char)) { const code = char.charCodeAt(0); const offset = code >= 65 && code <= 90 ? 65 : 97; return String.fromCharCode(((code - offset - shift + 26) % 26) + offset); } return char; }).join('');}const rawPath = process.argv[2];if (!rawPath) { console.error('Usage: node decode-instavpn-config.js <path-to-saved-response-body>'); process.exit(1);}const raw = fs.readFileSync(rawPath, 'utf8').trim();const decoded = caesarDecode(raw, 3);const config = JSON.parse(decoded);console.log('Payment domain override:', config.u || '(none present)');console.log('Proxy record count:', (config.s || []).length);(config.s || []).forEach((entry, i) => { const record = Buffer.from(entry, 'base64').toString('utf8'); const [creds, expiry, country, type] = record.split('|'); const [host, port, login] = creds.split(':'); console.log(`#${i + 1}`, host + ':' + port, 'login=' + login, 'pass=<redacted>', expiry, country, type);});- 1Save the raw body of a GET to the config URL as response.txt.
- 2Run: node decode-instavpn-config.js response.txt.
- 3Read the printed payment domain and proxy records.
Where it sends data
Destinations our analysis observed INSTAVPN - Инстаграм без блокировки contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.
- docs.google.com
INSTAVPN - Инстаграм без блокировки sends data to docs.google.com. 20 other extensions we have analysed send data here.
- s-extension.github.io
INSTAVPN - Инстаграм без блокировки sends data to s-extension.github.io. 7 other extensions we have analysed send data here.
- dtxtension.blogspot.com
INSTAVPN - Инстаграм без блокировки sends data to dtxtension.blogspot.com. 6 other extensions we have analysed send data here.
- api.hhos.ru
INSTAVPN - Инстаграм без блокировки sends data to api.hhos.ru. One other extension we have analysed sends data here.