Stylish, a chrome extension with over 2 million users got called out in 2018 exfiltrating every URL you go to, caught by Robert Heaton in this blog post. He also made a follow up when it came back here.
It’s back again, exfiltrating the same data as before.
Surprisingly, they actually got taken down after numerous articles:
- https://www.theregister.com/2018/07/05/browsers_pull_stylish_but_invasive_browser_extension/
- https://www.bleepingcomputer.com/news/software/chrome-and-firefox-pull-stylish-add-on-after-report-it-logged-browser-history/
- https://www.kitguru.net/tech-news/featured-tech-news/ryan-burgess/stylish-browser-extension-found-stealing-users-internet-browsing-history/
- http://www.xinhuanet.com/english/2018-07/09/c_137310755.htm
- https://sensorstechforum.com/stylish-extension-stole-browsing-history-chrome-firefox/
This was great, but now it’s back with a “verified publisher” badge and it’s a “featured” extension now. How did they manage this?
I’m not sure, but I’m going to show you what they are doing.
Now, I should point out a few things about their privacy policy, which states that they explicitly do sell personal data, as stated here, but on the Chrome Web Store in a larger font on the home page, it says that they don’t sell personal data:

Privacy? What Privacy?
And the approved use cases do not mention “selling data for business purposes”, it actually explicitly says that you are not allowed to do this, as shown here.

So again, why are they a verified publisher and why is this extension featured?
Now, I’m not a lawyer and this whole blog post is obviously my opinion, so let’s dig into the technical stuff.
How the data exfiltration works
Every time you go to a new page, a new request is sent off in the background service worker, the script that runs in the background. You don’t have to be using the extension for it to do this, it doesn’t have to be applying any styles but it does get back “styles” when you send off this request.

Sample request from visiting a webpage, you get a new request for every webpage
This POST request is obfuscated, which in my opinion is to make it harder for people to see what it’s doing or to get around the Chrome Web Store publishing review process, or perhaps even both, and I’ll explain why.
They build a payload like this:
{
"gp": "https://userstylesapi.com/top/styles",
"klm": "https://www.google.com/search?q=test+google&rlz=1C5OZZY_enGB1156GB1156&oq=test&gs_lcrp=Eg...",
"ver": "https://www.google.com/search?q=test+google&rlz=1C5OZZY_enGB1156GB1156&oq=test&gs_lcrp=Eg...",
"knl": "",
"dig": "2008511158",
"tmg": "link",
"trp": "exthead",
"st": "1772053130391",
"ch": "9",
"di": "a3e3e2a81",
"pxe": "Lk85G2SeiETEPNOWlrR15mLsZDsC",
"vmt": "6",
"lav": "21",
"wv": "1",
"gr": "3.4.10",
"craz": "AAEAAAAAAG0RCwIRdAAAAAAAAAAAAAAAAAAAAAAAAAA="
}
Where gp is your current URL, klm was your previous URL and pxe is your unique identifier, amongst other data.
This payload then goes through a multi stage process of:
- URL encoding to a query string, for example (gp=https://example.com…&klm=https://google.com…)
- Double base64 encoded
- JSON stringified, then base64 again
- Columnar transposition cipher, the base64 string is split into 48-character rows, then read column-by-column instead of row-by-row, scrambling the text
- AES-256-CBC encrypted using a symmetric key hardcoded in the extension source code
- Base64 encoded one final time
Then it gets posted to their servers.
This seems a little intense to get some fun styles, right?
I do like the use of a hardcoded encryption key as it makes my life so much easier, although I do wonder if they’ve heard of this revolutionary “asymmetric encryption” where they can avoid having this hardcoded key for encryption and decryption…
If you want to read the messages on your client and decode them, you can run this script:
async function decodeStylish(blob) {
const key = await crypto.subtle.importKey("jwk",
{alg:"A256CBC",ext:true,
k:"MaQ2KBEEiYcOcSCfszxMBVrKsXK3hxGmxZ8Zjq50KZg",
key_ops:["decrypt"],kty:"oct"},
"AES-CBC",false,["decrypt"]);
const raw = Uint8Array.from(atob(blob), c => c.charCodeAt(0));
const dec = await crypto.subtle.decrypt({name:"AES-CBC",iv:raw.slice(0,16)}, key, raw.slice(16));
const rows = new TextDecoder().decode(dec).split("\n");
let b64 = "";
for (let col = 0; col < rows[0].length; col++)
for (const row of rows) { const ch = row[col]; if (ch && ch !== " ") b64 += ch; }
const obj = JSON.parse(atob(b64));
const once = atob(obj.e.replace(/-/g,"+").replace(/_/g,"/"));
const qs = atob(once.replace(/-/g,"+").replace(/_/g,"/"));
return Object.fromEntries(new URLSearchParams(qs));
}
Now the problem with this is that it gets so much more information than it needs, for example, with their platform, they only really need to know the hostname (google.com) as opposed to the full URL with the path and query parameters (https://google.com/q?=test+google) as for example, that lets them see everything you're searching for.
Now, it’s not uncommon for Chrome Extensions to exfiltrate every full URL you visit, sending it back to their servers (although this is explicitly banned unless you make it obvious to the user), but this is probably the most extreme obfuscation I’ve come across.
this is what 1 too many base64 encodings do
Would you want all of your browsing data to get sent off to some data company? No?!
Right now, many millions of people are having all of their browsing data tracked by these data companies.
Imagine if you had signed an NDA and you googled things in relation to that NDA, would you be okay with that? What if someone wanted to start a spearphishing campaign against you or your company and knew all of the web based software you used? Imagine the consequences.
Now with this all said, Stylish was opensource, and when it got bought by SimilarWeb the open source community came through and forked it! This fork is called “Stylus” and has 900k users.
It doesn’t track you, it caches the styles on your local machine and doesn’t phone home every time you visit a new page. I don’t even think they need to use 4 layers of base64 to do that! If you’re using stylish, keep all of this in mind, and consider using the extension that doesn’t send everything URL you visit to a data company, at the time of writing of course, get stylus here and check out the source code here.
If you're looking for constant monitoring of your companies extensions or a one off scan to see what's going on, check out amibeingpwned.
