Lack of type validation in agent related REST API in Jenkins
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
0 → fixed in 2.277.22.278 → fixed in 2.287
Details
Jenkins 2.286 and earlier, LTS 2.277.1 and earlier does not validate the type of object created after loading the data submitted to the `config.xml` REST API endpoint of a node. This allows attackers with Computer/Configure permission to replace a node with one of a different type. Jenkins 2.287, LTS 2.277.2 validates the type of object created and rejects objects of unexpected types.
The fix
[SECURITY-1721]
core/src/main/java/hudson/model/Computer.java+9 −1
@@ -1545,8 +1545,16 @@ public void doConfigDotXml(StaplerRequest req, StaplerResponse rsp)*/public void updateByXml(final InputStream source) throws IOException, ServletException {checkPermission(CONFIGURE);+Node previous = getNode();+if (previous == null) {+throw HttpResponses.notFound();+}Node result = (Node)Jenkins.XSTREAM2.fromXML(source);-Jenkins.get().getNodesObject().replaceNode(this.getNode(), result);+if (previous.getClass() != result.getClass()) {+// ensure node type doesn't change+throw HttpResponses.errorWithoutStack(SC_BAD_REQUEST, "Node types do not match");+}+Jenkins.get().getNodesObject().replaceNode(previous, result);}/**
test/src/test/java/hudson/model/ComputerConfigDotXmlSEC1721Test.java+49 −0
@@ -0,0 +1,49 @@+package hudson.model;++import com.gargoylesoftware.htmlunit.HttpMethod;+import com.gargoylesoftware.htmlunit.WebRequest;+import com.gargoylesoftware.htmlunit.WebResponse;+import hudson.slaves.DumbSlave;+import jenkins.model.Jenkins;+import org.junit.Rule;+import org.junit.Test;+import org.junit.rules.TemporaryFolder;+import org.jvnet.hudson.test.FakeLauncher;+import org.jvnet.hudson.test.Issue;+import org.jvnet.hudson.test.JenkinsRule;+import org.jvnet.hudson.test.PretendSlave;++import static org.hamcrest.MatcherAssert.assertThat;+import static org.hamcrest.Matchers.*;++public class ComputerConfigDotXmlSEC1721Test {++@Rule+public final JenkinsRule j = new JenkinsRule();++@Rule+public final TemporaryFolder temporaryFolder = new TemporaryFolder();++@Issue("SECURITY-1721")+@Test+public void cannotChangeNodeType() throws Exception {+PretendSlave agent = j.createPretendSlave(p -> new FakeLauncher.FinishedProc(0));+String name = agent.getNodeName();+assertThat(name, is(not(emptyOrNullString())));+Computer computer = agent.toComputer();+assertThat(computer, is(notNullValue()));++JenkinsRule.WebClient wc = j.createWebClient().withThrowExceptionOnFailingStatusCode(false);+WebRequest req = new WebRequest(wc.createCrumbedUrl(String.format("%s/config.xml", computer.getUrl())), HttpMethod.POST);+req.setAdditionalHeader("Content-Type", "application/xml");+// to ensure maximum compatibility of payload, we'll serialize a real one with the same name+DumbSlave mole = new DumbSlave(name, temporaryFolder.newFolder().getPath(), j.createComputerLauncher(null));+req.setRequestBody(Jenkins.XSTREAM.toXML(mole));+WebResponse response = wc.getPage(req).getWebResponse();+assertThat(response.getStatusCode(), is(400));++// verify node hasn't been transformed into a DumbSlave+Node node = j.jenkins.getNode(name);+assertThat(node, instanceOf(PretendSlave.class));+}+}
References
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2021-21639
- WEBhttps://github.com/jenkinsci/jenkins/commit/84210baed0c866bdee3e59271f98a767a14a5509
- PACKAGEhttps://github.com/jenkinsci/jenkins
- WEBhttps://www.jenkins.io/security/advisory/2021-04-07/#SECURITY-1721
- WEBhttp://www.openwall.com/lists/oss-security/2021/04/07/2