Cookie Path attribute bypass when path ends in /%2e%2e? due to URL parser bug
Categories
(Core :: Networking: Cookies, defect, P1)
Tracking
()
Tracking | Status | |
---|---|---|
firefox-esr115 | --- | unaffected |
firefox-esr128 | --- | unaffected |
firefox134 | --- | wontfix |
firefox135 | --- | wontfix |
firefox136 | --- | fixed |
People
(Reporter: se0r12, Assigned: valentin)
References
(Regression)
Details
(Keywords: regression, reporter-external, sec-other, Whiteboard: [client-bounty-form][necko-triaged][necko-priority-queue][adv-main136-])
Attachments
(3 files)
I found Cookie path attribute bypass security issue.
version:
$ firefox --version
Mozilla Firefox 133.0.3
$ google-chrome-stable --version
Google Chrome 132.0.6834.83
overview:
If the value of the path attribute is /aaa/bbb (Path=/aaa/bbb), then /aaa, /aaa/ccc will not receive a cookie in the request.
However, /aaa/bbb/ccc and /aaa/bbb will.
The problem we discovered this time is that when /aaa/bbb/%2e%2e?, cookies are placed on some types of web servers.
Interesting behavior with new URL() in Firefox.
The reason I use new URL() assumes that the URL parsing procedure is the same as the parsing the browser does to send the request, but in the process of verification, I believe this to be correct
example 1
(firefox)
new URL("https://localhost/aaa/bbb/..").pathname
"/aaa/"
(Google Chrome)
new URL("https://localhost/aaa/bbb/..").pathname
'/aaa/'
example 2
(Firefox)
new URL("https://localhost/aaa/bbb/%2e%2e").pathname
"/aaa/"
(Google Chrome)
new URL("https://localhost/aaa/bbb/%2e%2e").pathname
'/aaa/'
example 3 (interesting)
(Firefox)
new URL("https://localhost/aaa/bbb/%2e%2e?").pathname
"/aaa/bbb/%2e%2e"
(Google Chrome)
new URL("https://localhost/aaa/bbb/%2e%2e?").pathname
'/aaa/'
example 4
(Firefox)
new URL("https://localhost/aaa/bbb/%2e%2e/?").pathname
"/aaa/"
(Google Chrome)
new URL("https://localhost/aaa/bbb/%2e%2e/?").pathname
'/aaa/'
Now, consider the attack.
For example, if there is a server that proxies /aaa/bbb/%2e%2e? to /aaa/, Firefox will recognize it as /aaa/bbb/%2e%2e? and will place a cookie in the request according to the Path attribute.
However, some web servers proxy to /aaa, so the cookie should not appear in the request.
This is an overview of the attack.
For example, we have confirmed that the attack succeeds because Apache's mod_proxy performs URL canonicalization.
Now, as a PoC, let us consider an application with the following composition.
client <-> Apache httpd <-> web app
With all attachments in the same directory, please follow the steps below to start up the verification environment.
$ unzip poc.zip
$ cd Poc
$ npm install
$ docker build -t webserv . && docker run -it --name instance -p 8000:80 --rm webserv
$ node app.js
Go to Access http://<IP>:8000/aaa/bbb.
When accessed, session=value; Path=/aaa/bbb is Set-Cookie.
After accessing /aaa and confirming that no cookies are placed, access /aaa/bbb/%2e%2e?.
The response body then indicates that /aaa is being accessed, but is interpreted as /aaa/bbb/%2e%2e?, indicating that a cookie will be placed on the request.
An example of bypassing of the path attribute due to the absence of /, which would normally be decoded and normalized.
Thanks.
Reporter | ||
Comment 1•1 month ago
|
||
MacOS PoC
version:
$ firefox --version
Mozilla Firefox 134.0
If the docker ip is 172.16.164.1
, set the ProxyPass, ProxyPassReverse ip to 172.16.164.1
in https.conf
.
/aaa/bbb response header
HTTP/1.1 200 OK
Date: Tue, 21 Jan 2025 14:43:45 GMT
Server: Apache/2.4.62 (Unix)
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 13
ETag: W/"d-If4fn/1S96xNJJpcVbftHy60A4I"
Set-Cookie: session=value; Path=/aaa/bbb
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
A cookie with the Path attribute /aaa/bbb is granted.
If you request to /aaa, no cookie will be given to the request due to the Path attribute.
/aaa request header
GET /aaa/ HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:134.0) Gecko/20100101 Firefox/134.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br, zstd
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
If-None-Match: W/"6e-a4dREYhBuv50awZwzSNpcutUsws"
Priority: u=0, I
request to /aaa/bbb/%2e%2e? (Request Header)
GET /aaa/bbb/%2e%2e HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:134.0) Gecko/20100101 Firefox/134.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br, zstd
Connection: keep-alive
Cookie: session=value
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Priority: u=0, I
A cookie is given to the request.
The response body also indicates that the request was recognized as a request to /aaa.
response body
/aaa session: session=value
Comment hidden (duplicate) |
Updated•1 month ago
|
Updated•1 month ago
|
Comment 3•1 month ago
|
||
I collapsed comment 2 to hide the 90 lines duplicated from comment 0. New information was:
I forgot to mention that the above verification was done on Linux (Manjaro Linux).
I can confirm that if the URL ends in "/%2e%2e" we correctly treat it as directory traversal, but if it is followed by a search string we do not. We handle "/..?foo" correctly, so its not simply that we mess up traversal because of the ?
.
You don't need a server to test this: you can play with it in the DevTools console on https://example.com which returns the same content for any path. It's slightly better to navigate to different URLs by setting location =
in the console than typing it into the address bar because that does some of its own URL manipulation to make things prettier.
Bug variant 1
The same bug happens if the URL has a hash reference instead of a search query.
https://example.com/a/b/..#foo
https://example.com/a/b/%2e%2e#foo
Bug variant 2
Internally, if a URL path has a %2e in it we seem to leave it alone and don't normalize it to .
. And if you set a cookie, that %2e is part of the stored path attribute, and will only match that URL if you visit it using a %2e. So, for example
- load https://example.com/a%2eb/foo.html
- in the dev console enter
location.href
and see that the %2e is still part of the URL - type in the dev console
document.cookie = "a.b=set"
- type
document.cookie
in the dev console and make sure you see the cookie - reload the page using the reload button, Ctrl-R, or console
location.reload()
- repeat step 4 and make sure you see the cookie
- click in the address bar and hit enter to "reload" the site
- repeat step 4 and see that although it's the same URL, you don't see the cookie
Step 1 works whether you paste that into the address bar or click the link, but then the address bar itself "prettifies" it and replaces %2e with a dot. So when you submit it again from the address bar in step 7 you are going to what Firefox thinks is a different URL.
This is not quite what the reporter is reporting, but similar and could result in the same kind of broken cookie behavior.
Bug variant 3
?
are there other path characters that we leave as escapes, but don't escape the plain ASCII equivalent? Apart from the ones that syntactically change the URL, of course? If so users could end up with similar "heisen-cookies" depending on what form the URL is given to the user.
On the other hand, cookie paths are a terrible security boundary, and newer features like __Host- prefixed cookies won't even let you use a path other than "/" if you want that level of protection. The spec says
Although seemingly useful for isolating cookies between different paths within a given host, the Path attribute cannot be relied upon for security
and that's from 2011! (See Section 8 for reasons why)
This is clearly a bug, but it would take an incredibly contrived situation to turn this into a practical exploit.
Reporter | ||
Comment 4•1 month ago
|
||
I will provide an answer for Bug variant 3.
As mentioned in Bug variant 1, #
causes the same behavior as ?
.
Aside from that, I have not found any other cases.
If there are any other questions that need answering, please let me know.
Thank you.
Assignee | ||
Comment 5•1 month ago
|
||
This is a URL bug which just happens to affect cookies since we use the same parser to match paths.
A path segment that is %2e or %2e%2e should be replaced by . or .. respectively. Apparently we don't properly normalize the path when it's followed by a ? or #
https://jsdom.github.io/whatwg-url/#url=aHR0cHM6Ly9sb2NhbGhvc3QvYWFhL2JiYi8lMmUlMmU/&base=YWJvdXQ6Ymxhbms=
Assignee | ||
Updated•1 month ago
|
Reporter | ||
Comment 6•1 month ago
|
||
(In reply to Valentin Gosu [:valentin] (he/him) from comment #5)
This is a URL bug which just happens to affect cookies since we use the same parser to match paths.
A path segment that is %2e or %2e%2e should be replaced by . or .. respectively. Apparently we don't properly normalize the path when it's followed by a ? or #
https://jsdom.github.io/whatwg-url/#url=aHR0cHM6Ly9sb2NhbGhvc3QvYWFhL2JiYi8lMmUlMmU/&base=YWJvdXQ6Ymxhbms=
I came up with Path Attribute Bypass with the thought that a CVE may not be issued if there is no way to exploit it.
(I am not sure if the conditions are met for a CVE to be issued this time, though.)
So I agree that this issue is a URL bug.
Assignee | ||
Comment 7•1 month ago
|
||
Assignee | ||
Comment 8•1 month ago
|
||
![]() |
||
Comment 10•1 month ago
|
||
https://hg.mozilla.org/mozilla-central/rev/15c8f35c481a
https://hg.mozilla.org/mozilla-central/rev/76ecd4fd31b3
Updated•1 month ago
|
Updated•28 days ago
|
Comment 11•28 days ago
|
||
Thanks for reporting this bug. Unfortunately it does not qualify for the security bug bounty
Updated•22 hours ago
|
Description
•