As a company that is constantly working with our penetration testing clients on understanding where they should focus their efforts, qualifying risk is second-nature to us. On one hand, we never want to undersell a risk, and have a client accept that risk based on an improperly informed position. On the other hand, I think it’s important to look at risks pragmatically, and avoid hyping things into larger issues than they actually are. Over-hyped issues hurt the credibility of information security professionals and distract from the issues that actually matter. They also waste our clients’ limited resources. In the case of a missing security control, we need to be realistic about how much security that control provides in the first place. That’s why I think we should talk about the httponly cookie flag.
In either a penetration test report, or an automated scan, you will often see a finding for cookies set without the httponly flag. This is a flag in the set-cookie header that tells the browser to prevent JavaScript on pages within the cookie scope from being able to access the cookie. The JavaScript will not be able to retrieve or overwrite the cookie due to this browser-enforced control. This is a good thing to have if this is a cookie that is set by the server, and only used by the server, such as the session cookies of traditional web applications. It means that a cross-site scripting (XSS) exploit will not be able to retrieve the contents of the cookie, and will therefore not be able to perform exploits like a classic session hijack.
That’s great, but its actual impact on the organization presented is quite small. The reason cookies work at all is that the browser can send them automatically. Standard requests generated by DOM elements in the browser will generate requests that send the cookie, and this flag doesn’t make a difference for that. These are events like form submissions, fetching images for img tags, and content for iframes. Furthermore, requests originating from within JavaScript on the page using XMLHttpRequest or Fetch (your typical AJAX stuff), can also be instructed to include cookies. This is a simple boolean flag when constructing the request, and again the httponly flag has no impact on this. I’m going to reiterate that to make sure it’s explicit: the httponly flag prevents JavaScript from reading and writing specific cookies, but it does not prevent JavaScript from instructing the browser to include them on requests that script is sending. This isn’t a new revelation, the internet has worked this way for most of the time it has been in popular use.
So if we go back to the scenario where the httponly flag was supposed to help us, we have 1) a cross-site scripting exploit running malicious script in a victim’s browser, and 2) an httponly session cookie acting as the authentication/authorization token for that use. The attacker cannot read the session cookie. But here are some examples of what the attacker can still do with regards to that application and that session:
- The attacker can read data from the page the victim user is on, and any other page on the same origin (protocol, and full domain or IP, and port). If they wanted to, they could spider the whole application for every piece of data and functionality that user can access, and they could do it all in, for example, a hidden iframe, with no artifacts detectable by the user unless they are monitoring their request traffic.
- The attacker can send requests and read responses from any service in the same origin, or any other service that allows the page’s origin via CORS policy, supplying all cookies the browser has set for the target origin including the session cookie where applicable. This can include target origins that are internal to the victim’s network, as long as the attacker has the inside knowledge or reconnaissance to target them. For example, if the application is at www.badapp.example, and the cookie is scoped to the top of the badapp.example domain, some of the application’s admin functionality uses the internal-api.badapp.example subdomain but accepts the same session cookie for auth, an attacker without direct access to internalapi.badapp.example could still target it through the victim’s browser.
- The attacker can defeat or bypass most forms cross-site request forgery (CSRF) protection, since their exploit is not cross-site, it’s executing in the context of same-site. This also includes the samesite cookie flag, again because the execution context of the payload is the first-party site.
That’s the high-level view. The point is that not being able to retrieve the session cookie might hinder an attacker with a cross-site scripting flaw but a very elementary skillset in web technologies, best case. Your run-of-the-mill web developer will have no trouble working around the limitations the httponly flag imposes.
That’s not to say you shouldn’t use this flag. You absolutely should use it where you can. That’s the best practice. When I perform an application penetration test, my goal is to deliver value in the form of a report that will help you make informed decisions about risks against that application and against your organization. In addition to findings that are directly exploitable, I would point out where breaks from best practices are causing you to needlessly take on additional risk – even if it’s a small amount. I generally include a low risk finding for this issue. But I would say that it should probably never be higher than a low risk, barring highly circumstantial app functionality that would allow it to be exposed without a XSS flaw.