Are cookies actually being signed?
rnd256 opened this issue · 7 comments
Using the example code in your readme, I can't find any trace of the app.keys
'some secret hurr'
actually being used to sign the cookie.
I assumed it would be used to generate the koa:sess.sig
cookie, but it looks like that's generated by simply stringifying the session's contents and running it through crc32 - A process that a malicious user could easily replicate after modifying the contents of the koa:sess
cookie.
What am I missing?
tl;dr the content of the cookie is not encoded, only the .sig
when signed
is used. Also, by default it uses the sha1
algorithm to generate the .sig
cookie, which can be cracked in minutes.
keys
is referenced in the koa
module in lib/context.js
:
get cookies() {
if (!this[COOKIES]) {
this[COOKIES] = new Cookies(this.req, this.res, {
keys: this.app.keys,
secure: this.request.secure
});
}
return this[COOKIES];
},
It originally used the KeyGrip
library to manage keys for signing, which is used by the module cookies
to create the *.sig
cookie. This behaviour was changed in koa
in version 0.4.0:
* remove app.keys getter/setter, update cookies, and remove keygrip deps
The code above in context.js
is the only reference in the koa
module to keys
, which is used whenever cookies are manipulated.
In the cookies
module, this is used to determine if a cookie should be signed:
var signed = opts && opts.signed !== undefined ? opts.signed : !!this.keys
From the cookies
api doc:
A Keygrip object or an array of keys can optionally be passed as options.keys to enable cryptographic signing based on SHA1 HMAC, using rotated credentials.
While koa
doesn't use keygrip
, cookies
does.
function Cookies(request, response, options) {
if (!(this instanceof Cookies)) return new Cookies(request, response, options)
...
if (options) {
if (Array.isArray(options)) {
this.keys = new Keygrip(options)
} else if (options.constructor && options.constructor.name === 'Keygrip') {
this.keys = options
} else {
this.keys = Array.isArray(options.keys) ? new Keygrip(options.keys) : options.keys
...
}
}
}
The signing method in keygrip
:
function sign(data, key) {
return crypto
.createHmac(algorithm, key)
.update(data).digest(encoding)
.replace(/\/|\+|=/g, function(x) {
return ({ "/": "_", "+": "-", "=": "" })[x]
})
}
The cookie data itself that could store session data is not encrypted, just the .sig
cookie is. Therefore, it's trivial to decode the signature method when:
- you have the content obfuscated only in base64
- you have the code freely available as OS
The only signed aspect of the cookie is the .sig
cookie, which by default is the sha1 hash, which can be cracked in minutes.
Thanks for the thorough details! I'm not particularly concerned about how trivial it is to parse the session cookie, but the .sig
cookie using a sha1 hash by default seems like a serious issue. Most people will use the default and assume it's secure.
I'm not particularly concerned about how trivial it is to parse the session cookie
Given the encrypted hash, and the raw data (which is just obfuscated in base64), it's trivial to determine the key. Signed cookies are inherently weak and offer no security benefit. Session ids are also not hashed or encrypted, so you can literally copy+paste cookies if you were to gain access to them.
There's also no origin checking on the cookies to prevent man in the middle, intercept, or even xss attacks if one were to gain access to the cookies.
That's not to say that a lot of the session management options out there are particularly strong either, they're all susceptible in one way or another.
The only signed aspect of the cookie is the .sig cookie, which by default is the sha1 hash, which can be cracked in minutes.
Unless I'm seriously mistaken it's HMAC-SHA1, not just a SHA1 hash. Despite the collision vulnerability of SHA1, HMAC is still relatively secure assuming the secret keys are strong and an attacker has no knowledge of them.
This thread has a nice explanation though it's several years old. The second comment here is more recent, reiterates the point, and links to a nice infographic. If there's an attack on HMAC-SHA1 I'm not aware of, I'd be interested to hear.
All that being said, in an ideal world something like SHA256 would be the default algo in Keygrip.
so, can we consider the tampering of the content of a cookie unfeasable? (once a decent key is provided)
I verified that Koa is actually checking the signature, making the session appear empty when a simple change is attempted to the content of the session (without updating the signature).
btw, I'm using a 30-chars random string conveying ~153 bits of actual randomness, generated at startup
so, can we consider the tampering of the content of a cookie unfeasable? (once a decent key is provided)
No, it seems alarmingly easy to tamper with these "signed" cookies. I wonder if the creator intended for them to be used alongside server-side session tracking in a DB? I bet a lot of people have been using these cookies as JWTs instead, which looks like it would be a critical security vulnerability for them (an attacker could brute force the signing key that's only protected by a SHA1 hash, change the userId in the cookie, re-sign it themselves, and get access to any account).
btw, I'm using a 30-chars random string conveying ~153 bits of actual randomness, generated at startup
If you generate it randomly at startup, then all your cookies will be invalidated every time the server restarts, and cookies won't work across multiple servers if you have more than one.
an attacker could brute force the signing key that's only protected by a SHA1 hash
what do you think of the #181 (comment) saying that HMAC-SHA1 is actually used and that it is enough?
If you generate it randomly at startup, then all your cookies will be invalidated every time the server restarts, and cookies won't work across multiple servers if you have more than one.
correct. I'm accepting these limitations ATM.