Skip to content

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:

  1. Subscribe to a mailing list with address A and confirm the subscription
  2. 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.
  3. Using your browsers inspector, change the form's URL by replacing subscribe with unsubscribe/
  4. Change the name attribute on the address <select> to "email".
  5. Change the value of the selected option to be address A.
  6. 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.

Edited by legoktm