X Marks the Spot - Secrets in Source Maps

Authored by Lachlan Davidson


Background

As the JavaScript front-end ecosystem has evolved in recent years with new technologies such as TypeScript and JSX emerging, there have also been new challenges for compatability and effiency. Modern frameworks encourage programmers to split their codebase across many files, rightfully so; gone are the days of a handful of jQuery files, each filled with 5,000 lines. However, you can't serve hundreds of JavaScript files to clients, so modern build tools 'bundle' them up, magically make the code backwards compatible, and minify it.

Introducing Source Maps

Ok, WTF are source maps? Tl;dr, because JavaScript code is always bundled into unreadable gibberish now, error messages on production builds are useless to developers.

An error message from minified JavaScript
Figure 1: An error message from minified JavaScript

Source maps are files used to associate the gibberish with the original line of human-written code that threw an error. How do they do this? They keep an entire copy of the original source code alongside a "map" which correlates the gibberish to the code. This gives developers nicer error messages.

Browsers look for comments within JavaScript files which look like this:

//# sourceMappingURL=main.js.map

Then, when displaying errors, they are associated with the original code.

A nicer error message from minified JavaScript, thanks to the source map
Figure 2: A nicer error message from minified JavaScript, thanks to the source map

So, why do we care?

Alright, we're hackers, not programmers, what's the deal? Well, notice the "entire copy of the original source code" part? Well that is why we care. Source maps usually contain all of the front-end source code, which is why unless you know what you're doing, don't publish source maps to production.

This can be super useful for:

  • Finding hidden API endpoints
  • Finding information about privileged functionality
  • Locating vulnerabilities, such as DOM-XSS and CSRF
  • Extracting GraphQL schemas

Better than that? Lots of modern web applications share one codebase between frontend and backend.

For example, once or twice I have seen something like the following:

// config/tokens.js
export const TOKENS = {
    GA_TOKEN: 'UA-xxxxxx',
    GOOGLE_MAPS_API_KEY: '(...omitted...)',
    DB_CONN_STRING: 'mongodb://<username>:<password>@mongodb.net/prod'
}

Figure 3: Code extract from a source map containing normal API keys, but also a mongodb connection string

Wait what? 'mongodb://<username>:<password>@mongodb.net/prod' That doesn't look like something we should be able to see! So what's happened here? Well, it appears that this file is likely shared between the backend and the frontend. As such, even though DB_CONN_STRING has likely been tree-shaken out of the front-end code, it remains in the source map.

Time to shill

So because I found all of this pretty cool, I decided to write a Burp Suite extension to automagically find and parse sourcemaps. Soon™ it will let you export the source to the file system.

It creates issues when it suspects there are source maps, and when it finds valid source maps. The source code of the front-end can then be viewed from a tab within burp.

Check it out here.