Εν συντομία: Το XSS στην PHP το αποτρέπετε αν κωδικοποιείτε κάθε έξοδο ανάλογα με το πλαίσιό της: htmlspecialchars() για HTML, json_encode() με επιλογές προστασίας για JavaScript, ελεγμένα URLs για συνδέσμους. Templates με αυτόματο escaping αναλαμβάνουν μεγάλο μέρος της δουλειάς. Μια Content Security Policy και τα cookies HttpOnly περιορίζουν τη ζημιά, αν παραμείνει παρ' όλα αυτά μια ευπάθεια.
Γιατί το Cross-Site Scripting πρέπει να ελεγχθεί και τώρα
Η προσοχή βρίσκεται τον Οκτώβριο του 2026 στο PHP2Shell, δύο κρίσιμες ευπάθειες στον διερμηνέα PHP, για τις οποίες υπάρχουν patches από τις 21.10.2026. Πολλές εταιρείες ελέγχουν στο πλαίσιο αυτό τις εφαρμογές ιστού τους. Είναι μια καλή ευκαιρία να εξετάσετε και αδυναμίες στον δικό σας κώδικα, που καμία ενημέρωση PHP δεν διορθώνει.
Το Cross-Site Scripting, εν συντομία XSS, είναι μία από αυτές. Το OWASP Top 10 κατατάσσει το XSS στην κατηγορία Injection. Σε αντίθεση με μια ευπάθεια στον διακομιστή, η επίθεση εδώ στρέφεται κατά των επισκεπτών και των χρηστών του ιστότοπού σας, π.χ. κατά πελατών στο κατάστημα ή εργαζομένων στο backend.
Οι συνέπειες μπορούν να είναι σημαντικές: κατειλημμένες συνεδρίες διαχειριστών, παραποιημένες φόρμες που στέλνουν δεδομένα σε τρίτους ή πλαστά περιεχόμενα στον ιστότοπό σας. Αν καταληφθεί ένας λογαριασμός διαχειριστή, αυτό μπορεί να φτάσει έως τον πλήρη έλεγχο της εφαρμογής.
Πώς προκύπτει το XSS σε εφαρμογές PHP;
Το XSS προκύπτει όταν μια εφαρμογή εμφανίζει δεδομένα από μη αξιόπιστη πηγή χωρίς έλεγχο σε μια σελίδα. Το πρόγραμμα περιήγησης δεν μπορεί τότε πλέον να ξεχωρίσει τα δικά σας περιεχόμενα από τον κώδικα που έχει εισαχθεί κρυφά και εκτελεί και τα δύο.
Διακρίνονται τρεις μορφές. Στο ανακλώμενο XSS το περιεχόμενο προέρχεται απευθείας από το τρέχον αίτημα, π.χ. από έναν όρο αναζήτησης στο URL. Στο αποθηκευμένο XSS βρίσκεται στη βάση δεδομένων, για παράδειγμα σε ένα σχόλιο ή σε πεδίο προφίλ, και πλήττει όποιον ανοίγει τη σελίδα. Το XSS βάσει DOM προκύπτει στη JavaScript του προγράμματος περιήγησης, όταν scripts γράφουν τιμές από το URL στη σελίδα.
Μη αξιόπιστα είναι όλα τα δεδομένα που δεν είναι σταθερά γραμμένα στον κώδικα: πεδία φορμών, παράμετροι URL, cookies, ονόματα αρχείων, απαντήσεις εξωτερικών διεπαφών και περιεχόμενα από τη δική σας βάση δεδομένων που κάποια στιγμή προήλθαν απ' έξω.
Σωστή κωδικοποίηση εξόδου ανάλογα με το πλαίσιο
Ο σημαντικότερος κανόνας είναι: κωδικοποιήστε κατά την έξοδο, και μάλιστα ανάλογα με το σημείο όπου καταλήγει η τιμή. Για περιεχόμενο HTML και χαρακτηριστικά σε εισαγωγικά, το htmlspecialchars() είναι το μέσο επιλογής. Μια μικρή βοηθητική συνάρτηση αποτρέπει να ξεχαστούν επιλογές:
<?php
function e(?string $wert): string
{
return htmlspecialchars($wert ?? '', ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
}
?>
<h2><?= e($produkt['name']) ?></h2>
<input type="text" name="suche" value="<?= e($_GET['q'] ?? '') ?>">
Άλλα πλαίσια χρειάζονται άλλα μέσα:
| Πλαίσιο | Ασφαλής μέθοδος |
|---|---|
| Περιεχόμενο HTML, χαρακτηριστικό σε εισαγωγικά | htmlspecialchars() με ENT_QUOTES και UTF-8 |
| Δεδομένα για JavaScript | json_encode() με JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_HEX_QUOT |
| Παράμετρος σε URL | rawurlencode() για την κάθε τιμή |
| Πλήρες URL από είσοδο χρήστη | έλεγχος του σχήματος, μόνο https και http, έπειτα htmlspecialchars() |
| Χαρακτηριστικά χωρίς εισαγωγικά, event handlers, CSS | αποφύγετε, μην εισάγετε εκεί τιμές |
Για δεδομένα που χρειάζεται ένα script, ένα χαρακτηριστικό δεδομένων ή ένα μπλοκ JSON είναι συνήθως ο πιο καθαρός δρόμος:
<?php
$daten = json_encode($konfiguration, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
?>
<div id="app" data-konfig="<?= e($daten) ?>"></div>
Στη JavaScript διαβάζετε στη συνέχεια την τιμή μέσω του χαρακτηριστικού δεδομένων και ορίζετε περιεχόμενα με textContent αντί για innerHTML.
Templates, καθαρισμός HTML και frameworks
Οι template engines όπως το Twig και το Blade κωδικοποιούν τις εξόδους αυτόματα από προεπιλογή. Αυτό αποκλείει μεγάλο μέρος των τυπικών λαθών. Επικίνδυνο γίνεται εκεί όπου αυτή η συμπεριφορά απενεργοποιείται συνειδητά, π.χ. με το φίλτρο raw στο Twig ή τη σύνταξη {!! !!} στο Blade. Τέτοια σημεία πρέπει να είναι σπάνια στον κώδικα και να ελέγχονται στοχευμένα σε κάθε review.
Αν η εφαρμογή σας πρέπει να επιτρέπει συνειδητά HTML από χρήστες, π.χ. μορφοποιήσεις σε σχόλια ή σε σύστημα συντακτών, η κωδικοποίηση δεν αρκεί, επειδή το HTML πρέπει να εμφανίζεται. Τότε χρειάζεστε μια δοκιμασμένη βιβλιοθήκη καθαρισμού όπως το HTML Purifier, που αφήνει να περάσει μόνο μια σταθερή λίστα επιτρεπόμενων στοιχείων και χαρακτηριστικών. Δικά σας φίλτρα με κανονικές εκφράσεις έχουν, κατά την εμπειρία, κενά.
Ελέγξτε ιδιαίτερα τις περιοχές backend. Το XSS σε προβολές που βλέπουν μόνο οι διαχειριστές υποτιμάται συχνά, πλήττει όμως ακριβώς τους λογαριασμούς με τα περισσότερα δικαιώματα.
Εντοπισμός XSS στον υπάρχοντα κώδικα PHP
Σε εφαρμογές που έχουν μεγαλώσει, το πρώτο ερώτημα δεν είναι συνήθως πώς κωδικοποιείτε σωστά, αλλά πού ξεχάστηκε μέχρι τώρα. Μια πραγματιστική αφετηρία είναι η αναζήτηση σημείων όπου μεταβλητές εμφανίζονται απευθείας:
# Άμεσες εμφανίσεις δεδομένων αιτήματος
grep -rnE "(echo|print|<\?=)\s*\\\$_(GET|POST|REQUEST|COOKIE|SERVER)" src/ templates/ --include="*.php"
# Απενεργοποιημένο auto-escaping σε Twig και Blade
grep -rnE "\|raw|\{!!" templates/ resources/views/
Τα ευρήματα είναι εργασίες ελέγχου και όχι έτοιμα πορίσματα. Το αποθηκευμένο XSS το βρίσκει μια τέτοια αναζήτηση μόνο περιορισμένα, επειδή οι τιμές εκεί προέρχονται από τη βάση δεδομένων. Ελέγξτε γι' αυτό επιπλέον όλες τις προβολές που εμφανίζουν περιεχόμενα χρηστών: σχόλια, προφίλ, σημειώσεις παραγγελιών, αιτήματα υποστήριξης. Στα CMS και στα καταστήματα ανήκουν ρητά σε αυτό plugins και θέματα τρίτων, επειδή εκεί βρίσκεται μεγάλο μέρος της λογικής εξόδου.
Δεύτερη γραμμή άμυνας: CSP και ασφαλή cookies
Ακόμη και με προσεκτικό κώδικα μπορεί να παραβλεφθεί ένα σημείο. Δύο μέτρα περιορίζουν τότε τη ζημιά. Μια Content Security Policy επιτρέπει στο πρόγραμμα περιήγησης μόνο scripts από καθορισμένες πηγές και απαγορεύει ενσωματωμένα scripts, εφόσον δεν έχουν εγκριθεί ρητά:
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"
Εισαγάγετε μια τέτοια πολιτική σταδιακά, πρώτα σε λειτουργία αναφοράς μέσω Content-Security-Policy-Report-Only, επειδή οι υπάρχουσες ενσωματώσεις υπηρεσιών ανάλυσης ή πληρωμών πρέπει συνήθως να συμπληρωθούν. Περαιτέρω κεφαλίδες περιγράφει ο οδηγός Κεφαλίδες ασφαλείας για ιστοτόπους.
Επιπλέον, τα cookies συνεδρίας πρέπει να φέρουν το χαρακτηριστικό HttpOnly, ώστε τα scripts να μην μπορούν να τα διαβάσουν. Πώς θωρακίζετε συνολικά τα sessions, αναφέρεται στον οδηγό Ασφάλεια των sessions στην PHP. Μια συγγενή αδυναμία από την πλευρά της βάσης δεδομένων πραγματεύεται ο οδηγός Αποτροπή του SQL injection στην PHP.
Πώς βοηθά συγκεκριμένα η PHP SOS;
- Ελέγχουμε τα templates και τις εξόδους της εφαρμογής σας για ελλιπή ή λανθασμένη κωδικοποίηση.
- Αξιολογούμε τα ευρήματα με βάση την προσβασιμότητα και τους επηρεαζόμενους λογαριασμούς και δείχνουμε συγκεκριμένες διορθώσεις.
- Εισάγουμε σταδιακά μια Content Security Policy, χωρίς να διαταράξουμε τις λειτουργίες του καταστήματος ή των πληρωμών.
- Δοκιμάζουμε ξανά μετά τη διόρθωση και τεκμηριώνουμε το αποτέλεσμα.
Έναν ανεξάρτητο έλεγχο περιγράφει η υπηρεσία Έλεγχος ασφάλειας και penetration test.
Συχνές ερωτήσεις
Αρκεί το htmlspecialchars ενάντια στο XSS;
Για κανονικό κείμενο στο περιεχόμενο HTML και σε χαρακτηριστικά μέσα σε εισαγωγικά ναι, εφόσον ορίσετε ENT_QUOTES και το σύνολο χαρακτήρων UTF-8. Στη JavaScript, σε URLs ή στο CSS ισχύουν άλλοι κανόνες, εκεί χρειάζεται δική της, κατάλληλη κωδικοποίηση.
Πρέπει να φιλτράρω τις εισόδους κατά την αποθήκευση ή κατά την έξοδο;
Κωδικοποιήστε κατά την έξοδο, ανάλογα με τον προορισμό. Τις εισόδους πρέπει να τις ελέγχετε κατά τη λήψη ως προς την αληθοφάνεια, π.χ. μήκος και μορφή. Όποιος φιλτράρει μόνο κατά την αποθήκευση, παραβλέπει δεδομένα από άλλες πηγές και δυσκολεύει τη μεταγενέστερη χρήση σε άλλα πλαίσια.
Τι προσφέρει μια Content Security Policy;
Μια Content Security Policy ορίζει από ποιες πηγές επιτρέπεται στο πρόγραμμα περιήγησης να φορτώνει και να εκτελεί scripts. Μπορεί να περιορίσει σημαντικά τις επιπτώσεις μιας παραβλεφθείσας ευπάθειας XSS, δεν αντικαθιστά όμως τη σωστή κωδικοποίηση.
Πώς επιτρέπω μορφοποιήσεις όπως έντονα γράμματα ή συνδέσμους σε σχόλια;
Χρησιμοποιήστε μια δοκιμασμένη βιβλιοθήκη καθαρισμού HTML με λίστα επιτρεπόμενων στοιχείων και χαρακτηριστικών. Οι δικές σας κανονικές εκφράσεις για την αφαίρεση scripts είναι επιρρεπείς σε λάθη και δεν πρέπει να χρησιμοποιούνται.