Closed Bug 1942820 Opened 1 month ago Closed 1 month ago

Cookie Path attribute bypass when path ends in /%2e%2e? due to URL parser bug

Categories

(Core :: Networking: Cookies, defect, P1)

defect

Tracking

()

RESOLVED FIXED
136 Branch
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)

Attached file poc.zip

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.

Flags: sec-bounty?

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
Group: firefox-core-security → core-security
Component: Security → Networking: Cookies
Product: Firefox → Core
Group: core-security → network-core-security

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

  1. load https://example.com/a%2eb/foo.html
  2. in the dev console enter location.href and see that the %2e is still part of the URL
  3. type in the dev console document.cookie = "a.b=set"
  4. type document.cookie in the dev console and make sure you see the cookie
  5. reload the page using the reload button, Ctrl-R, or console location.reload()
  6. repeat step 4 and make sure you see the cookie
  7. click in the address bar and hit enter to "reload" the site
  8. 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.

Status: UNCONFIRMED → NEW
Ever confirmed: true
Keywords: sec-low
Summary: Cookie Path attribute bypass → Cookie Path attribute bypass when path ends in /%2e%2e? due to URL parser bug

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.

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=

Severity: -- → S3
Priority: -- → P1
Whiteboard: [client-bounty-form] → [client-bounty-form][necko-triaged]
Assignee: nobody → valentin.gosu
Whiteboard: [client-bounty-form][necko-triaged] → [client-bounty-form][necko-triaged][necko-priority-queue]

(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.

Pushed by valentin.gosu@gmail.com: https://hg.mozilla.org/integration/autoland/rev/15c8f35c481a Make sure %2e%2e in the last path segment of the URL keeps the / r=necko-reviewers,kershaw https://hg.mozilla.org/integration/autoland/rev/76ecd4fd31b3 Test r=necko-reviewers,kershaw
Group: network-core-security → core-security-release
Status: NEW → RESOLVED
Closed: 1 month ago
Resolution: --- → FIXED
Target Milestone: --- → 136 Branch
Flags: sec-bounty? → sec-bounty-

Thanks for reporting this bug. Unfortunately it does not qualify for the security bug bounty

Group: core-security-release
Keywords: sec-low
Keywords: sec-other
Whiteboard: [client-bounty-form][necko-triaged][necko-priority-queue] → [client-bounty-form][necko-triaged][necko-priority-queue][adv-main136-]
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: