Research/openmeetings-9-1-0-leftovers
0-dayNo CVEHighPublic

Apache OpenMeetings 9.1.0 leftovers

OpenMeetings 9.1.0 closed the whiteboard fuid read. LOGIN calendar save still overwrites another meeting and closes the public room. A stale /install wizard still plants a second ADMIN. Official KMS docker still publishes unauth JSON-RPC on 8888. ADMIN_BACKUP import still skips Path.of.

Name
Apache OpenMeetings 9.1.0 leftovers
Type
0-day analysis
CVE
n/a
CVE Risk
high
Disclosure Status
public
Vendor
Apache OpenMeetings (ASF)
Affected
Apache OpenMeetings 9.1.0 (9773c59e1af327bf988018e661f456ae9997c29f). Java 21. Kurento 7.3.0+.
Published
16 Oct 2026
Updated
16 Oct 2026
Tags
0-day, openmeetings, apache, idor, privilege-leftover, remote, kms

The conference room was already closed. The calendar said I owned it.

I am @abraxas_null. This is a map of leftovers on Apache OpenMeetings 9.1.0 (9773c59e1af327bf988018e661f456ae9997c29f, 2026-07-14, latest GA). Java 21. Kurento 7.3.0+. Twenty source hunts. Loopback witness labs. No shells.

The 9.1.0 announcement is a security release. It closed CVE-2026-49488: a moderator whiteboard download that would follow fuid off the files tree. I pinned that tag on purpose. A security release is where leftovers hide.

Packs are AGPLv3 under github.com/abraxas. Vendor is security@openmeetings.apache.org. Unpaid ASF VDP. No public issue on apache/openmeetings.

Why this product

OpenMeetings is a video-conference stack with a Wicket UI, CXF SOAP+REST under /openmeetings/services, OpenJPA, optional Hazelcast, and Kurento for WebRTC. LMS plugins (Moodle, Sakai, Joomla) log in as a dedicated SOAP user and mint room hashes. Default self-register is on. That combination is a lot of object ids and a lot of "the SOAP account is supposed to stay below ADMIN."

I wanted the unpublished remainder after the 2026 wave, not a replay of 49488.

The pin and the skip list

Tag 9.1.0. Peel 9773c59e. The git tag object 273dda0 is not a commit; HEAD is the "9.1.0 Release" commit. Official image apache/openmeetings:min-9.1.0 is linux/amd64 and does not ship a JRE. Labs wrap Temurin 21.

Already public on this pin, so I did not re-file them:

  • CVE-2026-49488 whiteboard fuid OS read (OmFileHelper.validateLocation)
  • CVE-2026-34020 REST login GET password (now POST form)
  • CVE-2026-33266 hardcoded remember-me key (per-instance AES-GCM)
  • CVE-2026-33005 personal/group folder-by-id for non-owners
  • CVE-2025-7962 SMTP CR/LF (angus-mail 2.0.5)
  • CVE-2024-54676 OpenJPA docs whitelist
  • CVE-2023-29246 UI Path.of / backup XML NUL
  • CVE-2023-28936 invitation LIKE then equals
  • CVE-2023-29032 Wicket ADMIN copy from invite
  • CVE-2023-28326 invitation SOAPLogin.moderator
  • CVE-2021-27576 NetTest 3s delay + 50-client cap on type=upload

Also closed: ProcessHelper list-form argv, CXF 4.2.2 StAX XXE, Wicket CSRF listener, SameSite Lax, DEPLOYMENT, Tomcat manager excluded, H2 console off, AJP commented, install wizard GET after isInstalled() for new requests.

Privilege model, because SOAP is not ADMIN

There is no servlet auth filter on CXF. Every mutating/list call goes through BaseWebService.performCall, which loads Sessiondata by SID (UUID v4) and tests User.Right. POST /user/login mints a SID for any LOGIN user. SOAP methods still fail later unless that user also has Right.SOAP.

Default self-register grants LOGIN + DASHBOARD + ROOM. SOAP is the LMS integration account. Installer admin also gets SOAP. SOAP listing every user and minting room hashes is the documented CMS SSO surface. SOAP planting ADMIN via UserDTO.rights is leftover. Calendar save as LOGIN vs owner is leftover. Those are different bugs.

SID compare still uses JPQL LIKE then equals (same leftover pattern as CVE-2023-28936). Equality is the control.

Surfaces that returned nothing useful

Invitation wildcard redeem stays closed by equals. Generate-URL cannot target a registered USER. FileWebService personal folder-by-id for non-owners stays closed (room parentId < 0 metadata leftover is Medium). Conversion uses UUID + canonical path. Clustering OpenJPA lists are off in default startup.sh. Chat .html(cm.message) still stores markup; CSP 'strict-dynamic' is the remaining control, so I left XSS at Medium.

What I labbed

Critical on stock 9.1.0: none. High leftovers that proved:

Leftover Class Reach Pack
Calendar save IDOR Remote LOGIN openmeetings-calendar-save-idor
Private chat export IDOR Remote LOGIN openmeetings-chat-user-idor
SOAP UserDTO.rights Privilege leftover Remote SOAP openmeetings-soap-user-rights
NetTest ping POST DoS leftover Remote unauth openmeetings-nettest-ping-dos
Whiteboard fileId IDOR Remote presenter openmeetings-wb-fileid-idor
Type.USER invite recordings IDOR Remote hash openmeetings-invite-recording-acl
SOAP/REST OTP skip 2FA skip Remote password openmeetings-soap-otp-skip
Remember-me cookie ATO Remote cookie openmeetings-remember-me-password
Stale /install Privilege leftover Remote first-boot openmeetings-stale-install-admin
Backup PATH import File write leftover Remote ADMIN_BACKUP openmeetings-backup-config-path
Clustering TCP 5636 CWE-502 Remote docs-on openmeetings-cluster-openjpa
Published KMS 8888 Unauth KMS Remote unauth openmeetings-kms-unauth

Labs talk to loopback. Replay is ./run.sh. Witness last lines are in each pack README.

Calendar save IDOR

CalendarWebService.save allows a LOGIN caller when the request DTO owner is null or self. Delete on the same class checks the persisted owner. Save does not.

CalendarMapper.get loads appointmentDao.get(dto.getId()) then copies title, members, and rMapper.get(dto.getRoom()). RoomMapper.get writes closed, ispublic, name, capacity on the managed room entity. dao.update persists both.

I created two LOGIN users. Victim saved appointment 1 on public conference room 2, title ORIGINAL-CALENDAR-TITLE, closed=0. Attacker POSTed the same id, omitted owner, set title OPENMEETINGS-CALENDAR-SAVE-WITNESS, room.closed=true. Save returned 200. Owner 2→3. SOAP GET /room/2 showed closed=true, isPublic=false. Victim title/range went empty. Attacker range returned the witness row.

What an attacker can do

Method Sink Privilege
POST /services/calendar/?sid= owner omitted CalendarWebService.save Integrity (lab-proven): overwrite title, steal owner.
Same save room.closed=true RoomMapper.get Integrity + availability (lab-proven): close and hide the public conference room.
Attacker title/range GET post-write DTO Recon (lab-proven): witness title.

Closed: DELETE /calendar/{id} still owner or SOAP/ADMIN. Range reads stay owner-scoped. Recording MP4 is a different ACL. Unauthenticated save still needs a SID.

CWE-639. 7.6 High CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L. Pack: abraxas/openmeetings-calendar-save-idor (loopback client). Last line: SUCCESS OPENMEETINGS-CALENDAR-SAVE-WITNESS.

Suggested fix: load the persisted appointment. Require a.isOwner(caller) unless SOAP/ADMIN. Treat omitted owner as keep-persisted, not caller-becomes-owner. Do not dirty a room the caller cannot moderate.

Stale install wizard

InstallWizardPage constructor redirects new requests when Application.isInstalled(). That lock looks complete until you keep the Wicket page from before the flip.

ImportInitvalues.checkInstalled logs "no need to install" and does not return. Finish still calls loadAll(cfg, true). loadInitUserAndGroup inserts another ADMIN+SOAP user.

I started an uninstalled 9.1.0 tarball. Session 3E8EE0F6 walked Welcome, DB, user step, filled staleadm, jumped to Finish, and stayed live. A second web wizard completed first install as admin1. Fresh GET /install redirected to /signin. The stale session still POSTed ?3-4.0-wizard-form-buttons-finish. staleadm then SOAP-logged in. User list count=2. Rights: ADMIN, SOAP, LOGIN, DASHBOARD, ROOM.

admin.sh -i is a second JVM and does not load Tomcat CryptProvider, so REST login returns error.unknown after CLI install. The leftover is the stale web page, so the lab used a second web wizard as the operator.

What an attacker can do

Method Sink Privilege
Stale Finish POST checkInstalled continues; loadInitUserAndGroup Integrity (lab-proven): second ADMIN+SOAP account.
Same loadAll persistence / SMTP from stale form Integrity (inferred): rewrite persistence.xml.

Closed: new /install after install redirects. Tomcat manager excluded. H2 console off.

CWE-367. 8.1 High CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H. Pack: abraxas/openmeetings-stale-install-admin. Last line: SUCCESS OPENMEETINGS-STALE-INSTALL-WITNESS.

Suggested fix: checkInstalled must return. One-time install token. Drop the Wicket page when isInstalled() flips. Do not insert a second ADMIN if one exists.

Unauth Kurento on the docs port

OpenMeetings Java defaults kurento.ws.url to loopback. Official InstallMediaServer.md publishes Kurento with docker -p 8888:8888. That JSON-RPC has no auth.

The full apache/openmeetings:9.1.0 image starts kurento-media-server on 0.0.0.0:8888, then can fail catalina as nobody without JAVA_HOME. I labbed the leftover the docs teach: kurento/kurento-media-server:7.3.0 with 127.0.0.1:18164:8888.

HTTP POST is WebSocket-only (426). ws://127.0.0.1:18164/kurento upgraded 101. Unauth ping returned pong. Unauth connect minted a sessionId. Unauth invoke manager_ServerManager.getInfo returned version 7.3.0 and the module list.

What an attacker can do

Method Sink Privilege
Unauth ping / connect / getInfo Kurento JSON-RPC Recon + session mint (lab-proven).
Unauth PlayerEndpoint (same socket) KMS media elements Recon / integrity (inferred): HTTP/file fetch as the KMS UID. Not exercised.

Closed: OM Java default loopback. OM /openmeetings still wants a SID for rooms.

CWE-306. 8.6 High CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:L. Pack: abraxas/openmeetings-kms-unauth. Last line: SUCCESS OPENMEETINGS-KMS-UNAUTH-WITNESS.

Suggested fix: do not publish 8888. Bind KMS to a private network. Put a token on the JSON-RPC. Change the install doc example.

Backup import skips Path.of

CVE-2023-29246 closed NUL injection on Admin Config Type.PATH via Path.of. BackupImport.importConfigs still writes those rows with no Path.of. Java 21 ProcessBuilder also rejects NUL, so this is the leftover of the 29246 sink, not a new argv RCE.

I restored a 3822-byte backup zip via admin.sh -r (same BackupImport.performImport as Wicket ADMIN_BACKUP). configuration.path.imagemagick and path.ffmpeg stored as PATH /tmp/OPENMEETINGS-BACKUP-CONFIG-PATH-WITNESS. Wicket /admin/backup/upload with a REST SID returned access denied; it wants the websocket Client SID.

Control: Path.of accepts this Unix witness path. Path.of with a NUL suffix throws InvalidPathException. No converter was executed.

What an attacker can do

Method Sink Privilege
ADMIN_BACKUP restore importConfigs cfgDao.update Integrity (lab-proven): raw Type.PATH converter path.
Later conversion jobs ffmpeg/imagemagick argv from those rows Integrity / availability (inferred). NUL argv RCE closed on this JDK.

Closed: Admin Config UI still Path.of. ROOM cannot restore.

CWE-20. 6.5 High CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H. Pack: abraxas/openmeetings-backup-config-path. Last line: SUCCESS OPENMEETINGS-BACKUP-CONFIG-PATH-WITNESS.

Suggested fix: run the ConfigForm Path.of validator inside importConfigs before cfgDao.update.

Private chat export IDOR

ChatForm.process takes activeTab. Prefix chatTab-u loads any user id. ChatToolbar user branches are u -> true. Save/delete buttons are admin-visible only. AjaxDownloadBehavior is on the toolbar itself.

Attacker omuserB signed in through Wicket, set activeTab=chatTab-u2, downloaded the victim DM CSV: "Vic User","10/16/26, 10:39 PM","OPENMEETINGS-CHAT-IDOR-WITNESS".

Integrity (inferred): same activeTab on delete wipes that inbox. Export was the oracle.

Closed: global/room export still needs admin or room moderator.

CWE-639. 7.1 High. Pack: abraxas/openmeetings-chat-user-idor. Last line: SUCCESS OPENMEETINGS-CHAT-IDOR-WITNESS.

SOAP add copies ADMIN

SOAP is the LMS account. UserMapper loads by id and setRights(dto.getRights()). UserWebService.add adds ROOM/LOGIN/DASHBOARD and never strips ADMIN.

soaplab is SOAP, not ADMIN. Its DELETE /user/3 returned error.notallowed. As soaplab, POST /user/ created plantedadm with ADMIN and firstname OPENMEETINGS-SOAP-RIGHTS-WITNESS. plantedadm then DELETE /user/3 returned 200 Deleted. Extra IOC: id-merge overwrite of user 4.

Closed: ROOM SID cannot add. Frontend register has no rights array.

CWE-269. 8.8 High. Pack: abraxas/openmeetings-soap-user-rights. Last line: SUCCESS OPENMEETINGS-SOAP-RIGHTS-WITNESS.

NetTest ping leftover of CVE-2021-27576

RateLimitRequestFilter returns early for type=ping. POST still reads the entity to EOF. GET /signin mints a JSESSIONID. No login.

Two parallel 8 MiB ping POSTs both returned 200, bytes=8388608. Control type=upload: first 200, immediate second 429. Immediate ping after that 200. No cookie: ping 403.

CWE-770. 7.5 High. Pack: abraxas/openmeetings-nettest-ping-dos. Last line: SUCCESS OPENMEETINGS-NETTEST-PING-WITNESS.

Whiteboard fileId plant

CREATE_OBJ stores client JSON including any fileId. RoomResourceReference serves those bytes if isAtWb sees that id. CVE-2026-49488 closed OS path read. This leftover is a numeric id.

First joiner of public conference room 2 is presenter. Attacker planted victim fileId 7, then GET /room/file/7 returned HTTP 200 image/png 57 bytes with the witness.

Closed: validateLocation OS path. DOWNLOAD still moderator-only.

CWE-639. 6.5 High. Pack: abraxas/openmeetings-wb-fileid-idor. Last line: SUCCESS OPENMEETINGS-WB-FILEID-WITNESS.

Type.USER invitation is the recording owner

CVE-2023-29032 stopped copying Wicket ADMIN. checkHashes still setUser(invitation.getInvitee(), hrights). Recording MP4 ACL keys off ownerId == getUserId().

Unauth GET /recordings/mp4/2 404. CONTACT hash 404. Unauth GET /hash?invitation= then the same MP4: 200 video/mp4, witness in body.

Closed: LIKE wildcard redeem. CONTACT guests.

CWE-639. 7.5 High. Pack: abraxas/openmeetings-invite-recording-acl. Last line: SUCCESS OPENMEETINGS-INVITE-RECORDING-WITNESS.

SOAP/REST skip TOTP

When otp.enabled is true, TOTP runs only in Wicket SignInDialog. UserWebService.login is password then SID.

Wicket password-only showed the OTP challenge and did not grant a dashboard. POST /user/login with user/pass only returned Type.SUCCESS and SID b58de0be-.... That SID saved a calendar row whose description is the witness. Default OTP is off; the lab turns it on.

CWE-304. 8.1 High. Pack: abraxas/openmeetings-soap-otp-skip. Last line: SUCCESS OPENMEETINGS-SOAP-OTP-SKIP-WITNESS.

Remember-me still is the password

CVE-2026-33266 closed the hardcoded key. Cookie LoggedIn still holds username+password. Opt-in checkbox credentials:rememberMe.

AJAX login with remember-me set minted Set-Cookie: LoggedIn (HttpOnly, SameSite=Lax). A new client with only that cookie hit the authenticated main page. Control without the cookie stayed on sign-in. Manual AES-GCM decrypt hit InvalidTag in this run; replay still worked.

CWE-522. 8.1 High. Pack: abraxas/openmeetings-remember-me-password. Last line: SUCCESS OPENMEETINGS-REMEMBER-ME-WITNESS.

Clustering TCP 5636

CVE-2024-54676 docs-whitelisted OpenJPA lists. Default startup.sh does not set them. Clustering.xml still documents RemoteCommitProvider=tcp.

Stock: port 5636 closed. Docs-on: 5636 listened. A serialized HashMap containing the witness produced ClassCastException HashMap vs RemoteCommitEvent. That is readObject on the socket. No gadget chain.

CWE-502. 8.1 High because an admin must uncomment clustering. Pack: abraxas/openmeetings-cluster-openjpa. Last line: SUCCESS OPENMEETINGS-CLUSTER-OPENJPA-WITNESS.

Medium/Low stay off this post. Chat stored XSS is still behind CSP 'strict-dynamic'. File room-metadata leftover of CVE-2026-33005 is metadata only.

Contact

Abraxas Labs. abraxaslabs.tech. github.com/abraxas. @abraxas_null. abraxas.null@proton.me.

Please request the CVE for these reports and credit Abraxas Labs.