Ασφάλεια Ιστού: Πώς να περιορίσετε τα cookie HTTP σας

Σημείωση: αυτό είναι το μέρος 4 μιας σειράς ασφάλειας στον ιστό. Το μέρος 3 ήταν η ασφάλεια της εφαρμογής ιστού με αυτές τις κεφαλίδες HTTP.

Φανταστείτε να είστε προγραμματιστής backend που πρέπει να εφαρμόσει συνεδρίες σε μια εφαρμογή: το πρώτο πράγμα που έρχεται στο μυαλό σας είναι να εκδώσετε ένα διακριτικό στους πελάτες και να τους ζητήσετε να στείλουν αυτό το διακριτικό με τα επόμενα αιτήματά τους. Από εκεί και πέρα ​​θα μπορείτε να αναγνωρίζετε πελάτες βάσει του διακριτικού που περιλαμβάνεται στο αίτημά τους.

Τα cookie HTTP γεννήθηκαν για την τυποποίηση αυτού του είδους μηχανισμού στα προγράμματα περιήγησης. Δεν είναι παρά ένας τρόπος αποθήκευσης δεδομένων που αποστέλλονται από τον διακομιστή και να τα στέλνουν μαζί με μελλοντικά αιτήματα. Ο διακομιστής στέλνει ένα cookie, το οποίο περιέχει μικρά κομμάτια δεδομένων. Το πρόγραμμα περιήγησης το αποθηκεύει και το στέλνει μαζί με μελλοντικά αιτήματα στον ίδιο διακομιστή.

Γιατί θα ενοχλούσαμε τα cookie από την άποψη της ασφάλειας; Επειδή τα δεδομένα που περιέχουν είναι, πολύ συχνά, εξαιρετικά ευαίσθητα. Τα cookie χρησιμοποιούνται γενικά για την αποθήκευση αναγνωριστικών περιόδου σύνδεσης ή πρόσβασης σε διακριτικά, ένα ιερό grail ενός εισβολέα. Μόλις εκτεθούν ή παραβιαστούν, οι εισβολείς μπορούν να πλαστοπροσωπήσουν τους χρήστες ή να κλιμακώσουν τα προνόμιά τους στην εφαρμογή σας.

Η εξασφάλιση των cookie είναι μία από τις πιο σημαντικές πτυχές κατά την εφαρμογή συνεδριών στον Ιστό. Αυτό το κεφάλαιο, επομένως, θα σας δώσει μια καλύτερη κατανόηση των cookies, πώς να τα ασφαλίσετε και ποιες εναλλακτικές λύσεις μπορούν να χρησιμοποιηθούν.

Τι κρύβεται πίσω από ένα cookie;

Ένας διακομιστής μπορεί να ορίσει ένα cookie χρησιμοποιώντας την Set-Cookieκεφαλίδα:

HTTP/1.1 200 OkSet-Cookie: access_token=1234...

Στη συνέχεια, ένας πελάτης θα αποθηκεύσει αυτά τα δεδομένα και θα τα στείλει σε επόμενα αιτήματα μέσω της Cookieκεφαλίδας:

GET / HTTP/1.1Host: example.comCookie: access_token=1234...

Σημειώστε ότι οι διακομιστές μπορούν να ορίσουν πολλά cookie ταυτόχρονα:

HTTP/1.1 200 OkSet-Cookie: access_token=1234Set-Cookie: user_id=10...

και οι πελάτες μπορούν να αποθηκεύσουν πολλά cookie και να τα στείλουν στο αίτημά τους:

GET / HTTP/1.1Host: example.comCookie: access_token=1234; user_id=10...

Εκτός από το απλό κλειδί και την αξία , τα cookie μπορούν να φέρουν πρόσθετες οδηγίες που περιορίζουν το χρόνο και τη διάθεσή τους.

Λήγει

Καθορίζει πότε πρέπει να λήξει ένα cookie, έτσι ώστε τα προγράμματα περιήγησης να μην αποθηκεύουν και να το μεταδίδουν επ 'αόριστον Ένα σαφές παράδειγμα είναι ένα αναγνωριστικό περιόδου σύνδεσης, το οποίο συνήθως λήγει μετά από κάποιο χρονικό διάστημα. Αυτή η οδηγία εκφράζεται ως ημερομηνία με τη μορφή Date: , <ώρας> ;:: GMT, όπως Ημερομηνία: Παρ, 24 Αυγ 2018, 04:33:00 GMT. Ακολουθεί ένα πλήρες παράδειγμα cookie που λήγει την 1η Ιανουαρίου 2018:

access_token=1234;Expires=Mon, 1st Jan 2018 00:00:00 GMT

Μέγιστη ηλικία

Παρόμοια με την Expiresοδηγία, Max-Ageκαθορίζει τον αριθμό των δευτερολέπτων έως ότου λήξει το cookie. Ένα cookie που θα διαρκέσει 1 ώρα μοιάζει με το ακόλουθο

access_token=1234;Max-Age=3600

Τομέα

Αυτή η οδηγία καθορίζει σε ποιους φιλοξενεί το cookie στο οποίο πρέπει να σταλεί. Θυμηθείτε, τα cookie γενικά περιέχουν ευαίσθητα δεδομένα, επομένως είναι σημαντικό για τα προγράμματα περιήγησης να μην τα διαρρέουν σε μη αξιόπιστους κεντρικούς υπολογιστές. Ένα cookie με την οδηγία Domain=trusted.example.comδεν θα αποστέλλεται μαζί με αιτήματα σε άλλο τομέα εκτός από trusted.example.com, ούτε στον ριζικό τομέα example.com. Ακολουθεί ένα έγκυρο παράδειγμα ενός cookie που περιορίζεται σε έναν συγκεκριμένο υποτομέα:

access_token=1234;Domain=trusted.example.com

Μονοπάτι

Παρόμοιο με την Domainοδηγία, αλλά ισχύει για τη διαδρομή URL ( /some/path). Αυτή η οδηγία αποτρέπει την κοινή χρήση ενός cookie με μη αξιόπιστες διαδρομές, όπως στο ακόλουθο παράδειγμα:

access_token=1234;Path=/trusted/path

Συνεδρία και επίμονα cookie

Όταν ένας διακομιστής στέλνει ένα cookie χωρίς να το ορίζει Expiresή Max-Age, τα προγράμματα περιήγησης το αντιμετωπίζουν ως cookie συνεδρίας : αντί να μαντέψουν το χρόνο ζωής του ή να εφαρμόσουν αστείες ευρετικές, το πρόγραμμα περιήγησης το διαγράφει όταν τερματίζεται.

Ένα μόνιμο cookie , αντίθετα, αποθηκεύονται στον υπολογιστή-πελάτη μέχρι την καθορισμένη προθεσμία από της Expiresή Max-Ageοδηγίες.

Αξίζει να σημειωθεί ότι τα προγράμματα περιήγησης ενδέχεται να χρησιμοποιούν έναν μηχανισμό γνωστό ως επαναφορά συνεδρίας , όπου τα cookie συνεδρίας μπορούν να ανακτηθούν μετά τον τερματισμό του προγράμματος-πελάτη. Τα προγράμματα περιήγησης έχουν εφαρμόσει αυτό το είδος μηχανισμού για να επιτρέπουν στους χρήστες να συνεχίσουν μια περίοδο λειτουργίας μετά από, για παράδειγμα, ένα σφάλμα. Η επαναφορά συνεδρίας θα μπορούσε να οδηγήσει σε απροσδόκητα προβλήματα εάν αναμένουμε ότι τα cookie περιόδου λειτουργίας θα λήξουν εντός ενός συγκεκριμένου χρονικού πλαισίου (για παράδειγμα, είμαστε απολύτως θετικοί ότι μια περίοδος σύνδεσης δεν θα διαρκέσει περισσότερο από το Χ χρονικό διάστημα).

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

Δεν νομίζω ότι υπάρχει ξεκάθαρος νικητής μεταξύ συνεδρίας και επίμονων cookie, καθώς και οι δύο εξυπηρετούν πολύ διαφορετικούς σκοπούς. Αυτό που έχω παρατηρήσει είναι ότι το Facebook, το Google και παρόμοιες υπηρεσίες θα χρησιμοποιούν μόνιμα cookie. Από προσωπική εμπειρία, συνήθως χρησιμοποιούσα μόνιμα cookie, αλλά δεν χρειάστηκε ποτέ να συνδέσω κρίσιμες πληροφορίες, όπως έναν αριθμό κοινωνικής ασφάλισης ή ένα υπόλοιπο τραπεζικού λογαριασμού σε μια συνεδρία.

Σε ορισμένα περιβάλλοντα ενδέχεται να σας ζητηθεί να χρησιμοποιήσετε cookie περιόδου λειτουργίας λόγω των απαιτήσεων συμμόρφωσης. Έχω δει τους ελεγκτές να ζητούν να μετατρέψουν όλα τα επίμονα cookie σε αυτά της περιόδου λειτουργίας. Όταν οι άνθρωποι με ρωτούν « πρέπει να χρησιμοποιήσω το X ή το Y; «Η απάντησή μου είναι« εξαρτάται από το πλαίσιο ». Η δημιουργία ενός βιβλίου επισκεπτών για το ιστολόγιό σας έχει διαφορετικές επιπτώσεις ασφαλείας από την οικοδόμηση ενός τραπεζικού συστήματος. Όπως θα δούμε αργότερα σε αυτήν τη σειρά, θα συνιστούσα να κατανοήσετε το περιεχόμενό σας και να προσπαθήσετε να δημιουργήσετε ένα σύστημα που να είναι αρκετά ασφαλές : η απόλυτη ασφάλεια είναι ουτοπία, ακριβώς όπως ένα 100% SLA.

Μόνο κεντρικός υπολογιστής

Όταν ένας διακομιστής δεν περιλαμβάνει Domainοδηγία, το cookie πρέπει να θεωρείται ως host-onlyένα, πράγμα που σημαίνει ότι η ισχύς του περιορίζεται μόνο στον τρέχοντα τομέα.

This is a sort of “default” behavior from browsers when they receive a cookie that does not have a Domain set. You can find a small example I wrote at github.com/odino/wasec/tree/master/cookies. It’s a simple web app that sets cookies based on URL parameters, and prints cookies on the page, through some JavaScript code:

 let content = "none";
if (document.cookie) { let cookies = document.cookie.split(';') content = ''
cookies.forEach(c => { content += "

" + c + "

" }) }
document.getElementById('output').innerHTML = "Cookies on this document: " + content + " " 

If you follow the instructions in the README you will be able to access a webserver at wasec.local:7888, which illustrates how host-only cookies work:

If we then try to visit a subdomain, the cookies we set on the main domain are not going to be visible — try navigating to sub.wasec.local:7888:

A way to circumvent this limitation is, as we’ve seen earlier, to specify the Domain directive of the cookie, something that we can do by visiting wasec.local:7888/?domain=on:

If we have a look at the application running on the subdomain, we will now be able to see cookies set on the parent domain, as they use Domain=wasec.local, which allows any domain “under” wasec.local to access the cookies:

In HTTP terms, this is how the responses sent from the server look like:

~ ᐅ curl -I //wasec.local:7888HTTP/1.1 200 OKSet-Cookie: example=test_cookieDate: Fri, 24 Aug 2018 09:34:08 GMTConnection: keep-alive
~ ᐅ curl -I "//wasec.local:7888/?domain=on"HTTP/1.1 200 OKSet-Cookie: example=test_cookieSet-Cookie: example_with_domain=test_domain_cookie;Domain=wasec.localDate: Fri, 24 Aug 2018 09:34:11 GMTConnection: keep-alive

Supercookies

What if we were able to set a cookie on a top-level domain (TLD) such as .com or .org? That would definitely be a huge security concern, for two main reasons:

  • user privacy: every website running on that specific TLD would be able to track information about the user in a shared storage
  • information leakage: a server could mistakenly store a sensitive piece of data in a cookie available to other sites

Luckily, TLD-cookies, otherwise known as supercookies, are disabled by web browsers for the reasons I mentioned above. If you try to set a supercookie, the browser will simply refuse to do so. If we append the parameter super=on in our example, we will see the server trying to set a supercookie, while the browser ignores it:

In today’s web, though, there are other ways to keep track of users, ETag tracking being an example of this. Since cookies are usually associated with tracking, these techniques are often referred to as supercookies as well, even though they do not rely on HTTP cookies. Other terms that may refer to the same set of technologies and practices are permacookies (permanent cookies) or zombiecookies (cookies that never die).

Unwanted Verizon Ads Companies love to make money out of ads, that’s no news. But when ISPs start to aggressively track their customers in order to serve unwanted ads, well, that’s a different story. In 2016, Verizon was found guilty of tracking users without their consent, and sharing their information with advertisers. This resulted in a fine of $1.35 million and the inability, for the company, to continue with their questionable tracking policy. Another interesting example was Comcast, who used to include custom JavaScript code in web pages served through its network. Needless to say, if all web traffic would be served through HTTPS we wouldn’t have this problem, as ISPs wouldn’t be able to decrypt and manipulate traffic on-the-fly.

Cookie flags that matter

Until now we’ve barely scratched the surface of HTTP cookies. It’s now time for us to taste the real juice.

There are 3 very important directives (Secure, HttpOnly, and SameSite) that should be understood before using cookies, as they heavily impact how cookies are stored and secured.

Encrypt it or forget it

Cookies contain very sensitive information. If attackers get hold of a session ID, they can impersonate users by hijacking their sessions.

Most session hijacking attacks usually happen through a man-in-the-middle who can listen to the unencrypted traffic between the client and server, and steal any information that’s been exchanged. If a cookie is exchanged via HTTP, then it’s vulnerable to MITM attacks and session hijacking.

To overcome the issue, we can use HTTPS when issuing the cookie and add the Secure flag to it. This instructs browsers to never send the cookie in plain HTTP requests.

Going back to our practical example, we can test this out by navigating to //wasec.local:7889/?secure=on. The server sets 2 additional cookies, one with the Secure flag and one without:

When we go back and navigate to the HTTP version of the site, we can clearly see that the Secure cookie is not available in the page. Try navigating to wasec.local:7888.

We can clearly see that the HTTPS version of our app set a cookie that’s available to the HTTP one (the not_secure one), but the other cookie, flagged as Secure, is nowhere to be seen.

Marking sensitive cookies as Secure is an incredibly important aspect of cookie security. Even if you serve all of your traffic over HTTPS, attackers can find a way to set up a plain old HTTP page under your domain and redirect users there. Unless your cookies are Secure, they will then have access to a very delicious meal.

JavaScript can’t touch this

As we’ve seen earlier in this series, XSS attacks allow a malicious user to execute arbitrary JavaScript on a page. Considering that you could read the contents of the cookie jar with a simple document.cookie, protecting our cookies from untrusted JavaScript access is a very important aspect of hardening cookies from a security standpoint.

Luckily, the HTTP spec took care of this with the HttpOnly flag. By using this directive we can instruct the browser not to share the cookie with JavaScript. The browser then removes the cookie from the window.cookie variable, making it impossible to access the cookie via JavaScript.

If we look at the example at wasec.local:7888/?httponly=on, we can clearly see how this works. The browser has stored the cookie (as seen in the DevTools screenshot below) but won’t share it with JavaScript:

The browser will then keep sending the cookie to the server in subsequent requests, so the server can still keep track of the client through the cookie. The trick, in this case, is that the cookie is never exposed to the end-user, and remains “private” between the browser and the server.

The HttpOnly flag helps mitigate XSS attacks by denying access to critical information stored in a cookie. Using it makes it harder for an attacker to hijack a session.

In 2003, researchers found an interesting vulnerability around the HttpOnly flag, Cross-Site Tracing (XST). In a nutshell, browsers wouldn’t prevent access to HttpOnly cookies when using the TRACE request method. While most browsers have now disabled this method, my recommendation would be to disable TRACE at your webserver’s level, returning the 405 Not allowed status code.

SameSite: The CSRF killer

Last but not least, the SameSite flag, one of the latest entries in the cookie world.

Introduced by Google Chrome v51, this flag effectively eliminates Cross-Site Request Forgery (CSRF) from the web. SameSite is a simple yet groundbreaking innovation as previous solutions to CSRF attacks were either incomplete or too much of a burden to site owners.

In order to understand SameSite, we first need to have a look at the vulnerability it neutralizes. A CSRF is an unwanted request made by site A to site B while the user is authenticated on site B.

Sounds complicated? Let me rephrase.

Suppose that you are logged in on your banking website, which has a mechanism to transfer money based on an HTML rm> and a few additional parameters (destination account and amount). When the website recei ves a POST request with those parameters and your session cookie, it will process the transfer. Now, suppose a malicious 3rd party website sets up an HTML form as such:

See where this is getting?

If you click on the submit button, cleverly disguised as an attractive prize, $1000 is going to be transferred from your account. This is a cross-site request forgery - nothing more, nothing less.

Traditionally, there have been 2 ways to get rid of CSRF:

  • Origin and Referer headers: the server could verify that these headers come from trusted sources (For example //bank.com). The downside of this approach is that, as we’ve seen earlier in this series, neither the Origin nor the Referer are very reliable and could be “turned off” by the client in order to protect the user’s privacy.
  • CSRF tokens: the server could include a signed token in the form, and verify its validity once the form is submitted. This is generally a solid approach and it’s been the recommended best practice for years. The drawback of CSRF tokens is that they’re a technical burden for the backend, as you’d have to integrate token generation and validation in your web application. This might not seem to be a complicated task, but a simpler solution would be more than welcome.

SameSite cookies aim to supersede the solutions mentioned above once and for all. When you tag a cookie with this flag, you tell the browser not to include the cookie in requests that were generated by different origins. When the browser initiates a request to your server and a cookie is tagged as SameSite, the browser will first check whether the origin of the request is the same origin that issued the cookie. If it’s not, the browser will not include the cookie in the request.

We can have a practical look at SameSite with the example at github.com/odino/wasec/tree/master/cookies. When you browse to wasec.local:7888/?samesite=on the server will set a SameSite cookie and a “regular” one.

If we then visit wasec2.local:7888/same-site-form we will see an example HTML form that will trigger a cross-site request:

If we click on the submit button of the form, we will then be able to understand the true power of this flag. The form will redirect us to wasec.local:7888, but there is no trace of the SameSite cookie in the request made by the browser:

Don’t get confused by seeing same_site_cookie=test on your screen: the cookie is made available by the browser, but it wasn’t sent in the request itself. We can verify this by simply typing //wasec.local:7888/ in the address bar:

Since the originator of the request is “safe” (no origin, GET method) the browser sends the SameSite cookie with the request.

This ingenious flag has 2 variants, Lax and Strict. Our example uses the former, as it allows top-level navigation to a website to include the cookie. When you tag a cookies as SameSite=Strict instead, the browser will not send the cookie across any cross-origin request, including top-level navigation. This means that if you click a link to a website that uses strict cookies you won’t be logged in at all. An extremely high amount of protection that, on the other hand, might surprise users. The Lax mode allows these cookies to be sent across requests using safe methods (such as GET), creating a very useful mix between security and user experience.

Let’s recap what we’ve learned about cookies flags as they are crucial when you’re storing, or allowing access to, sensitive data through them, which is a very standard practice:- marking cookies as Secure will make sure that they won’t be sent across unencrypted requests, rendering man-in-the-middle attacks fairly useless- with the HttpOnly flag we tell the browser not to share the cookie with the client (for example, allowing JavaScript access to the cookie), limiting the blast radius of an XSS attack- tagging the cookie as SameSite=Lax|Strict will prevent the browser from sending it in cross-origin requests, rendering any kind of CSRF attack ineffective

Alternatives

Reading all of this material about cookies and security, you might be tempted to say “I really want to stay away from cookies!”. The reality is that, as of now, cookies are your best bet if you want to implement some sort of session mechanism over HTTP. Every now and then I’m asked to evaluate alternatives to cookies, so I’m going to try and summarize a couple things that get mentioned very often:

  • localStorage: especially in the context of single-page applications (SPA), localStorage gets sometimes mentioned when discussing where to store sensitive tokens. The problem with this approach, though, is that localStorage does not offer any kind of protection against XSS attacks. If an attacker is able to execute a simple localStorage.getItem('token') on a victim’s browser, it’s game over. HttpOnly cookies easily overcome this issue.
  • JWT: JSON Web Tokens define a way to securely create access tokens for a client. JWT is a specification that defines how an access token would look like and does not define where is the token going to be stored. In other words, you could store a JWT in a cookie, the localStorage or even in memory, so it doesn’t make sense to consider JWTs an “alternative” to cookies.

What would LeBron do?

It’s time to move on from the HTTP protocol and its features, such as cookies. Throughout this series we’ve been on a long journey, dissecting why cookies were born, how they’re structured and how you can protect them by applying some restrictions on their Domain, Expires, Max-Age and Path attributes, and how other flags such as Secure, HttpOnly and SameSite are vital in hardening cookies.

Let’s move forward and try to understand what we should do, from a security perspective, when we encounter a particular situation. The next article will try to provide advice based on best practices and past experience.

The next article in this series will introduce what I call “The Situationals”.

Originally published at odino.org (14 September 2018).

You can follow me on Twitter — rants are welcome! ?