Κουίζ: Μπορείτε να αμυνθείτε ενάντια στην ευπάθεια XSS;

πριν από ένα χρόνο Από το David Grudl  

Δοκιμάστε τις γνώσεις και τις ικανότητές σας σε θέματα ασφάλειας σε αυτό το κουίζ! Μπορείτε να αποτρέψετε έναν εισβολέα από το να πάρει τον έλεγχο μιας σελίδας HTML;

Σε όλες τις εργασίες, θα ασχοληθείτε με το ίδιο ερώτημα: πώς να εμφανίσετε σωστά τη μεταβλητή $str σε μια σελίδα HTML χωρίς να δημιουργήσετε μια ευπάθεια XSS. Η βάση της άμυνας είναι το escaping, δηλαδή η αντικατάσταση χαρακτήρων με ειδική σημασία με αντίστοιχες ακολουθίες. Για παράδειγμα, κατά την έξοδο μιας συμβολοσειράς σε κείμενο HTML, στην οποία ο χαρακτήρας < έχει ειδική σημασία (υποδεικνύει την αρχή μιας ετικέτας), τον αντικαθιστούμε με την οντότητα HTML &lt;, και το πρόγραμμα περιήγησης εμφανίζει σωστά το σύμβολο <.

Να είστε σε εγρήγορση, καθώς η ευπάθεια XSS είναι πολύ σοβαρή. Μπορεί να προκαλέσει έναν εισβολέα να αναλάβει τον έλεγχο μιας σελίδας ή ακόμη και του λογαριασμού ενός χρήστη. Καλή τύχη και μακάρι να καταφέρετε να διατηρήσετε τη σελίδα HTML ασφαλή!

Η πρώτη τριάδα ερωτήσεων

Προσδιορίστε ποιοι χαρακτήρες πρέπει να αντιμετωπιστούν και πώς στο πρώτο, δεύτερο και τρίτο παράδειγμα:

1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>

Εάν η έξοδος δεν αντιμετωπιζόταν με κανέναν τρόπο, θα γινόταν μέρος της εμφανιζόμενης σελίδας. Εάν ένας εισβολέας κατάφερνε να εισάγει το αλφαριθμητικό 'foo" onclick="evilCode()' σε μια μεταβλητή και η έξοδος δεν αντιμετωπιζόταν, θα προκαλούσε την εκτέλεση του κώδικά του όταν έκανε κλικ στο στοιχείο:

$str = 'foo" onclick="evilCode()'
❌ not treated: <input value="foo" onclick="evilCode()">
✅ treated:     <input value="foo&quot; onclick=&quot;evilCode()">

Λύσεις για κάθε παράδειγμα:

  1. οι χαρακτήρες < και & αντιπροσωπεύουν την αρχή μιας ετικέτας HTML και μιας οντότητας- αντικαταστήστε τους με τους χαρακτήρες &lt; και &amp;
  2. οι χαρακτήρες " και & αντιπροσωπεύουν το τέλος μιας τιμής χαρακτηριστικού και την αρχή μιας οντότητας HTML- αντικαταστήστε τους με &quot; και &amp;
  3. οι χαρακτήρες ' και & αντιπροσωπεύουν το τέλος μιας τιμής χαρακτηριστικού και την αρχή μιας οντότητας HTML- αντικαταστήστε τους με &apos; και &amp;

Παίρνετε έναν πόντο για κάθε σωστή απάντηση. Φυσικά, και στις τρεις περιπτώσεις, μπορείτε να αντικαταστήσετε και άλλους χαρακτήρες με οντότητες- δεν προκαλεί καμία ζημιά, αλλά δεν είναι απαραίτητο.

Ερώτηση αριθ. 4

Συνεχίζοντας, ποιοι χαρακτήρες πρέπει να αντικατασταθούν κατά την εμφάνιση μιας μεταβλητής σε αυτό το πλαίσιο;

<input value=<?= $str ?>>

Λύση: Όπως μπορείτε να δείτε, τα εισαγωγικά λείπουν εδώ. Ο ευκολότερος τρόπος είναι απλά να προσθέσετε τα εισαγωγικά και στη συνέχεια να αποφύγετε όπως στην προηγούμενη ερώτηση. Υπάρχει επίσης μια δεύτερη λύση, η οποία είναι να αντικαταστήσετε τα κενά και όλους τους χαρακτήρες που έχουν ειδική σημασία μέσα σε μια ετικέτα, όπως >, /, = και μερικοί άλλοι με οντότητες HTML.

Ερώτηση αριθ. 5

Τώρα γίνεται πιο ενδιαφέρον. Ποιοι χαρακτήρες πρέπει να αντιμετωπιστούν σε αυτό το πλαίσιο:

<script>
	let foo = '<?= $str ?>';
</script>

Λύση: Μέσα στο <script> tag, οι κανόνες διαφυγής καθορίζονται από τη JavaScript. Οι οντότητες HTML δεν χρησιμοποιούνται εδώ, αλλά υπάρχει ένας ειδικός κανόνας. Έτσι, ποιους χαρακτήρες αποφεύγουμε; Στο εσωτερικό της συμβολοσειράς JavaScript, φυσικά αποφεύγουμε τον χαρακτήρα ' που την οριοθετεί, χρησιμοποιώντας backslash, αντικαθιστώντας τον με \'. Δεδομένου ότι η JavaScript δεν υποστηρίζει συμβολοσειρές πολλαπλών γραμμών (εκτός από τις περιπτώσεις προτύπων γραμματικών), πρέπει επίσης να αποφύγουμε τους χαρακτήρες νέας γραμμής. Ωστόσο, πρέπει να γνωρίζετε ότι εκτός από τους συνήθεις χαρακτήρες \n και \r, η JavaScript θεωρεί επίσης τους χαρακτήρες Unicode \u2028 και \u2029 ως χαρακτήρες νέας γραμμής, τους οποίους πρέπει επίσης να αποφύγουμε. Τέλος, ο αναφερόμενος ειδικός κανόνας: η συμβολοσειρά δεν πρέπει να περιέχει το </script. Αυτό μπορεί να αποτραπεί, για παράδειγμα, αντικαθιστώντας το με το <\/script.

Αν το γνωρίζατε αυτό, συγχαρητήρια.

Ερώτηση αριθ. 6

Το ακόλουθο πλαίσιο φαίνεται να είναι απλώς μια παραλλαγή του προηγούμενου. Πιστεύετε ότι η αντιμετώπιση θα είναι διαφορετική;

<p onclick="foo('<?= $str ?>')"></p>

Λύση: αλλά σε αντίθεση με το προηγούμενο πλαίσιο, όπου οι οντότητες HTML δεν διαφυλάσσονταν, εδώ διαφυλάσσονται. Έτσι, πρώτα, αποφεύγουμε τη συμβολοσειρά JavaScript χρησιμοποιώντας backslashes και στη συνέχεια αντικαθιστούμε τους ειδικούς χαρακτήρες (" και &) με οντότητες HTML. Προσέξτε, η σωστή σειρά είναι σημαντική.

Όπως μπορείτε να δείτε, το ίδιο λεκτικό JavaScript μπορεί να κωδικοποιηθεί διαφορετικά σε ένα <script> στοιχείο και διαφορετικά σε ένα χαρακτηριστικό!

Ερώτηση αριθ. 7

Ας επιστρέψουμε από τη JavaScript στην HTML. Ποιους χαρακτήρες πρέπει να αντικαταστήσουμε μέσα στο σχόλιο και πώς;

<!-- <?= $str ?> -->

Λύση: Μέσα σε ένα σχόλιο HTML (και XML), μπορούν να εμφανιστούν όλοι οι παραδοσιακοί ειδικοί χαρακτήρες, όπως <, &, " και '. Αυτό που απαγορεύεται, και αυτό μπορεί να σας εκπλήξει, είναι το ζεύγος χαρακτήρων --. Η αποφυγή αυτής της ακολουθίας δεν καθορίζεται, οπότε εξαρτάται από εσάς πώς θα την αντικαταστήσετε. Μπορείτε να τους διανθίσετε με κενά. Ή, για παράδειγμα, να τους αντικαταστήσετε με ==.

Ερώτηση αριθ. 8

Πλησιάζουμε στο τέλος, οπότε ας προσπαθήσουμε να διαφοροποιήσουμε την ερώτηση. Προσπαθήστε να σκεφτείτε τι πρέπει να προσέχετε όταν εκτυπώνετε μια μεταβλητή σε αυτό το πλαίσιο:

<a href="<?= $str ?>">...</a>

Λύση: javascript:, επειδή μια διεύθυνση URL που αποτελείται με αυτόν τον τρόπο θα εκτελούσε τον κώδικα του εισβολέα όταν έκανε κλικ.

Ερώτηση αριθ. 9

Επιτέλους, μια απόλαυση για τους πραγματικούς γνώστες. Αυτό είναι ένα παράδειγμα εφαρμογής που χρησιμοποιεί ένα σύγχρονο πλαίσιο JavaScript, συγκεκριμένα το Vue. Ας δούμε αν μπορείτε να καταλάβετε τι πρέπει να προσέξετε όταν εκτυπώνετε μια μεταβλητή μέσα στο στοιχείο #app:

<div id="app">
    <?= $str ?>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
    el: '#app',
    ...
})
</script>

Αυτός ο κώδικας δημιουργεί μια εφαρμογή Vue που θα αποδοθεί στο στοιχείο #app. Η Vue ερμηνεύει το περιεχόμενο αυτού του στοιχείου ως το πρότυπό της. Και μέσα στο πρότυπο, ερμηνεύει τις διπλές αγκύλες, οι οποίες αντιπροσωπεύουν την έξοδο μεταβλητών ή την κλήση κώδικα JavaScript (πχ, {{ foo }}).

Έτσι, μέσα στο στοιχείο #app, εκτός από τους χαρακτήρες < και &, το ζεύγος {{ έχει επίσης μια ειδική σημασία, την οποία πρέπει να αντικαταστήσουμε με μια άλλη κατάλληλη ακολουθία για να αποτρέψουμε το Vue από το να το ερμηνεύσει ως δική του ετικέτα. Η αντικατάσταση με οντότητες HTML δεν βοηθάει σε αυτή την περίπτωση. Πώς να το αντιμετωπίσουμε; Υπάρχει ένα τέχνασμα: εισάγετε ένα κενό σχόλιο HTML ανάμεσα στις αγκύλες {<!-- -->{, και η Vue αγνοεί αυτή την ακολουθία.

Αποτελέσματα κουίζ

Πώς τα πήγατε στο κουίζ; Πόσες σωστές απαντήσεις έχετε; Αν απαντήσατε τουλάχιστον 4 ερωτήσεις σωστά, ανήκετε στο κορυφαίο 8% των λύσεων – συγχαρητήρια!

Πάντως, η διασφάλιση της ασφάλειας του ιστοτόπου σας απαιτεί τον σωστό χειρισμό της εξόδου σε όλες τις περιπτώσεις.

Αν εκπλαγήκατε από το πόσα διαφορετικά συμφραζόμενα μπορούν να εμφανιστούν σε μια τυπική σελίδα HTML, να ξέρετε ότι δεν τα έχουμε αναφέρει όλα μέχρι στιγμής. Αυτό θα έκανε το κουίζ πολύ μεγαλύτερο. Παρ' όλα αυτά, δεν χρειάζεται να είστε ειδικός στο escaping σε κάθε πλαίσιο, αν το σύστημα template σας μπορεί να το χειριστεί.

Ας τα δοκιμάσουμε λοιπόν.

Πώς αποδίδουν τα συστήματα διαμόρφωσης προτύπων;

Όλα τα σύγχρονα συστήματα δημιουργίας προτύπων διαθέτουν ένα χαρακτηριστικό autoescaping που διαφυλάσσει αυτόματα όλες τις μεταβλητές που εξάγονται. Εάν το κάνουν σωστά, ο ιστότοπός σας είναι ασφαλής. Αν το κάνουν κακώς, ο ιστότοπος είναι εκτεθειμένος στον κίνδυνο ευπάθειας XSS με όλες τις σοβαρές συνέπειές της.

Θα δοκιμάσουμε δημοφιλή συστήματα templating από τις ερωτήσεις σε αυτό το κουίζ για να διαπιστώσουμε την αποτελεσματικότητα της αυτόματης απομάκρυνσής τους. Ας αρχίσει η ανασκόπηση των συστημάτων templating της PHP.

Twig ❌

Πρώτο είναι το σύστημα δημιουργίας προτύπων Twig (έκδοση 3.5), το οποίο χρησιμοποιείται συνήθως σε συνδυασμό με το πλαίσιο Symfony. Θα του αναθέσουμε να απαντήσει σε όλες τις ερωτήσεις του κουίζ. Η μεταβλητή $str θα γεμίζει πάντα με ένα δύσκολο αλφαριθμητικό και θα δούμε πώς χειρίζεται την έξοδό του. Μπορείτε να δείτε τα αποτελέσματα στα δεξιά. Μπορείτε επίσης να εξερευνήσετε τις απαντήσεις και τη συμπεριφορά του στην παιδική χαρά.

   {% set str = "<'\"&" %}
1) <p>{{ str }}</p>
2) <input value="{{ str }}">
3) <input value='{{ str }}'>

   {% set str = "foo onclick=evilCode()" %}
4) <input value={{ str }}>

   {% set str = "'\"\n\u{2028}" %}
5) <script>	let foo = '{{ str }}'; </script>
6) <p onclick="foo('{{ str }}')"></p>

   {% set str = "-- ---" %}
7) <!-- {{ str }} -->

   {% set str = "javascript:evilCode()" %}
8) <a href="{{ str }}">...</a>

   {% set str = "{{ foo }}" %}
9) <div id="app"> {{ str }} </div>

✅ <p>&lt;&#039;&quot;&amp;</p>
✅ <input value="&lt;&#039;&quot;&amp;">
✅ <input value='&lt;&#039;&quot;&amp;'>


❌ <input value=foo onclick=evilCode()>


❌ <script> let foo = &#039;&quot;u{2028}; </script>
❌ <p onclick="foo(&#039;&quot;u{2028})"></p>


❌ <!-- -- --- -->


❌ <a href="javascript:evilCode()">...</a>


❌ <div id="app"> {{ foo }} </div>

Το Ξυλάκι απέτυχε σε έξι από τα εννέα τεστ!

Δυστυχώς, η αυτόματη διαφυγή του Twig λειτουργεί μόνο σε κείμενο και χαρακτηριστικά HTML, και ακόμα και τότε μόνο όταν αυτά περικλείονται σε εισαγωγικά. Μόλις λείπουν τα εισαγωγικά, το Twig δεν αναφέρει κανένα σφάλμα και δημιουργεί ένα κενό ασφαλείας XSS.

Αυτό είναι ιδιαίτερα δυσάρεστο επειδή με αυτόν τον τρόπο γράφονται οι τιμές των χαρακτηριστικών σε δημοφιλείς βιβλιοθήκες όπως το React ή το Svelte. Ένας προγραμματιστής που χρησιμοποιεί τόσο το Twig όσο και το React μπορεί πολύ φυσικά να ξεχάσει τα εισαγωγικά.

Το autoescaping του Twig αποτυγχάνει επίσης σε όλα τα άλλα παραδείγματα. Στα συμφραζόμενα (5) και (6), απαιτείται χειροκίνητη διαφυγή με τη χρήση του {{ str|escape('js') }}, ενώ για άλλα συμφραζόμενα, το Twig δεν προσφέρει καν μια λειτουργία διαφυγής. Επίσης, δεν διαθέτει προστασία από την εκτύπωση κακόβουλου συνδέσμου (8) ή υποστήριξη για πρότυπα Vue (9).

Blade ❌❌

Ο δεύτερος συμμετέχων είναι το σύστημα templating Blade (έκδοση 10.9), το οποίο είναι στενά ενσωματωμένο με το Laravel και το οικοσύστημά του. Και πάλι, θα δοκιμάσουμε τις ικανότητές του στις ερωτήσεις του κουίζ μας. Μπορείτε επίσης να εξερευνήσετε τις απαντήσεις του στην παιδική χαρά.

   @php($str = "<'\"&")
1) <p>{{ $str }}</p>
2) <input value="{{ $str }}">
3) <input value='{{ $str }}'>

   @php($str = "foo onclick=evilCode()")
4) <input value={{ $str }}>

   @php($str = "'\"\n\u{2028}")
5) <script> let foo = {{ $str }}; </script>
6) <p onclick="foo({{ $str }})"></p>

   @php($str = "-- ---")
7) <!-- {{ $str }} -->

   @php($str = "javascript:evilCode()")
8) <a href="{{ $str }}">...</a>

   @php($str = "{{ foo }}")
9) <div id="app"> {{ $str }} </div>

✅ <p>&lt;&#039;&quot;&amp;</p>
✅ <input value="&lt;&#039;&quot;&amp;">
✅ <input value='&lt;&#039;&quot;&amp;'>


❌ <input value=foo onclick=evilCode()>


❌ <script>	let foo = &#039;&quot; ; </script>
❌ <p onclick="foo(&#039;&quot; )"></p>


❌ <!-- -- --- -->


❌ <a href="javascript:evilCode()">...</a>


❌❌ <div id="app"> &lt;?php echo e(foo); ?&gt; </div>

Η λεπίδα απέτυχε σε έξι από τις εννέα δοκιμές!

Το αποτέλεσμα είναι παρόμοιο με το Twig. Και πάλι, η αυτόματη διαφυγή λειτουργεί μόνο σε κείμενο και χαρακτηριστικά HTML και μόνο αν αυτά περικλείονται σε εισαγωγικά. Το autoescaping του Blade αποτυγχάνει επίσης σε όλα τα άλλα παραδείγματα. Στα συμφραζόμενα (5) και (6), απαιτείται χειροκίνητη αποφυγή με τη χρήση του {{ Js::from($str) }}. Για άλλα πλαίσια, το Blade δεν προσφέρει καν μια λειτουργία διαφυγής. Δεν διαθέτει επίσης προστασία από την εκτύπωση κακόβουλου συνδέσμου (8) ή υποστήριξη για πρότυπα Vue (9).

Ωστόσο, αυτό που προκαλεί έκπληξη είναι η αποτυχία της οδηγίας @php στο Blade, η οποία προκαλεί την έξοδο του δικού της κώδικα PHP απευθείας στην έξοδο, όπως φαίνεται στην τελευταία γραμμή.

Latte ✅

Το τρίο ολοκληρώνεται με το σύστημα δημιουργίας προτύπων Latte (έκδοση 3.0). Θα δοκιμάσουμε την αυτόματη διαγραφή του. Μπορείτε επίσης να εξερευνήσετε τις απαντήσεις και τη συμπεριφορά του στην παιδική χαρά.

   {var $str = "<'\"&"}
1) <p>{$str}</p>
2) <input value="{$str}">
3) <input value='{$str}'>

   {var $str = "foo onclick=evilCode()"}
4) <input value={$str}>

   {var $str = "'\"\n\u{2028}"}
5) <script>	let foo = {$str}; </script>
6) <p onclick="foo({$str})"></p>

   {var $str = "-- ---"}
7) <!-- {$str} -->

   {var $str = "javascript:evilCode()"}
8) <a href="{$str}">...</a>

   {var $str = "{{ foo }}"}
9) <div id="app"> {$str} </div>

✅ <p>&lt;'"&amp;</p>
✅ <input value="&lt;&apos;&quot;&amp;">
✅ <input value='&lt;&apos;&quot;&amp;'>


✅ <input value="foo onclick=evilCode()">


✅ <script> let foo = "'\"\n\u2028"; </script>
✅ <p onclick="foo(&quot;&apos;\&quot;\n\u2028&quot;)"></p>


✅ <!--  - -  - - -  -->


✅ <a href="">...</a>


✅ <div id="app"> {<!-- -->{ foo }} </div>

Ο Λάττε αρίστευσε και στις εννέα εργασίες!

Κατάφερε να χειριστεί τα χαμένα εισαγωγικά σε χαρακτηριστικά HTML, να επεξεργαστεί JavaScript τόσο στην <script> στοιχείο και στα χαρακτηριστικά, και αντιμετώπισε την απαγορευμένη ακολουθία στα σχόλια HTML.

Επιπλέον, απέτρεψε μια κατάσταση όπου κάνοντας κλικ σε έναν κακόβουλο σύνδεσμο που παρείχε ένας επιτιθέμενος θα μπορούσε να εκτελέσει τον κώδικά του. Και κατάφερε να χειριστεί το escaping των ετικετών για το Vue.

Δοκιμή μπόνους

Μία από τις βασικές δυνατότητες όλων των συστημάτων διαμόρφωσης προτύπων είναι η εργασία με μπλοκ και η σχετική κληρονομικότητα προτύπων. Ως εκ τούτου, θα δώσουμε σε όλα τα δοκιμασμένα συστήματα δημιουργίας προτύπων μία ακόμη εργασία. Θα δημιουργήσουμε ένα μπλοκ description, το οποίο θα εκτυπώσουμε σε ένα χαρακτηριστικό HTML. Στον πραγματικό κόσμο, ο ορισμός του μπλοκ θα βρισκόταν, φυσικά, στο πρότυπο παιδί και η έξοδός του στο πρότυπο γονέα, όπως η διάταξη. Αυτή είναι απλώς μια απλοποιημένη μορφή, αλλά είναι αρκετή για να δοκιμάσουμε την αυτόματη διαγραφή κατά την έξοδο μπλοκ. Πώς πήγαν οι επιδόσεις;

Twig: απέτυχε ❌ κατά την έξοδο μπλοκ, οι χαρακτήρες δεν διαφυλάσσονται σωστά

{% block description %}
	rock n' roll
{% endblock %}

<meta name='description'
	content='{{ block('description') }}'>




<meta name='description'
	content=' rock n' roll '> ❌

Blade: αποτυχία ❌ κατά την εξαγωγή μπλοκ, οι χαρακτήρες δεν έχουν διαχωριστεί σωστά

@section('description')
	rock n' roll
@endsection

<meta name='description'
	content='@yield('description')'>




<meta name='description'
	content=' rock n' roll '> ❌

Latte: πέρασε ✅ κατά την εξαγωγή μπλοκ, χειρίστηκε σωστά τους προβληματικούς χαρακτήρες

{block description}
	rock n' roll
{/block}

<meta name='description'
	content='{include description}'>




<meta name='description'
	content=' rock n&apos; roll '> ✅

Γιατί είναι τόσοι πολλοί ιστότοποι ευάλωτοι;

Το Autoescaping σε συστήματα όπως το Twig, το Blade ή το Smarty λειτουργεί με απλή αντικατάσταση πέντε χαρακτήρων <>"'& με οντότητες HTML και δεν διακρίνει το περιεχόμενο. Ως εκ τούτου, λειτουργεί μόνο σε ορισμένες περιπτώσεις και αποτυγχάνει σε όλες τις άλλες. Η αφελής αυτόματη κωδικοποίηση είναι ένα επικίνδυνο χαρακτηριστικό, επειδή δημιουργεί μια ψευδή αίσθηση ασφάλειας.

Δεν αποτελεί έκπληξη, λοιπόν, το γεγονός ότι επί του παρόντος πάνω από το 27% των ιστότοπων έχουν κρίσιμες ευπάθειες, κυρίως XSS (πηγή: Acunetix Web Vulnerability Report). Πώς να βγείτε από αυτή την κατάσταση; Χρησιμοποιήστε ένα σύστημα templating που διακρίνει τα συμφραζόμενα.

Το Latte είναι το μοναδικό σύστημα δημιουργίας προτύπων PHP που δεν αντιλαμβάνεται ένα πρότυπο ως μια απλή σειρά χαρακτήρων, αλλά κατανοεί την HTML. Γνωρίζει τι είναι οι ετικέτες, τα χαρακτηριστικά, κ.λπ. Διακρίνει τα συμφραζόμενα. Και ως εκ τούτου, αποφεύγει σωστά τις αποδράσεις σε κείμενο HTML, διαφορετικά μέσα σε ετικέτες HTML, διαφορετικά μέσα σε JavaScript κ.λπ.

Το Latte αντιπροσωπεύει έτσι το μόνο ασφαλές σύστημα δημιουργίας προτύπων.


Επιπλέον, χάρη στην κατανόηση της HTML, προσφέρει τα υπέροχα n:attributes, τα οποία αγαπούν οι χρήστες:

<ul n:if="$menu">
	<li n:foreach="$menu->getItems() as $item">{$item->title}</li>
</ul>

Τελευταίες θέσεις