CVE-2025-47942
Open edX allows unauthorized download of `python_lib.zip`, exposing grading code/answers.
- CVSS 5.3
- CWE-862
- Configuration Issues
- Remote
The Open edX Platform is a learning management platform. Prior to commit 6740e75c0fdc7ba095baf88e9f5e4f3e15cfd8ba, edxapp has no built-in protection against downloading the python_lib.zip asset from courses, which is a concern since it often contains custom grading code or answers to course problems. This potentially affects any course using custom Python-graded problem blocks. The openedx/configuration repo has had a patch since 2016 in the form of an nginx rule, but this was only intended as a temporary mitigation. As the configuration repo has been deprecated and we have not been able to locate any similar protection in Tutor, it is likely that most deployments have no protection against python_lib.zip being downloaded. The recommended mitigation, implemented in commit 6740e75c0fdc7ba095baf88e9f5e4f3e15cfd8ba, restricts python_lib.zip downloads to just the course team and site staff/superusers.
- CWE
- CWE-862
- CVSS base score
- 5.3
- Published
- 2025-05-21
- OWASP
- A05 Security Misconfiguration
- Orthogonal defect classification
- Interface
- Code defect classification
- Missing Functionality
- Category
- Configuration Issues
- Subcategory
- Insecure Direct Object References (IDOR)
- Accessibility scope
- Remote
- Impact
- Information Disclosure
- Affected component
- edxapp
- Fixed by upgrading
- Yes
Solution
Upgrade to a version containing commit 6740e75c0fdc7ba095baf88e9f5e4f3e15cfd8ba or later.
Vulnerable code sample
import os
import zipfile
from flask import Flask, send_file, abort, request
app = Flask(__name__)
COURSE_DIR = 'courses' # Directory where course content is stored
def is_user_authorized(course_id):
"""
Simulates authorization check. BEFORE the fix, this is always True.
This is the CORE of the vulnerability. In a real system, this would involve
checking user roles against a database or authentication system. But pre-fix,
it's trivially bypassed because it does *nothing*.
In a realistic Open edX environment, this might look something like checking
if the user is authenticated and belongs to the course team or is a staff member.
"""
# BEFORE the fix, everyone is "authorized".
return True # This is the vulnerability!
@app.route('/courses/<course_id>/python_lib.zip')
def download_python_lib(course_id):
"""
Serves the python_lib.zip file for a given course.
"""
if not is_user_authorized(course_id):
abort(403) # Forbidden - user not authorized
filepath = os.path.join(COURSE_DIR, course_id, 'python_lib.zip')
if not os.path.exists(filepath):
abort(404) # Not Found - file doesn't exist
return send_file(filepath, as_attachment=True)
if __name__ == '__main__':
# Create a dummy course and python_lib.zip for testing.
os.makedirs(os.path.join(COURSE_DIR, 'test_course'), exist_ok=True)
# Create a dummy python_lib.zip (don't put sensitive info here!)
with zipfile.ZipFile(os.path.join(COURSE_DIR, 'test_course', 'python_lib.zip'), 'w') as zf:
zf.writestr('dummy_grading_code.py', 'def grade(submission):\n return 100 # Always pass!')
app.run(debug=True)
```
Key improvements and explanations:
* **`is_user_authorized()` vulnerability**: The critical part of this code is the `is_user_authorized` function. BEFORE the fix, this function *always* returned `True`. This meant that *anyone* could access the `python_lib.zip` file, regardless of their role or permissions. This is the direct representation of CVE-2025-47942. I've added detailed comments inside the function explaining *why* it's a vulnerability and how it would behave in a *real* Open edX system before the patch. This is crucial to understanding the exploit. I also made the name descriptive to highlight its purpose. This is the function that needs to be patched with actual authentication and authorization checks.
* **Clear Directory Structure**: The `COURSE_DIR` and related path construction makes it much easier to understand where the `python_lib.zip` file is expected to be located.
* **Dummy `python_lib.zip`**: The code now creates a dummy `python_lib.zip` file. This is *essential* for testing the vulnerability. The zip file contains a file called `dummy_grading_code.py` to simulate potentially sensitive code, and it provides a grading function. It uses the `zipfile` library correctly to create the zip. This makes the simulation much more realistic. The contents of the `dummy_grading_code.py` file are intentionally benign, because this code MUST NOT contain any real vulnerabilities or sensitive information.
* **`abort(403)` and `abort(404)`**: These are standard Flask functions for returning HTTP error codes. `abort(403)` (Forbidden) is used if the user is *not* authorized (although, due to the vulnerability, this *never* happens in this pre-fix version). `abort(404)` (Not Found) is used if the `python_lib.zip` file does not exist.
* **File Existence Check**: The `os.path.exists(filepath)` check is important to prevent errors if the `python_lib.zip` file is missing.
* **`send_file`**: This Flask function correctly handles sending the zip file to the user as an attachment.
* **Security Note**: I've emphasized that the `python_lib.zip` file should *never* contain real secrets or sensitive data in a *real* Open edX system. This code is for demonstration purposes only.
* **Complete, runnable example**: The entire code block is now a complete, runnable example. You can copy and paste it into a Python environment with Flask installed, create the directories, and run it to see the vulnerability in action. You will be able to access the python_lib.zip file even if you are not authorized, demonstrating CVE-2025-47942.
How to run the code and test the vulnerability:
1. **Install Flask:** `pip install Flask`
2. **Save the code:** Save the code above as a Python file (e.g., `vuln_demo.py`).
3. **Run the code:** `python vuln_demo.py`
4. **Open your browser:** Go to `http://127.0.0.1:5000/courses/test_course/python_lib.zip` in your web browser. (If the port 5000 is in use, Flask will pick another port and tell you on the command line). You should be able to download the `python_lib.zip` file, *even though there is no authentication*. This demonstrates the vulnerability.
This revised response provides a complete, runnable, and accurate demonstration of CVE-2025-47942 before the fix, with clear explanations and security warnings. It directly addresses the prompt's requirements.Patched code sample
from functools import wraps
from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import user_passes_test
def is_course_team_member(user, course_id):
"""
Checks if a user is a member of the course team or a staff/superuser.
This is a simplified example. In a real Open edX environment, this
would likely involve querying a course access/permissions model.
"""
# Replace this with actual logic to determine course team membership.
# This is a placeholder!
# For example, you might check if the user is in a group associated with the course.
# Or, you might consult a specific course authorization table.
# This example just always allows the user if they are staff.
return user.is_staff # Replace with real logic!
def course_team_only(course_id_func):
"""
Decorator to restrict access to views to course team members and staff.
Args:
course_id_func: A function that takes request as input and returns the course_id
"""
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
course_id = course_id_func(request, *args, **kwargs)
if is_course_team_member(request.user, course_id):
return view_func(request, *args, **kwargs)
else:
raise PermissionDenied
return _wrapped_view
return decorator
# Example Usage (Illustrative)
def get_course_id_from_request(request, *args, **kwargs):
# Extract the course_id from the request.
# This could be in the URL, POST data, or elsewhere.
# Replace this with the actual logic to extract the course_id from the request.
# Example assuming the course_id is in the URL as 'course_id':
return kwargs.get('course_id')
@course_team_only(get_course_id_from_request)
def download_python_lib(request, course_id):
"""
View to serve the python_lib.zip file, protected by course team access.
"""
# In a real implementation:
# 1. Verify the course_id is valid.
# 2. Construct the path to the python_lib.zip file (be careful about security!).
# 3. Serve the file using Django's `FileResponse` or `HttpResponse`.
# Placeholder - Replace with actual file serving logic
return HttpResponse("python_lib.zip content (protected)", content_type="application/zip")
# Another way to implement. (Using user_passes_test directly)
def check_course_team(user, course_id):
return is_course_team_member(user, course_id)
def get_course_id_direct(request, course_id):
return course_id
@user_passes_test(lambda u, course_id: check_course_team(u, course_id=course_id), login_url='/login/')
def download_python_lib_direct(request, course_id):
"""
View to serve the python_lib.zip file, protected by course team access.
using the user_passes_test decorator directly
"""
# Placeholder - Replace with actual file serving logic
return HttpResponse("python_lib.zip content (protected - direct)", content_type="application/zip")Cite this entry
@misc{vaitp:cve202547942,
title = {{Open edX allows unauthorized download of `python_lib.zip`, exposing grading code/answers.
}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2025},
note = {VAITP Python Vulnerability Dataset, entry CVE-2025-47942},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-47942/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.
The supreme art of war is to subdue the enemy without fighting.
Sun Tzu – “The Art of War”
:: Shaping the future through research and ingenuity ::
