Bugcrowd LevelUp 0x07: How to Do Chrome Extension Code Reviews

This is the blog counterpart of my 22 August 2020 talk for Bugcrowd’s event, LevelUp 0x07.

A year ago right now, I was an SRE, and my only thoughts of Chrome extensions were that they were 1. something that existed, and 2. useful but also prone to news-making security problems. This year, though, I moved to an appsec engineer role, and suddenly they’re a pretty big part of my professional life. 

Since I became an engineer, I’ve been fascinated by the kinds of threats that live in places people are less prone to suspect – or even to consider. Once I learned that my current team sometimes review extensions, in addition to the third-party vendor testing that’s a more central part of our responsibilities, I realized I’d found another one of those somewhat neglected areas of security review. Challenge: accepted.

So that’s why I like Chrome extension reviews, the process of which I’ll lead you through in just a few paragraphs, I promise. But why should you care about this? You as an appsec enthusiast or engineer, you as a bug bounty hunter, you as someone else with an interest in the strange corners of the internet that vulnerabilities can hide in. 

Ok, but why do these reviews?

Extensions open up a really interesting attack surface. They’re part of the browser, so embedded in the client, which can let them sidestep some of the protections an unaltered browser might offer. Beyond that, people often have alert fatigue and don’t think that much of a warning like “this extension can read and change your data on every website you visit.” It should be alarming, but that warning applies to so many extensions that it’s easy for people to ignore. 

They also update themselves automatically, on a timetable determined by the browser using the update path that has to be part of any extension listed in the Chrome Web Store. So you have third-party code nestled right in where the business happens, AND said third-party code can change with no warning at a cadence that isn’t set by the end user. 

Last year, Google made some moves to bolster user security, making permission scopes around Gmail and Google Drive more granular, while requiring developers using these scopes to use the smallest access to information possible. Useful; not a panacea. Alas, the Chrome API permission scopes are still fairly broad.

Google has done some mass culls of extensions, and the team behind CRXcavator (which we’ll get to shortly) has uncovered a lot of, uh, interesting extension functionality too. 

However, the automatic updates still leave even polished products open to strange second and third acts. Bad actors buy popular extensions, which then give them a direct line to thousands and thousands of browsers, which are used by folks who generally aren’t extremely vigilant about monitoring their installed extensions. By which I mean: just people, in general. Most of us don’t do a monthly extensions check, which is understandable – but it does leave the door open to some interesting things. 

Including, happily, bug bounties. 

How to review Chrome extensions: tl;dr edition

When I review a Chrome extension, I start by building a story about the extension I’ve been assigned. I research the proposed use case, read the extension’s Chrome Web Store page and other online descriptions, and do a cursory review of the code and try to learn these things:

What does this extension say it wants to do?

What does it actually do? Is that answer different?

What permissions does it need to have to accomplish its stated mission? 

Do the permissions or code make anything possible outside of the extension’s stated mission?

This is where I figure out where to focus. Most extensions, like most people, are on the up and up. But – and sit down for this one – sometimes developers get sloppy when they’re writing extensions. I wouldn’t even call this the developers’ fault a lot of the time, because Chrome API permissions are broad, and most devs are strapped for time. It’s a recipe that makes for some messy stuff ending up in extensions. 

How to review Chrome extensions: in depth

Once I’ve got a sense of what the extension is supposed to do and what it actually seems to be doing, I work through the code in three steps. 

First, I paste the extension’s Web Store ID (long, all lowercase letters, part of the URL of its Web Store page) into CRXcavator. CRXcavator gives you an aggregated risk score for the extension, based on several metrics. The most relevant to our needs are the content security policy and Chrome API permissions. (Others include externally hosted Javascript libraries and its Chrome Web Store Score.) Here’s CRXcavator’s score for one of its associated extensions.

We’re going to focus more on other parts of the manifest for the purposes of this post, but content security policy can also make a lot of good, weird stuff possible that perhaps shouldn’t be. Like I said, this setting can literally override the settings of individual websites, so it’s worth spending some time looking at the CSP of an extension as part of your broader exploration.

CRXcavator gives me an early indicator of where risk might live. You can also read through the code on their site, or you can do what I do and use an extension like CRX Extractor/Downloader to get it locally and explore in your text editor. 

Once I have the code in front of me, I read the manifest.json file.

A text editor showing a sample manifest.json file

This is where I spend a lot of my time, because it tells me a lot about what’s possible. These files can get long and intricate: there are 56 different fields that can be included. However, most of the extensions I see don’t use a ton of those. Only three are required, and only four are marked in the docs as recommended. That excludes content_scripts, though, so pretty much all extensions have to go a bit beyond the minimum in order to be able to actually do much. 

In manifest.json, I look most closely at the permissions listed, both the Chrome API permissions included and the URLs cited. From there, I read through the different Javascript files being used and what they’re going to be allowed to do. 

Finally, I read the code. I’m looking for weird uses of permissions (chrome.identity and chrome.cookies are fun, but I look up all uses of any permission methods used), any connections to third-party servers, and anything else that seems overly ambitious. 

Assessing the risks

Permissions are the sketch of what can happen; the code shows what will actually happen with them. Because of that, I spend a fair amount of time on those permitted Chrome API operations. The permissions don’t encompass all the possible risk in an extension, but they cover a lot. 

Here’s what I’m looking for across all of the extension code.

Is it sending data? If so, to where? I want to see innocuous and clearly described changes to the appearance and contents of the DOM. I do not want to see data being collected or sent. If I do see data being collected or sent, I have found the new primary focus of my time for the duration of my testing. There are legitimate reasons to do this, of course, but they need to be safely done and well justified. 

Is it storing anything unsafely in your browser? I don’t want to see plaintext secrets stored in the browser, not in session storage and especially not in local storage. This is something I touch on early because of my job’s context; it may be less important to you, but it’s still worth looking into.

Do the scopes of the Chrome API permissions let the extension snoop on everything we’re doing? We don’t like that much either. 

Is the extension snooping on everything you’re doing and then sending it somewhere else? Oh dear. 

Again, there’s opt-in functionality that can justify all of this. But it does need to be justified (from my point of view as a reviewer of risk), and then it needs to be done safely.

This second part is where the bug bounty hunter can have a lot of fun. 

Anatomy of an extension

Let’s start with manifest.json, since it’s where I spend most of my time and it most concisely shows a lot of the weird possibilities in a given extension. The key parts I look at are the general permissions of the extension and then content_scripts and their associated permissions 

Seeking bounties? manifest.json is more likely to be a hint at what will lie in the rest of the Javascript, which is where you’ll find what you’re actually looking for. The really interesting stuff will be in the code, of course. Some extensions have a single fairly simple JS file; others can have files upon files. The official size limit for a CRX file, which is the zip-formatted file format for a Chrome extension, is 2 GB. That’s a lot of room for function and also just utter mayhem. 

Most extensions also have an HTML component, often for creating a customized menu for the extension. If you see popup.html, it’s likely this. These are subject to the same vulnerabilities as any webpage, so it’s worth testing for XSS and anything else you’d try to find in a front-end. 

Permissions and their risks

Permissions for an extension come in two forms: URLs (specific ones or patterns) and Chrome API permissions. These cover all kinds of possible interactions: alerts, accessing and altering browser storage, accessing bookmarks and tabs, among many others. 

There’s a safe use for every permission, but there are also permissions that, if included, are a great place to start when reviewing an extension to make sure it’s safe for the user. Problems arise when the dev needs to invoke a permission or a manifest.json field, like web_accessible_resources, that is necessary for some perfectly ordinary extension function, like access packaged resources to use in the context of a web page. Unfortunately, that same permission can be used to execute remote scripts. Like most anything involving Javascript, there are risks that come with using the tools at hand. 

Google has a great guide where they list the permissions they think are most dangerous, with surprisingly high specificity. Removing the escape key’s ability to exit full-screen? Spying on the user with their own camera or microphone? Nightmares! But all are possible with the correct, terrible combination of host and API permissions.

Let’s look at a few of these permissions, now that I’ve spent some time broadly demonizing them.

URLs and pattern matching

For an extension to access a site, its URL must either be specified or match a pattern. Conveniently for devs, there are patterns that make matching easy. Conveniently for bug bounty hunters, this means that lots of devs leave the doors more open than is ideal because they’re trying to futureproof their extension (or, less commonly, because it’s something that has any business doing something on every site you visit). manifest.json can include and exclude URLs based on these patterns, but including is much more common. 

The patterns and matches I’m most wary of are *://*/* and <all_urls>, with a close second anything that matches a common host (google.com, for instance) with asterisks before or after. *://*/* and <all_urls>, however, match the entire internet, so long as the address being visited is a permitted protocol (http, https, ftp, or file). These permissions give an extension carte blanche to work on any website you visit. Your email, your bank, your employer’s web portal when everyone’s working at home… I find this permission hard to justify. It isn’t that no extensions should use it, but it should be relatively few. Google themselves declares wide-open wildcard patterns to be the highest-risk type of permission, with <all_urls> the next most dangerous. 

URL permissions can be at the top level permissions in an extension’s manifest.json or a match connected to a particular content_script. 

On top of allowing an extension free access to all of the user’s browsing, including those wide-open permissions also allows cross-origin requests with none of the usual barriers your browser would present. It also allows Javascript injection on any site the user visits. Fun!

That said, one reason people do this (for non-nefarious reasons) is precisely to avoid CORS errors. However, I promise this can be done effectively and more securely – it just requires a little more work to narrow down exactly what URL or pattern needs to be put into place. Beware asterisks; especially beware asterisks with only a host or no host at all. On the plus side, it opens up a lot of interesting territory for the bug bounty hunter. Silver lining, I guess? 

webRequest

webRequest lets the extension change and add HTTP headers. It also lets it add listeners and change behavior upon receiving the first byte of a response, at the initiation of a redirect, when a request is completed, and anywhere else in the lifecycle of a web request. This can, of course, be used totally legitimately. However, it’s also been used to intercept and forward user traffic, so if it shows up in the manifest, it’s worth looking at every one of its methods that’s used in the code. Another check on this behavior is that this permission also has to be paired with an appropriate host permission to be able to do anything. 

This permission is likely to be in flux in the future, but for now it’s still here and still weird enough that even the EFF has weighed in on its present and future

cookies

The cookies permission offers methods around getting, setting, and removing cookies. This can result in a lot of weird possibilities, particularly when paired with gleaning data and sending it elsewhere. Fortunately, the solution for this is the same as for XSS: set that HttpOnly flag on your cookie so it isn’t accessible via the Javascript of a content_script. However, if people faithfully set that flag, my job might not need to exist, so it’s worth mentioning. This also requires appropriate host permissions to be able to do anything. 

The code, more generally

The manifest.json file can point to a few different uses for Javascript files. The background section of the file, which is optional, includes code that is loaded when needed, including when a content script sends a message, when certain events happen, or when the extension is first installed or updated. In contract, content_scripts (which, as I mentioned, have their own permissions) run in the context of webpages. 

When I read these Javascript files, the things I watch for are shaped by the particular concerns of my team at my very large company. My primary concerns, however, are pretty universal: how the extension might be accessing, storing, and sending information. I watch for any input text being saved or otherwise manipulated. I find out what is being collected, if the collection and handling of that information supports the story I’ve put together about the extension through my research, and, if not, where the two diverge. After that, I check for http requests. If I find them, I look at what they’re doing. What’s being stored and sent? How is it being sent, and where is it going? Is it something the user of the extension will have opted into via an account, or is this being done more quietly? 

I also watch for tells like the use of innerHtml – basically, if it’s something you’d watch for when reviewing webapp code, it’s worth trying on the menus and other popups that are part of an extension. 

Good news for bug bounty hunters

Companies’ official Chrome extensions can create weird and surprising vulnerabilities for their customers. Think about the permissions involved in an extension that alters text typed into a field. It can read what you type, store it, and send it to be judged by an algorithm across the internet, with the goal of furnishing, say, spelling corrections in a way that reads as nearly instant. We type a lot of sensitive things into text fields – passwords, credit card numbers, questions to our insurance companies – and I personally don’t trust an extension to have fine enough control to not at least process the text written in fields other than the ones it’s supposed to be targeting. 

Let me tell you: even well-funded companies have too-loose permissions and other weird stuff happening in their extensions. It’s the nature of this very particular medium. Let me also tell you: there are bounties for reporting extension issues, both from Google and sometimes from the companies that make them.  

Because extensions are both everywhere and, in my experience, undervalued as a bug bounty target, I think the world of extensions in particular would benefit from your attention. Beyond what the permissions and code can yield the first time you look at an extension, the particular opportunity of how extensions are updated offers a lot of possibilities for repeated investigation. The releases for extensions, unlike those for websites, are numbered. You know if something changed, enough that Chrome has a page for it. Chrome also has its own program, in addition to extensions being in scope for many companies with existing bug bounty programs. People get paid for extension findings.

Extensions have depths that can contain a lot of, let us say, interesting things. I hope they’re inviting enough for you to dive in and start finding vulnerabilities that make this a safer ecosystem. 

Want to learn more?

If the dazzling galaxy of links throughout this post wasn’t enough, I’d suggest two more. This tutorial is how I created my first extension to install locally to better understand the process. This 2016 DEF CON talk isn’t completely applicable to the capabilities of extension today, but it’s an excellent guide to how good and weird it has been possible to get with extensions.