vciy
vciy-2026-0003 · openimsdk/open-im-server · CWE-862

Incomplete fix for CVE-2026-69115: missing authverify.CheckAccess and CheckAdmin in open-im-server RPC handlers

Commit 193870b2f938278b27a2d8347bd7e4db5f8f9dfc (PR #3781, fixing CVE-2026-69115) added authverify.CheckAdmin(ctx) to GetPaginationUsers, GetAllUserID, and GetGroups. Verification of sibling RPC handlers in internal/rpc/third/ and internal/rpc/user/ identified four handlers that still omit authverify.CheckAccess or authverify.CheckAdmin: FcmUpdateToken, SetGlobalRecvMessageOpt, UserRegisterCount, and SortQuery.

Receipt & provenance · vciy-2026-0003
Advisory ID
vciy-2026-0003canonical vciy advisory identifier
Upstream project
openimsdk/open-im-serverGo
Affected versions
open-im-server <= v3.8.3-patch.18verified on upstream default branch
Weakness class
CWE-862Missing Authorization
Related CVE records
CVE-2026-69115upstream fix commit inspected
Upstream tracking
openimsdk/open-im-server#3825reported 2026-10-22 · Published (upstream issue open)
Credit
Jonathan Corners (@JCorners68), Voxell, Inc. (https://vciy.com)discovered and reported
01

Root cause and sibling analysis.

1. POST /third/fcm_update_token -> thirdServer.FcmUpdateToken (internal/rpc/third/third.go:124-130): Unlike its immediate sibling SetAppBadge (lines 132-135, which calls authverify.CheckAccess(ctx, req.FromUserID)), FcmUpdateToken does not call authverify.CheckAccess(ctx, req.Account) before writing req.FcmToken into Redis under req.Account. Because internal/push/offlinepush/fcm/push.go reads GetFcmToken(ctx, account, platformID) when delivering offline push notifications, any authenticated user can bind their own FCM token to an arbitrary userID and intercept offline push notifications.

2. POST /user/set_global_msg_recv_opt -> userServer.SetGlobalRecvMessageOpt (internal/rpc/user/user.go:229-241): Unlike UpdateUserInfo (line 161) and UpdateUserInfoEx (line 188), SetGlobalRecvMessageOpt does not call authverify.CheckAccess(ctx, req.UserID), allowing any authenticated user to modify global_recv_msg_opt for any target userID.

3. POST /statistics/user/register -> userServer.UserRegisterCount (internal/rpc/user/statistics.go:25-43): Unlike all three sibling routes in statisticsGroup (GetActiveUser, GroupCreateCount, and GetActiveGroup), UserRegisterCount omits authverify.CheckAdmin(ctx).

4. userServer.SortQuery (internal/rpc/user/user.go:721-727): The gRPC handler SortQuery queries sorted user records across the database without checking authverify.CheckAdmin(ctx).

02

Remediation.

Enforce authverify.CheckAccess(ctx, req.Account) in FcmUpdateToken, authverify.CheckAccess(ctx, req.UserID) in SetGlobalRecvMessageOpt, and authverify.CheckAdmin(ctx) in UserRegisterCount and SortQuery.

Suggested defensive diff
--- a/internal/rpc/third/third.go
+++ b/internal/rpc/third/third.go
@@ -122,6 +122,9 @@ func (t *thirdServer) AccessURL(ctx context.Context, req *third.AccessURLReq) (*
 }
 
 func (t *thirdServer) FcmUpdateToken(ctx context.Context, req *third.FcmUpdateTokenReq) (*third.FcmUpdateTokenResp, error) {
+	if err := authverify.CheckAccess(ctx, req.Account); err != nil {
+		return nil, err
+	}
 	err := t.thirdDatabase.SetFcmToken(ctx, req.Account, int(req.PlatformID), req.FcmToken, req.ExpireTime)
--- a/internal/rpc/user/user.go
+++ b/internal/rpc/user/user.go
@@ -227,6 +227,9 @@ func (s *userServer) GetDesignateUsers(ctx context.Context, req *pbuser.GetDesig
 }
 
 func (s *userServer) SetGlobalRecvMessageOpt(ctx context.Context, req *pbuser.SetGlobalRecvMessageOptReq) (*pbuser.SetGlobalRecvMessageOptResp, error) {
+	if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
+		return nil, err
+	}
 	if _, err := s.db.GetUserByID(ctx, req.UserID); err != nil {
@@ -719,6 +722,9 @@ func (s *userServer) GetNotificationAccount(ctx context.Context, req *pbuser.Get
 }
 
 func (s *userServer) SortQuery(ctx context.Context, req *pbuser.SortQueryReq) (*pbuser.SortQueryResp, error) {
+	if err := authverify.CheckAdmin(ctx); err != nil {
+		return nil, err
+	}
 	users, err := s.db.SortQuery(ctx, req.UserIdName, req.Asc)
--- a/internal/rpc/user/statistics.go
+++ b/internal/rpc/user/statistics.go
@@ -23,6 +23,9 @@ import (
 )
 
 func (s *userServer) UserRegisterCount(ctx context.Context, req *pbuser.UserRegisterCountReq) (*pbuser.UserRegisterCountResp, error) {
+	if err := authverify.CheckAdmin(ctx); err != nil {
+		return nil, err
+	}
 	if req.Start <= 0 || req.End <= 0 {