Is Qui-Quo safe?
Qui-Quo is high risk. When an agency user clicks Qui-Quo's Quick Login on a booking site, the script asks for the saved password. The worker POSTs to extension.qui-quo.ru/info/get-password with agency, manager, password, OS fields, returning it for decryption.…
Who publishes itООО "ДАТАГРАМ" - no other listings under this identity, 30 shared hostnames
ООО "ДАТАГРАМ" - no other listings under this identity, 30 shared hostnames
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 30 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
Quick Login Posts Agency Password Requests
When an agency user clicks Qui-Quo's Quick Login on a booking site, the script asks for the saved password.
The worker POSTs to extension.qui-quo.ru/info/get-password with agency, manager, password, OS fields, returning it for decryption.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-522
- Source
- Dynamic sandbox
You click the Qui-Quo Quick Login button for a saved booking-site account.
The selected option provides both a login value and a password record ID.
The extension requests that saved password from Qui-Quo and fills it into the booking-site form.
The same path adds agency and manager identifiers to the password request.
- Selected loginlogin=agency.operator@example-travel.ru (illustrative)
This identifies which saved booking-site account you selected for Quick Login.
- Password record IDpwdId=746392 (illustrative)
This tells Qui-Quo which saved password record to return for the selected login.
- Agency and manager IDsagencyId=58214, managerId=31827 (illustrative)
These fields tie the password request to an agency account and manager profile.
- Add-on installation IDaddonGuid=550e8400-e29b-41d4-a716-446655440000 (illustrative)
This can distinguish one browser installation of the extension from another.
- Browser and extension versionaddonVersion=2.1.37, browser=Chrome 126.0.6478.127 (illustrative)
This adds device context to the password request and helps identify the client environment.
The source code parses a successful response as JSON and forwards it as a password object; dynamic analysis did not capture a successful password-response body because the test account state was rejected before this path completed.
The code path from button click to password request and form fill
Readable copy: Quick Login click handler sends the selected password ID
data/operators/quickLogin.jsfunction loginAction(event, quickLoginStatus) { event.preventDefault(); event.stopPropagation(); const wrapper = event.target.closest('.qq-auto-login-wrapper'); const select = (wrapper || document).querySelector(".qq-quick-login-select"); const option = selectedOption(select, false); if ( select && option ) { const login = option.getAttribute("value"); const id = option.getAttribute("id"); if ( id && login ) { sendMessageToAddon("get password by id", {id, login, isActive: quickLoginStatus.isActive, inactivityReason: quickLoginStatus.inactivityReason}); localStorage.setItem("quickLoginLastSelected", option.id); } }}Readable copy: Service worker posts the password request
lib/main.jsaddWorkerMessageListener("get password by id", async function (data, worker, sendResponse) { sendResponse(true) if (data.isActive === false) { sendMessageToWorker(worker, "password response", { login: data["login"], isActive: data.isActive, inactivityReason: data.inactivityReason }); return; } const {"agency-id": agencyId, managerId} = await getStorageData(["agency-id", "managerId"]); const userSalt = checkActualUserSalt(); const actor = await getActorData(); let fd = new FormData(); fd.append('agencyId', agencyId); fd.append('pwdId', data["id"]); fd.append('managerId', managerId); fd.append('addonVersion', chrome.runtime.getManifest().version); fd.append('operatingSystem', getOS()); fd.append('addonGuid', actor['addonGuid']); fd.append('browser', actor['browser']); const passObj = await fetch(`https://${DOMAIN}/info/get-password`, { method: 'POST', body: fd }) .then(async resp => resp.ok ? resp.json() : { "error": true, "status": resp.status, "text": await resp.text() || resp.statusText }) .catch(_ => { return {"error": true, "status": 0, "text": 'Internet failure'} }); sendMessageToWorker(worker, "password response", {passObj, login: data["login"], userSalt})});Readable copy: Password response is decrypted and pasted
data/operators/quickLogin.jsaddAddonMessageListener("password response", async (data) => { if ( data.isActive === false ) { await pasteLoginAndPassword(Object.assign(data, {insertPassword: false})); createQuickLoginPopup(null, null, "alert", data.inactivityReason || 'Нет описания'); return; } if ( !data.passObj ) { createQuickLoginPopup(null, null, 'alert', `Неизвестная ошибка!`); return; } if ( data.passObj.error ) { createQuickLoginPopup(null, null, 'alert', `${data.passObj.text}`); return; } if ( data.passObj.isUserSalted ) { data.userSalt ? decryptAndPastePassword(data, data.userSalt) : createQuickLoginPopup(data.passObj.password, data, 'promt'); return; } decryptAndPastePassword(data, (data.passObj && data.passObj.id) || AGENCY_ID);});async function decryptAndPastePassword(data, salt) { const password = window.crypto.subtle ? await decrypt(data.passObj.password, `resolve${salt}`) : await decryptFromBackground('decrypt password', {password: data.passObj.password, salt: `resolve${salt}`}); pasteLoginAndPassword(Object.assign(data, {password: password}));}Readable copy: Form fields receive the login and decrypted password
data/operators/quickLogin.jsasync function pasteLoginAndPassword({login, password, insertPassword}) { const {form, usernameInp, passwordInp, loginBtn, event, specialInputs, eventsDelay, notHideInputs, isReact, loginFunction} = getElementsForQuickLoginAction(); if ( specialInputs === true ) { pasteSpecialLoginAndPassword({login, password, insertPassword}); return; } if ( typeof loginFunction !== 'undefined') { return loginFunction(login, password); } if ( usernameInp && insertPassword === false ) { usernameInp.value = login; return; } // if ( !passwordInp ) { // return; // } setInputsValue(usernameInp, passwordInp, login, password, notHideInputs, isReact); if ( event ) { simulateEvent(usernameInp, event); if (eventsDelay) { await waitingFor(() => null, 2, eventsDelay) } simulateEvent(passwordInp, event); if (eventsDelay) { await waitingFor(() => null, 2, eventsDelay) } } if ( loginBtn ) { loginBtn.removeAttribute('disabled'); setTimeout(()=> { loginBtn.click() clearInputsValue(usernameInp, passwordInp); }, 100); return; } if ( form ) { form.submit(); }}- extension.qui-quo.ru
Qui-Quo extension service that receives the password lookup request and returns the password object used by Quick Login.
Service worker pulls remote JS manifest every 5 min and injects into all tabs
Qui-Quo's service worker contacts extension.qui-quo.ru on startup and every 5 minutes for a JSON file controlling which scripts inject into your tabs.
Since the server picks the scripts, altering its response lets it run code in any tab.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-506
- Source
- Dynamic sandbox
The extension loads in your browser.
On startup and every 5 minutes, loadContentScriptsLoop() calls loadContentScriptsProduction().
The extension downloads a server-controlled list of JavaScript files and injects them into your open tabs.
The server at extension.qui-quo.ru specifies which scripts run in which sites; chrome.scripting.executeScript delivers them.
JSON object with contentScriptsVersion and content_scripts array, each entry has matches[], js[] file paths, and optional all_frames flag. Replaced in memory on every successful fetch.
- Cache-Control
- no-cache
Every 5 minutes
loadContentScriptsLoop() recursively reschedules itself with setTimeout every 5 minutes (CONTENT_SCRIPTS_POLL_INTERVAL = 5 * 60 * 1000 ms). The loop starts unconditionally on module load.
loadContentScriptsProduction(), fetches and applies remote manifest
setContentScriptsData() — replaces in-memory routing table
lib/main.jsfunction setContentScriptsData(data) { CONTENT_SCRIPTS_DATA["contentScriptsVersion"] = data.contentScriptsVersion || null; CONTENT_SCRIPTS_DATA["content_scripts"] = data.content_scripts; CONTENT_SCRIPTS_DATA["ignore_bug_fixes_for_files"] = data.ignore_bug_fixes_for_files; filterBugFixesData(data.bug_fixes);}- extension.qui-quo.ru
Primary CDN for the content-scripts.json manifest and associated JS files. Controls which scripts are injected into user tabs.
- extension.qui-quo.com
Fallback domain for manifest delivery (one of three SERVER_DOMAINS; selected by getFastestDomain() race).
- extension.qui-quo.online
Second fallback domain for manifest delivery.