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 _id → 200 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
- Logged in as student; confirmed session via /api/auth/me.
- Used DevTools Network on Attendance / Profile / Quiz / Payments.
- Baselined each call with own identifiers → expected 200.
- Changed only the object id (with permission where needed).
- Confirmed no-cookie requests → 401 (auth present, authz missing).
- 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.