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.
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.
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.