Fix stored XSS in finance notification charge summary report - #1779
Fix stored XSS in finance notification charge summary report#1779labkey-martyp wants to merge 5 commits into
Conversation
FinanceNotification.createChargeSummaryReport is a divergent copy of the ehr_billing BillingNotification report and interpolated editor-entered database values (investigator, project, debitedAccount, projectNumber, category) and their derived URLs directly into HTML without escaping. That 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 in both the per-financial-analyst tables and the top category summary table (which was also unescaped in this copy), matching the fix applied to BillingNotification.
DCMFinanceNotification overrides FinanceNotification.writeResultTable with its own copy of the report, so the escaping fix to the parent class did not cover it. It concatenated editor-entered database values (project, alias/account, OGA project number, category) and their derived URLs directly into HTML without escaping, reaching the same LDK RunNotificationAction admin preview (HtmlString.unsafe) and HTML email sink. It is registered as an active notification in ONPRC_BillingModule. Wrap every tainted value with PageFlowUtil.filter in both the per-project tables and the top category summary table, mapping to this override's token layout (tokens[0] project, tokens[1] alias, tokens[2] OGA project number).
|
@labkey-martyp here's the comment from Copilot: At a quick review, the change looks directionally correct for fixing stored XSS because it is HTML-encoding user-controlled values before inserting them into generated HTML. Specifically, A few things I'd look at before approving: URL encoding vs. HTML encoding
"&query.project/displayName~eq=" + tokens[1]
Partial XSS coverage
Numeric values are not filtered
Deletion of
Overall I don't see an obvious regression in the displayed diff. The biggest thing I'd comment on is whether query parameter values should be URL-encoded rather than relying solely on |
PageFlowUtil.filter escapes the HTML metacharacters but leaves %, #, + and space untouched, so HTML-encoding the assembled URL protected the href attribute without making the appended query parameter values well-formed. A project name or alias containing % produced an invalid percent-escape; one containing & split into an extra query parameter, because the browser decodes the escaped ampersand back to a literal & when parsing the attribute; and # truncated the query string at a fragment. Each appended value is now wrapped in PageFlowUtil.encodeURIComponent so it is percent-encoded before PageFlowUtil.filter encodes the resulting URL for the attribute context. The two layers address different contexts and both are needed. Also encodes the StartDate and EndDate parameter values, which are formatted with the admin-configured LookAndFeel date format and can therefore contain spaces. This is a link-correctness fix rather than a residual XSS: filter already prevented breaking out of the quoted href, and the scheme and host come from urlExecuteQuery. Raised in code review.
|
@brentlogan I added the additional URL encoding to the method that is the focus of this PR. |
The testNotifications set still named "DCM Finance Notification", whose class and NotificationService registration were removed earlier on this branch. The test iterates the notification admin page and only acts on rows whose label is in the set, so the stale name produced no failure — it simply never matched, silently dropping that assertion instead of reporting the lost coverage.
Rationale
FinanceNotification.writeResultTable builds the ONPRC charge summary report by concatenating editor-entered database values (financial analyst, project, alias/account, OGA project number, category) and their derived URLs directly into HTML with no escaping. That 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 — a stored XSS with privilege-escalation potential toward admins. This is the ONPRC counterpart to the BillingNotification fix. DCMFinanceNotification was a registered subclass that overrode writeResultTable with a near-duplicate copy of the same unescaped report; rather than carry and fix a second copy, it is removed entirely.
Related Pull Requests
Changes