52

I am learning about session middleware.

You have to supply a secret or the middleware complains:

app.use(session({
  secret: "abc",
  resave: false,
  saveUninitialized: false,
  store: new MongoStore({
    mongooseConnection: mongoose.connection
  })
}));

I did some investiagation and the actual session ID is

eKeYlF1DR6AtVkeFZK9vEIHSZT8e0jqZ

But according to the cookie, the session ID is

s%3AeKeYlF1DR6AtVkeFZK9vEIHSZT8e0jqZ.on5ifVE079C4ctKNdkNiJSh8NkQMckjd5fn%2FsxIQWCk

I am confused. Why is it insecure to store the session ID in the cookie directly? Can I not store the session ID directly?

It looks like the secret is a private key and the session ID is being encrypted?

As I understand it, if an attacker can attain this cookie, maybe via XSS or social engineering, the attacker can still hijack the session. I am not sure what the point of the secret is.

peterh
  • 2,938
  • 6
  • 25
  • 31
Angular noob
  • 717
  • 1
  • 6
  • 7
  • 2
    Hi, welcome to [security.se]. I'm not sure what makes you think that it is at all insecure to store the user's sessionid in his cookie (as long as you do it right). From what I can tell, you found that some random js library does something unintuitive. That doesn't mean it is insecure, or that that is the reason for it. You shouldn't be surprised if this is a halfbaked idea... – AviD Jun 21 '15 at 14:49
  • 1
    Hello. [I think I found the author's justification](https://github.com/expressjs/session/issues/68). What do you think @AviD? – Angular noob Jun 21 '15 at 15:14
  • 4
    "It looks like the secret is a private key and the session ID is being encrypted?" - probably not, considering that what you describe as the "actual session ID" appears verbatim in the cookie. It's immediately after "s%3A", at the start. – user2357112 Jun 22 '15 at 04:30
  • So is the secret a... string that is used as a private key that is hashed and then capable of being compared to verify that it came from the intended source? – DeltaFlyer Nov 03 '21 at 00:20

3 Answers3

62

The author of that JS library seems to have made a common, yet mistaken, assumption, though based on just enough knowledge to get things wrong.

You can't just sprinkle magik crypto faerie dust and expect to get more security, like chocolate chips.

What the author is missing is that once you sign the session id, and put that in the cookie - the signed session id IS the session id. That is literally the identifier with which end users will tell the server which session is theirs. It doesn't matter that the server will like to do some internal processing for associating the actual session identifier with the internal representation of the session's memory.

To be clear, if the signed session id is stolen - in any way, whether via XSS, social engineering, or anything else - then the attacker will be able to simply use that signed session id to hijack the user's session, internal representation notwithstanding.


That said, there is one nominal benefit in signing the cookie value before sending it to the browser: tamper detection. That is, upon receiving the user's cookie, the web application can verify that the cookie was not tampered with before using it to look up the session memory, namely by validating the signature. This might possibly prevent certain advanced / active attacks on the session management, by avoiding a session lookup on a session id that was never valid.

However, this supposed benefit is doubtful, since the session id is typically just an id, used to look up the session variables. Also, most attacks targeting the session id would not be stopped by this - session hijacking, session fixation, session donation, session riding, etc...

Anyway, if you really want to verify that the session cookie was not tampered with before using it, there are simpler ways than a digital signature...

AviD
  • 72,138
  • 22
  • 136
  • 218
  • 1
    What would you suggest to avoid session cookie tampering then? – gabeio Jun 21 '15 at 19:55
  • 8
    @gabeio What is the threat you are trying to mitigate here? An attacker trying to guess valid sessionids? If so, then tampering is irrelevant, as long as you don't accept invalid ids (though you might want to look at some rate limiting). Or are you worried about something more esoteric, like SQL injection based on the sessionid? Well then you should prevent SQLi by using all the proper mitigations, and anyway the sessionid should just be a lookup to retrieve the session data (usually in memory, though this could be in some persistent state db). – AviD Jun 21 '15 at 20:20
  • @gabeio My point is that there is typically no need for this, however if you have a specific threat you are worried about then that is what should be mitigated specifically. – AviD Jun 21 '15 at 20:20
  • 1
    This is a library, not an application it only deals with the session, the store connects an app to the database so the store has to deal with sqli mitigation. The biggest threat at this point for this module is tampering. Rate-limiting is for the developer to decide in the main application. How is tampering irrelevant for blocking guessing valid sessionid's? If they don't know the signature they can't even begin to guess session id's to steal. – gabeio Jun 21 '15 at 20:27
  • 4
    Again, if the session id is properly constructed - long, random, etc - then guessing the session id is irrelevant, and tamper detection does not add any significant protection from that threat. I still don't understand why you are worried about tampering as a threat - if the attacker tampers with his sessionid, then it would no longer be valid... which is exactly what we want. – AviD Jun 21 '15 at 20:31
  • 3
    On the other hand, the downside of just throwing on some cryptodust all willy nilly without considering the context and threats, is that these things are *delicate*. If you throw on broken crypto, on top of an already strong sessionid, you might weaken it needlessly. (I'm not saying that library's crypto IS broken - but I am saying it is a non-negligible probability that it might be). – AviD Jun 21 '15 at 20:33
  • Please come see the library for yourself it uses a uid generated directly from node.js's crypto library, base64'ed for the session id. The signing uses node.js's crypto library also to sign using Hmac-sha256 so unless node.js crypto library is broken this library is signing with node.js's core. If node.js's crypto library is broken please let them know. – gabeio Jun 21 '15 at 20:59
  • 10
    @gabeio 1) While I appreciate the offer, its big task to cover all relevant dependencies and such. So thanks, but I'm not going to review all the relevant code. 2) Usually it is not the crypto library that is broken, but how it is used. 3) HMAC is not what is typically known as "signing", so if the devs are confusing between known terminology it just enforces what I suggested, the chance of getting something wrong in the implementation. 4) all that aside, it doesn't matter, since what I said still stands: crypto is delicate, don't throw it around contextless, if you do you may break something. – AviD Jun 21 '15 at 21:33
  • 2
    I'm confused, what's wrong with this? "Users can use whatever session IDs they want. Perhaps someone feels like they should use an auto incrementing number from the SQL database, it doesn't matter, because we protect their uninformed decision by signing the value, elongating the key." Sounds to me like this feature exists to abstract secure session management away from the user of the library, to ensure session IDs can't be spoofed even if the library's user is clueless and uses an insecure method for generating the session id. Seems reasonable to me. – Ajedi32 Jun 22 '15 at 13:29
  • 3
    @Ajedi32 There's nothing exactly wrong with it if taken in the limited context you're talking about. But to me it's not the best of practices to rely on a JS library to save you from bad session management. It seems like an ugly patch to a larger problem. Odds are your application is going to use multiple libraries which don't do some form of session signing. What do you do then? Applications only get larger, and pass off to other people. Is the guy working on this thing in 5 years going to understand the importance of this patch if your underlying session management is insecure? – Steve Sether Jun 22 '15 at 19:23
  • 1
    @SteveSether No, as an application author it's not a good practice to rely on a library like this for security. But look at it from the library author's point of view. This is a library whose purpose is to send potentially sensitive data to a place outside the server's control. Is it better practice to rely on the user of your library to get security right, or to make things as secure as possible even in the face of a poorly written application? – Jander Jul 08 '15 at 22:02
21

A more complete answer from https://web.archive.org/web/20160306144238/http://hueniverse.com/2015/07/08/on-securing-web-session-ids/

Disclaimer: like any security advice from someone who doesn't know the specifics of your own system, this is for educational purposes only. Security is a complex and very specific area and if you are concerned about the security of your system you should hire an expert that can review your system along with a threat analysis and provide the appropriate advice.

Brute Force

Brute force attacks are those in which the attacker is trying to gain access to the system by making repeated requests using different credentials (until one works). The most common example is of an attacker who tries guessing a user password. This is why passwords should be long and avoid using dictionary words to make it harder to guess. Properly designed systems keep track of failed authentication requests and escalate the issue when it appears an attack is in progress.

Passwords are not the only credential used in web authentication. The most common implementation includes a login page which upon successful authentication sets a session cookie on the client. The session cookie acts as a bearer token – whoever shows up with the token is considered to be the authenticated user. Setting a session cookie removes the need to enter your username and password on every page. However, this session cookie now acts as the sole authentication key and anyone who gains access to this key will gain access to the system. Cookies are, after all, just a simple string of characters.

A session id guessing attack is a type of brute force attack. Instead of trying to guess the password, the attacker is trying to guess the session id and forge the authentication cookie. The attacker generates session ids and tries to make requests using those ids, in hope that they will match actual active sessions. For example, if a web application session ids are generated in sequence, an attacker can look up their own session id and based on that forge requests using nearby session id values. To protect against this attack we need to make guessing session ids impractical. Note I’m saying “impractical,” not “impossible”.

Impracticality

The first step is to make sure session ids are sufficiently long and non-sequential. Just like passwords, the longer the session id is, the harder it is to find a valid one by guessing. It is also critical that session ids are not generated using a predictable algorithm such as a counter because if such logic exists, the attacker is no longer guessing but generating session ids. Using a cryptographically secure random number generator to produce sufficiently long session ids is the best common practice. What’s “sufficiently long”? Well, that depends on the nature of your system. The size has to translate into an impractical effort to guess a valid session id.

Another way to prevent an attacker from guessing session ids is to build integrity into the token by adding a hash or signature to the session cookie. The way the Express session middleware does this is by calculating a hash over the combination of the session id and a secret. Since calculating the hash requires possession of the secret, an attacker will not be able to generate valid session ids without guessing the secret (or just trying to guess the hash). Just like strong random session ids, the hash size must match the security requirements of the specific application it is meant to protect. This is because at the end, the session cookie is still just a string and open to guessing attacks.

Session ids must be sufficiently long and impractical to guess. There are a few ways to accomplish this. The randomness and hashing techniques above are the two most common ways but not the only ones.

Layers

If we generate strong random session ids, do we still need the hash? Absolutely!

The core security principal is layering. This is also known as not putting all your eggs in one basket. If you rely on a single source of security, you end up with no security at all if that single source fails. For example, what if someone finds a bug in your random number generator? What if they find a way to hack that part of your system and replace it? There are countless of known attacks exploiting exactly this – the generation of random numbers that turns out not to be so random after all.

Combining a strong random session id with hash for integrity will protect against flaws in the random number generator. It will also protect against developer errors such as using the wrong random number generator function (e.g. the not so random method every system offers alongside the strong method). We all write bad code no matter how great our process is or how experienced we are. It is part of software engineering. This is why it is so important to layer your security. A moat is not enough, you also want a wall behind it, and probably some guards behind the wall.

If you think using the wrong random function or a deep bug in OpenSSL are the only two issues here consider the common practice of monkey patching code in JavaScript and other dynamic languages. If someone anywhere in an entire application deployment messes with the global random facilities (for testing, logging, etc.) and breaks it (or it is part of a malicious code injection), session ids relying solely on randomness are no longer secure.

Alarms

An important difference between guessing passwords and guessing session ids is the fact that passwords are associated with an account (e.g. username). The account-password pair makes it easier to keep track of brute force attacks because it provides a relatively straightforward way to keep track of failed attempts. However, when it comes to session ids, it is not as simple because sessions expire and do not include an account context. This means an invalid session id could come from an expired session or from an attacker, but without additional data (e.g. IP address) it would be hard to tell the difference in a large scale system.

By including an integrity component in the session id (via a hash or signature), the server can immediately tell the difference between an expired session, an unallocated session id, and an invalid session. Even if you just log invalid authentication attempts (and you should), you would want to log an expired session differently than an invalid one. Beside the security value of knowing the difference, it will also provide useful insight about how your users behave.

Hygiene

Credentials should expire and therefore session ids should have a finite lifespan (where duration is very much a system-specific value). While cookies come with an expiration policy, there is no way to ensure it is actually obeyed. An attacker can set the cookie expiration to any value without the server being able to detect it. A common best practice is to include a timestamp in every credential issued, which can be as simple as adding a timestamp suffix to the randomly generate session id. However, in order to rely on this timestamp, we must be able to verify it was not tempered with and the way to accomplish that is with a hash or signature.

Adding a timestamp to the session id allows the server to quickly handle expired sessions without having to make an expensive database lookup. While this might sound unrelated to security, it actually is very much core to maintaining a secure application.

A denial of service attack (or DoS) is an attack in which the attacker makes repeated requests with the sole purpose of consuming too much resources on the server and either shutting it down or making it inaccessible to others. If every request authentication requires a full database lookup at the application tier, an attacker can use forged session ids to stage a DoS attack with ease. By including an integrity component in the cookie, the server can immediately identify forged or expired credentials without any backend lookup cost.

Kill Switch

Sometimes things go wrong. And when they go very wrong, you need to have a way to immediately invalidate entire classes of sessions. Because generating a hash or signature requires a server-side secret or key, replacing the secret will immediately cause all session ids to fail validation. By using different secrets for different types of session ids, entire classes of sessions can be segregated and managed. Without such a mechanism, the application itself has to make a computed decision about the state of each session or perform mass database updates.

In addition, in large distributed systems with database replication over different geographic locations, invalidating a session record in one location can take seconds and even minutes to replicate. This means the session stays active until the full system is back in sync. Compared to a self-describing and self-validating session id, the benefits are obvious.

General Purpose

An important feature of the Express session middleware is its support for user-generated session ids. This allows developer to deploy the middleware in an existing environment where session ids are generated by an existing entity which might reside on a completely different platform. Without adding a hash to the user-provided session ids, the burden of building a secure system moves from the expert (the module author) to the user (which is likely to be a security novice). Applying a hash is a much better approach than forcing an internal session id generator.

Crocodiles

Adding a hash to a strong random session id is not all you should do. Whether your moat can benefit from crocodiles is, again, a castle-specific decision. Without going too far from the topic, there are plenty of other layers you can add to your session management tier. For example, you can use two session credentials, one long lived (lasts as long as the session) and another short lived (good for minutes or hours). To refresh the short lived you use the long lived but by doing so, you are reducing the exposure of the long lived credential on the network (especially when not using TLS).

Another common practice is to have one cookie with general information about the user (e.g. first name, recently viewed items, etc.) alongside the session and to then include something from that cookie in the hash to create a binding between the user’s active state to the authentication. It’s a way of bringing back the “username” into the workflow.

To go even further, hashing can be replaced with signatures and cookie content can be encrypted (and then hashed or signed on top). The security verbosity must match the threat.

Blaz
  • 103
  • 3
Eran Hammer
  • 311
  • 1
  • 2
  • 4
    I think you've based your "layers" section on faulty assumptions. Hashing a random number is actually not an additional "layer of security" and more of a weakest-link thing. If the base CSPRNG is broken then no amount of hashing its results will improve it (always evaluate degenerated cases for intuitions; say a PRNG has .99 probability to output bit 1, you should quickly reach another conclusion). Saying "absolutely do always hash" sounds a little like security theatre IMHO, and that can lead to a false sense of security, which is arguably worse than no security. – tne Dec 23 '15 at 12:27
  • What are some examples of classes of sessions for the Kill Switch? – makerofthings7 Feb 07 '17 at 19:12
  • 1
    @Eran the link you have provided is broken, do you have any updated ones please? – Mustafa Ehsan Alokozay Oct 14 '21 at 08:48
14

Eran Hammer, one of the maintainers of the yar session management module for NodeJS, had this to say on the matter:

Disclaimer: like any security advice from someone who doesn't know the specifics of your own system, this is for educational purposes only. Security is a complex and very specific area and if you are concerned about the security of your system you should hire an expert that can review your system along it's threat analysis and provide the appropriate advice.

[...] Signing a bearer token for internal integrity and validation is common security practice. While I don't consider what express-session is doing sufficient, it is certainly the bare minimum required.

You don't want people to be able to guess a session in order to gain access to someone else's account. [...] Preventing guessing a session id by includes [sic] a cryptographic components [sic] is the correct way to accomplish this. It is the industry standard.

Session cookies are bearer tokens which means whoever has them can gain full access as the rightful owner. You want to be able to do a few things to reduce risk. Making sure that no one other than the server can issue valid credentials is the bare minimum and that's what is being done in express-session.

I do not consider a simple signature to be sufficient protection for session management which is why this module [yar] uses iron encoding which both encrypts the content as well as sign it for integrity validation. This makes it practically impossible for anyone to temper with the state (as well as provides the ability to maintain some state in the cookie itself), but more importantly, it allows you to set an expiration for the credentials.

While cookies come with expiration instructions, an attacker is not required to obey. This means if someone steals a cookie, if there is no way of forcing that cookie to be short lived, it will leave the system exposed for a long time. Keeping the cookie expiration policy in sync with the server side policy is another layer of complexity and one that should not be part of the security profile of the system.

There are plenty other attack vectors here to consider. For example, if every request triggers a session storage lookup, it makes it easier to DOS with invalid session ids. If you have a quick way of detecting that a request is coming from an attacker, you can both block it but also take measures to fight it. An invalid cookie signature can only come from a malicious source (unless you changed they way you generate cookies in which case you know that and can handle it).

Along the same lines, being able to tell when a session id is forged vs. expired is critical for maintaining a secure system. If you just generate random ids, assuming they are sufficiently long and sufficiently random, you have no way of knowing unless you keep track of every session id ever generated which would make scaling your system costly and painful. And it should be pretty obvious to tell why you want to know if an bad cookie request is coming from an attacker or not.

(View Full Comment)

So basically, while storing a random session ID in a cookie directly isn't necessarily insecure (as long as the ID is unpredictable and sufficiently long), encrypting and signing the session information offers a number of additional capabilities (like the ability to store expiration information in the cookie itself, and the ability to identify attempts to forge a session ID) which can be used to improve security.

For expressjs in particular, the current maintainer also provides the following justification:

Users can use whatever session IDs they want. Perhaps someone feels like they should use an auto incrementing number from the SQL database, it doesn't matter, because we protect their uninformed decision by signing the value, elongating the key.

In other words, signing the session ID protects users of the library who might be (incorrectly) using predictable or insufficiently long session IDs in their applications (instead of letting the library handle generation of session IDs). Obviously this reason isn't applicable if the user is using securely chosen session IDs, but this library chose not to make that assumption.

Hammer reiterates this point:

Also, this is a generic module assuming as it's core requirement a wide range of users. It absolutely has to assume that some people will use it poorly (e.g. increment ids) and accommodate that. This is also common practice when building modules for a wide audience.

Ajedi32
  • 4,637
  • 2
  • 26
  • 60