openSIS Classic 9.3 - Insecure Direct Object Reference (IDOR)

9.3

Critical

Discovered by

Daniel Esteban Celis

External researcher

Summary

Full name

openSIS Classic 9.3 - Insecure Direct Object Reference (IDOR)

Code name

State

Public

Release date

Affected product

openSIS Classic

Vendor

openSIS

Affected version(s)

9.3

Vulnerability name

Insecure object reference

Remotely exploitable

Yes

CVSS v4.0 vector string

CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N

CVSS v4.0 base score

9.3

Exploit available

Yes

Description

An authenticated user with the built-in teacher role can select an arbitrary staff record through staff_id and cause the School Information update path to reset that selected account's password. The server authorizes the request only at the broad role/module level; it never verifies that the selected staff record belongs to the authenticated teacher or that the teacher has edit permission for it.

Consequently, a teacher can set a new password for an administrator account and authenticate as that administrator. This is not intended self-service profile behavior: the teacher menu labels the endpoint as “My Info,” and the default permission data grants teachers CAN_USE but not CAN_EDIT for the relevant staff-information categories.

Vulnerability

Root cause

The staff-management workflow conflates the authenticated user with the staff record selected for editing. In a new Teacher session, Staff.php accepts an arbitrary request-supplied staff_id and persists it as $_SESSION['staff_id']. Subsequent authorization checks permit the Teacher role but do not verify that this selected record belongs to that Teacher. The password-update workflow then uses that selected session ID to update an authentication credential.

Source-to-sink path

  1. Attacker-controlled object selection — modules/users/Staff.php:33-34 copies $_REQUEST['staff_id'] to $_SESSION['staff_id'] when no staff record is selected. UserStaffID() is only a wrapper over that session value (functions/CurrentFnc.php:78-81); it is not the authenticated principal ($_SESSION['STAFF_ID']).

    
    
    
    
    
    
    
    

    A direct request selecting another staff ID therefore establishes that other account as the active object in the teacher's session. There is no equality/ownership check against UserID() or $_SESSION['STAFF_ID'].

  2. Role-only access control — the endpoint explicitly allows both administrators and teachers past its target-selection gate (modules/users/Staff.php:159-164). The teacher menu exposes users/Staff.php as “My Info” (modules/users/Menu.php:48-51), and the supplied default profile data grants profile 2 (teacher) use of that endpoint and its School Information category while leaving CAN_EDIT unset (install/Upgrade6.php:1577-1579). These UI/permission settings do not enforce object ownership at the write sink.

  3. Credential-update sink — when SchoolsInfoInc.php processes staff_school[PASSWORD], it obtains the selected staff record using UserStaffID() (modules/users/includes/SchoolsInfoInc.php:482-487), generates a password hash, and writes it to login_authentication for the selected account (modules/users/includes/SchoolsInfoInc.php:499-541). No condition verifies either ownership of that USER_ID or AllowEdit().

    
    
    
    
    
    
    
    

The reporter's original explanation conflates this with the earlier UPDATE staff ... WHERE STAFF_ID=$_REQUEST[staff_id] code in Staff.php. That is not the credential-table sink used by the supplied staff_school[PASSWORD] request. The exploitable credential update is the SchoolsInfoInc.php flow above.

Impact

An authenticated user with the Teacher role can reset the password of an Administrator account and log in as that Administrator. This provides unauthorized access to administrator-only functions and the information and integrity controls available to that role. The demonstrated attack requires neither victim interaction nor knowledge of the Administrator's previous password.

PoC

The following procedure should be performed only against a locally controlled installation.

  1. Log in as a teacher and identify an administrator's numeric STAFF_ID, denoted below as TARGET_ID.

  2. In the same authenticated session, load the Staff endpoint with staff_id=TARGET_ID. This causes Staff.php:33-34 to store TARGET_ID in $_SESSION['staff_id']. If the teacher had already selected their own record, first use the normal deselect/back flow (or start a fresh session) so that the target-selection assignment can occur.

  3. Submit a multipart POST to the normal Staff update route with modfunc=update, include=SchoolsInfoInc, and a new value for staff_school[PASSWORD]. Preserve the session cookie. The request must contain the ordinary School Information fields required by the deployment; school_info_id is not the authorization control.

    POST /Modules.php?modname=users/Staff.php&custom=staff&include=SchoolsInfoInc&category_id=3&modfunc=update HTTP/1.1
    Cookie: PHPSESSID=<teacher-session>
    Content-Type: multipart/form-data; boundary=<boundary>
    
    --<boundary>
    Content-Disposition: form-data; name="staff_school[PASSWORD]"
    
    <new-password>
    --<boundary>
    
    
    POST /Modules.php?modname=users/Staff.php&custom=staff&include=SchoolsInfoInc&category_id=3&modfunc=update HTTP/1.1
    Cookie: PHPSESSID=<teacher-session>
    Content-Type: multipart/form-data; boundary=<boundary>
    
    --<boundary>
    Content-Disposition: form-data; name="staff_school[PASSWORD]"
    
    <new-password>
    --<boundary>
    
    
    POST /Modules.php?modname=users/Staff.php&custom=staff&include=SchoolsInfoInc&category_id=3&modfunc=update HTTP/1.1
    Cookie: PHPSESSID=<teacher-session>
    Content-Type: multipart/form-data; boundary=<boundary>
    
    --<boundary>
    Content-Disposition: form-data; name="staff_school[PASSWORD]"
    
    <new-password>
    --<boundary>
    
    
    POST /Modules.php?modname=users/Staff.php&custom=staff&include=SchoolsInfoInc&category_id=3&modfunc=update HTTP/1.1
    Cookie: PHPSESSID=<teacher-session>
    Content-Type: multipart/form-data; boundary=<boundary>
    
    --<boundary>
    Content-Disposition: form-data; name="staff_school[PASSWORD]"
    
    <new-password>
    --<boundary>
    
    
  4. The handler hashes the supplied password and updates login_authentication.USER_ID=TARGET_ID. Authenticate as the target administrator using the new password to confirm the account takeover.

Evidence of Exploitation

  • Video of exploitation:

  • Static evidence:

Our security policy

We have reserved the ID CVE-2026-91107 to refer to this issue from now on.

Disclosure policy

System Information

  • openSIS Classic

  • Version: 9.3 Community Edition

  • Operating System: Any deployment running the affected openSIS Classic messaging module

References

Mitigation

An updated version of OpenSIS is available on the vendor page.

Credits

The vulnerability was discovered by Daniel Celis, an independent security researcher.

Timeline

Vulnerability discovered

Vendor contacted

Vendor replied

Vendor confirmed

Vulnerability patched

Public disclosure

Does your application use this vulnerable software?

During our free trial, our tools assess your application, identify vulnerabilities, and provide recommendations for their remediation.