Is РуТрекер VPN - расширение для доступа к сайту safe?

High risk

The extension fetches proxy server configurations at runtime from remote dead-drop URLs, allowing operators to silently reroute all proxied browser traffic.

RuTracker VPN retrieves its proxy server list from external sources including GitHub Pages, Blogspot, Google Docs, and Telegram using ROT3-obfuscated JSON, replacing the built-in server list stored in browser storage. This design gives the extension operator the ability to redirect all proxied traffic through any server without user action or an extension update. The extension also ships multiple shared proxy credentials in plaintext within its bundle and sends a persistent checkout identifier to api.hhos.ru on every subscription check.

sherechevskiyv2.4Chrome Web Store
75Risk

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI SANDBOX

Proxy and payment-API endpoints set at runtime from rotating dead-drop URLs

This VPN extension's service worker fetches remote config from GitHub Pages, Blogspot, or a Google Doc.

The ROT3-obfuscated JSON replaces the proxy list and overrides the payment host, letting the controller redirect traffic or payments.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open the extension popup to use the proxy.

The popup sends a message to the service worker requesting the current configuration.

The extension did this

The service worker fetches a configuration file from a remote dead-drop URL and the popup applies it to local storage.

The decoded response replaces the proxy server list and the payment-API host stored on the device.

02EvidenceCODE COMPARE
The code that does this

The dead-drop URLs and the fetch-with-fallback routine

What it actually does
Decode, parse, and apply the remote config (popup/main.js)popup/main.js
function getDcnfJson(data){
  var c=showx(data.trim(),3);          // ROT3 decode
  var j=JSON.parse(c);
  if(j['u'] && j['u'].length>0){
    ucheckout="https://"+j['u'];
    chrome.storage.local.set({"apiurl":j['u']});   // override payment host
  }
  if(j['s']!==undefined){
    chrome.storage.local.set({"servers":JSON.stringify(j['s'])});  // replace proxy list
  }
}
function showx(text,shift){            // Caesar/ROT shift
  return text.split('').map((ch)=>{
    if(ch.match(/[a-z]/i)){
      const code=ch.charCodeAt(0);
      const offset=code>=65&&code<=90?65:97;
      return String.fromCharCode(((code-offset-shift+26)%26)+offset);
    }
    return ch;
  }).join('');
}
03EvidenceOPAQUE REVEAL
Why you can't catch this in DevTools

The configuration file is served as JSON with a uniform ROT3 letter shift applied to the whole document, so the keys and values are not human-readable on the wire. Applying the inverse shift recovers the JSON, which carries the payment-API host (key 'u') and a list of base64-encoded proxy-server records (key 's'). Values shown are from the response observed during dynamic analysis (the proxy record is illustrative of the decoded host:port:login:pass|expiry|country format).

What's actually being sent
{"u":"obratx.shop","s":["MTY4LjgxLjc1LjEyNjo5MDkxOnRGQzQ3TjpCRHBtSnB8MjAyNi0wNy0xNHxpbg=="]}

// 's' entry base64-decodes to:
//   168.81.75.126:9091:tFC47N:BDpmJp|2026-07-14|in
//   host : port : login : pass | expiry | country
//
// 'u' = obratx.shop  -> chrome.storage.local['apiurl'] (overrides default api.hhos.ru)
04EvidenceNETWORK CAPTURE
Captured request
GEThttps://s-extension.github.io/DataExst/data.json
HTTP 200, ~4.6 KB ROT3-obfuscated JSON. Decoded key 'u' resolved to obratx.shop (replacing the default api.hhos.ru payment host); key 's' carried 65 base64-encoded proxy-server records that were written to chrome.storage.local['servers']. Observed during dynamic analysis on version 2.3.
Headers
Cache-Controlno-cache
05EvidenceTHIRD PARTY LIST
Locations the configuration was fetched from, in fallback order
  • s-extension.github.io

    Primary config source (GitHub Pages, path /DataExst/data.json). Served the proxy list and payment host during analysis.

  • dtxtension.blogspot.com

    Fallback config source (Blogspot page /p/kronshtat.html) used if the primary host fails.

  • docs.google.com

    Fallback config source (a Google Docs document) used if the first two fail.

06EvidenceARTIFACT
Reproduce it yourself

Fetches the extension's configuration dead-drop URL, reverses the ROT3 letter shift, and prints the decoded JSON along with each base64-decoded proxy-server record so you can see the payment host and server list the extension would apply.

RequiresNode.js 18+
decode-rutracker-config.js · js
// node decode-rutracker-config.js
// Reverses the ROT3 obfuscation the extension applies to its remote config.
const URLS = [
  'https://s-extension.github.io/DataExst/data.json',
  'https://dtxtension.blogspot.com/p/kronshtat.html',
];

function rot(text, shift) {
  return text.split('').map((ch) => {
    if (ch.match(/[a-z]/i)) {
      const code = ch.charCodeAt(0);
      const offset = code >= 65 && code <= 90 ? 65 : 97;
      return String.fromCharCode(((code - offset - shift + 26) % 26) + offset);
    }
    return ch;
  }).join('');
}

(async () => {
  for (const url of URLS) {
    try {
      const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
      if (!res.ok) continue;
      const wire = (await res.text()).trim();
      const decoded = rot(wire, 3);          // showx(data, 3)
      const j = JSON.parse(decoded);
      console.log('source        :', url);
      console.log('payment host  :', j.u || j.url || '(none)');
      const servers = j.s || j.servers || [];
      console.log('server records:', servers.length);
      servers.forEach((b64, i) => {
        const rec = Buffer.from(b64, 'base64').toString('utf8');
        console.log(`  [${i}] ${rec}`);  // host:port:login:pass|expiry|country
      });
      return;
    } catch (e) {
      console.warn('skip', url, e.message);
    }
  }
  console.error('no config source responded');
})();
How to run it
  1. 1
    Save this file as decode-rutracker-config.js.
  2. 2
    Run `node decode-rutracker-config.js` (Node 18+ has a built-in fetch).
  3. 3
    Read the printed payment host and the base64-decoded proxy-server records (host:port:login:pass|expiry|country).
SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-522
SourceAI SANDBOX

Shared proxy usernames and passwords shipped in plaintext in the bundle

This proxy/VPN extension ships its server list as plain text in js/conf.js.

Several entries include a username/password as host:port:login:pass, visible in the files.

The pairs recur across servers, auto-answering proxy-auth prompts.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You enable a proxy server from the extension popup.

The selected server entry is parsed into host, port, login, and password.

The extension did this

When the proxy asks for authentication, the extension replies with the username and password embedded in its bundle.

The onAuthRequired handler returns the login/password parsed from the host:port:login:pass entry in js/conf.js.

02EvidenceFIELD TABLE
Distinct plaintext proxy credential pairs found in js/conf.js (passwords redacted)
FieldValueWhy it matters
Shared port-20000 login
DHaNuRsEhGAL007 : <redacted>One username/password pair reused across 20-plus proxy server IP entries on port 20000.
Discord-named proxy login
discord_0.0.2 : <redacted>Credential for srv10.undiscord.com on port 443.
freeruproxy.ink login
openproxy : <redacted>Credential reused for nl-hub and us-hub freeruproxy.ink entries on port 443.
accessyoutube login
ytpass_50JtWMp4 : <redacted>Credential for srv11.accessyoutube.com on port 443.
03EvidenceCODE COMPARE
The code that does this

Credentials in conf.js and the handler that supplies them

What it actually does
Parsing the tuple and answering proxy auth (js/bg.js)js/bg.js
// getProxyData(): split the selected entry into its parts
const [host,port,login,pass]=dpx.split(":");
let ob={host,port,login,pass};
proxyData=ob;

// authHandler: respond to the server's auth prompt with those values
authHandler=function(details){
  if(proxyData && proxyData.login && proxyData.pass){
    return { authCredentials:{ username:proxyData.login, password:proxyData.pass } };
  }
  return { cancel:true };
};
chrome.webRequest.onAuthRequired.addListener(authHandler,{urls:["<all_urls>"]},["blocking"]);
04EvidenceARTIFACT
Check if you're affected

Reads js/conf.js from the unpacked extension, parses the serverConfigs array, and lists the distinct login:password pairs and how many server entries reuse each one. Run it against the extension package to confirm the credentials are present in plaintext.

RequiresNode.js 18+
extract-proxy-creds.js · js
// node extract-proxy-creds.js /path/to/extracted/js/conf.js
const fs = require('fs');
const path = process.argv[2] || 'js/conf.js';
const src = fs.readFileSync(path, 'utf8');

// Pull the serverConfigs array literal
const m = src.match(/serverConfigs\s*=\s*\[([\s\S]*?)\]/);
if (!m) { console.error('serverConfigs array not found'); process.exit(1); }

const entries = [...m[1].matchAll(/['\"]([^'\"]+)['\"]/g)].map((x) => x[1]);
const byPair = new Map();
for (const e of entries) {
  const parts = e.split(':');
  if (parts.length >= 4) {                 // host:port:login:pass
    const login = parts[2];
    const pass = parts[3];
    const key = `${login}:${pass}`;
    byPair.set(key, (byPair.get(key) || 0) + 1);
  }
}

console.log(`entries: ${entries.length}, distinct credential pairs: ${byPair.size}`);
for (const [pair, count] of byPair) {
  const [login] = pair.split(':');
  console.log(`  login=${login}  reused across ${count} server entr${count === 1 ? 'y' : 'ies'}`);
}
How to run it
  1. 1
    Unpack the .crx (it's a zip).
  2. 2
    Save as extract-proxy-creds.js.
  3. 3
    Run `node extract-proxy-creds.js path/to/extracted/js/conf.js`.
  4. 4
    Review the credential-pair count and reuse per login.

Data recipients

s-extension.github.iodtxtension.blogspot.comdocs.google.comapi.hhos.ru
Updated 17 September 2026llngkcnndicadfcbikbjnhbcikpmaknj