Security context
High· 8.0GHSA-69vw-3pcm-84rw CVE-2023-39151CWE-79Published Jul 26, 2023

Jenkins Stored Cross-site Scripting vulnerability

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

2.402 → fixed in 2.414.10 → fixed in 2.401.32.415 → fixed in 2.416

Details

Jenkins applies formatting to the console output of builds, transforming plain URLs into hyperlinks. Jenkins 2.415 and earlier, 2.414 and earlier, and LTS 2.401.2 and earlier does not sanitize or properly encode URLs of these hyperlinks in build logs. This results in a stored cross-site scripting (XSS) vulnerability exploitable by attackers able to control build log contents. Jenkins 2.416, 2.414.1, and LTS 2.401.3 encodes URLs of affected hyperlink annotations in build logs.

The fix

[SECURITY-3188]

Yaroslav Afenkin· Jul 13, 2023, 07:24 AM+5421b9f1ccdbb
core/src/main/java/hudson/AbstractMarkupText.java+2 2
@@ -83,7 +83,7 @@ public final int length() {
* @since 1.349
*/
public void addHyperlink(int startPos, int endPos, String url) {
- addMarkup(startPos, endPos, "<a href='" + url + "'>", "</a>");
+ addMarkup(startPos, endPos, "<a href='" + Functions.htmlAttributeEscape(url) + "'>", "</a>");
}
/**
@@ -93,7 +93,7 @@ public void addHyperlink(int startPos, int endPos, String url) {
* @since 1.395
*/
public void addHyperlinkLowKey(int startPos, int endPos, String url) {
- addMarkup(startPos, endPos, "<a class='lowkey' href='" + url + "'>", "</a>");
+ addMarkup(startPos, endPos, "<a class='lowkey' href='" + Functions.htmlAttributeEscape(url) + "'>", "</a>");
}
/**
test/src/test/java/jenkins/security/Security3188Test.java+52 0
@@ -0,0 +1,52 @@
+package jenkins.security;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+
+import hudson.Functions;
+import hudson.model.FreeStyleBuild;
+import hudson.model.FreeStyleProject;
+import hudson.tasks.BatchFile;
+import hudson.tasks.CommandInterpreter;
+import hudson.tasks.Shell;
+import java.util.concurrent.atomic.AtomicBoolean;
+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 Security3188Test {
+
+ @Rule
+ public JenkinsRule j = new JenkinsRule();
+
+ @Issue("SECURITY-3188")
+ @Test
+ public void linkCannotAttributeEscape() throws Exception {
+ FreeStyleProject p = j.createFreeStyleProject("p");
+ p.getBuildersList().add(getScript("echo \"https://acme.com/search?q='onmouseover=alert(1);'Hello World\""));
+ 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("<a href='https://acme.com/search?q=&#39;onmouseover=alert(1);&#39;Hello'"));
+
+ // Execute JavaScript code to simulate mouseover event
+ String jsCode = "document.querySelector('pre.console-output a:nth-child(2)').dispatchEvent(new MouseEvent('mouseover'));";
+ page.executeJavaScript(jsCode);
+
+ Assert.assertFalse("Alert not expected", alerts.get());
+ }
+ }
+
+ private static CommandInterpreter getScript(String script) {
+ return Functions.isWindows()
+ ? new BatchFile(script)
+ : new Shell(script);
+ }
+}

References