Logged-in user can unsubscribe anyone from any list using specially crafted POST request (CVE-2021-40347)
I'm forwarding this security report from Kevin Israel (Wikipedia user PleaseStand), who discovered this on lists.wikimedia.org and filed a reporti n our tracker: https://phabricator.wikimedia.org/T289798. Using a specially crafted POST request, a logged-in user can unsubscribe anyone from any list.
Reproduction steps:
- Subscribe to a mailing list with address A and confirm the subscription
- Create an account using address B, and visit a mailing list page that you are not subscribed to, you should see the subscription form and an address dropdown.
- Using your browsers inspector, change the form's URL by replacing
subscribe
withunsubscribe/
- Change the
name
attribute on the address<select>
to "email". - Change the value of the selected option to be address A.
- Click the "Subscribe" button.
You'll see that address A has now been unsubscribed. Additionally, address B now knows that A was subscribed to the list, allowing for leaking the subscriber list as they'd receive an error like ""a@example.com is not a member address of ..." if A was not subscribed.
The cause is https://gitlab.com/mailman/postorius/-/blob/master/src/postorius/views/list.py#L553, which does not verify the user owns the email address being unsubscribed.
Here's a patch that I've tested works functionally and deployed to lists.wikimedia.org:
diff --git a/src/postorius/views/list.py b/src/postorius/views/list.py
index f03f1c13..1864c71f 100644
--- a/src/postorius/views/list.py
+++ b/src/postorius/views/list.py
@@ -553,6 +553,15 @@ class ListUnsubscribeView(MailingListView):
@method_decorator(login_required)
def post(self, request, *args, **kwargs):
email = request.POST['email']
+ # Verify the user actually controls this email
+ user_emails = EmailAddress.objects.filter(
+ user=request.user, verified=True).order_by(
+ "email").values_list("email", flat=True)
+ if email not in user_emails:
+ messages.error(
+ request,
+ _('You can only unsubscribe yourself.'))
+ return redirect('list_summary', self.mailing_list.list_id)
if self._has_pending_unsub_req(email):
messages.error(
request,
I copied the user_emails chunk from earlier in this file to make sure I didn't implement the query wrong. If that looks good I can add the news entry, etc. and submit a MR - let me know.