diff --git a/core/src/main/java/hudson/console/ExpandableDetailsNote.java b/core/src/main/java/hudson/console/ExpandableDetailsNote.java index 6dcc949199b1..1f558e6a260b 100644 --- a/core/src/main/java/hudson/console/ExpandableDetailsNote.java +++ b/core/src/main/java/hudson/console/ExpandableDetailsNote.java @@ -26,6 +26,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; +import hudson.Functions; import hudson.MarkupText; import java.io.IOException; import java.util.logging.Level; @@ -52,7 +53,7 @@ public ExpandableDetailsNote(String caption, String html) { @Override public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) { text.addMarkup(charPos, - "
" + html + "
"); + "
" + html + "
"); return null; } diff --git a/test/src/test/java/jenkins/security/Security3245Test.java b/test/src/test/java/jenkins/security/Security3245Test.java new file mode 100644 index 000000000000..9c9a37b46581 --- /dev/null +++ b/test/src/test/java/jenkins/security/Security3245Test.java @@ -0,0 +1,68 @@ +package jenkins.security; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.EnvVars; +import hudson.FilePath; +import hudson.Launcher; +import hudson.console.ExpandableDetailsNote; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.Run; +import hudson.model.TaskListener; +import hudson.tasks.Builder; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; +import jenkins.tasks.SimpleBuildStep; +import org.htmlunit.html.HtmlPage; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +public class Security3245Test { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Issue("SECURITY-3245") + @Test + public void captionCannotAttributeEscape() throws Exception { + FreeStyleProject p = j.createFreeStyleProject("p"); + p.getBuildersList().add(new ExpandableDetailsNoteTestAction("' onclick=alert(1) foo='bar", "

")); + FreeStyleBuild build = j.buildAndAssertSuccess(p); + + AtomicBoolean alerts = new AtomicBoolean(); + try (JenkinsRule.WebClient wc = j.createWebClient()) { + wc.setAlertHandler((pr, s) -> alerts.set(true)); + final HtmlPage page = wc.goTo(build.getUrl() + "console"); + String content = page.getWebResponse().getContentAsString(); + assertThat(content, containsString("")); + + // Execute JavaScript code to simulate click event + String jsCode = "document.querySelector('.reveal-expandable-detail').dispatchEvent(new MouseEvent('click'));"; + page.executeJavaScript(jsCode); + + Assert.assertFalse("Alert not expected", alerts.get()); + } + } + + static class ExpandableDetailsNoteTestAction extends Builder implements SimpleBuildStep { + + final String caption; + final String html; + + ExpandableDetailsNoteTestAction(String caption, String html) { + this.caption = caption; + this.html = html; + } + + @Override + public void perform(@NonNull Run run, @NonNull FilePath workspace, @NonNull EnvVars env, @NonNull Launcher launcher, @NonNull TaskListener listener) throws IOException { + listener.annotate(new ExpandableDetailsNote(caption, html)); + } + } +}