Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ private void setMapping(PageSource ps) {
}

private void setDetail(PageSource ps) {
setAdditional(KeyConstants._Detail, "File not found: " + ps.getDisplayPath());
// CVE-2026-29519: the requested path is reflected into HTML error output, so escape it here at the
// single point where untrusted request-path data enters the exception detail.
setAdditional(KeyConstants._Detail, "File not found: " + StringUtil.escapeHTML(StringUtil.emptyIfNull(ps.getDisplayPath())));
}

/**
Expand All @@ -74,9 +76,11 @@ public PageSource getPageSource() {
}

private static String createMessage(PageSource pageSource) {
// CVE-2026-29519: escape the requested path before it becomes the (HTML-rendered) exception message.
String realpath = StringUtil.escapeHTML(StringUtil.emptyIfNull(pageSource.getRealpathWithVirtual()));
String dsp = pageSource.getDisplayPath();
if (dsp == null) return "Page [" + pageSource.getRealpathWithVirtual() + "] not found";
return "Page [" + pageSource.getRealpathWithVirtual() + "] [" + dsp + "] not found";
if (dsp == null) return "Page [" + realpath + "] not found";
return "Page [" + realpath + "] [" + StringUtil.escapeHTML(dsp) + "] not found";
}

@Override
Expand Down
64 changes: 64 additions & 0 deletions test/tickets/LDEV3027.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
component extends="org.lucee.cfml.test.LuceeTestCase" labels="security" {

/*
* CVE-2026-29519 / LDEV-3027 — reflected XSS via HTML in the request path.
*
* When a requested template cannot be found, Lucee raises a
* MissingIncludeException whose message and detail embed the requested
* path (PageSource.getRealpathWithVirtual() / getDisplayPath()). That
* text is reflected into the (HTML) detailed error page unescaped, so an
* attacker-supplied path segment such as
* /foo/<img src=x onerror=alert(1)>/index.cfm/
* executes as live markup in the victim's browser.
*
* Fix: escape the requested path where it enters the exception message
* and detail (MissingIncludeException), mirroring the existing escaping
* of the REST 404 path in PageContextImpl. These tests assert the raw
* markup never survives into message/detail and that the escaped form is
* present instead.
*/

function run( testResults, testBox ) {

describe( "CVE-2026-29519: MissingInclude must HTML-escape the requested path", function() {

it( title = "message + detail must not contain raw <img onerror> markup", body = function( currentSpec ) {
var payload = "<img src=x onerror=alert(1)>";
var caught = false;
try {
// Non-existent template whose path carries the payload — this
// is exactly what the request-path vector produces internally.
include template = "/#payload#_LDEV3027_does_not_exist.cfm";
}
catch ( missinginclude e ) {
caught = true;
// Only a RAW tag-open is dangerous. Escaping turns "<" into "&lt;",
// which neutralises the XSS; the attribute text "onerror=" legitimately
// survives inside the escaped "&lt;img ... onerror=...&gt;" and must NOT
// be asserted against (that was a false-positive in an earlier draft).
expect( e.message ).notToInclude( "<img" );
expect( e.detail ).notToInclude( "<img" );
// the path is still reported, just neutralised
expect( e.message & " " & e.detail ).toInclude( "&lt;img" );
}
expect( caught ).toBeTrue( "expected a missinginclude exception" );
});

it( title = "script-tag payload must be escaped in message + detail", body = function( currentSpec ) {
var payload = "<script>alert(document.domain)</script>";
var caught = false;
try {
include template = "/#payload#_LDEV3027_does_not_exist.cfm";
}
catch ( missinginclude e ) {
caught = true;
expect( e.message ).notToInclude( "<script>" );
expect( e.detail ).notToInclude( "<script>" );
expect( e.message & " " & e.detail ).toInclude( "&lt;script&gt;" );
}
expect( caught ).toBeTrue( "expected a missinginclude exception" );
});

});
}
}