VYPR
High severityNVD Advisory· Published Dec 13, 2023· Updated Aug 2, 2024

CVE-2023-47323

CVE-2023-47323

Description

Silverpeas Core 6.3.1 notification/messaging lacks access control on the ID parameter, allowing any attacker to read all private messages including administrator-only ones.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Silverpeas Core 6.3.1 notification/messaging lacks access control on the ID parameter, allowing any attacker to read all private messages including administrator-only ones.

Vulnerability

Description

The notification/messaging feature in Silverpeas Core 6.3.1 fails to enforce access control on the ID parameter when retrieving messages [2]. This means that the application does not verify that the user requesting a message is the intended recipient or sender. The root cause is a missing authorization check in the SILVERMAILPersistence.getMessage() method, which originally accepted only the message ID without ensuring that the requesting user owns that message [3]. The official description and the fix commit both confirm that the method lacked a userId parameter and any ownership validation [2][3].

Attack

Vector and Prerequisites

An attacker can exploit this vulnerability by sending a crafted request to the notification/messaging endpoint with a different message ID [2]. No special privileges or authentication bypass is required—any authenticated user can enumerate message IDs and read the content of messages belonging to other users, including messages sent only to administrators [2]. The attack is simple to execute because the messaging feature exposes a direct object reference (ID) that is not properly scoped to the current user.

Impact

Successful exploitation allows an attacker to read all messages exchanged between other users within the Silverpeas platform [2]. This includes sensitive communications, internal announcements, and administrator-only messages. The vulnerability leads to unauthorized disclosure of private information, compromising the confidentiality of the entire messaging system.

Mitigation

The vendor addressed the vulnerability in a commit that adds a userId parameter to the getMessage method and throws a ForbiddenRuntimeException if the message is not owned by the requesting user [3]. Silverpeas Core 6.3.1 is affected; users should upgrade to a version that includes this fix or apply the patch manually. No CISA KEV listing or public exploit code has been reported at the time of publication.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.silverpeas.core:silverpeas-core-apiMaven
< 6.3.26.3.2
org.silverpeas.core:silverpeas-core-webMaven
< 6.3.26.3.2

Affected products

3

Patches

1
6383746372d4

Bug #13813: fixing broken access control allows an attacker to read all messages

https://github.com/Silverpeas/Silverpeas-CoreSilverYoChaNov 6, 2023via ghsa
11 files changed · +120 98
  • core-api/src/main/java/org/silverpeas/core/NotFoundException.java+1 0 modified
    @@ -29,6 +29,7 @@
      * @author mmoquillon
      */
     public class NotFoundException extends SilverpeasRuntimeException {
    +  private static final long serialVersionUID = 4337528679207374468L;
     
       public NotFoundException(final String message) {
         super(message);
    
  • core-library/src/main/java/org/silverpeas/core/notification/user/client/model/SentNotificationDAO.java+4 6 modified
    @@ -141,21 +141,19 @@ public static List<String> getReceivers(Connection con, int notifId)
     
       public static SentNotificationDetail getNotif(Connection con, int notifId)
           throws SQLException {
    -
    -    SentNotificationDetail notif = new SentNotificationDetail();
    -
    +    SentNotificationDetail notif = null;
         String query = "select " + COLUMNS + " from ST_NotifSended where notifId = ?";
         PreparedStatement prepStmt = null;
         ResultSet rs = null;
         try {
           prepStmt = con.prepareStatement(query);
           prepStmt.setInt(1, notifId);
           rs = prepStmt.executeQuery();
    -      while (rs.next()) {
    +      if (rs.next()) {
             notif = convertFrom(rs);
    +        // récupérer les destinataires
    +        notif.setUsers(getReceivers(con, notifId));
           }
    -      // récupérer les destinataires
    -      notif.setUsers(getReceivers(con, notifId));
         } finally {
           DBUtil.close(rs, prepStmt);
         }
    
  • core-library/src/main/java/org/silverpeas/core/notification/user/client/model/SentNotificationInterfaceImpl.java+19 12 modified
    @@ -23,6 +23,7 @@
      */
     package org.silverpeas.core.notification.user.client.model;
     
    +import org.silverpeas.core.NotFoundException;
     import org.silverpeas.core.notification.NotificationException;
     import org.silverpeas.core.notification.user.client.NotificationMetaData;
     import org.silverpeas.core.notification.user.client.UserRecipient;
    @@ -80,27 +81,33 @@ public List<SentNotificationDetail> getAllNotifByUser(String userId)
       }
     
       @Override
    -  public SentNotificationDetail getNotification(int notifId) throws NotificationException {
    +  public SentNotificationDetail getNotification(final String userId, int notifId)
    +      throws NotificationException {
    +    final SentNotificationDetail notif;
         try (Connection con = openConnection()) {
    -      return SentNotificationDAO.getNotif(con, notifId);
    +      notif = SentNotificationDAO.getNotif(con, notifId);
         } catch (Exception e) {
           throw new NotificationException(e);
         }
    +    if (notif == null) {
    +      throw new NotFoundException(
    +          String.format("Sent notification %s not found for user %s", notifId, userId));
    +    } else if (Integer.parseInt(userId) != notif.getUserId()) {
    +      throw new ForbiddenRuntimeException(
    +          String.format("Forbidden access to the sent notification %s for user %s", notifId,
    +              userId));
    +    }
    +    return notif;
       }
     
       @Transactional
       @Override
    -  public void deleteNotif(int notifId, String userId) throws NotificationException {
    +  public void deleteNotif(String userId, int notifId) throws NotificationException {
         try (Connection con = openConnection()) {
    -      SentNotificationDetail toDel = getNotification(notifId);
    -
    -      //check rights : check that the current user has the rights to delete the notification
    -      if(Integer.parseInt(userId) == toDel.getUserId()) {
    -        SentNotificationDAO.deleteNotif(con, notifId);
    -      } else {
    -        throw new ForbiddenRuntimeException(
    -            "Unauthorized to delete the notification " + notifId + " for user " + userId);
    -      }
    +      SentNotificationDetail toDel = getNotification(userId, notifId);
    +      SentNotificationDAO.deleteNotif(con, toDel.getNotifId());
    +    } catch (NotFoundException | ForbiddenRuntimeException e) {
    +      throw e;
         } catch (Exception e) {
           throw new NotificationException(e);
         }
    
  • core-library/src/main/java/org/silverpeas/core/notification/user/client/model/SentNotificationInterface.java+3 2 modified
    @@ -46,9 +46,10 @@ void saveNotifUser(NotificationMetaData metaData, Set<UserRecipient> usersSet)
       List<SentNotificationDetail> getAllNotifByUser(String userId)
           throws NotificationException;
     
    -  SentNotificationDetail getNotification(int notifId) throws NotificationException;
    +  SentNotificationDetail getNotification(final String userId, int notifId)
    +      throws NotificationException;
     
    -  void deleteNotif(int notifId, String userId) throws NotificationException;
    +  void deleteNotif(String userId, int notifId) throws NotificationException;
     
       void deleteNotifByUser(String userId) throws NotificationException;
     }
    \ No newline at end of file
    
  • core-library/src/main/java/org/silverpeas/core/notification/user/server/channel/silvermail/SILVERMAILPersistence.java+56 44 modified
    @@ -23,6 +23,7 @@
      */
     package org.silverpeas.core.notification.user.server.channel.silvermail;
     
    +import org.silverpeas.core.NotFoundException;
     import org.silverpeas.core.admin.PaginationPage;
     import org.silverpeas.core.admin.user.model.User;
     import org.silverpeas.core.backgroundprocess.AbstractBackgroundProcessRequest;
    @@ -62,19 +63,14 @@ public class SILVERMAILPersistence {
       private SILVERMAILPersistence() {
       }
     
    -  private static void markMessageAsRead(SILVERMAILMessageBean smb)
    -      throws SILVERMAILException {
    -    try {
    -      boolean hasToUpdate = smb.getReaden() != 1;
    -      if (hasToUpdate) {
    -        smb.setReaden(1);
    -        Transaction.performInOne(() -> getRepository().save(smb));
    -        DefaultServerEventNotifier.get().notify(UserNotificationServerEvent
    -            .readOf(String.valueOf(smb.getUserId()), smb.getId(), smb.getSubject(), smb
    -                .getSenderName()));
    -      }
    -    } catch (Exception e) {
    -      throw new SILVERMAILException("Cannot mark the message " + smb.getId() + " as read", e);
    +  private static void markMessageAsRead(SILVERMAILMessageBean smb) {
    +    boolean hasToUpdate = smb.getReaden() != 1;
    +    if (hasToUpdate) {
    +      smb.setReaden(1);
    +      Transaction.performInOne(() -> getRepository().save(smb));
    +      DefaultServerEventNotifier.get().notify(UserNotificationServerEvent
    +          .readOf(String.valueOf(smb.getUserId()), smb.getId(), smb.getSubject(), smb
    +              .getSenderName()));
         }
       }
     
    @@ -166,50 +162,66 @@ public static SilverpeasList<SILVERMAILMessage> getMessageOfFolder(String userId
     
       /**
        * Gets a message by its identifier.
    +   * @param userId the identifier of the user owning the message.
        * @param msgId the message identifier.
    +   * @throws NotFoundException if message with given identifier does not exist.
    +   * @throws ForbiddenRuntimeException if message with given identifier is not owned by the user
    +   * with the given identifier.
        */
    -  public static SILVERMAILMessage getMessage(long msgId) {
    -    return findByCriteria(SilvermailCriteria.get().byId(msgId)).stream().findFirst().orElse(null);
    +  public static SILVERMAILMessage getMessage(final String userId, long msgId) {
    +    final SILVERMAILMessage message = findByCriteria(SilvermailCriteria.get().byId(msgId)).stream()
    +        .findFirst()
    +        .orElseThrow(() -> new NotFoundException(
    +            String.format("Message %s not found for user %s", msgId, userId)));
    +    if (message.getUserId() != Long.parseLong(userId)) {
    +      throw new ForbiddenRuntimeException(
    +          String.format("Forbidden access to message %s for user %s", msgId, userId));
    +    }
    +    return message;
       }
     
       /**
        * Gets a message by its identifier and mark it as read.
    +   * @param userId the identifier of the user owning the message.
        * @param msgId the message identifier.
    +   * @throws NotFoundException if message with given identifier does not exist.
    +   * @throws ForbiddenRuntimeException if message with given identifier is not owned by the user
    +   * with the given identifier.
        */
    -  public static SILVERMAILMessage getMessageAndMarkAsRead(long msgId) throws SILVERMAILException {
    -    final SILVERMAILMessage silverMailMessage = getMessage(msgId);
    -    if (silverMailMessage != null) {
    -      final SILVERMAILMessageBean smb = getRepository().getById(String.valueOf(msgId));
    -      markMessageAsRead(smb);
    -    }
    +  public static SILVERMAILMessage getMessageAndMarkAsRead(final String userId, long msgId) {
    +    final SILVERMAILMessage silverMailMessage = getMessage(userId, msgId);
    +    final SILVERMAILMessageBean smb = getRepository().getById(String.valueOf(msgId));
    +    markMessageAsRead(smb);
         return silverMailMessage;
       }
     
       /**
    -   *
    +   * Deletes a message by its identifier.
    +   * @param userId the identifier of the user owning the message.
    +   * @param msgId the identifier of the message to delete.
    +   * @throws NotFoundException if message with given identifier does not exist.
    +   * @throws ForbiddenRuntimeException if message with given identifier is not owned by the user
    +   * with the given identifier.
        */
    -  public static void deleteMessage(long msgId, String userId) throws SILVERMAILException {
    -    try {
    -      Transaction.performInOne(() -> {
    -        SILVERMAILMessageBeanRepository repository = getRepository();
    -        SILVERMAILMessageBean toDel = repository.getById(String.valueOf(msgId));
    -
    -        //check rights : check that the current user has the rights to delete the message
    -        // notification
    -        if (Long.parseLong(userId) == toDel.getUserId()) {
    -          BackgroundProcessTask.push(new LongTextDeletionRequest(toDel.getBody()));
    -          repository.delete(toDel);
    -        } else {
    -          throw new ForbiddenRuntimeException(
    -              "Unauthorized deletion of message " + msgId + " for user " + userId);
    -        }
    -        return null;
    -      });
    -      DefaultServerEventNotifier.get()
    -          .notify(UserNotificationServerEvent.deletionOf(userId, String.valueOf(msgId)));
    -    } catch (Exception e) {
    -      throw new SILVERMAILException("Cannot delete the message " + msgId, e);
    -    }
    +  public static void deleteMessage(String userId, long msgId) {
    +    Transaction.performInOne(() -> {
    +      SILVERMAILMessageBeanRepository repository = getRepository();
    +      SILVERMAILMessageBean toDel = repository.getById(String.valueOf(msgId));
    +      if (toDel == null) {
    +        throw new NotFoundException(
    +            String.format("Error when deleting message %s (not found) for user %s", msgId, userId));
    +      }
    +      if (Long.parseLong(userId) == toDel.getUserId()) {
    +        BackgroundProcessTask.push(new LongTextDeletionRequest(toDel.getBody()));
    +        repository.delete(toDel);
    +      } else {
    +        throw new ForbiddenRuntimeException(
    +            "Unauthorized deletion of message " + msgId + " for user " + userId);
    +      }
    +      return null;
    +    });
    +    DefaultServerEventNotifier.get()
    +        .notify(UserNotificationServerEvent.deletionOf(userId, String.valueOf(msgId)));
       }
     
       public static void deleteAllMessagesInFolder(String currentUserId) {
    
  • core-rs/src/main/java/org/silverpeas/core/web/rs/RESTWebService.java+6 0 modified
    @@ -24,12 +24,14 @@
      */
     package org.silverpeas.core.web.rs;
     
    +import org.silverpeas.core.NotFoundException;
     import org.silverpeas.core.admin.PaginationPage;
     import org.silverpeas.core.admin.service.OrganizationController;
     import org.silverpeas.core.admin.user.model.SilverpeasRole;
     import org.silverpeas.core.admin.user.model.User;
     import org.silverpeas.core.personalization.UserPreferences;
     import org.silverpeas.core.personalization.service.PersonalizationServiceProvider;
    +import org.silverpeas.core.security.authorization.ForbiddenRuntimeException;
     import org.silverpeas.core.util.LocalizationBundle;
     import org.silverpeas.core.util.ResourceLocator;
     import org.silverpeas.core.util.StringUtil;
    @@ -329,6 +331,10 @@ public R execute() {
               throw new WebApplicationException(Response.Status.FORBIDDEN);
             }
             return webTreatment.execute();
    +      } catch (final NotFoundException ex) {
    +        throw new WebApplicationException(ex, Response.Status.NOT_FOUND);
    +      } catch (final ForbiddenRuntimeException ex) {
    +        throw new WebApplicationException(ex, Response.Status.FORBIDDEN);
           } catch (final WebApplicationException ex) {
             throw ex;
           } catch (final Exception ex) {
    
  • core-war/src/main/java/org/silverpeas/web/notificationserver/channel/silvermail/requesthandlers/ReadMessage.java+3 2 modified
    @@ -42,11 +42,12 @@ public class ReadMessage implements SILVERMAILRequestHandler {
       @Override
       public String handleRequest(ComponentSessionController componentSC,
           HttpServletRequest request) {
    +    SILVERMAILSessionController silvermailScc = (SILVERMAILSessionController) componentSC;
         try {
           String sId = request.getParameter("ID");
           long id = Long.parseLong(sId);
    -
    -      ((SILVERMAILSessionController) componentSC).setCurrentMessageId(id);
    +      silvermailScc.setCurrentMessageId(id);
    +      request.setAttribute("currentMessage", silvermailScc.getCurrentMessage());
         } catch (NumberFormatException e) {
           SilverLogger.getLogger(this).error(e.getMessage(), e);
         }
    
  • core-war/src/main/java/org/silverpeas/web/notificationserver/channel/silvermail/SILVERMAILSessionController.java+11 19 modified
    @@ -34,7 +34,6 @@
     import org.silverpeas.core.notification.NotificationException;
     import org.silverpeas.core.notification.user.client.model.SentNotificationDetail;
     import org.silverpeas.core.notification.user.client.model.SentNotificationInterface;
    -import org.silverpeas.core.notification.user.server.channel.silvermail.SILVERMAILException;
     import org.silverpeas.core.notification.user.server.channel.silvermail.SILVERMAILMessage;
     import org.silverpeas.core.notification.user.server.channel.silvermail.SILVERMAILPersistence;
     import org.silverpeas.core.notification.user.server.channel.silvermail.SilvermailCriteria.QUERY_ORDER_BY;
    @@ -149,9 +148,9 @@ public List<SentUserNotificationItem> getUserMessageList() throws NotificationEx
       }
     
       public SentNotificationDetail getSentNotification(String notifId) throws NotificationException {
    -    SentNotificationDetail sentNotification = null;
    -      sentNotification = getNotificationInterface().getNotification(Integer.parseInt(notifId));
    -      sentNotification.setSource(getSource(sentNotification.getComponentId()));
    +    SentNotificationDetail sentNotification = getNotificationInterface().getNotification(
    +        getUserId(), Integer.parseInt(notifId));
    +    sentNotification.setSource(getSource(sentNotification.getComponentId()));
         return sentNotification;
       }
     
    @@ -193,20 +192,19 @@ private String getSource(String componentId) {
        * @throws NotificationException
        */
       public void deleteSentNotif(String notifId) throws NotificationException {
    -      getNotificationInterface().deleteNotif(Integer.parseInt(notifId), getUserId());
    +      getNotificationInterface().deleteNotif(getUserId(), Integer.parseInt(notifId));
       }
     
       public void deleteAllSentNotif() throws NotificationException {
           getNotificationInterface().deleteNotifByUser(getUserId());
       }
     
    -  private SentNotificationInterface getNotificationInterface() throws NotificationException {
    +  private SentNotificationInterface getNotificationInterface() {
         return SentNotificationInterface.get();
       }
     
    -  public SILVERMAILMessage getMessage(long messageId)
    -      throws SILVERMAILException {
    -    return SILVERMAILPersistence.getMessageAndMarkAsRead(messageId);
    +  public SILVERMAILMessage getMessage(long messageId) {
    +    return SILVERMAILPersistence.getMessageAndMarkAsRead(getUserId(), messageId);
       }
     
       /**
    @@ -229,23 +227,17 @@ public void setCurrentMessageId(long value) {
         currentMessageId = value;
       }
     
    -  public SILVERMAILMessage getCurrentMessage() throws SILVERMAILException {
    +  public SILVERMAILMessage getCurrentMessage() {
         return getMessage(currentMessageId);
       }
     
       /**
        * Delete the message notification
    -   *
        * @param notifId
    -   * @throws SILVERMAILException
        */
    -  public void deleteMessage(String notifId) throws SILVERMAILException {
    -    try {
    -      long notificationId = Long.parseLong(notifId);
    -      SILVERMAILPersistence.deleteMessage(notificationId, getUserId());
    -    } catch (SILVERMAILException e) {
    -      throw new SILVERMAILException(e);
    -    }
    +  public void deleteMessage(String notifId) {
    +    long notificationId = Long.parseLong(notifId);
    +    SILVERMAILPersistence.deleteMessage(getUserId(), notificationId);
       }
     
       static {
    
  • core-war/src/main/webapp/admin/jsp/errorpageMain.jsp+5 0 modified
    @@ -50,6 +50,7 @@ if (response.isCommitted() == false) {
     <%@ page import="javax.ws.rs.WebApplicationException" %>
     <%@ page import="org.silverpeas.core.util.logging.SilverLogger" %>
     <%@ page import="org.silverpeas.core.web.mvc.webcomponent.SilverpeasHttpServlet" %>
    +<%@ page import="org.silverpeas.core.NotFoundException" %>
     
     <%@ include file="import.jsp" %>
     
    @@ -68,6 +69,10 @@ if (exception instanceof SilverpeasTrappedException) {
       extraInfos = StringUtil.defaultStringIfNotDefined(ste.getExtraInfos(), null);
       // Trace the exception
       HomePageUtil.traceException(exception);
    +} else if (exception instanceof NotFoundException) {
    +  SilverLogger.getLogger(SilverpeasHttpServlet.class).error(exception.getMessage());
    +  response.sendError(HttpServletResponse.SC_NOT_FOUND, exception.getMessage());
    +  return;
     } else if (exception instanceof ForbiddenRuntimeException) {
       SilverLogger.getLogger(SilverpeasHttpServlet.class).error(exception.getMessage());
       response.sendError(HttpServletResponse.SC_FORBIDDEN, exception.getMessage());
    
  • core-war/src/main/webapp/SILVERMAIL/jsp/readMessage.jsp+1 1 modified
    @@ -34,7 +34,7 @@
     <c:set var="componentId" value="${requestScope.componentId}"/>
     <c:set var="sessionController" value="${requestScope.SILVERMAIL}"/>
     <c:set var="from" value="${param.from}"/>
    -<c:set var="msg" value="${sessionController.currentMessage}"/>
    +<c:set var="msg" value="${requestScope.currentMessage}"/>
     <fmt:setLocale value="${sessionScope[sessionController].language}"/>
     <view:setBundle bundle="${requestScope.resources.multilangBundle}"/>
     <view:setBundle bundle="${requestScope.resources.iconsBundle}" var="icons"/>
    
  • core-web/src/main/java/org/silverpeas/core/webapi/notification/user/InboxUserNotificationResource.java+11 12 modified
    @@ -25,7 +25,6 @@
     
     import org.silverpeas.core.admin.PaginationPage;
     import org.silverpeas.core.annotation.WebService;
    -import org.silverpeas.core.notification.user.server.channel.silvermail.SILVERMAILException;
     import org.silverpeas.core.notification.user.server.channel.silvermail.SILVERMAILMessage;
     import org.silverpeas.core.util.SilverpeasList;
     import org.silverpeas.core.web.rs.RESTWebService;
    @@ -86,7 +85,9 @@ protected String getResourceBasePath() {
       @Path("{id}")
       @Produces(MediaType.APPLICATION_JSON)
       public InboxUserNotificationEntity get(@PathParam("id") long id) {
    -    return asWebEntity(getMessage(id));
    +    return process(() -> asWebEntity(getMessage(getUser().getId(), id)))
    +        .lowestAccessRole(null)
    +        .execute();
       }
     
       /**
    @@ -96,11 +97,11 @@ public InboxUserNotificationEntity get(@PathParam("id") long id) {
       @Path("{id}")
       @Produces(MediaType.APPLICATION_JSON)
       public Response delete(@PathParam("id") long id) {
    -    try {
    -      deleteMessage(id, getUser().getId());
    -    } catch (SILVERMAILException e) {
    -      throw new WebApplicationException(e);
    -    }
    +    process(() -> {
    +      deleteMessage(getUser().getId(), id);
    +      return null;
    +    }).lowestAccessRole(null)
    +      .execute();
         return Response.ok().build();
       }
     
    @@ -115,11 +116,9 @@ public Response delete(@PathParam("id") long id) {
       @Path("{id}/read")
       @Produces(MediaType.APPLICATION_JSON)
       public InboxUserNotificationEntity markAsRead(@PathParam("id") long id) {
    -    try {
    -      return asWebEntity(getMessageAndMarkAsRead(id));
    -    } catch (SILVERMAILException e) {
    -      throw new WebApplicationException(e);
    -    }
    +    return process(() -> asWebEntity(getMessageAndMarkAsRead(getUser().getId(), id)))
    +        .lowestAccessRole(null)
    +        .execute();
       }
     
       /**
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.