-
Notifications
You must be signed in to change notification settings - Fork 339
Description
Summary
Nearly all @frappe.whitelist() functions in the Education module are callable by any authenticated user (including students) and perform zero permission checks before acting on arbitrary resource IDs. This allows any authenticated user to manipulate fees, enroll students, mark attendance, modify assessment results, and access data for any student.
The permission_query_conditions and has_permission hooks in hooks.py (lines 167-173) are commented out, and there are zero frappe.has_permission() or frappe.only_for() calls in the entire codebase.
Findings (8 vulnerabilities)
1. CRITICAL: Fee Payment Manipulation (collect_fees)
File: education/education/api.py:246-252
Any authenticated user can mark any student's fees as paid:
@frappe.whitelist()
def collect_fees(fees, amt):
paid_amount = flt(amt) + flt(frappe.db.get_value("Fees", fees, "paid_amount"))
total_amount = flt(frappe.db.get_value("Fees", fees, "total_amount"))
frappe.db.set_value("Fees", fees, "paid_amount", paid_amount)
frappe.db.set_value("Fees", fees, "outstanding_amount", (total_amount - paid_amount))Uses frappe.db.set_value() which bypasses ORM permissions entirely. No permission check.
2. HIGH: Unauthorized Student Enrollment (enroll_student)
File: education/education/api.py:27-69
Any user can convert Student Applicants into Students with ignore_permissions=True.
3. HIGH: Cross-Student Attendance Marking (mark_attendance)
File: education/education/api.py:90-129
Any user can mark attendance (present/absent) for any student. Accepts arbitrary student IDs in JSON arrays.
4. HIGH: Cross-Student Leave Application (apply_leave)
File: education/education/api.py:606-614
Any user can submit leave for any student. leave_data.get("student") is fully client-controlled.
5. HIGH: Assessment Result Manipulation (mark_assessment_result)
File: education/education/api.py:385-419
Any user can modify assessment scores for any student. scores["student"] and scores["total_score"] are client-controlled.
6. HIGH: Bulk Assessment Submission (submit_assessment_results)
File: education/education/api.py:422-431
Any user can permanently finalize (submit, docstatus=1) assessment results for entire student groups.
7. MEDIUM: Cross-Student Data Disclosure (multiple functions)
File: education/education/api.py
13+ functions use frappe.get_all() (which bypasses permissions) to return data for any student: get_student_guardians, get_student_group_students, get_assessment_students, get_fee_components, get_fee_schedule, get_current_enrollment, get_student_programs, get_student_invoices, get_student_attendance, etc.
Compare with get_student_info() (line 524) which correctly scopes to frappe.session.user.
8. MEDIUM: Unauthorized Email Group Creation (update_email_group)
File: education/education/api.py:454-469
Any user can create Email Groups and populate them with guardian email addresses from any student group.
Root Cause
The developers assumed DocType-level role permissions protect @frappe.whitelist() endpoints - they don't. In Frappe:
@frappe.whitelist()makes functions callable by any logged-in user via/api/method/frappe.get_all()bypasses DocType permissions (unlikefrappe.get_list())frappe.db.set_value()bypasses all ORM permissions
This matches the pattern found in Frappe HR (Issue #4191), Frappe LMS (Issue #2147), and Frappe CRM (Issue #1787).
Remediation
- Uncomment the permission hooks in
hooks.py(lines 167-173) - Add
frappe.has_permission()checks to all whitelisted functions - Replace
frappe.get_all()withfrappe.get_list()for student-facing queries - Add
frappe.only_for()role restrictions to administrative functions - Replace
frappe.db.set_value()with document-level updates that go through permission checks
Found via automated security research by Lighthouse