From 3fc07e611a52ff5940306388e51f0dd92cf7054f Mon Sep 17 00:00:00 2001 From: Ramon Leon Date: Mon, 29 Mar 2021 12:38:27 +0200 Subject: [PATCH] SECURITY-2204 (cherry picked from commit 5f845bc015be769e595088bab11ec36c767671e1) --- .../maven/security/CredentialsHelper.java | 9 +- .../configfiles/sec/Security2204Test.java | 74 ++++++ .../plugins/configfiles/sec/settings-xxe.xml | 8 + .../plugins/configfiles/sec/settings.xml | 233 ++++++++++++++++++ 4 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/jenkinsci/plugins/configfiles/sec/Security2204Test.java create mode 100644 src/test/resources/org/jenkinsci/plugins/configfiles/sec/settings-xxe.xml create mode 100644 src/test/resources/org/jenkinsci/plugins/configfiles/sec/settings.xml diff --git a/src/main/java/org/jenkinsci/plugins/configfiles/maven/security/CredentialsHelper.java b/src/main/java/org/jenkinsci/plugins/configfiles/maven/security/CredentialsHelper.java index be9b9502..0ce9136c 100644 --- a/src/main/java/org/jenkinsci/plugins/configfiles/maven/security/CredentialsHelper.java +++ b/src/main/java/org/jenkinsci/plugins/configfiles/maven/security/CredentialsHelper.java @@ -15,6 +15,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; @@ -115,7 +116,13 @@ public static String fillAuthentication(String mavenSettingsContent, final Boole if (mavenServerId2jenkinsCredential.isEmpty()) { return mavenSettingsContent; } - Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new InputSource(new StringReader(content))); + + // TODO: switch to XMLUtils.parse(Reader) when the baseline > 2.179 or XMLUtils.parse(InputSteam) > 2.265 + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + //documentBuilderFactory.isValidating() is false by default, so these attributes won't avoid to parse an usual maven settings. + documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + Document doc = documentBuilderFactory.newDocumentBuilder().parse(new InputSource(new StringReader(content))); Map removedMavenServers = Collections.emptyMap(); diff --git a/src/test/java/org/jenkinsci/plugins/configfiles/sec/Security2204Test.java b/src/test/java/org/jenkinsci/plugins/configfiles/sec/Security2204Test.java new file mode 100644 index 00000000..f30218b1 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/configfiles/sec/Security2204Test.java @@ -0,0 +1,74 @@ +package org.jenkinsci.plugins.configfiles.sec; + +import com.cloudbees.plugins.credentials.CredentialsScope; +import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; +import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; +import hudson.FilePath; +import org.apache.commons.io.IOUtils; +import org.hamcrest.core.StringContains; +import org.jenkinsci.plugins.configfiles.maven.security.CredentialsHelper; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.xml.sax.SAXParseException; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.ibm.icu.impl.Assert.fail; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; + +public class Security2204Test { + @Rule + public JenkinsRule rr = new JenkinsRule(); + + private static final String NEW_PASSWORD = "PASSWORD-REPLACED"; + + /** + * If we use an xml with an external entity, the parser fails to read it. + * @throws Exception if an error happens while reading the xml or parsing it and it's not the expected one. + */ + @Test + @Issue("SECURITY-2204") + public void xxeOnMavenXMLDetected() throws Exception { + try { + tryToReplaceAuthOnXml("settings-xxe.xml"); + } catch (SAXParseException e) { + assertThat(e.getMessage(), containsString("access is not allowed due to restriction set by the accessExternalDTD property")); + return; + // The console also prints the message out: + // [Fatal Error] :5:13: External Entity: Failed to read external document 'oob.xml', because 'http' access is not allowed due to restriction set by the accessExternalDTD property. + } + fail("The fillAuthentication should have detected the XXE but it's not the case"); + } + + /** + * An usual maven settings.xml is correctly parsed because the parser doesn't have activated the validation by + * default, so no need to go to the declared dtd or schema urls. + * @throws Exception if an error happens while reading or parsing the xml. + */ + @Test + @Issue("SECURITY-2204") + public void mavenSettingsAuthFilled() throws Exception { + String settings = "settings.xml"; + String settingsReplaced = tryToReplaceAuthOnXml(settings); + assertThat(settingsReplaced, not(StringContains.containsString("whatever-password-because-its-replaced"))); + assertThat(settingsReplaced, StringContains.containsString(NEW_PASSWORD)); + } + + private String tryToReplaceAuthOnXml(String file) throws Exception { + String mavenSettingsContent = IOUtils.toString(this.getClass().getResource(file), StandardCharsets.UTF_8); + Map credentials = new HashMap(); + + StandardUsernameCredentials usernameCredentials = new UsernamePasswordCredentialsImpl(CredentialsScope.GLOBAL, "credentialId", "user", "credential description", NEW_PASSWORD); + credentials.put("credential", usernameCredentials); + List tempFiles = new ArrayList<>(); + return CredentialsHelper.fillAuthentication(mavenSettingsContent, true, credentials, new FilePath(rr.jenkins.root), tempFiles); + } +} diff --git a/src/test/resources/org/jenkinsci/plugins/configfiles/sec/settings-xxe.xml b/src/test/resources/org/jenkinsci/plugins/configfiles/sec/settings-xxe.xml new file mode 100644 index 00000000..2c148944 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/configfiles/sec/settings-xxe.xml @@ -0,0 +1,8 @@ + + + + %sp; + %param1; + ]> +&oob; \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/configfiles/sec/settings.xml b/src/test/resources/org/jenkinsci/plugins/configfiles/sec/settings.xml new file mode 100644 index 00000000..7192aa07 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/configfiles/sec/settings.xml @@ -0,0 +1,233 @@ + + + + + + + + + + + + + + + + + + + + + + serverId + whatever-username-because-its-replaced + whatever-password-because-its-replaced + + + + + + + + + + + + + + + + + + \ No newline at end of file