Εν ολίγοις: Το XXE στην PHP το αποτρέπετε αν φορτώνετε XML από ξένες πηγές χωρίς τις επιλογές LIBXML_NOENT, LIBXML_DTDLOAD και LIBXML_DTDVALID, απορρίπτετε έγγραφα με δική τους DTD και ορίζετε έναν entity loader που αρνείται εξωτερικούς πόρους. Από το libxml 2.9.0 η PHP δεν αντικαθιστά πια εξ ορισμού entities, γι' αυτό η libxml_disable_entity_loader() είναι παρωχημένη από την PHP 8.0. Από την PHP 8.4 με libxml 2.13 διατίθεται επιπλέον το LIBXML_NO_XXE.
Μια παλιά ευπάθεια που δεν εξαφανίζεται
Τα XML External Entities, εν συντομία XXE, θεωρούνται από πολλούς προγραμματιστές λυμένο θέμα. Η PHP έχει απενεργοποιήσει εξ ορισμού τη φόρτωση εξωτερικών entities με σύγχρονες εκδόσεις libxml, και η παλιά συνάρτηση προστασίας χαρακτηρίστηκε παρωχημένη με την PHP 8.0. Παρ' όλα αυτά οι αδυναμίες XXE συνεχίζουν να εμφανίζονται στους ελέγχους.
Ο λόγος βρίσκεται σχεδόν πάντα σε μία από τρεις περιπτώσεις: Κάποιος όρισε μια επιλογή για να λύσει ένα πρόβλημα εισαγωγής και ενεργοποίησε έτσι ξανά την αντικατάσταση entities. Μια βιβλιοθήκη το κάνει αυτό εσωτερικά. Ή η εφαρμογή εκτελείται σε παλαιό σύστημα, του οποίου η PHP χτίστηκε με πολύ παλιά libxml.
Το XXE είναι επικίνδυνο, επειδή ο αναλυτής XML φορτώνει τότε περιεχόμενο που στο έγγραφο απλώς αναφέρεται: τοπικά αρχεία ή διευθύνσεις στο δίκτυο. Πιθανές συνέπειες είναι η ανάγνωση αρχείων ρυθμίσεων, αιτήματα προς εσωτερικά συστήματα παρόμοια με το SSRF και υπερφόρτωση του διακομιστή.
Τι ισχύει από το libxml 2.9 και την PHP 8.0
Το εγχειρίδιο της PHP περιγράφει την κατάσταση ως εξής: Από το libxml 2.9.0 η αντικατάσταση entities είναι εξ ορισμού απενεργοποιημένη. Τα εξωτερικά entities δεν χρειάζεται επομένως πια να απενεργοποιούνται ειδικά, εκτός αν τα εσωτερικά entity references πρόκειται να επιλυθούν με συγκεκριμένες επιλογές. Από αυτό προκύπτει η παρακάτω επισκόπηση:
| Επιλογή | Επίδραση | Κίνδυνος με ξένο XML |
|---|---|---|
LIBXML_NOENT |
αντικαθιστά entities | σύμφωνα με το εγχειρίδιο μπορεί να διευκολύνει επιθέσεις XXE |
LIBXML_DTDLOAD |
φορτώνει το εξωτερικό υποσύνολο | ενεργοποιεί τη φόρτωση εξωτερικών entities |
LIBXML_DTDVALID |
επικυρώνει απέναντι στη DTD | μπορεί να διευκολύνει επιθέσεις XXE |
LIBXML_NONET |
απαγορεύει προσβάσεις δικτύου κατά τη φόρτωση | προστατευτική πρόσθετη επιλογή |
LIBXML_NO_XXE |
απενεργοποιεί τα εξωτερικά entities κατά την αντικατάσταση | προστατευτική, από την PHP 8.4 και libxml 2.13.0 |
LIBXML_PARSEHUGE |
άρει εσωτερικά όρια του αναλυτή | διευκολύνει την υπερφόρτωση, μόνο για αξιόπιστα δεδομένα |
Το παραπλανητικό όνομα του LIBXML_NOENT είναι γνωστή παγίδα: ακούγεται σαν «χωρίς entities», προκαλεί όμως το αντίθετο.
Για τους προγραμματιστές αυτό σημαίνει: επικίνδυνη δεν είναι η φόρτωση XML από μόνη της, αλλά κάθε σημείο που αλλάζει συνειδητά την προεπιλεγμένη συμπεριφορά. Μια αναζήτηση αυτών των σταθερών στον κώδικα είναι γι' αυτό ο πιο γρήγορος δρόμος για μια πρώτη εκτίμηση, πριν από κάθε επίπονο έλεγχο.
Ασφαλής άμυνα απέναντι στο XXE στην PHP: ένα πρότυπο για το DOMDocument
Το παρακάτω παράδειγμα συνδυάζει περισσότερα επίπεδα προστασίας. Ορίζει έναν entity loader που αρνείται κάθε εξωτερικό πόρο, φορτώνει χωρίς επικίνδυνες επιλογές και απορρίπτει έγγραφα με δική τους DTD:
<?php
declare(strict_types=1);
function ladeFremdesXml(string $xml): DOMDocument
{
// να απαγορεύονται καταρχήν οι εξωτερικοί πόροι
libxml_set_external_entity_loader(
static fn (?string $public, string $system, array $context) => null
);
$optionen = LIBXML_NONET;
if (defined('LIBXML_NO_XXE')) {
$optionen |= LIBXML_NO_XXE;
}
$vorher = libxml_use_internal_errors(true);
$dom = new DOMDocument();
$ok = $dom->loadXML($xml, $optionen);
libxml_use_internal_errors($vorher);
if (!$ok) {
throw new InvalidArgumentException('XML ungültig.');
}
if ($dom->doctype !== null) {
throw new InvalidArgumentException('Dokumente mit DTD werden nicht angenommen.');
}
return $dom;
}
Σύμφωνα με το εγχειρίδιο της PHP, μια τιμή επιστροφής null στον loader εμποδίζει τη φόρτωση του εξωτερικού πόρου. Το OWASP επισημαίνει ότι δεν αρκεί να μεταβιβάσετε απευθείας null στο libxml_set_external_entity_loader()· χρειάζεται μια συνάρτηση που επιστρέφει null. Για το SimpleXML και το XMLReader ισχύουν οι ίδιες επιλογές. Όπου είναι δυνατό, το OWASP συνιστά να απορρίπτονται εντελώς οι DTD.
Αν μπορείτε να καθορίσετε μόνοι σας τη μορφή δεδομένων, το JSON είναι συνήθως η απλούστερη επιλογή για ανταλλαγή μεταξύ των δικών σας συστημάτων. Για ξένες μορφές όπως SAML ή αρχεία Office, το XML παραμένει αναπόφευκτο.
Εντοπισμός κρυφών εισόδων XML
Πολλές εφαρμογές επεξεργάζονται περισσότερο XML απ' όσο νομίζουν οι προγραμματιστές τους. Περάστε από αυτή τη λίστα:
- Διεπαφές: πελάτες και διακομιστές SOAP, XML-RPC, υπηρεσίες πληρωμών και αποστολών με απαντήσεις XML.
- Σύνδεση: Το single sign-on με SAML βασίζεται σε έγγραφα XML που έρχονται απ' έξω.
- Εισαγωγές: δεδομένα προϊόντων, feeds, sitemaps, μορφές καταλόγων.
- Uploads: Οι εικόνες SVG είναι XML· μορφές Office όπως DOCX και XLSX αποτελούνται εσωτερικά από αρχεία XML.
- Βιβλιοθήκες: πακέτα Composer για έγγραφα, υπολογιστικά φύλλα ή SAML, που χρησιμοποιούν εσωτερικά το libxml.
Στον κώδικα βοηθούν δύο αναζητήσεις:
grep -rnE "LIBXML_(NOENT|DTDLOAD|DTDVALID|PARSEHUGE)" src/ vendor/ --include="*.php"
grep -rnE "(loadXML|simplexml_load_(string|file)|XMLReader|->load\()" src/ --include="*.php"
Ευρήματα στο vendor/ δεν είναι λόγος πανικού, αλλά αφορμή να ελέγξετε την έκδοση και τις ανακοινώσεις ασφαλείας της βιβλιοθήκης.
Στα uploads SVG το XXE είναι μόνο ένας από πολλούς κινδύνους, επειδή τα αρχεία SVG μπορούν να περιέχουν και σενάρια. Αν η εφαρμογή σας δεν χρειάζεται οπωσδήποτε τη μορφή, μην την επιτρέπετε καθόλου για uploads. Διαφορετικά επεξεργαστείτε το SVG μόνο με το ασφαλές πρότυπο παραπάνω και παραδίδετε τα αρχεία έτσι ώστε ο browser να μην εκτελεί τα σενάρια που περιέχονται.
Προσέξτε επίσης τα μηνύματα σφάλματος. Αν μια εφαρμογή επιστρέφει αφιλτράριστα τα μηνύματα του libxml στους χρήστες, μπορεί να περιέχουν τμήματα του περιεχομένου που επεξεργάστηκε. Τα σφάλματα ανήκουν στο αρχείο καταγραφής, ο χρήστης λαμβάνει ένα γενικό μήνυμα· στο παραπάνω παράδειγμα το libxml_use_internal_errors(true) φροντίζει ώστε οι προειδοποιήσεις να μην καταλήγουν στην έξοδο. Περιορίστε επιπλέον το μέγεθος των εγγράφων XML που λαμβάνετε, προτού τα δει καν ο αναλυτής.
Παλαιά συστήματα: πότε λείπει η βασική προστασία
Η προστασία από την προεπιλεγμένη συμπεριφορά εξαρτάται από την έκδοση libxml με την οποία χτίστηκε η PHP. Ποια έκδοση εκτελείται, δείχνει το php -r 'echo LIBXML_DOTTED_VERSION, PHP_EOL;' στη γραμμή εντολών ή η έξοδος του phpinfo() σε προστατευμένο περιβάλλον. Ελέγξτε την τιμή για κάθε έκδοση PHP στον διακομιστή, επειδή το CLI και ο διακομιστής ιστού μπορεί να χρησιμοποιούν διαφορετικά builds. Οι εικόνες container φέρνουν δική τους libxml· εκεί ελέγχετε στο εκτελούμενο container και όχι στον host.
Ιδιαίτερα επηρεασμένα είναι συστήματα που εκτελούνται ήδη σε παλιές εκδόσεις PHP. Για την PHP 8.1 και παλαιότερες δεν υπάρχουν μετά το PHP2Shell επίσημες ενημερώσεις από το έργο PHP· τι σημαίνει αυτό, περιγράφει το PHP 8.1 και παλαιότερες: τι ισχύει τώρα. Ένα παλαιό σύστημα με απηρχαιωμένο libxml και ανοιχτές ευπάθειες του διερμηνέα πρέπει όχι μόνο να ασφαλιστεί στον κώδικα, αλλά να μεταφερθεί. Συγγενείς κινδύνους στην επεξεργασία ξένων δεδομένων περιγράφει το Ανασφαλής αποσειριοποίηση στην PHP, τις ανακτήσεις εσωτερικών προορισμών το Αποτροπή του Server-Side Request Forgery στην PHP.
Πώς βοηθά συγκεκριμένα η PHP SOS;
- Εντοπίζουμε όλα τα σημεία όπου η εφαρμογή σας και οι βιβλιοθήκες της επεξεργάζονται XML από ξένες πηγές.
- Αφαιρούμε επικίνδυνες επιλογές του αναλυτή και ενσωματώνουμε μια κεντρική, ασφαλισμένη συνάρτηση φόρτωσης.
- Ελέγχουμε τις εκδόσεις libxml και PHP σε όλους τους διακομιστές και σχεδιάζουμε τη μετάβαση σε παλαιά συστήματα.
- Δοκιμάζουμε τις αλλαγές με ακίνδυνα έγγραφα ελέγχου και τεκμηριώνουμε το αποτέλεσμα.
Τον έλεγχο του δικού σας λογισμικού περιγράφει η υπηρεσία Έλεγχος ασφαλείας και δοκιμή διείσδυσης.
Συχνές ερωτήσεις
Προστατεύονται οι εφαρμογές PHP αυτόματα από το XXE από την PHP 8 και μετά;
Στην προεπιλεγμένη περίπτωση σε μεγάλο βαθμό, επειδή το libxml από την έκδοση 2.9.0 δεν αντικαθιστά πια entities. Η προστασία όμως χάνεται μόλις ο κώδικας ορίσει επιλογές όπως οι LIBXML_NOENT, LIBXML_DTDLOAD ή LIBXML_DTDVALID ή αν η PHP έχει χτιστεί με πολύ παλιά έκδοση libxml.
Πρέπει να καλώ ακόμη το libxml_disable_entity_loader;
Όχι. Η συνάρτηση είναι παρωχημένη από την PHP 8.0, επειδή η φόρτωση εξωτερικών entities είναι εξ ορισμού απενεργοποιημένη. Για πρόσθετη προστασία το εγχειρίδιο της PHP συνιστά το libxml_set_external_entity_loader και, από την PHP 8.4 με κατάλληλο libxml, τη σταθερά LIBXML_NO_XXE.
Πού επεξεργάζεται η εφαρμογή μου εν γένει XML;
Συχνά σε απροσδόκητα σημεία: διεπαφές SOAP, single sign-on με SAML, feeds RSS, sitemaps, εισαγωγές προϊόντων, uploads SVG και αρχεία Office που αποτελούνται εσωτερικά από XML. Και βιβλιοθήκες μπορούν να επεξεργάζονται XML χωρίς να φαίνεται στον δικό σας κώδικα.
Τι είναι η επίθεση Billion Laughs;
Ένα έγγραφο με εμφωλευμένα entities, που κατά την επίλυση αυξάνεται τεράστια και καταναλώνει μνήμη και χρόνο υπολογισμού. Ανήκει στην ίδια οικογένεια με το XXE. Προστασία προσφέρουν η απενεργοποίηση της αντικατάστασης entities και η απόρριψη εγγράφων με δική τους DTD.