From ae3b6980d9526500d390e1813d9fc22b0a1ed8d1 Mon Sep 17 00:00:00 2001 From: Marty Pradere Date: Mon, 22 Jun 2026 15:27:15 -0600 Subject: [PATCH] Fix stored XSS in billing notification charge summary report The per-financial-analyst tables in BillingNotification.createChargeSummaryReport interpolated editor-entered database values (investigator, project, debitedAccount, projectNumber, category) and their derived URLs directly into HTML without escaping. This HTML is rendered verbatim into the LDK RunNotificationAction admin preview via HtmlString.unsafe and is also sent as the HTML email body, so a stored payload in any of those project/alias fields executed in the browser of any user who previewed the notification or received the email. Wrap every tainted value with PageFlowUtil.filter, applying the same encoding the adjacent Charge Summary category table already used. --- .../notification/BillingNotification.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ehr_billing/src/org/labkey/ehr_billing/notification/BillingNotification.java b/ehr_billing/src/org/labkey/ehr_billing/notification/BillingNotification.java index fdde62318..feb5edd73 100644 --- a/ehr_billing/src/org/labkey/ehr_billing/notification/BillingNotification.java +++ b/ehr_billing/src/org/labkey/ehr_billing/notification/BillingNotification.java @@ -464,8 +464,8 @@ protected void createChargeSummaryReport(final StringBuilder msg, Date lastInvoi String baseUrl = createURL(containerMap.get(category), centerSpecificBillingSchema, categoryToQuery.get(category), null) + "&query.param.StartDate=" + getDateFormat(c).format(start.getTime()) + "&query.param.EndDate=" + getDateFormat(c).format(endDate.getTime()); String projUrl = baseUrl + ("None".equals(tokens[1]) ? "&query.project~isblank" : "&query.project~eq=" + tokens[1]); - msg.append("" + financialAnalyst + ""); //the FA - msg.append("" + tokens[1] + ""); + msg.append("" + PageFlowUtil.filter(financialAnalyst) + ""); //the FA + msg.append("" + PageFlowUtil.filter(tokens[1]) + ""); String accountUrl = null; Container financeContainer = EHR_BillingManager.get().getBillingContainer(containerMap.get(category)); @@ -476,22 +476,22 @@ protected void createChargeSummaryReport(final StringBuilder msg, Date lastInvoi if (accountUrl != null) { - msg.append("" + tokens[2] + ""); + msg.append("" + PageFlowUtil.filter(tokens[2]) + ""); } else { - msg.append("" + (tokens[2]) + ""); + msg.append("" + PageFlowUtil.filter(tokens[2]) + ""); } - msg.append("" + (tokens[3]) + ""); - msg.append("" + category + ""); + msg.append("" + PageFlowUtil.filter(tokens[3]) + ""); + msg.append("" + PageFlowUtil.filter(category) + ""); for (FieldDescriptor fd : foundCols) { if (totals.containsKey(fd.getFieldName())) { String url = projUrl + fd.getFilter(); - msg.append("" + totals.get(fd.getFieldName()) + ""); + msg.append("" + totals.get(fd.getFieldName()) + ""); } else {