Εν συντομία: Το Local File Inclusion στην PHP προκύπτει όταν τα include, require ή συναρτήσεις αρχείων χρησιμοποιούν μια διαδρομή που μπορεί να επηρεάσει ο επισκέπτης. Η ασφαλής λύση είναι μια σταθερή λίστα επιτρεπόμενων: η παράμετρος επιλέγει μόνο ανάμεσα σε γνωστές τιμές, η ίδια η διαδρομή δεν προέρχεται ποτέ από το αίτημα. Στην πλευρά του διακομιστή συμπληρώνουν την προστασία τα allow_url_include=Off, open_basedir και περιοριστικά δικαιώματα αρχείων.
Τι είναι το Local File Inclusion στην PHP;
Πολλές παλαιότερες εφαρμογές PHP φορτώνουν σελίδες με ένα απλό μοτίβο: μια παράμετρος στη διεύθυνση καθορίζει ποιο αρχείο ενσωματώνεται. Αν αυτή η παράμετρος περάσει χωρίς έλεγχο στο include, ένας επιτιθέμενος μπορεί να ζητήσει άλλα αρχεία στον διακομιστή. Στην ηπιότερη περίπτωση διαβάζει αρχεία διαμόρφωσης με διαπιστευτήρια. Στη χειρότερη κάνει την PHP να εκτελέσει περιεχόμενα που είχε τοποθετήσει προηγουμένως αλλού, π.χ. σε ένα upload ή σε ένα αρχείο καταγραφής.
Για τη διοίκηση είναι σημαντικό: αυτή η ευπάθεια σπάνια βρίσκεται στον πυρήνα του CMS, συνήθως σε ιδιόκτητες υλοποιήσεις, παλιές επεκτάσεις ή βιαστικά φτιαγμένες πρόσθετες σελίδες. Δεν γίνεται αντιληπτή στην καθημερινότητα, επειδή ο ιστότοπος λειτουργεί κανονικά.
Στο Remote File Inclusion η PHP φορτώνει κώδικα από ξένη διεύθυνση. Αυτό προϋποθέτει ότι το allow_url_include είναι ενεργό. Στις τρέχουσες εκδόσεις PHP αυτή η ρύθμιση είναι από προεπιλογή απενεργοποιημένη, σε παλιούς ή εσφαλμένα διαμορφωμένους διακομιστές τη συναντάτε όμως ακόμη.
Τυπικά ευάλωτα μοτίβα στον κώδικα
Επικίνδυνα είναι τα σημεία όπου μια τιμή από το αίτημα, από cookies ή από τη βάση δεδομένων γίνεται τμήμα μιας διαδρομής αρχείου. Συχνά παραδείγματα είναι οι εναλλακτές γλώσσας που φορτώνουν ένα αρχείο γλώσσας βάσει παραμέτρου, η επιλογή σελίδων μέσω παραμέτρου στη διεύθυνση, η επιλογή template ή θέματος και τα scripts λήψης εγγράφων. Ένα .php που προστίθεται στο τέλος της διαδρομής δεν προστατεύει αξιόπιστα, επειδή οι επιτιθέμενοι μπορούν να στραφούν σε υπάρχοντα αρχεία PHP.
Μια πρώτη εικόνα αποκτάτε με αναζήτηση ενσωματώσεων που περιέχουν μεταβλητές. Επιστρέφει και αβλαβή ευρήματα, δείχνει όμως πού πρέπει να κοιτάξετε με μεγαλύτερη προσοχή:
grep -rnE --include='*.php' '(include|require)(_once)?\s*\(?\s*\$' /var/www/projekt \
| grep -v '/vendor/'
Ελέγξτε κάθε εύρημα ως προς το από πού προέρχεται η μεταβλητή και αν ένας επισκέπτης μπορεί να την επηρεάσει.
Το ασφαλές μοτίβο: λίστα επιτρεπόμενων αντί για διαδρομή από το αίτημα
Η πιο αξιόπιστη προστασία είναι ότι ο επισκέπτης επιλέγει μόνο ένα κλειδί και η ίδια η εφαρμογή αποφασίζει ποιο αρχείο αντιστοιχεί. Η διαδρομή δεν συντίθεται ποτέ από εισόδους.
<?php
declare(strict_types=1);
$seiten = [
'start' => __DIR__ . '/../vorlagen/start.php',
'leistungen' => __DIR__ . '/../vorlagen/leistungen.php',
'kontakt' => __DIR__ . '/../vorlagen/kontakt.php',
];
$wahl = is_string($_GET['seite'] ?? null) ? $_GET['seite'] : 'start';
if (!array_key_exists($wahl, $seiten)) {
http_response_code(404);
$wahl = 'start';
}
require $seiten[$wahl];
Αυτό το μοτίβο είναι σύντομο, εύκολο να ελεγχθεί και δεν αφήνει περιθώρια. Στα σύγχρονα frameworks αυτή τη δουλειά αναλαμβάνει ο router· και εκεί οι controllers και τα templates δεν πρέπει ποτέ να καθορίζονται από ελεύθερες παραμέτρους.
Όταν τα ονόματα αρχείων πρέπει να είναι δυναμικά: realpath και βασικός κατάλογος
Μερικές φορές μια σταθερή λίστα δεν είναι πρακτική, π.χ. σε λήψεις από έναν φάκελο εγγράφων. Τότε ελέγχετε την επιλυμένη διαδρομή έναντι ενός σταθερού βασικού καταλόγου και επιτρέπετε μόνο απλά ονόματα αρχείων.
<?php
declare(strict_types=1);
const BASIS = '/var/www/daten/dokumente';
function sichererPfad(string $name): string
{
if (!preg_match('/^[a-z0-9_-]{1,64}\.pdf$/', $name)) {
throw new InvalidArgumentException('Ungültiger Dateiname.');
}
$basis = realpath(BASIS);
$pfad = realpath($basis . DIRECTORY_SEPARATOR . $name);
if ($basis === false || $pfad === false
|| !str_starts_with($pfad, $basis . DIRECTORY_SEPARATOR)) {
throw new RuntimeException('Datei nicht gefunden.');
}
return $pfad;
}
// Παράδοση αντί για ενσωμάτωση: το readfile() δεν εκτελεί κώδικα.
$datei = sichererPfad((string) ($_GET['datei'] ?? ''));
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . basename($datei) . '"');
readfile($datei);
Δύο σημεία είναι καθοριστικά. Η κανονική έκφραση δεν επιτρέπει διαχωριστικά καταλόγων ούτε ειδικούς χαρακτήρες. Και τα αρχεία που πρέπει μόνο να παραδοθούν διαβάζονται με readfile(), όχι με include, ώστε ο κώδικας που ενδεχομένως περιέχουν να μην εκτελείται ποτέ. Το str_starts_with() είναι διαθέσιμο από την PHP 8.0.
Θωράκιση στην πλευρά του διακομιστή: php.ini, open_basedir και δικαιώματα
Ο ασφαλής κώδικας είναι η πρώτη γραμμή άμυνας. Η διαμόρφωση του διακομιστή φροντίζει ώστε ένα παραβλεφθέν σφάλμα να προκαλέσει λιγότερη ζημιά.
; php.ini ή pool FPM (php_admin_value / php_admin_flag)
allow_url_include = Off
allow_url_fopen = Off ; μόνο αν η εφαρμογή δεν φορτώνει εξωτερικά URL
open_basedir = /var/www/kunde-a/:/tmp/kunde-a/
display_errors = Off
log_errors = On
Το allow_url_fopen το χρειάζονται ορισμένες εφαρμογές για ενημερώσεις ή διεπαφές. Δοκιμάστε την απενεργοποίηση πρώτα σε περιβάλλον δοκιμών (η σημείωση στο τέλος της γραμμής ισχύει μόνο αν η εφαρμογή δεν φορτώνει εξωτερικά URLs). Το open_basedir περιορίζει την PHP στους δικούς της καταλόγους του ιστότοπου, κάτι ιδιαίτερα σημαντικό σε διακομιστές με πολλούς πελάτες. Λεπτομέρειες υπάρχουν στους οδηγούς open_basedir και απομόνωση ανά ιστότοπο και Ασφαλής διαμόρφωση του php.ini.
Συμπληρωματικά, οι κατάλογοι upload και cache δεν πρέπει να είναι εκτελέσιμοι και τα αρχεία καταγραφής δεν πρέπει να βρίσκονται στο webroot. Έτσι λείπουν από έναν επιτιθέμενο τα σημεία όπου θα μπορούσε να τοποθετήσει δικό του περιεχόμενο για ενσωμάτωση.
Πώς αναγνωρίζετε απόπειρες εκμετάλλευσης στα logs
Οι απόπειρες εκμετάλλευσης ενός file inclusion αφήνουν συνήθως ίχνη στο access log. Ύποπτες είναι παράμετροι που περιέχουν αλλαγές καταλόγου, απόλυτες διαδρομές συστήματος, stream wrappers ή πλήρεις ξένες διευθύνσεις, ιδίως σε παραμέτρους όπως page, file, template ή lang. Ένα μεμονωμένο εύρημα είναι συνήθως αυτοματοποιημένη σάρωση. Σημασία έχει αν ένα τέτοιο αίτημα απαντήθηκε με κωδικό κατάστασης 200 και ασυνήθιστο μέγεθος απάντησης.
Πώς προχωράτε συστηματικά, περιγράφει ο οδηγός Ανάλυση των logs του διακομιστή ιστού μετά από επίθεση. Αν βρείτε ενδείξεις επιτυχημένων προσβάσεων, θεωρήστε ότι διαβάστηκαν αρχεία διαμόρφωσης και ανανεώστε τα διαπιστευτήρια που περιέχουν.
Σε αυτά ανήκουν κυρίως οι κωδικοί βάσης δεδομένων, τα κλειδιά για υπηρεσίες πληρωμών και αποστολής, οι προσβάσεις SMTP και τα μυστικά κλειδιά του CMS. Ελέγξτε επιπλέον αν από την πρώτη ύποπτη πρόσβαση έχουν δημιουργηθεί νέα αρχεία. Όποιος μπορούσε να διαβάσει αρχεία, ίσως προσπάθησε και να τοποθετήσει δικά του. Σε αυτή την περίπτωση δεν πρόκειται πλέον μόνο για μια ευπάθεια στον κώδικα, αλλά για ένα περιστατικό ασφαλείας που πρέπει να αντιμετωπιστεί δομημένα.
Μόνιμη διασφάλιση των διορθώσεων
Μετά από μια διόρθωση αξίζει να διασφαλίσετε τη λειτουργία προστασίας με αυτοματοποιημένες δοκιμές. Μια απλή δοκιμή περνά στην επιλογή σελίδων άγνωστες τιμές, αλλαγές καταλόγου και κενές εισόδους και αναμένει σε όλες τις περιπτώσεις την προεπιλεγμένη σελίδα ή ένα σφάλμα 404. Τέτοιες δοκιμές αποτρέπουν μια μεταγενέστερη αλλαγή από το να ανοίξει ξανά απαρατήρητα την ευπάθεια.
Για τη διοίκηση και τους υπεύθυνους έργων είναι χρήσιμο ένα ερώτημα προς το πρακτορείο: Υπάρχουν στην εφαρμογή σημεία όπου αρχεία φορτώνονται βάσει παραμέτρων και πώς προστατεύονται; Μια συγκεκριμένη απάντηση με τα σημεία που εντοπίστηκαν και δοκιμές είναι καλό σημάδι.
Πώς βοηθά συγκεκριμένα η PHP SOS;
- Αναζητούμε στοχευμένα στον κώδικά σας
include,requireκαι συναρτήσεις αρχείων με εισόδους από το αίτημα και αξιολογούμε κάθε εύρημα. - Ορίζουμε
allow_url_include,open_basedirκαι κατάλληλα δικαιώματα ανά ιστότοπο και ελέγχουμε ότι η εφαρμογή εξακολουθεί να λειτουργεί μετά. - Αναλύουμε τα logs για απόπειρες εκμετάλλευσης και ξεκαθαρίζουμε αν πρέπει να ανανεωθούν διαπιστευτήρια.
- Για έναν ανεξάρτητο έλεγχο προσφέρουμε ένα έλεγχο ασφάλειας με penetration test για εφαρμογές PHP.
Συχνές ερωτήσεις
Ποια η διαφορά μεταξύ LFI και RFI;
Στο Local File Inclusion η εφαρμογή φορτώνει ένα αρχείο που βρίσκεται ήδη στον διακομιστή, αλλά δεν έπρεπε να φορτωθεί. Στο Remote File Inclusion κώδικας φορτώνεται από ξένη διεύθυνση. Το RFI προϋποθέτει μια μη ασφαλή ρύθμιση της PHP, το LFI όχι.
Αρκεί να αφαιρέσω το ../ από την παράμετρο;
Όχι. Τέτοια φίλτρα παρακάμπτονται συχνά μέσω κωδικοποίησης ή παραλλαγών και παραβλέπουν απόλυτες διαδρομές. Ασφαλής είναι μόνο μια σταθερή αντιστοίχιση επιτρεπόμενων τιμών σε αρχεία ή ένας έλεγχος της επιλυμένης διαδρομής έναντι ενός βασικού καταλόγου.
Προστατεύει πλήρως το open_basedir από το file inclusion;
Όχι, περιορίζει μόνο τους καταλόγους στους οποίους επιτρέπεται η πρόσβαση της PHP. Μέσα σε αυτά τα όρια η ευπάθεια παραμένει εκμεταλλεύσιμη. Το open_basedir είναι ένα πρόσθετο προστατευτικό επίπεδο και δεν αντικαθιστά ασφαλή κώδικα.
Επηρεάζονται CMS όπως το WordPress από LFI;
Ο πυρήνας των συνηθισμένων CMS θεωρείται σε αυτό το σημείο καλά ελεγμένος. Οι ευπάθειες εμφανίζονται μάλλον σε επεκτάσεις, θέματα και ιδιόκτητες υλοποιήσεις. Οι ενημερώσεις των επεκτάσεων και μια ματιά στις ανακοινώσεις ασφαλείας τους είναι γι' αυτό σημαντικές.