New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Semicolons are accepted in attribute values but they can't be set #396
Comments
Sorry, I misread your issue. I'll give it more thought later. In the meantime, I would advise not to allow untrusted sources to set the cookie, even its attributes. That can lead to a README change. |
Ok, cool, here's the long version. I've tested and confirmed this locally, here are my thoughts: There's no such APIThere's no such API as The problem is in the application code, not js-cookieThe problem happens when the developer allows an untrusted source to set the cookie attributes. The following example can't be exploited if the developer pass an untrusted input in the cookie value due to the default encoding mechanism and the fact RFC 6265 doesn't allow Cookies.set('nameofcookie', 'cookievalue;path=/path') In this case, the cookie is escaped. The browser creates it as However, the following example can be exploited if the developer uses a raw converter: const RawWriteCookies = Cookies.withConverter({ write: (rawValue) => rawValue });
RawWriteCookies.set('nameofcookie', 'cookievalue;path=/path') In this case, the cookie is created as Besides the cookie value, the The following is also vulnerable to XSS: Cookies.set('nameofcookie', 'cookievalue', { path: '/;domain=sub.domain.com' }) js-cookie will set as Since js-cookie is just an adapter on @macintorsten Can you think of real cases where this can be exploited? ConclusionThis is not a problem with js-cookie. It's a problem with an application code that sets the path of the cookie using an untrusted input. However, because this behavior is astonishing, we might be able to do something to make this issue more obvious, or explicitly sanitize the attributes that are set through the API. The README has an example that mentions the use of @macintorsten Do you have any suggestions on which approach to take for this? This issue is probably present since forever, and since this is the first time somebody is reporting, I wonder if it's a big deal. |
If you really allow untrusted input to your cookies, the attacker can do some funny things such as: Cookies.set('nameofcookie', 'cookievalue', { path: '/', domain: '; secure' }) Again, I'm not sure how practical is that and I guess the assumption is that people shouldn't be sending untrusted input for the same reason as: document.cookie = "nameofcookie=cookievalue; Path=/" + untrustedInput; |
Hmm, it isn't. It used explicit attribute positioning up to version The commit has been in the codebase for a year. |
Sorry for the back-and-forth, but yeah it is present for a long time 😅. The problem is the same if we explicitly position the value on the attributes: return (document.cookie = [
key, '=', value,
attributes.expires ? '; expires=' + attributes.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
// attributes.path ? '; path=' + attributes.path : '',
attributes.path ? '; path=' + '/;domain=sub.domain.com' : '',
attributes.domain ? '; domain=' + attributes.domain : '',
attributes.secure ? '; secure' : ''
].join('')); See v2.1.3. |
Looking for ideas on how to fix this, if that's at all possible, without removing or unnecessarily escaping the QUnit.only('sanitization of attributes to prevent XSS from untrusted input', function (assert) {
assert.expect(1);
var expectedResult = 'c=v; path=/;domain=sub.domain.com';
assert.strictEqual(Cookies.set('c', 'v', {
path: '/;domain=sub.domain.com'
}), expectedResult, 'should not set domain from withing the path attribute');
}); @macintorsten what's the @Badger Could you lend a hand to this one? It's not clear from the RFC 6265 that there are any limitations to what |
Hi, I am not personally affected by this issue and I don't know enough about RFC 6265 (or the interpretation of it) to give a definite answer how it should be handled. I stumbled upon this behavior when I tried to find a way to set a path containing a ;, something that seems impossible. This behavior might not have a huge impact but I can think of a theoretical example where it can be exploited. Let's assume you have a webapp on www.site.com/appname/. Because the application can be installed on any path (eg. /appname/ or /something/else/) the developer might not be able to make assumptions about the URL on which the application resides, therefore he/she will set the cookie as in the README example. Let's further assume this code is present a login page directly on http://www.site.com/appname/:
(If CSRF value is available to js-code it might not be needed to also put in a cookie, but it can be a convenient place to store it perhaps). The cookie will store an anti CSRF token whose value will be sent in a request (as post/get parameter) to the server to protect against CSRF attacks, for all important state-changing requests. If an attacker directs a victim to http://www.site.com/appname;domain=.site.com/, it can given special circumstanses give the same page back as http://www.site.com/appname/ because everything after a ; in a path is not considered to be part of the path as seen by the (server side part) application according to many web servers (i think these are called path parameters). This URL will make the victim's browser store a cookie scoped for all subdomains to site.com. If the attacker then has an XSS (or controls a DNS entry) on a subdomain to site.com (not matter what path) it is possible for the attacker to steal the CSRF-token and perform a CSRF attack after the user has logged in. This is a made up example, but I guess there should be more examples like this that can be made up. |
I think the goal for the API should be that when reading back an attribute-value (if it can be done) or when viewing it in browser's developer tool, the value should correspond to the same value as the API was fed with for the attribute of the same name. The value provided should be the value that is in effect in the browser. According to the RFC 6265 it's mentioned that path (not limited to just path-value) can't contain a a semi-colon:
Already here we see it's impossible to fulfill the goal because the API can be called with a value containing a semi-colon which is not a valid value according to RFC: Link: https://tools.ietf.org/html/rfc6265#section-4.1 And in section 5.2 describes how a client should parse a set-cookie header:
In particular step 3 further down, describing how to parse
This means I think that if you call But this is just my point of view. |
Good catch! I think we should respect RFC 6265 and truncate in the first QUnit.only('sanitization of attributes to prevent XSS from untrusted input', function (assert) {
assert.expect(1);
var expectedResult = 'c=v; path=/';
assert.strictEqual(Cookies.set('c', 'v', {
path: '/;domain=sub.domain.com'
}), expectedResult, 'should not set domain from withing the path attribute');
}); Do you want to create a PR for that since you're the one bringing this up? I'll probably schedule a patch version. @carhartl If you have some time, can you please read through and make sure this is the correct approach to take? There should be enough information in this thread to understand the issue. |
I am slightly leaning towards throwing an exception if that What about the other attributes? Would it be possible to inject something malicious using the |
By default, js-cookie has never thrown any exception or logged any message in the console. Similar things have been suggested on #86 and #88. We just have to be aware that any message we write in the code will increase the "~900 bytes GZip" size, which is one of its advertised features as stated in the README. For a couple of years there's been a plan to implement a There's only one report of this problem with the Or we can add to the README docs, then we wouldn't need more characters in the code. |
So ignoring that for a while, I’m wondering how |
When I get some time, I'll investigate the possibilities and see how many bytes this may add. Maybe because the problem is in an attribute and the class is encapsulated, it is expected from the developer that js-cookie doesn't have this kind of problems. The interim solution can be to document the problem in the README and remove the example that shows the |
I'm not sure I know how this should be fixed. I think there is small difference between using the js-cookie api and calling document.cookie directly. The latter accepts a string similar to http response header In the js-cookie API however you can set just the Having that said, deleting everything after ; in the path value will make it impossible to introduce new attributes and will in most cases be backwards compatible with existing code using js-cookie. Maybe that's a pragmatic approach even if the API silently accepts a value that cannot, and will not, be set. |
Unrecognized attributes are ignored.
Is treated as:
|
This is a convincing reason for doing the truncating ourselves. :) |
I’m not against it, but where do we stop sanitizing attributes? Once we go down that route, wouldn’t it be expected that unexpected/invalid values for |
@carhartl I agree with truncating the attribute AND adding a bullet point to build a debug tool later if we can. In regards to other sanitizing, perhaps we can address them on a per request basis? That would be the most pragmatic approach IMHO. |
I've created a Pull Request on #400. @carhartl @macintorsten can you guys please review to make sure I'm not missing anything? Thanks. |
Closing as #400 has landed. |
When setting a cookie the API accepts an attribute value that contains a semicolon, and it does not give any error. If the attribute value is coming from an intrusted source it can lead to injection of new attributes than the intended one.
Example: path = '/numbers;domain=.iana.org'
Cookies('nameofcookie', 'cookievalue', {path: '/numbers;domain=.iana.org'})
Reproduce;
Output: [EMPTY]
Output: nameofcookie=cookievalue
I think the path value cannot contain a semicolon but I am not completely sure.
The text was updated successfully, but these errors were encountered: