webpush requests do not require vapid key
Categories
(Firefox for Android :: Push, defect)
Tracking
()
People
(Reporter: mozbugz, Assigned: teshaq)
Details
(Keywords: reporter-external, sec-moderate, Whiteboard: [reporter-external] [client-bounty-form] [verif?] [adv-main121+])
Attachments
(5 files, 1 obsolete file)
In testing webpush to Firefox, I discovered that if you send a push request w/o the request signed w/ the vapid key, even though it was specified in the PushManager.subscribe applicationServerKey.
I have verified that this is required on Chrome. Safari just hangs on the webpush.
Attached is a project that will demonstrate the issue.
extract, run make run
to build things, then sudo make run EMAIL=youremail@example.com
and go to http://localhost:80/ request notification, and maybe hit reload a time or two.
console output should look similar to:
192.168.0.2 - - [18/Nov/2023 11:03:34] "GET / HTTP/1.1" 200 -
'/private/tmp/wpn/templates/../static'
192.168.0.2 - - [18/Nov/2023 11:03:35] "GET /S3_sw.js HTTP/1.1" 200 -
sub: '{"endpoint": "https://updates.push.services.mozilla.com/wpush/v1/xxx-xxx
-xx-xxx", "expirationTime": null, "keys": {"auth": "xxx", "p256dh": "xxx"}}'
sending notification...
done
I have inadvertently made this public on my mastodon account. I can take it down if you'd like:
https://flyovercountry.social/@encthenet/111432938559622939
Note that the bug can be manually observed after getting the sub by using the included pushnotify.sh
script. After getting the sub information above, put the JSON in a file, e.g. sub.info.txt
, and then run:
. ./venv/bin/activate
(echo Subject: test title; echo; echo a message) | sh -x pushnotify.sh sub.info.txt
You'll notice it runs the command pywebpush w/ args: -v --data /dev/stdin --info sub.info.txt
, but the arguments --claims
and --key
are not specified.
My test client is Firefox for Android 121.0a1 (Build #2015986545), 6a7bee8e2c+.
Comment 4•2 years ago
|
||
Does this happen in a Desktop Firefox as well?
- If it's just Android then the bug should be moved to the "Push" component of the "Fenix" product
- If it's both then the bug should be in the "DOM: Notifications" component of the "Core" product
Updated•2 years ago
|
Updated•2 years ago
|
Comment 5•2 years ago
|
||
Looking at MDN docs it looks like this is expected: chrome and edge require it, but it's optional in Firefox. Or at least it's optional in PushManager.subscribe()
; I don't know if using it is supposed to be optional if you -do- supply it as you said you did.
Kagami: do you know?
(In reply to Daniel Veditz [:dveditz] from comment #5)
Looking at MDN docs it looks like this is expected: chrome and edge require it, but it's optional in Firefox. Or at least it's optional in
PushManager.subscribe()
;
That sounds wrong, Blink doesn't require it either: https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/push_messaging/push_manager.idl;l=14;drc=047c7dc4ee1ce908d7fea38ca063fa2f80f92c77
But per the spec they might reject it during the method call per step 5: If the options argument does not include a non-null value for the applicationServerKey member, and the push service requires one to be given, queue a global task on the networking task source using global to reject promise with a "NotSupportedError" DOMException.
I don't know if using it is supposed to be optional if you -do- supply it as you said you did.
Doesn't seem so per the spec: the push service will reject any push message unless the corresponding private key is used to generate an authentication token.
(In reply to Daniel Veditz [:dveditz] from comment #4)
Does this happen in a Desktop Firefox as well?
- If it's just Android then the bug should be moved to the "Push" component of the "Fenix" product
- If it's both then the bug should be in the "DOM: Notifications" component of the "Core" product
Good question, the push service module is different between desktop and android so it's possible that only one side is affected. (I've not confirmed this anywhere though, will do soon)
I can confirm that for Desktop Firefox, that it requires the vapid key:
ERROR: WebPushException: Push failed: 401 Unauthorized
Response body:{"code":401,"errno":109,"error":"Unauthorized","message":"Missing VAPID public key","more_info":"http://autopush.readthedocs.io/en/latest/http.html#erro
r-codes"}
And that when I provide the key, it works, so it is limited to Fenix.
Hmm, sounds like the error is coming from our backend server used by the desktop build and not by Fenix (which depends on Google service instead AFAIK).
correct. The above error is showing that desktop push server requires the provided vapid key (and throws an error), unlike mobile firefox.
This command works (cell.sub.txt
contains the subscription information for fenix):
pywebpush -v --data /dev/stdin --info cell.sub.txt --key '' --claims ''
[...]
Response:
code: 200
body: Empty
<Response [200]>
but shouldn't as it's pushing to Fenix when the public key was requested, but the key and claim are not specified.
This command properly fails as it's pushing to desktop firefox:
pywebpush -v --data /dev/stdin --info desk.firefox.txt --key '' --claims ''
[...]
Response:
code: 401
body: {"code":401,"errno":109,"error":"Unauthorized","message":"Missing VAPID public key","more_info":"http://autopush.readthedocs.io/en/latest/http.html#error-codes"}
ERROR: WebPushException: Push failed: 401 Unauthorized
Response body:{"code":401,"errno":109,"error":"Unauthorized","message":"Missing VAPID public key","more_info":"http://autopush.readthedocs.io/en/latest/http.html#error-codes"}
Updated•2 years ago
|
Have Fenix people confirmed this or investigating it? Is there any owner of push component of Fenix?
NI'ing a push backend person in case they have more info.
Comment 12•2 years ago
|
||
I'm investigating the server, but a few things to note:
The V in VAPID stands for "Voluntary". It's (supposed) to be an optional field provided by the subscription provider to self identify a point of contact if there's an issue. The VAPID Key signature, however, should be used to sign the authorization header.
Where things get a bit odd is that we don't check to see if the aud
value matches the originally submitted aud
because we don't have to. We should only check that the VAPID key used to sign things is the same. You could use a different value in aud
, or since it's all optional, you could leave everything blank and sign an empty JSON array. The bits that matter are the Endpoint URL and the VAPID public key specified as the k
component of the Authorization token. We use that key value as a check to make sure that the publisher matches who the App said should be able to send push messages, not the content of the VAPID claims. (This is why if you change your VAPID key, old endpoints no longer work.)
Of course, you can request an endpoint without a VAPID Key signature, and then include a VAPID Authorization header to publish to the Push Endpoint URL, if you wanted. You could also publish to that endpoint without a VAPID header if you wanted.
What you should absolutely not be able to do is request an endpoint with a key signature and then be able to publish a push message to the Push Endpoint URL without a Authorization header or with a Authorization header that uses a different VAPID key. That is checked the same way for both desktop and mobile. (I'm currently verifying that there's not some weird edge case since we recently updated code.)
The server does tell you if a given Endpoint URL has a signature value associated with it.
A registered endpoint that does not have a VAPID key is a /v1/
URL. One that does is a /v2/
URL. (For instance, the Endpoint URL specified in the example in the description was not created with a VAPID key associated with it since no key
value was specified either in the Websocket messageType
=register
command, or the body of the mobile /registration/.../subscription
call.)
Comment 13•2 years ago
|
||
The server code runs all endpoints through the same validation code (desktop or mobile), so I'm not seeing where a signed Endpoint URL would work for desktop but not mobile. Could it be that mobile isn't returning a signed URL?
(I tried using the example code to generate a push notification on Android, but the sample code was not triggering the Notification request. I may try experimenting tomorrow to see if I can replicate what is happening with mobile.)
Reporter | ||
Comment 14•2 years ago
|
||
It looks like firefox android nightly is ignoring the provided VAPID key then. I get a v1 url from Firefox nightly, but a v2 from the desktop..
from firefox for android:
{"endpoint":"https://updates.push.services.mozilla.com/wpush/v1/[...]","expirationTime":null,"keys":{"auth":"[...]","p256dh":"[...]"}}
and from firefox desktop on macosx:
{"endpoint":"https://updates.push.services.mozilla.com/wpush/v2/[...]","expirationTime":null,"keys":{"auth":"[...]","p256dh":"[...]"}}
This is using the exact same JS code for both:
// (B3) SUBSCRIBE TO PUSH SERVER
navigator.serviceWorker.ready
.then(reg => {
console.log("registered...");
reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: publicKey
}).then(
// (B3-1) OK - TEST PUSH NOTIFICATION
sub => {
console.log("pushing...");
var data = new FormData();
data.append("sub", JSON.stringify(sub));
fetch("push", { method:"POST", body:data })
.then(res => res.text())
.then(txt => console.log(txt))
.catch(err => console.error(err));
},
// (B3-2) ERROR!
err => console.error(err)
);
});
Comment 15•2 years ago
|
||
Thanks! That's very useful.
Comment 16•2 years ago
|
||
So, I think I see one potential problem (maybe).
When the client first connects and does a POST
to https://push.services.mozilla.com/v1/.../.../registration
, the server returns an unsigned v1 endpoint and a channelID
Later, when the client sends a POST
to https://push.services.mozilla.com/v1/.../.../registration/.../subscription
with the key
included in the body JSON, the server returns a .../v2/...
endpoint (It also returns a different channelID
if one was not specified in the body of the POST)
I'm not sure if Fennec might be returning the first endpoint and channelID, which would cause the bug we're seeing here. To be honest, the server probably should NOT return an endpoint during the first registration event, but I'll need to talk to someone on the client side before the server changes anything.
Assignee | ||
Comment 17•2 years ago
|
||
I believe I found the culprit in https://github.com/mozilla-mobile/firefox-android/blob/b9ed285c624d29cebab5e9da2c3fb9097c2c9e96/fenix/app/src/main/java/org/mozilla/fenix/push/WebPushEngineIntegration.kt#L75-L77 - which is the shim in-between gecko-view and Fenix
That comment is as old as Fenix and I believe things have changed since (i.e. I believe we can and should pass the server key properly now).
Comment 18•2 years ago
|
||
Just to be clear, in order to send a notification to a Push user, you still need the endpoint and encryption keys, both of which should not be made public. What this bug does expose is that if someone were to use a non signed endpoint to send a properly encrypted push notification, it would succeed. (Honestly, if your endpoint and encryption keys are compromised, then your VAPID key is probably also compromised.)
Thank You for noting this bug!
Assignee | ||
Comment 19•2 years ago
|
||
Comment 20•2 years ago
|
||
Comment 21•2 years ago
|
||
Comment 22•2 years ago
|
||
Authored by https://github.com/tarikeshaq
https://github.com/mozilla-mobile/firefox-android/commit/f66bc9d4981d9bba7091389d9f0a6864291d38fe
[main] Bug 1865488: Adds server parameter to push subscription
Updated•2 years ago
|
Comment 23•2 years ago
|
||
Is this something we should uplift to Beta also?
Assignee | ||
Comment 24•2 years ago
|
||
The patch is trivial and low-risk so I'd vote for uplifting it
I don't know the full impact of not having it from a security perspective but it should be safe to uplift (I am happy to be corrected as well by the Android team if this needs to bake more in nightly)
Comment 25•2 years ago
|
||
The patch landed in nightly and beta is affected.
:teshaq, is this bug important enough to require an uplift?
- If yes, please nominate the patch for beta approval.
- If no, please set
status-firefox121
towontfix
.
For more information, please visit BugBot documentation.
Comment 26•2 years ago
|
||
Assignee | ||
Comment 27•2 years ago
|
||
Comment on attachment 9367331 [details] [review]
[mozilla-mobile/firefox-android] Bug 1865488: Adds server parameter to push subscription (backport #4698) (#4736)
Beta/Release Uplift Approval Request
- User impact if declined: Push subscription endpoint wouldn't be signed using the VAPID key, and thus can be triggered by arbitrary senders if they acquire the endpoint and the encryption key.
- Is this code covered by automated tests?: Unknown
- Has the fix been verified in Nightly?: Yes
- Needs manual test from QE?: No
- If yes, steps to reproduce:
- List of other uplifts needed: None
- Risk to taking this patch: Low
- Why is the change risky/not risky? (and alternatives if risky): It's a trivial (and small) patch that passes the application server key provided by consumers of web push to the autopush server.
- String changes made/needed:
- Is Android affected?: Yes
Comment 28•2 years ago
|
||
Comment 29•2 years ago
|
||
Authored by https://github.com/tarikeshaq
https://github.com/mozilla-mobile/firefox-android/commit/f92b16a611b471546a6ffc7f53edfee361a78bb9
[releases_v121] Bug 1865488: Adds server parameter to push subscription
Comment 30•2 years ago
|
||
Hello,
Is there any QA manual verification needed for this issue?
Thank you!
Comment 31•2 years ago
|
||
Hi Mira, I don't think so. Thanks for checking!
Updated•2 years ago
|
Updated•2 years ago
|
Comment 32•2 years ago
|
||
Reporter | ||
Comment 33•2 years ago
|
||
If you want to add a bit more color to the text of the advisory, obviously should be reviewed by someone like JR Conlin [:jrconlin,:jconlin] who knows WebPush better (I only started learning/using it a month or so ago), but:
The confidentiality of push notifications are not impacted by this vulnerability.
The VAPID key is used by the Web Push service to restrict the server that is allowed to push notifications to the client, and allow the Web Push service to contact the server in case there are issues with push notifications (e.g. too many, bad requests). If the Push URL is leaked, (e.g. database compromise), it would allow third parties to send PUSH notifications (which the web service worked may not handle correctly).
This SO seems to provide more information.
https://stackoverflow.com/questions/40392257/what-is-vapid-and-why-is-it-useful
The RFCs are also useful: https://www.rfc-editor.org/rfc/rfc8291
and:
https://www.rfc-editor.org/rfc/rfc8292
Comment 34•2 years ago
|
||
(which the web service worked may not handle correctly)
Might need to fix that up a bit, but yeah.
Honestly, the one possible exploit that could be done would be:
- Create a service worker that takes a Data Free push message as it's action, store the unsigned
/v1/
URL. - Leak that specific
/v1/
Push URL. - Evil Party uses that
/v1/
URL to trigger your webapp action.
Data free Push messages do not require encryption and thus could be open for anyone to use.
(heh, honestly, when I wrote that article not a lot of folk really knew what made VAPID useful. We've learned a lot since then.)
Comment 35•2 years ago
|
||
Updated•2 years ago
|
Reporter | ||
Comment 36•2 years ago
|
||
Just a FYI, I have verified that Fenix nightly (123.0a1) gives a v2 url, and that if the vapid key isn't used, the server returns a 401 vapid key required, and that when a vapid key is provided, that notifications work.
Comment 37•1 year ago
|
||
Bulk-unhiding security bugs fixed in Firefox 119-121 (Fall 2023). Use "moo-doctrine-subsidy" to filter
Updated•1 year ago
|
Description
•