8 ms·
Web Storage: the lesser evil for session tokens
- albinowax_ 10y agoLet me know if you think it's all wrong :)
- deleted 10y ago[deleted]
- gregwebs 10y agoIt would be great if you could talk in more specifics about attack scenarios against local storage. In particular one attack cited in favor of cookies is the scenario where your 3rd party CDN js hacked and some code is inserted into the JS to lift out the token from local storage.
- d1plo1d 10y agoSo that is similar to an XSS in that it gains you the ability to inject arbitrary JS in to the page. That scenario is covered in the article with as I understood it the TL;DR being that lifting tokens is less practical in practice then using the browser directly to send malicious requests. The result of either attack are also similar in that as soon as your injecting JS into the page you've gained access to the users session.
- vec 10y agoIf a third party can inject arbitrary JS onto your page then how you store your session token is far from your biggest problem.
- mark242 10y agoYes, this. If you are working with any sort of confidential data, be it personal information, or payment info, or whatever, a determined attacker with access to executing Javascript on your page is going to cause a world of problems, the least of which is gaining access to localStorage. A much more concise example is adding a simple eventlistener on keypress, and just logging that data to a third party. localStorage is ready for widespread use, imo, you just need to know what and when to use it; using localStorage as initial cached state on application startup is extremely useful.
- albinowax_ 10y agoIn the scenario you mention, you're scuppered regardless of whether sessions are stored. The best way to mitigate this particular problem is to use subresource integrity: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity https://developer.mozilla.org/en-US/docs/Web/Security/Subres...
- deleted 10y ago[deleted]
- awinograd 10y agoI definitely agree with the premise that cookies are inherently insecure. With sessionStorage it's unclear to me if there's a clean way to send the secret with every http request. Does that mean binding to every link / button click event? Also you mentioned this, but disappearing on tab-close makes it less of a drop-in replacement for cookies since that breaks existing behavior. Interesting idea though! Enjoyed the read.
- CGamesPlay 10y agoPresumably the ideal use case here is for primarily client-side applications. However, it's worth pointing out that you'd never need to bind to every element individually because of event bubbling: $('body').on('click', 'a', function() { this.href += getToken(); });
- timtadh 10y agoProbably not the best idea to just tack the token onto the URL. There is a reason that people are not generally using tokens on URLs in lieu of cookies for session tokens. A better idea is to add a parameter in the POST body. This means that using this method all routes need a request body and you are therefore going to be mostly using POST in your API.
- albinowax_ 10y agoAgreed, definitely don't put a session token in the URL. I'd recommend using a custom HTTP header to transmit it - this way you aren't forced to use POST for everything.
- sametmax 10y agoDon't. People will copy/paste links. Better use an AJAX lib allowing a global override. Most of them allow it including the one in jquery and angular.
- somlor 10y agoFor single-page apps I use two session tokens. One in localStorage (csrf_token), one in a secure, HttpOnly cookie (auth_token). Both tokens are required for the API to authenticate a request.
- d1plo1d 10y agoCould you explain your reasoning behind the two tokens approach? I'm guessing the HTTPOnly cookie is there to prevent token stealing (low risk as described in the article) and add defence in depth against local storage/cookie zero days?
- dspillett 10y agoThat is what HTTPOnly is intended for: if the client-side code doesn't need to know the value then it shouldn't see the value. It doesn't make a lot of difference, but it make some difference worth having (security in depth, and all that) by blocking session stealing by malicious code that is somehow injected into your application's client-side payload. Of course the value is still sent over the wire so is vulnerable to MiTM attacks that are not otherwise mitigated. I can't think of a benefit off the top of my head for having a second token that is accessible client-side, presumably that is something application specific. Perhaps there is a short-cut to getting a new session token after server-side session expiry (or the user accidentally closing their browser) which can only be used in the presence of a valid client-side token (though shortcuts like that are security holes waiting to happen IMO).
- d1plo1d 10y agoJust to note that the session can still be thoroughly hijacked through malicious javascript on a page protected by HTTPOnly cookies in that the malicious code can make AJAX requests in the users browser to your domain and the HTTPOnly cookies will automatically authorize them. The difference of HTTPOnly cookies vs Local Storage is that the hijacked session is limited to the users browser/computer in the HTTPOnly scenario and in the Local Storage scenario the token can be downloaded and used later by the attacker (this is somewhat mitigated by things like JWT expirations).
- andersonmvd 10y agoAlthough it's true that tokens in localstorage doesn't expire, the token itself can expire if you set it to expire when using Json Web Tokens (JWT) for example.
- avolcano 10y agoIt's funny, I got started with web development relatively recently (2012 or so) and immediately started working on single-page/client-side applications, so I used localStorage from the start. It seemed easy enough to make a little Ajax wrapper for all my requests to tack the authentication token on (whether as a URL param, form data, or header data). Imagine my surprise when I later learned how cookie storage worked! There was a brief moment of "oh, I guess not having to manually append this to my requests would be nice," only to read about CSRFs and other horrors cookies enabled. Since then, I've fixed multiple existing CSRF exploits in production (or near-production) applications, and continued to avoid cookies wherever possible.
- spriggan3 10y ago> Imagine my surprise when I later learned how cookie storage worked! There was a brief moment of "oh, I guess not having to manually append this to my requests would be nice," only to read about CSRFs and other horrors cookies enabled. Cookies do not require javascript, unlike whatever solution using web apis.
- abritinthebay 10y agoWhich is relevant for the ~1% of the web that doesn't use JS then (even screen readers support JS) and LOTS of sites won't even work at that point - cookies or not.
- bobajeff 10y agoI just want to say that while a lot of people have browsers with JavaScript enabled you shouldn't use it for things that the browser does natively. Not just for performance reasons. But because that puts the burden of maintenance of those elements on the website. It's a lot like how you shouldn't use fixed width and margins for pages. There are a lot of assumptions developers make when building these elements that can easily change and obsolete their work. So unless you absolutely have to don't take on that burden.
- 10y ago
- therealmarv 10y agoVery newbie question but is this always true? "Another distinction is that sessionStorage will expire when you close the tab rather than when you close the browser" So I cannot use session tokens in the same way as (secured) cookies for letting the user e.g. logged in when all tabs closed?
- avolcano 10y agoThere are two types of "web storage" that you could use for this, localStorage and sessionStorage. The former persists indefinitely, the latter is removed when the tab is closed. If you wanted user tokens to persist past the current browser session, you'd use localStorage.
- therealmarv 10y agothx, last question: Is localStorage more/less/same secure than sessionStorage?
- warfangle 10y agoThe only difference between the two is what causes the data stored within to flush.
- advisedwang 10y agoMy concern with a Web Storage based token is additional latency. With this system, a page has to be served, javascript executed, and an XHR sent before an authenticated response can be sent. With Cookies, the very weakness the author points out - sending it on the first HTTP resonse - is a massive asset for reducing latency. Is there a way to mitigate this?
- avolcano 10y agoI think you could allow cookies only for the initial request (you may even be able to simply only allow cookies for GET requests, as long as you're careful to ensure your GET endpoints don't have data-manipulating side effects) and require passed tokens for all other kinds of requests. For example, you could use a cookie to authenticate serving GET /user/account-settings to render an HTML form, but then require submitting the form (e.g. POST /user/account-settings) to pass a token from localStorage. This wouldn't protect you from all kinds of cookie-based attacks, but at least you'd have a guarantee your endpoints aren't vulnerable to CSRFs.
- albinowax_ 10y agoThis approach is more suited to single-page apps, where the first request only fetches a template and all the subsequent requests to the website are done via AJAX. In the post I'm not really arguing that nobody should ever use cookies - just that Web Storage is an alternative with comparable security.
- ninjakeyboard 10y agoisn't this sort of a use-case-specific choice? for unsecure data you can sign it and put it in a token as a cookie eg JWT. Different use cases will support different approaches and have benefits and draw backs. The more state you put in the browser, the less you have in your app. That can be better or worse for many reasons - simplicity, performance, scalability etc are all impacted.
- zoomzoom 10y agoThe overall gist here seems to be that XSS is not anything to worry about because if there was an exploit the attacker will always choose CSRF. However, there are well-understood and proven mitigation strategies for almost all versions of CSRF attacks. For XSS you are reliant on a CDN or your own code review of your hosted JS to prevent all XSS. I'm not willing to bet I'll never make a mistake like that. An XSS can expose a lot of users very quickly in that scenario, and the mitigating factor usually offered is some time-expiring attribute of the tokens stored locally, but for usability reasons such expiration is usually long enough that an attacker will have plenty of time to use the tokens. Http-only secure cookies provide strong mitigation here that covers more scenarios. In general, it's annoying because local/session storage are so close to being ready for prime-time, and as outlined here there are various advantages. But the XSS mitigations are too important to abandon if you really want the most security available, IMO.
- tptacek 10y agoNo, that's not the overall gist. What he's saying is HttpOnly mitigates only a subset of the exploits for Javascript injection attacks, and does nothing to mitigate the vulnerability. Attackers don't "choose" CSRF over XSS; Javascript injection vulnerabilities create new CSRF-like exploits even when the session cookie is "protected" by HttpOnly. Generally, you can take anything Portswigger says about web security to the bank.
- EGreg 10y agoHTTPOnly is security theater!! It is worse than useless because it makes you think you are secure. XSS attackers are more likely to generate arbitrary requests to secure endpoints as you via JS than they are to send the cookie to themselves at 3AM so they can rush to craft requests. And httponly does didly squat to prevent that. Better is to focus entirely on santizing your output properly in the context it is outputted. And use whitelists, never blacklists.
- tptacek 10y agoHopefully it's clear that I agree with this.
- sly010 10y agoIt absolutely makes sense for large single page applications where you are doing multiple roundtrips to load the application anyway and you will keep the tab open (e.g gmail / trello / etc).
- sgarman 10y agoRight - the author talks about it - tokens should really be expired on the server side not the client side.
- omgitstom 10y agoAs a developer, I feel like I have more control over mitigating CSRF then XSS. But where I have more issues is that OWASP clearly advises not to use web storage for identities: + A single Cross Site Scripting can be used to steal all the data in these objects, so again it's recommended not to store sensitive information in local storage. + A single Cross Site Scripting can be used to load malicious data into these objects too, so don't consider objects in these to be trusted. + Pay extra attention to “localStorage.getItem” and “setItem” calls implemented in HTML5 page. It helps in detecting when developers build solutions that put sensitive information in local storage, which is a bad practice. + Do not store session identifiers in local storage as the data is always accessible by JavaScript. Cookies can mitigate this risk using the httpOnly flag. https://www.owasp.org/index.php/HTML5_Security_Cheat_Sheet https://www.owasp.org/index.php/HTML5_Security_Cheat_Sheet
- albinowax_ 10y agoThis page is one of the ones that persuaded me to write that blog post - I've read it and don't think it's accurate. In spite of the OWASP brand name it's just a wiki - I could edit it myself. If my own post is persuasive and technically sound then that page may be updated in due course.
- raesene9 10y agoThis is an important point and one that's often overlooked with OWASP content. Being on the wiki doesn't necessarily constitute well reviewed up to date advice (indeed there's a lot of outdated content there) That said as it is a wiki anyone is free to create an account and improve it :)
- omgitstom 10y agoWhat has happened with web storage since 2015-09-09 that makes their recommendations out of date?
- CiPHPerCoder 10y ago> As a developer, I feel like I have more control over mitigating CSRF then XSS. Your feeling is correct. Let me put it this way: You mitigate SQL injection effectively by making sure no data (user input) can affect the code (SQL query). i.e. Prepared statements. There is no analogous equivalent for defeating XSS. You have to escape output. https://paragonie.com/blog/2015/06/preventing-xss-vulnerabilities-in-php-everything-you-need-know https://paragonie.com/blog/2015/06/preventing-xss-vulnerabil... Escaping input for SQL injection "works", but has failed pretty hard in the past: http://stackoverflow.com/a/12118602/2224584 http://stackoverflow.com/a/12118602/2224584 (Character encoding, for the lose.) By comparison, CSRF is trivial. You use a token that only the client should know, and implement a trivial challenge/response authentication layer onto your HTTP POST APIs, make sure you're using TLS, and call it a day.
- tptacek 10y agoI'm worried that people are confusing what Kettle is saying in this post. He's not saying that XSS protections don't matter because attackers "prefer" CSRF over XSS. They do not. What he's saying is that pages that are vulnerable to XSS attacks --- or, properly: pages that are vulnerable to Javascript injection and DOM corruption --- open up a wide vista of exploits, and "stealing session tokens" is just the most naive of them. Many of the more sophisticated exploits for Javascript injection resemble CSRF. But I think it's a little confusing to overload the term that way: Javascript-injectors have more latitude than conventional "blind" CSRF attackers do. Long story short: once you've lost control of the Javascript on a page in your same-origin, you're almost always toast. Attackers can hijack sessions. That they don't store and retain the token is irrelevant; the token is just an artifact. I don't know anybody who works full-time in software security who thinks HttpOnly is a meaningful defense, but, if you're out there, chime in and make a case for it. :)
- zielmicha 10y agoI think HttpOnly can help in case when you serve untrusted HTML on your site, but in a different origin. For example, you may have cookie for *.example.com and uploads.example.com serves user uploaded content. Correct me if this is exploitable. (I haven't done this myself, but I've seen this in a wild).
- amjo324 10y agoWhen an application residing at one.example.com sets a cookie, the browser by default resubmits the cookie in all subsequent requests to one.example.com and also to any subdomains, such as sub.one.example.com. It does not submit the cookie to any other domains, including the parent domain (example.com) and any other subdomains of the parent, such as two.example.com. A server can override this default behavior by including a domain attribute in the Set-cookie instruction but this is pretty uncommon. Cookie scoping (and therefore cross-domain protection) can be managed differently if the default behaviour is not intended and HTTPOnly is not relevant here. HTTPOnly is really only a simple mitigation against the most obvious and trivial Cross-Site Scripting (XSS) exploitation technique (i.e stealing a session token).
- 10y ago
- vladgur 10y agoI would agree that Session storage is an ideal way for handling your session token if it wasnt for the fact that its only visible in a current window/tab. I do want my users to be able to keep their session across tabs and not be forced to sign in again and as far as I know the only way to do this is to use cookie storage Does anyone have a strategy use one of Web Storage APIs AND keep your session across tabs/windows?
- TheCoelacanth 10y agoUse localStorage instead of sessionStorage?
- vladgur 10y agoI was going to reply that localStorage persists even when entire browser is closed, whereas cookies do not, but i cant find the docs to confirm it. One use case would be somebody logging into a sensitive site from a public computer and expecting the session to end when the browser is closed.
- slykat 10y agoThe distinction you are making doesn't exist. localStorage and cookies both persist after the browser is closed so if you want that use case you won't get it from either.
- yxlx 10y agoMaybe they were thinking of the options most browsers have to clear selected private data on exit. In that case, though, it's still possible to clear both localStorage and cookies I am sure. Maybe parent commenter currently has the options set so that cookies are cleared but localStorage is not and that's why they thought that this is the way it always is.
- dcosson 10y agoThese are all good points, but ultimately it seems like a well-configured cookie-based storage is equally secure as a similarly well-configured web storage session. There are more hacks required with cookies, but you hopefully only have to configure them in one place and there are libraries to help with the tricky parts like CSRF mitigation. OTOH, there are some usability issues with web storage. Mobile safari in private mode (which can be a not-insignificant percentage of iOS traffic) doesn't support localstorage. Also, if you have any server-rendered pages, say, you have both a single-page web app and some admin pages with server-rendered html content, you need cookies anyway for the server-rendered pages (unless you want to make all your non-ajax forms to submit via ajax to use the local storage token, which is a pain because you'll end up fighting against most web frameworks and re-inventing the wheel). Given this, my takeaway is that cookies basically cover a super-set of the scenarios that local storage tokens do, so it seems preferable to stick with cookies everywhere vs. using two separate auth flows, even though I agree that web storage is conceptually a nicer model with a smaller attack surface.
- vonklaus 10y agoI was curious about the useability. Assuming your configurations are both in perfect parity from a security standpoint, I would be curious to know how broadly available or unavail SS is. A user can turn it off (true of cookies), and iirc browsers have a shared storage max of 5mb ish but it varies from browser to browser. not sure what the wxact num is, but if is it possivlble to max this out? or is a page/domain given its own allocation? Also, is this workable on most mobile devices? it us avail in electron because its essentially a chromium wrapper i think but are there any other compatibility issues / synchronization issues where one or the other makes more sense?
- danjoc 10y ago>I agree that web storage is conceptually a nicer model with a smaller attack surface. I see the exact opposite. The attack surface provided by enabling JavaScript is far greater. Cookies work with JavaScript disabled while Web Storage does not. If one is really really paranoid, hiding behind 7 proxies, using Tor, then one has JavaScript off entirely.
- hoodoof 10y agoOK so I am just about to write some code for storing tokens. What is the definitive answer for where I should store them? Based on this blog post, the answer seems to be "Web Storage"
- slykat 10y agoThere's no definite answer. For example, if you want a site that functions without javascript (i.e. degrades gracefully) you need cookies. Localstorage requires JS while cookies do not. It really depends what kind of web app you are developing.
- pfooti 10y agoI've been doing a fair bit of web app development (single-page, CMS like stuff with user-generated content in the vein of a wiki, forum, or adjacent use space). I keep my session tokens in localstorage, websql, or indexeddb (depending on the platform, [0] is a really nice polyfill / shim to give you good key-value stores on many different browsers). My session tokens are just 48 characters of base64 randomness, because that way I don't have to worry about dealing with JWT - it's a neat idea, but there have been enough bugs in the implementation that I'm still leery. Plus: this way I can expire tokens on the server side. I keep session tokens stored in redis, and do an expire on them by default. Each time the token is fetched, it gets expired again (so the token actually expires N days after the last time the user visits). So yeah, each API request is burdened with two redis calls - one to verify the token and another to reset exipiry. It doesn't seem to weigh on my overall server perf - the actual API call is where most of the cpu goes (relatively speaking, it's still straightforward indexed sql queries most of the time). The token is attached as a bearer token in the header of all my XHR requests. The big problem is images. I work with teachers, who may want to post images of students or student work (to share with colleagues in a research project, behind security is okay, but not if those images can leak to the broader world). You can't (AFAIK) set browser-wide headers on the request that gets generated when the browser encounters an img src="url" tag. That's unfortunate, and I've played with a few options. One is: don't protect images at all - if the images aren't sensitive, then leaking them to twitter or pinterest when a user right clicks isn't the end of the world. One is: use a cookie that's only good for authenticating on the image server, and set it at app init time. One is: fetch the image data in an XHR, set the image up as a blob in the page, and set the img src attribute to the blob's dataurl. None are particularly satisfying. You can crash the chrome tab (at least in chrome, I didn't test in safari / ff once I knew I was crashing chrome) if you go with the blob direction if the blob image is too large (goes OOM, then goes "aww snap"). Unfortunately, the blob approach is probably the best one if you're really worried about leaking images due to user foolishness - any user could download and re-upload an image anywhere, but you want to prevent someone from right-click, copy-image-url, tweeting the image. The blob approach prevents the image url from leaking in that situation, whereas the special-cookie approach doesn't. Ultimately: we decided that we would allow images to leak if they had to - we're training users that they need to not embed potentially sensitive images inline at all. It's annoying, but there we are. Here's where session storage doesn't work as well as cookies. 0: https://mozilla.github.io/localForage/ https://mozilla.github.io/localForage/