8000 Fix stored XSS→RCE in OfflineCause.UserCause and auth bypass in RunParameterValue by Copilot · Pull Request #4 · DominicBreuker/jenkins · GitHub
[go: up one dir, main page]

Skip to content
Draft
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
16 changes: 14 additions & 2 deletions core/src/main/java/hudson/model/RunParameterValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@
import hudson.EnvVars;
import java.util.Locale;
import jenkins.model.Jenkins;
import jenkins.util.SystemProperties;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.export.Exported;

public class RunParameterValue extends ParameterValue {

private static /* non-final for Script console */ boolean SKIP_EXISTENCE_CHECK = SystemProperties.getBoolean(RunParameterValue.class.getName() + ".SKIP_EXISTENCE_CHECK", false);
private final String runId;

@DataBoundConstructor
Expand All @@ -49,9 +51,19 @@ public RunParameterValue(String name, String runId) {
private static String check(String runId) {
if (runId == null || runId.indexOf('#') == -1) {
throw new IllegalArgumentException(runId);
} else {
return runId;
}

if (!SKIP_EXISTENCE_CHECK) {
try {
if (Run.fromExternalizableId(runId) == null) {
throw new IllegalArgumentException(runId);
}
} catch (RuntimeException e) {
throw new IllegalArgumentException(runId);
}
}

return runId;
}

/**
Expand Down
8 changes: 7 additions & 1 deletion core/src/main/java/hudson/slaves/OfflineCause.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Util;
import hudson.model.Computer;
import hudson.model.User;
import java.io.ObjectStreamException;
Expand Down Expand Up @@ -166,7 +167,7 @@ public User getUser() {
* @return the message that was provided when the computer was taken offline
*/
public String getMessage() {
return message;
return Util.escape(message);
}

// Storing the User in a filed was a mistake, switch to userId
Expand Down Expand Up @@ -202,6 +203,11 @@ public String getComputerIconAltText() {
public String getIcon() {
return "symbol-person";
}

@Override
public String toString() {
return Util.escape(super.toString());
}
}

public static class ByCLI extends UserCause {
Expand Down
8 changes: 3 additions & 5 deletions core/src/test/java/hudson/model/RunParameterValueTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,14 @@ class RunParameterValueTest {
@SuppressWarnings("ResultOfObjectAllocationIgnored")
@Test
void robustness() {
RunParameterValue rpv = new RunParameterValue("whatever", "folder/job#57");
assertEquals("whatever", rpv.getName());
assertEquals("folder/job", rpv.getJobName());
assertEquals("57", rpv.getNumber());
// Constructor-based tests that require a Jenkins instance have been moved
// to test/src/test/java/hudson/model/RunParameterValueIntegrationTest.java

assertThrows(IllegalArgumentException.class, () -> new RunParameterValue("whatever", null));
assertThrows(IllegalArgumentException.class, () -> new RunParameterValue("whatever", "invalid"));
assertThrows(IllegalArgumentException.class, () -> new RunParameterValue("whatever", "invalid", "desc"));

rpv = (RunParameterValue) Run.XSTREAM2.fromXML("<hudson.model.RunParameterValue><name>whatever</name><runId>bogus</runId></hudson.model.RunParameterValue>");
RunParameterValue rpv = (RunParameterValue) Run.XSTREAM2.fromXML("<hudson.model.RunParameterValue><name>whatever</name><runId>bogus</runId></hudson.model.RunParameterValue>");
assertEquals("whatever", rpv.getName());
assertNull(rpv.getJobName());
assertNull(rpv.getNumber());
Expand Down
24 changes: 24 additions & 0 deletions core/src/test/java/hudson/slaves/OfflineCauseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,28 @@ void testChannelTermination_NoStacktrace() {
assertThat(cause.toString(), not(containsString(exceptionMessage)));
}

@Test
void userCauseEscapesHtmlInToString() {
OfflineCause.UserCause cause = new OfflineCause.UserCause(null, "<img src=x >
String result = cause.toString();
assertThat("HTML should be escaped in toString()", result, not(containsString("<img")));
assertThat("HTML entities should be present", result, containsString("&lt;img"));
}

@Test
void userCauseEscapesHtmlInGetMessage() {
OfflineCause.UserCause cause = new OfflineCause.UserCause(null, "<script>alert('xss')</script>");
String message = cause.getMessage();
assertThat("HTML should be escaped in getMessage()", message, not(containsString("<script>")));
assertThat("HTML entities should be present", message, containsString("&lt;script&gt;"));
}

@Test
void userCauseHandlesNullMessage() {
OfflineCause.UserCause cause = new OfflineCause.UserCause(null, null);
// Should not throw
cause.toString();
cause.getMessage();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package hudson.model;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockFolder;
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;

@WithJenkins
class RunParameterValueIntegrationTest {

@SuppressWarnings("ResultOfObjectAllocationIgnored")
@Test
void existingRunAccepted(JenkinsRule jenkinsRule) throws Exception {
final MockFolder folder = jenkinsRule.createFolder("folder");
final FreeStyleProject job = folder.createProject(FreeStyleProject.class, "job");
job.updateNextBuildNumber(57);
jenkinsRule.assertBuildStatusSuccess(job.scheduleBuild2(0));

RunParameterValue rpv = new RunParameterValue("whatever", "folder/job#57");
assertEquals("whatever", rpv.getName());
assertEquals("folder/job", rpv.getJobName());
assertEquals("57", rpv.getNumber());
}

@SuppressWarnings("ResultOfObjectAllocationIgnored")
@Test
void nonexistentRunRejected(JenkinsRule jenkinsRule) throws Exception {
// A run referencing a non-existent job should be rejected
assertThrows(IllegalArgumentException.class, () -> new RunParameterValue("whatever", "nonexistent/job#1"));
}
}
0