muiz.tech

Saylani LMS · Broken object access

LMS Access Control Series

How one attendance IDOR led to profile, payments, quiz, write access, and a password-reset chain — documented in discovery order.

6Confirmed findings
1Fixed — profile CNIC
401No-cookie baseline
200Other-object reads

Context

What this is

Authorized student testing on Saylani LMS, reported through LMS Bug Feedback and to instructors. This page is a technical timeline — not a live exploit tool. Other students’ personal data is redacted.

Target
Frontend lms.saylanimit.com · API api.saylanimit.com
Auth seen
Cookie session (uat), Axios withCredentials: true. Boot via GET /api/auth/me.
Reporter
Muhammad Muiz Ahmed · Student roll 777251
Core class
Authenticated IDOR / Broken Object Level Authorization — login required, ownership not enforced.

Discovery order

Bug series — first to last

Each step unlocked identifiers or impact for the next. Jump to any finding for full endpoint detail.

Bug 01 · First

Attendance IDOR

Medium–High

Any logged-in student can read another student’s attendance identity by changing roll_number. Authentication works; ownership does not.

Affected endpoint
GET /api/attendance/single-student?roll_number={roll} Base: https://api.saylanimit.com Credentials: include (session cookie)
Response shape (fields)
status, message, student.{full_name, roll_number}, total_classes, total_present, total_leave, total_absent, attendance[{date, status}]
Observed behavior
Own roll → 200 · Other valid roll → 200 + other identity · Unknown roll → 404 Student not found · No cookie → 401
Related (not vulnerable the same way)
GET /api/attendance/single-student-attendance?roll_number={roll} → student session 403 Unauthorized role
IDOR object: roll_number read
Why first
Visible in Network on Attendance page; easiest object id to swap.
Unlocks next
Confirms pattern: session required, object id trusted. Led to hunting other object params (CNIC, student_id).
Impact
Cross-student attendance and name disclosure for any guessable/known roll.
Bug 02 · Fixed

Profile read IDOR

Fixed · Aug 8, 2026

Frontend loads profile by CNIC (not Mongo _id). Originally another student’s CNIC returned full profile and enrollments. Retest: other CNIC now returns 403 Forbidden.

Affected endpoint
GET /api/student_induction/students/profile/{cnic} Frontend path param = student_cnic Credentials: include
Original exposed fields (when vulnerable)
full_name, father_name, email, contact_number, full_address, gender, date_of_birth, image, _id + course / slot / roll keys on induction list
Status
Before: other CNIC → 200 full object · After (Aug 8, 2026): other CNIC → 403 Forbidden
IDOR object: cnic fixed
Why it mattered
High-value PII + identifiers that fed Bugs 03–06.
Chain effect
Bug 06 step A (read other profile for CNIC + DOB) is blocked for other students. Remaining findings still documented below.
Original impact
Broad PII and enrollment disclosure across students.
Bug 03

Payments IDOR

High

Payment history accepts arbitrary student_id (plus course) without proving the caller owns that student.

Affected endpoint
GET /api/student_induction/students/payments?student_id={id}&course_id={id} Credentials: include
Response
status, data[]200 with data array for other student_id
IDOR object: student_id financial
Depends on
Bug 02 (or UI Network) for real student_id / course_id.
Impact
Cross-student payment record visibility.
Bug 04

Quiz results IDOR

Medium–High

Active quiz results keyed by student_id (with slot/course) return another student’s results.

Affected endpoint
GET /api/quiz/active_results?student_id={id}&slot_id={id}&course_id={id} Credentials: include
Response
status, data, isTodayAttendanceMarked — other student_id → 200 with data present
IDOR object: student_id academic
Depends on
IDs from Bug 02 / Network on quiz pages.
Impact
Cross-student academic result disclosure.
Bug 05

Profile write IDOR

Critical

Same broken ownership pattern on update: another student’s _id in the body is accepted and persisted.

Affected endpoint
PUT /api/student_induction/students/profile/update Credentials: include Body shape: { student: { _id, full_name, email, contact_number, image, full_address, last_qualification, gender, date_of_birth }, _id }
Observed behavior
Other student’s _id200 and profile fields updated on that account
IDOR write integrity
Depends on
Bug 02 for target _id.
Impact
Unauthorized modification of another student’s profile (integrity + further social-engineering surface).
Note
Tested with classmate permission; changes reverted where needed.
Bug 06 · Chain

Password set → account takeover

Critical

Originally: profile IDOR exposed CNIC + DOB, then password generation (no token) + login completed takeover. With Bug 02 fixed, reading another student’s profile for that fuel is blocked (403). Documented here as the historical chain that was verified with permission.

A
Read profile
GET /api/student_induction/students/profile/{cnic}
Obtain CNIC + DOB (+ _id). Other CNIC now 403 (Bug 02 fixed).
B
Set password (frontend isToken: false)
POST /api/student_induction/students/generate-password Body: { cnic, dob, password }
C
Login as that student
POST /api/auth/login Body: { user_type: "student", cnic, password }
account takeover chained auth design

Verified only with explicit classmate permission. Friend account was logged out afterward and password reset again. Do not use this chain against accounts without authorization.

Reference

Endpoints involved

Complete map of endpoints touched in this series, plus useful session baselines.

Endpoint Role in series Object key Result seen
GET /api/auth/me Session baseline / own user fields 200 with cookie · 401 without
GET /api/attendance/single-student?roll_number= Bug 01 — IDOR read roll_number 200 other student
GET /api/attendance/single-student-attendance?roll_number= Contrast — role locked roll_number 403 for student
GET /api/student_induction/students/profile/{cnic} Bug 02 — was IDOR read / chain fuel cnic 403 other CNIC (fixed Aug 8, 2026)
GET /api/student_induction/students/payments?student_id=&course_id= Bug 03 — IDOR read student_id 200 data[]
GET /api/quiz/active_results?student_id=&slot_id=&course_id= Bug 04 — IDOR read student_id 200 data
PUT /api/student_induction/students/profile/update Bug 05 — IDOR write student._id / _id 200 applies update
POST /api/student_induction/students/generate-password Bug 06 — set password cnic + dob isToken false; sets password
POST /api/auth/login Bug 06 — complete takeover cnic + password student login succeeds after B
GET /api/certificate/student/{id} Checked during hunt id 404 in test path used
GET /api/course_progress?course=&slot=&show_assignment= Checked — no student_id in path course / slot 200 (scoped by params)
GET /api/online_class/student?slot= Checked slot 500 in one probe
POST /api/auth/logout · /refresh · /2fa Auth surface (not IDOR findings) Present in frontend bundle

Method

How the series was built

  1. Logged in as student; confirmed session via /api/auth/me.
  2. Used DevTools Network on Attendance / Profile / Quiz / Payments.
  3. Baselined each call with own identifiers → expected 200.
  4. Changed only the object id (with permission where needed).
  5. Confirmed no-cookie requests → 401 (auth present, authz missing).
  6. Reported via LMS Bug Feedback; shared technical detail with instructor on request.

Fix recommendation for all object endpoints: derive identity from session; if requested roll / cnic / student_id ≠ caller (and role is not privileged), return 403. Password generation should require a strong, single-use proof beyond CNIC+DOB alone.