Εν συντομία: Στην ασφάλεια του PHP unserialize ισχύει ένας απλός κανόνας: δεδομένα που έρχονται απ' έξω να μην επεξεργάζονται ποτέ με unserialize(). Χρησιμοποιήστε αντί γι' αυτό JSON. Όπου το unserialize() πρέπει να παραμείνει από παλιό κώδικα, περιορίστε το με allowed_classes => false και ελέγξτε την αυθεντικότητα των δεδομένων με υπογραφή.
Τι μπορεί να πάει στραβά με το unserialize()
Η PHP μπορεί να μετατρέπει πίνακες και αντικείμενα με το serialize() σε αλφαριθμητική και να τα ανακτά με το unserialize(). Αυτό είναι βολικό για την αποθήκευση δεδομένων σε cookies, caches ή πεδία βάσης δεδομένων. Το πρόβλημα: το unserialize() δημιουργεί παράλληλα και αντικείμενα τυχαίων κλάσεων που μπορούν να φορτωθούν στην εφαρμογή.
Κατά τη δημιουργία και το αργότερο κατά την καταστροφή ενός τέτοιου αντικειμένου, η PHP καλεί αυτόματα ορισμένες μεθόδους, όπως οι __wakeup(), __unserialize() ή __destruct(). Αν κλάσεις στο έργο ή σε βιβλιοθήκες περιέχουν τέτοιες μεθόδους με ενέργειες αρχείων, βάσης δεδομένων ή συστήματος, ένας επιτιθέμενος μπορεί να τις καταχραστεί μέσω παραποιημένων δεδομένων. Οι ειδικοί μιλούν για PHP Object Injection. Ανάλογα με τις υπάρχουσες κλάσεις, το αποτέλεσμα εκτείνεται από τη διαγραφή μεμονωμένων αρχείων έως την εκτέλεση κακόβουλου κώδικα.
Συνοπτικά για μη τεχνικούς: μία μόνο απρόσεκτη κλήση στο λάθος σημείο μπορεί να αφήσει έναν επιτιθέμενο βαθιά μέσα στην εφαρμογή. Αυτή η αδυναμία ανήκει γι' αυτό εδώ και χρόνια στα θέματα του OWASP Top 10.
Η ασφαλής εναλλακτική: JSON αντί για unserialize
Για σχεδόν όλες τις περιπτώσεις όπου δεδομένα έρχονται απ' έξω ή φεύγουν από τον διακομιστή, το JSON είναι η σωστή επιλογή. Το json_decode() δημιουργεί μόνο πίνακες, αριθμούς, αλφαριθμητικά και λογικές τιμές, όχι αντικείμενα δικών σας κλάσεων.
<?php
declare(strict_types=1);
function leseEinstellungen(string $eingabe): array
{
try {
$daten = json_decode($eingabe, true, 16, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new InvalidArgumentException('Ungültige Daten.');
}
if (!is_array($daten)) {
throw new InvalidArgumentException('Erwartet wurde ein Objekt.');
}
// Αποδοχή μόνο γνωστών πεδίων με τους αναμενόμενους τύπους
return [
'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
'seiten' => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
];
}
Σημαντικό είναι το δεύτερο μέρος: και τα δεδομένα JSON είναι είσοδοι. Αποδεχτείτε μόνο αναμενόμενα πεδία και ελέγξτε τύπους και εύρη τιμών προτού εργαστείτε με αυτά.
Όταν το unserialize είναι αναπόφευκτο: allowed_classes και υπογραφή
Σε παλαιότερες εφαρμογές τα σειριοποιημένα δεδομένα βρίσκονται συχνά σε βάσεις δεδομένων ή caches που δεν μπορούν να μετατραπούν αμέσως. Τότε περιορίζετε τον κίνδυνο σε δύο βήματα. Πρώτον, αποτρέπετε με το allowed_classes τη δημιουργία αντικειμένων. Δεύτερον, διασφαλίζετε με μια υπογραφή HMAC ότι τα δεδομένα προέρχονται πράγματι από την εφαρμογή σας και δεν τροποποιήθηκαν στη διαδρομή.
<?php
declare(strict_types=1);
const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // ποτέ στο αποθετήριο κώδικα
function verpacken(array $daten): string
{
$roh = serialize($daten);
return hash_hmac('sha256', $roh, SCHLUESSEL) . '.' . base64_encode($roh);
}
function auspacken(string $paket): array
{
[$signatur, $b64] = array_pad(explode('.', $paket, 2), 2, '');
$roh = base64_decode($b64, true);
if ($roh === false || !hash_equals(hash_hmac('sha256', $roh, SCHLUESSEL), $signatur)) {
throw new RuntimeException('Signatur ungültig.');
}
$daten = unserialize($roh, ['allowed_classes' => false, 'max_depth' => 16]);
if (!is_array($daten)) {
throw new RuntimeException('Unerwartetes Format.');
}
return $daten;
}
Αν χρειάζονται παρ' όλα αυτά συγκεκριμένες κλάσεις, ονομάστε τις μία προς μία στο allowed_classes, αντί να τις επιτρέψετε όλες. Το κλειδί ανήκει στη διαμόρφωση του διακομιστή ή σε αποθήκη μυστικών και πρέπει να ανανεώνεται μετά από ένα περιστατικό.
Σταδιακή μετάβαση των υπαρχόντων δεδομένων σε JSON
Σε εφαρμογές που έχουν μεγαλώσει, οι σειριοποιημένες τιμές βρίσκονται συχνά επί χρόνια στη βάση δεδομένων. Μια μετάβαση σε ένα βήμα είναι τότε επικίνδυνη. Αποτελεσματική έχει αποδειχθεί μια μεταβατική περίοδος: η εφαρμογή διαβάζει και τις δύο μορφές, γράφει όμως πλέον μόνο JSON. Κατά την ανάγνωση μιας παλιάς τιμής, αυτή αποσυσκευάζεται εφάπαξ με allowed_classes => false και ξαναγράφεται αμέσως ως JSON. Μετά από κάποιο χρονικό διάστημα οι περισσότερες εγγραφές έχουν μετατραπεί και ένα εφάπαξ script κάνει τα υπόλοιπα.
Καταγράφετε κατά τη μεταβατική περίοδο πόσο συχνά διαβάζεται ακόμη η παλιά μορφή και επίσης όταν οι υπογραφές δεν ταιριάζουν. Τέτοιες αποτυχίες μπορεί να υποδεικνύουν απόπειρες παραποίησης και ανήκουν στην παρακολούθηση. Μόνο όταν δεν εμφανίζεται πια παλιά μορφή, αφαιρείτε εντελώς τον κλάδο unserialize() από τον κώδικα.
Κρυφή deserialization: sessions, caches και διαδρομές αρχείων
Δεν φαίνεται κάθε deserialization στον δικό σας κώδικα. Ελέγξτε και αυτά τα σημεία:
- Sessions: Η PHP σειριοποιεί αυτόματα τα δεδομένα session. Όσο οι επιτιθέμενοι δεν μπορούν να γράψουν τα αρχεία session, αυτό δεν είναι κρίσιμο. Χωριστές διαδρομές session ανά ιστότοπο περιγράφει ο οδηγός Ασφάλεια των sessions στην PHP.
- Serializer των sessions: Η υπόδειξη για το CVE-2026-115560 αφορά μια ασυνήθιστη διαμόρφωση όπου ο
serialize_handlerαλλάζει κατά την εκτέλεση μεini_set()και το session upload progress είναι ενεργό. Προσωρινό μέτρο σύμφωνα με το php.net:session.upload_progress.enabled=0στο php.ini ή στο pool του FPM, στη συνέχεια επανεκκίνηση των workers. - Caches και ουρές: Το Redis, το Memcached ή οι πίνακες βάσης δεδομένων με σειριοποιημένες τιμές είναι τόσο ασφαλείς όσο η πρόσβαση σε αυτούς. Δεν ανήκουν ανοιχτά στο δίκτυο.
- Συναρτήσεις αρχείων με διαδρομές από εισόδους: Μη δίνετε ποτέ σε συναρτήσεις αρχείων διαδρομές από το αίτημα. Stream wrappers όπως το
phar://μπορούν αλλιώς να προκαλέσουν απροσδόκητη επεξεργασία. Μια λίστα επιτρεπόμενων ονομάτων αρχείων προστατεύει εδώ πιο αξιόπιστα από οποιοδήποτε φιλτράρισμα. - Εξαρτήσεις: Οι βιβλιοθήκες με γνωστές αδυναμίες διευρύνουν τις δυνατότητες ενός επιτιθέμενου. Διατηρήστε τις επικαιροποιημένες, όπως περιγράφεται στον οδηγό Έλεγχος εξαρτήσεων Composer για ευπάθειες.
Εντοπισμός επικίνδυνων σημείων στον κώδικα
Μια πρώτη καταγραφή επιτυγχάνεται με μια απλή αναζήτηση. Δεν αλλάζει τίποτα στο σύστημα και δείχνει όλες τις κλήσεις εκτός του καταλόγου vendor:
grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
| grep -v '/vendor/'
Αξιολογήστε κάθε εύρημα με τρία ερωτήματα: Από πού προέρχονται τα δεδομένα; Μπορεί ένας χρήστης να τα αλλάξει; Είναι ορισμένο το allowed_classes; Τα ευρήματα με εισόδους από cookies, $_GET, $_POST, $_REQUEST ή εξωτερικές διεπαφές έχουν προτεραιότητα. Αντικαταστήστε τα με JSON και δοκιμάστε τη μετάβαση προτού την εφαρμόσετε.
Ένα περαιτέρω στοιχείο βρίσκεται στο πρόγραμμα περιήγησης. Τα σειριοποιημένα δεδομένα PHP έχουν μια τυπική δομή με αναγνωριστικά τύπων και ενδείξεις μήκους. Αν βρείτε τέτοιες τιμές σε cookies, κρυφά πεδία φορμών ή παραμέτρους του δικού σας ιστότοπου, η εφαρμογή έχει δώσει σειριοποιημένα δεδομένα στον επισκέπτη και τα περιμένει πιθανότατα πίσω. Ακριβώς αυτά τα σημεία πρέπει να ελεγχθούν.
Για τη διοίκηση αρκεί ένα απλό ερώτημα προς τους προγραμματιστές ή το πρακτορείο: «Επεξεργάζεται η εφαρμογή μας κάπου σειριοποιημένα δεδομένα που έρχονται απ' έξω και, αν ναι, πώς προστατεύονται;» Μια σαφής απάντηση με τα σημεία που εντοπίστηκαν είναι καλό σημάδι. Μια αόριστη απάντηση είναι λόγος να ελεγχθεί η εφαρμογή.
Πώς βοηθά συγκεκριμένα η PHP SOS;
- Αναζητούμε στον κώδικά σας deserialization με ξένα δεδομένα και αξιολογούμε κάθε εύρημα ως προς την προσβασιμότητα και τις επιπτώσεις.
- Προτείνουμε συγκεκριμένες μεταβάσεις σε JSON ή ασφαλισμένες κλήσεις και υποστηρίζουμε τους προγραμματιστές σας στην υλοποίηση.
- Ελέγχουμε τις εξαρτήσεις για γνωστές αδυναμίες και τεκμηριώνουμε την κατάσταση.
- Κατόπιν αιτήματος αφήνουμε την εφαρμογή να ελεγχθεί ανεξάρτητα στο πλαίσιο ενός ελέγχου ασφάλειας και penetration test.
Συχνές ερωτήσεις
Είναι το unserialize() στην PHP θεμελιωδώς μη ασφαλές;
Όχι για δεδομένα που δημιουργεί μόνο η δική σας εφαρμογή και που κανείς δεν μπορεί να αλλάξει. Επικίνδυνο γίνεται μόλις χρήστες ή τρίτοι μπορούν να επηρεάσουν την είσοδο, π.χ. μέσω cookies, πεδίων φορμών ή εξωτερικών διεπαφών.
Αρκεί το allowed_classes => false ως προστασία;
Αποτρέπει τη δημιουργία τυχαίων αντικειμένων και κλείνει έτσι τον σημαντικότερο δρόμο επίθεσης. Για εισόδους απ' έξω το JSON είναι παρ' όλα αυτά η καλύτερη επιλογή, επειδή ο parser είναι απλούστερος και δεν ανασυνθέτει αντικείμενα.
Βοηθά μια ενημέρωση βιβλιοθηκών απέναντι σε επιθέσεις deserialization;
Εν μέρει. Οι επιθέσεις χρησιμοποιούν συχνά κλάσεις από εξαρτήσεις, που εκτελούν ενέργειες κατά την καταστροφή ή την αφύπνιση ενός αντικειμένου. Τα τρέχοντα πακέτα κλείνουν γνωστές αλυσίδες, η πραγματική αιτία όμως είναι η κλήση unserialize με ξένα δεδομένα.
Έχει το PHP2Shell σχέση με την deserialization;
Οι δύο κύριες ευπάθειες βρίσκονται στην επεξεργασία αιτημάτων της PHP. Η υπόδειξη για το CVE-2026-115560 αφορά αντίθετα μια ασυνήθιστη διαμόρφωση του serialization των sessions. Το προσωρινό μέτρο εκεί είναι το session.upload_progress.enabled=0.