-
Notifications
You must be signed in to change notification settings - Fork 460
Security: Systemic Missing Role-Based Access Control — Students Can Modify Attendance and Marks #49
Description
Summary
During an authorized security audit, 16 security vulnerabilities (6 CRITICAL) were identified in College-ERP. The most severe issue is a systemic lack of role-based access control: all teacher endpoints use @login_required but never verify the user's role, allowing any authenticated student to perform teacher-level operations — including modifying attendance records and entering marks.
Findings
1. Systemic Role Bypass (CRITICAL)
All 16+ teacher views use @login_required but zero check whether the authenticated user actually holds the teacher role. A student can access every teacher endpoint simply by navigating directly to /teacher/... URLs.
This includes write operations:
- Cancel classes
- Submit and toggle attendance
- Enter and confirm marks
- Create extra class sessions
Root cause: @login_required only verifies "is someone logged in," not "is this person a teacher."
2. Student-to-Teacher Privilege Escalation (CRITICAL)
A student account can perform the following teacher-only actions:
| Action | Function | Location |
|---|---|---|
| Cancel any class | cancel_class |
views.py:80 |
| Submit attendance for any class | confirm |
views.py:113 |
| Toggle any attendance record (present ↔ absent) | change_att |
views.py:150 |
| Enter marks for any student | marks_confirm |
views.py:310 |
| Create extra class sessions | e_confirm |
views.py:169 |
Impact: Complete academic integrity compromise. A student can set their own attendance to 100% and assign themselves any marks.
3. IDOR on Student Data (HIGH)
The attendance(stud_id) and marks_list(stud_id) views accept a student ID from the URL but do not verify that request.user.student.USN == stud_id. Any authenticated user can view any student's attendance and marks records by enumerating student IDs.
4. Teacher-to-Teacher IDOR (HIGH)
Teacher views accept teacher_id from the URL without verifying it matches request.user.teacher.id. This allows Teacher A to operate on Teacher B's classes — viewing, modifying attendance, and entering marks for classes they do not teach.
5. Predictable Passwords (CRITICAL)
Passwords are generated as firstname_birthyear (e.g., john_1998) during user creation (views.py:367, 406). Both the user's name and date of birth are visible to other users through attendance and marks views. There is no forced password change on first login. This makes every account trivially compromisable.
6. Hardcoded SECRET_KEY (HIGH)
The Django SECRET_KEY is committed directly to source code in settings.py:23. Anyone with access to the repository can forge session cookies and escalate privileges, bypassing all authentication entirely.
Impact Summary
| Severity | Count | Examples |
|---|---|---|
| CRITICAL | 6 | Role bypass on all teacher views, privilege escalation, predictable passwords |
| HIGH | 10 | Student data IDOR, teacher-to-teacher IDOR, hardcoded SECRET_KEY |
Worst-case scenario: A student logs in, navigates to teacher URLs, sets their own attendance to 100%, enters maximum marks for themselves, and compromises other accounts using predictable passwords — all without any exploit tooling.
Recommended Fixes
1. Add Role-Checking Decorators
Create and apply @teacher_required and @student_required decorators that verify the user's role before granting access:
from functools import wraps
from django.http import HttpResponseForbidden
def teacher_required(view_func):
@wraps(view_func)
@login_required
def wrapper(request, *args, **kwargs):
if not hasattr(request.user, 'teacher'):
return HttpResponseForbidden("Access denied.")
return view_func(request, *args, **kwargs)
return wrapperApply @teacher_required to all teacher views and @student_required to all student views.
2. Add Ownership Checks
In every view that accepts a resource ID (student ID, teacher ID, class ID), verify the resource belongs to the requesting user:
# Example for teacher views
if teacher_id != request.user.teacher.id:
return HttpResponseForbidden("Access denied.")3. Generate Random Passwords
Replace the predictable firstname_birthyear pattern with cryptographically random passwords and enforce a password change on first login:
from django.utils.crypto import get_random_string
password = get_random_string(12)4. Move SECRET_KEY to Environment Variable
# settings.py
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')Rotate the current key immediately, as it is already public.
Disclosure
This report was produced during an authorized security research audit. Findings are disclosed here because this repository does not have GitHub Private Vulnerability Reporting enabled or a SECURITY.md policy. No exploit code is included. The goal is to help improve the security of this project.