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.
| Package | Affected versions | Patched versions |
|---|---|---|
org.silverpeas.core:silverpeas-core-apiMaven | < 6.3.2 | 6.3.2 |
org.silverpeas.core:silverpeas-core-webMaven | < 6.3.2 | 6.3.2 |
Affected products
3- Silverpeas/Coredescription
- ghsa-coords2 versions
< 6.3.2+ 1 more
- (no CPE)range: < 6.3.2
- (no CPE)range: < 6.3.2
Patches
16383746372d4Bug #13813: fixing broken access control allows an attacker to read all messages
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
5News mentions
0No linked articles in our index yet.