Apache OpenMeetings 9.1.0 - High - Remote - IDOR - LOGIN calendar save overwrites appointment and closes public room
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · openmeetings-calendar-save-idor
Class: IDOR Reach: Remote
Apache OpenMeetings 9.1.0 (9773c59e) - Apache Software Foundation
Any LOGIN user can POST /services/calendar/ with another appointment's id and omit owner. The save predicate never loads the persisted owner. CalendarMapper then RoomMapper write closed / ispublic / name on the attached room. A registered user overwrites someone else's meeting and closes a public conference room.
| ID | no CVE yet |
| Class | IDOR |
| Reach | Remote (authenticated LOGIN REST) |
| CWE | CWE-639, CWE-284 |
| CVSS | High: 7.6 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L |
| Product | Apache OpenMeetings |
| Affected | 9.1.0 (9773c59e1af327bf988018e661f456ae9997c29f) |
| Auth | LOGIN (default self-register mints this) |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1:18150 only. apache/openmeetings:min-9.1.0 plus Temurin 21. |
I pinned tag 9.1.0, peel 9773c59e1af327bf988018e661f456ae9997c29f (2026-07-14). Latest GA. Java 21. Not master.
Register (wizard allowFrontendRegister=true) or hold any LOGIN password. POST /openmeetings/services/user/login mints a SID. Appointment ids are sequential. Public room id 2 is the stock conference room.
| Method | Sink | Privilege |
|---|---|---|
POST /services/calendar/?sid= form appointment JSON, owner omitted, victim id | CalendarWebService.save predicate (CalendarWebService.java:310-329) | Integrity (lab-proven): overwrite title, times, members. Lab: title became OPENMEETINGS-CALENDAR-SAVE-WITNESS, owner 2→3. |
Same save; room.closed=true | RoomMapper.get writes on the managed entity (RoomMapper.java:62-92) then dao.update | Integrity + availability (lab-proven): public conference room persisted closed=true, isPublic=false. SOAP GET /room/2 returned that. |
Attacker GET title/range after save | response DTO is the post-write row | Recon (lab-proven): witness title. Victim range empty after owner steal. |
Lab-proven vs inferred: save HTTP 200, title, owner steal, room close are lab. Capacity / moderated bits on the same mapper are inferred.
Requires: network path to /openmeetings/services. A LOGIN account. A victim appointment id (sequential, or create one and wait). Default open signup.
| Surface | Why |
|---|---|
DELETE /calendar/{id} | owner or SOAP/ADMIN. Regular LOGIN cannot delete others. |
| Range / next / title reads | owner-scoped. Attacker does not get a clean read of the original row. |
| Recording MP4 | different ACL (RecordingResourceReference). |
| SOAP-only list-by-userid | Right.SOAP. Default registered users do not have it. |
| Unauthenticated save | performCall needs a SID with LOGIN. |
9.1.0 closed CVE-2026-49488 (whiteboard fuid OS read). I kept hunting REST object ids. Calendar delete checks a.isOwner(sd.getUserId()). Calendar save checks the request DTO owner, or null. CalendarMapper.get loads appointmentDao.get(dto.getId()) with no owner scope. RoomMapper then dirties the room JPA entity before dao.update.
SOAP listing every appointment is the LMS integration account. This leftover is LOGIN vs owner.
cd lab
./run.sh
Loopback https://127.0.0.1:18150/openmeetings. Official docker admin om_admin / 1Q2w3e4r5t^y. Self-signed TLS. Witness last line:
SUCCESS OPENMEETINGS-CALENDAR-SAVE-WITNESS
Load the persisted appointment before save. Require a.isOwner(caller) unless SOAP/ADMIN. Do not apply RoomMapper writes to a room the caller cannot moderate. Treat omitted owner as "keep persisted owner", not "caller becomes owner".
Abraxas Labs - https://abraxaslabs.tech - https://github.com/abraxas - https://x.com/abraxas_null - [email protected]
Vendor: [email protected]