CVE-2019-10305
Description
A missing permission check in Jenkins XebiaLabs XL Deploy Plugin in the Credential#doValidateUserNamePassword form validation method allows attackers with Overall/Read permission to initiate a connection to an attacker-specified server.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Missing permission check in Jenkins XebiaLabs XL Deploy Plugin allows users with Overall/Read to initiate connections to attacker-specified servers.
Vulnerability
CVE-2019-10305 is a missing permission check vulnerability in the Jenkins XebiaLabs XL Deploy Plugin. The Credential#doValidateUserNamePassword form validation method did not verify that the user had the required permissions before allowing a connection to an attacker-specified server. This issue is described in the official advisory [1][3].
Exploitation
An attacker with only Overall/Read permission (the lowest permission level) can exploit this flaw. No special authentication or network position is required beyond that. The method also lacked a POST request requirement, making it potentially exploitable via cross-site request forgery (CSRF) as well, though the primary impact is the unauthorized connection initiation. The fix introduced in commit [2] adds a @RequirePOST annotation and requires Overall/Administer permissions for the method.
Impact
By exploiting this vulnerability, an attacker can initiate a connection from the Jenkins server to an arbitrary server of their choosing. This could be used to exfiltrate data, perform port scanning, or leverage Jenkins as a pivot point for further attacks. The attack does not directly leak credentials, but the connection itself can be abused for reconnaissance or to interact with internal services.
Mitigation
The Jenkins security advisory [3] recommends updating the XebiaLabs XL Deploy Plugin to a version that includes the fix. The fix is present in plugin version 7.0.0 or later, as indicated by the commit history. Users should upgrade as soon as possible to prevent exploitation.
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
com.xebialabs.deployit.ci:deployit-pluginMaven | < 7.5.5 | 7.5.5 |
Affected products
3- Range: all versions as of 2019-04-17
Patches
15acf9d797fe0Merge pull request #62 from FellipeAvanci/XLINT-629
8 files changed · +72 −47
src/main/java/com/xebialabs/deployit/ci/Credential.java+22 −26 modified@@ -35,10 +35,9 @@ import java.util.logging.Logger; import com.cloudbees.plugins.credentials.CredentialsMatchers; -import com.cloudbees.plugins.credentials.common.IdCredentials; +import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.common.StandardUsernameListBoxModel; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; -import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; import com.cloudbees.plugins.credentials.domains.SchemeRequirement; import com.google.common.base.Function; import com.google.common.base.Strings; @@ -49,6 +48,7 @@ import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; import hudson.Extension; import hudson.model.AbstractDescribableImpl; @@ -115,15 +115,6 @@ public boolean isUseGlobalCredential() { return useGlobalCredential; } - public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Project context) { - // TODO: also add requirement on host derived from URL ? - List<StandardUsernamePasswordCredentials> creds = lookupCredentials(StandardUsernamePasswordCredentials.class, context, - ACL.SYSTEM, - HTTP_SCHEME, HTTPS_SCHEME); - - return new StandardUsernameListBoxModel().withAll(creds); - } - public String getSecondaryServerUrl() { if (secondaryServerInfo != null) { return secondaryServerInfo.secondaryServerUrl; @@ -152,14 +143,6 @@ public String resolveProxyUrl(String defaultUrl) { return defaultUrl; } - public boolean showSecondaryServerSettings() { - return secondaryServerInfo != null && secondaryServerInfo.showSecondaryServerSettings(); - } - - public boolean showGolbalCredentials() { - return useGlobalCredential; - } - @Override public String toString() { return name; @@ -200,10 +183,6 @@ public SecondaryServerInfo(String secondaryServerUrl, String secondaryProxyUrl) this.secondaryProxyUrl = secondaryProxyUrl; } - public boolean showSecondaryServerSettings() { - return !Strings.isNullOrEmpty(secondaryServerUrl) || !Strings.isNullOrEmpty(secondaryProxyUrl); - } - public String resolveServerUrl(String defaultUrl) { if (!Strings.isNullOrEmpty(secondaryServerUrl)) { return secondaryServerUrl; @@ -241,7 +220,7 @@ public int hashCode() { } } - public static StandardUsernamePasswordCredentials lookupSystemCredentials(String credentialsId, ItemGroup<?> item) + public static StandardUsernamePasswordCredentials lookupSystemCredentials(String credentialsId, ItemGroup<?> item) { StandardUsernamePasswordCredentials result = null; @@ -261,7 +240,7 @@ public static StandardUsernamePasswordCredentials lookupSystemCredentials(String LOGGER.fine(String.format("[XLD] using credentails '%s'", result.getId())); } - return result; + return result; } @Extension @@ -273,14 +252,23 @@ public String getDisplayName() { public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Project context) { // TODO: also add requirement on host derived from URL ? + + if (context == null && !Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER) || + context != null && !context.hasPermission(context.EXTENDED_READ) && + !context.hasPermission(CredentialsProvider.USE_ITEM)) { + return new StandardUsernameListBoxModel(); + } + List<StandardUsernamePasswordCredentials> creds = lookupCredentials(StandardUsernamePasswordCredentials.class, context, ACL.SYSTEM, HTTP_SCHEME, HTTPS_SCHEME); return new StandardUsernameListBoxModel().withAll(creds); } + @RequirePOST private FormValidation validateOptionalUrl(String url) { + Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); try { if (!Strings.isNullOrEmpty(url)) { new URL(url); @@ -291,11 +279,15 @@ private FormValidation validateOptionalUrl(String url) { return ok(); } + @RequirePOST public FormValidation doCheckSecondaryServerUrl(@QueryParameter String secondaryServerUrl) { + Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); return validateOptionalUrl(secondaryServerUrl); } + @RequirePOST public FormValidation doCheckSecondaryProxyUrl(@QueryParameter String secondaryProxyUrl) { + Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); return validateOptionalUrl(secondaryProxyUrl); } @@ -306,8 +298,10 @@ public static Credential fromStapler(@QueryParameter String name, @QueryParamete return new Credential(name, username, password, credentialsId, new SecondaryServerInfo(secondaryServerUrl, secondaryProxyUrl), useGlobalCredential); } + @RequirePOST public FormValidation doValidateUserNamePassword(@QueryParameter String deployitServerUrl, @QueryParameter String deployitClientProxyUrl, @QueryParameter String username, @QueryParameter Secret password, @QueryParameter String secondaryServerUrl, @QueryParameter String secondaryProxyUrl) throws IOException { + Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); try { String serverUrl = Strings.isNullOrEmpty(secondaryServerUrl) ? deployitServerUrl : secondaryServerUrl; String proxyUrl = Strings.isNullOrEmpty(secondaryProxyUrl) ? deployitClientProxyUrl : secondaryProxyUrl; @@ -324,12 +318,14 @@ public FormValidation doValidateUserNamePassword(@QueryParameter String deployit } } + @RequirePOST private FormValidation validateConnection(String serverUrl, String proxyUrl, String username, String password) throws Exception { + Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); DeployitServer deployitServer = DeployitServerFactory.newInstance(serverUrl, proxyUrl, username, password, 10, DeployitServer.DEFAULT_SOCKET_TIMEOUT); ServerInfo serverInfo = deployitServer.getServerInfo(); deployitServer.newCommunicator(); return FormValidation.ok("Your XL Deploy instance [%s] is alive, and your credentials are valid!", serverInfo.getVersion()); } } -} +} \ No newline at end of file
src/main/java/com/xebialabs/deployit/ci/DeployitNotifier.java+26 −2 modified@@ -46,15 +46,16 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.logging.Level; import java.util.logging.Logger; + import net.sf.json.JSONObject; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.interceptor.RequirePOST; import com.google.common.base.Strings; @@ -67,6 +68,7 @@ import static hudson.util.FormValidation.error; import static hudson.util.FormValidation.ok; import static hudson.util.FormValidation.warning; +import jenkins.model.Jenkins; /** * Runs XL Deploy tasks after the build has completed. @@ -253,6 +255,7 @@ public int getConnectionPoolSize() { return connectionPoolSize; } + private FormValidation validateOptionalUrl(String url) { try { if (!Strings.isNullOrEmpty(url)) { @@ -265,18 +268,27 @@ private FormValidation validateOptionalUrl(String url) { } + @RequirePOST public FormValidation doCheckDeployitServerUrl(@QueryParameter String deployitServerUrl) { + + Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); if (Strings.isNullOrEmpty(deployitServerUrl)) { return error("Url required."); } return validateOptionalUrl(deployitServerUrl); } + @RequirePOST public FormValidation doCheckDeployitClientProxyUrl(@QueryParameter String deployitClientProxyUrl) { + + Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); return validateOptionalUrl(deployitClientProxyUrl); } + @RequirePOST public FormValidation doCheckConnectionPoolSize(@QueryParameter String connectionPoolSize) { + + Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); if (Strings.isNullOrEmpty(connectionPoolSize)) { return error("Connection pool size is required."); } @@ -307,7 +319,10 @@ public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context) return new StandardUsernameListBoxModel().withAll(creds); } + @RequirePOST public FormValidation doCheckCredential(@QueryParameter String credential, @AncestorInPath AbstractProject project) { + + project.checkPermission(Jenkins.ADMINISTER); DeployitNotifier deployitNotifier = RepositoryUtils.retrieveDeployitNotifierFromProject(project); String warningMsg = "Changing credentials may unintentionally change your deployables' types - check the definitions afterward."; if (null != deployitNotifier) { @@ -321,7 +336,10 @@ public FormValidation doCheckCredential(@QueryParameter String credential, @Ance return ok(); } + @RequirePOST public AutoCompletionCandidates doAutoCompleteApplication(@QueryParameter final String value, @AncestorInPath AbstractProject project) { + + project.checkPermission(Jenkins.ADMINISTER); String resolvedApplicationName = expandValue(value, project); final AutoCompletionCandidates applicationCadidates = new AutoCompletionCandidates(); @@ -342,7 +360,10 @@ public AutoCompletionCandidates doAutoCompleteApplication(@QueryParameter final return applicationCadidates; } + @RequirePOST public FormValidation doCheckApplication(@QueryParameter String credential, @QueryParameter final String value, @AncestorInPath AbstractProject<?, ?> project) { + + project.checkPermission(Jenkins.ADMINISTER); if ("Applications/".equals(value)) return ok("Fill in the application ID, eg Applications/PetClinic"); @@ -364,7 +385,10 @@ public FormValidation doCheckApplication(@QueryParameter String credential, @Que return warning("Application does not exist, but will be created upon package import."); } + @RequirePOST public FormValidation doReloadTypes(@QueryParameter String credential, @AncestorInPath AbstractProject project) { + + project.checkPermission(Jenkins.ADMINISTER); Credential overridingcredential = RepositoryUtils.retrieveOverridingCredentialFromProject(project); try { DeployitServer deployitServer = RepositoryUtils.getDeployitServer(credential, overridingcredential, project); @@ -391,4 +415,4 @@ public String expandValue(final String value, final Job project) { return resolvedValue; } } -} +} \ No newline at end of file
src/main/java/com/xebialabs/deployit/ci/JenkinsDeploymentOptions.java+11 −6 modified@@ -39,6 +39,7 @@ import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; import com.xebialabs.deployit.ci.server.DeployitDescriptorRegistry; import com.xebialabs.deployit.ci.server.DeployitServer; @@ -83,10 +84,12 @@ public String getDisplayName() { return "DeploymentOptions"; } + @RequirePOST public ComboBoxModel doFillEnvironmentItems(@QueryParameter(value = "credential") @RelativePath(value = "..") String credential, - @QueryParameter(value = "credential") String credential2, - @AncestorInPath AbstractProject project) + @QueryParameter(value = "credential") String credential2, + @AncestorInPath AbstractProject project) { + project.checkPermission(Jenkins.ADMINISTER); String creds = !isNullOrEmpty(credential) ? credential : credential2; Credential overridingCredential = RepositoryUtils.retrieveOverridingCredentialFromProject(project); List<String> environments = new ArrayList<String>(); @@ -97,11 +100,13 @@ public ComboBoxModel doFillEnvironmentItems(@QueryParameter(value = "credential" return new ComboBoxModel(environments); } + @RequirePOST public FormValidation doCheckEnvironment(@QueryParameter(value = "credential") @RelativePath(value = "..") String credential, - @QueryParameter(value = "credential") String credential2, - @QueryParameter final String value, - @AncestorInPath AbstractProject<?,?> project) + @QueryParameter(value = "credential") String credential2, + @QueryParameter final String value, + @AncestorInPath AbstractProject<?,?> project) { + project.checkPermission(Jenkins.ADMINISTER); if (isNullOrEmpty(value) || "Environments/".equals(value)) return ok("Fill in the target environment ID, eg Environments/MyEnv"); @@ -134,4 +139,4 @@ public String getVersion() { public void setVersion(String version) { this.version = version; } -} +} \ No newline at end of file
src/main/resources/com/xebialabs/deployit/ci/Credential/config.jelly+2 −2 modified@@ -24,10 +24,10 @@ </f:radioBlock> <f:optionalBlock field="secondaryServerInfo" title="Use non-default XL Deploy Server" checked="${instance.showSecondaryServerSettings()}"> <f:entry title="${%Server Url}" field="secondaryServerUrl"> - <f:textbox/> + <f:textbox checkMethod="post" /> </f:entry> <f:entry title="${%Proxy Url}" field="secondaryProxyUrl"> - <f:textbox /> + <f:textbox checkMethod="post" /> </f:entry> </f:optionalBlock>
src/main/resources/com/xebialabs/deployit/ci/DeployitNotifier/config.jelly+3 −3 modified@@ -3,7 +3,7 @@ <f:block> <f:entry title="${%Global server credential}" field="credential" help="/plugin/deployit-plugin/help-credential.html"> - <f:select /> + <f:select checkMethod="post" /> </f:entry> </f:block> @@ -34,7 +34,7 @@ <f:validateButton title="${%Reload types for credential}" with="credential" method="reloadTypes" /> <f:entry title="${%Application}" field="application" help="/plugin/deployit-plugin/help-application.html"> - <f:textbox default="Applications/" /> + <f:textbox default="Applications/" checkMethod="post" /> </f:entry> <f:entry title="${%Version}" field="version" help="/plugin/deployit-plugin/help-version.html"> @@ -104,4 +104,4 @@ </f:block> </xld:publisherFix> -</j:jelly> +</j:jelly> \ No newline at end of file
src/main/resources/com/xebialabs/deployit/ci/DeployitNotifier/global.jelly+4 −4 modified@@ -1,13 +1,13 @@ <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"> <f:section title="XL Deploy"> <f:entry title="${%Default Server Url}" field="deployitServerUrl"> - <f:textbox/> + <f:textbox checkMethod="post" /> </f:entry> <f:entry title="${%Default Proxy Url}" field="deployitClientProxyUrl"> - <f:textbox/> + <f:textbox checkMethod="post" /> </f:entry> <f:entry title="${%Connection Pool Size}" field="connectionPoolSize"> - <f:number/> + <f:number checkMethod="post" /> </f:entry> <f:entry title="Credentials" field="credentials"> <f:repeatable field="credentials" minimum="${1}"> @@ -23,4 +23,4 @@ </f:entry> </f:section> -</j:jelly> +</j:jelly> \ No newline at end of file
src/main/resources/com/xebialabs/deployit/ci/JenkinsDeploymentOptions/config.jelly+2 −2 modified@@ -2,7 +2,7 @@ <script type="text/javascript" src="${rootURL}/plugin/deployit-plugin/js/combobox.js" /> <f:entry title="${%Environment}" field="environment" help="/plugin/deployit-plugin/help-environment.html"> - <f:combobox field="environment" clazz="setting-input"/> + <f:combobox field="environment" clazz="setting-input" checkMethod="post" /> </f:entry> <f:entry title="${%Version}" field="versionKind"> @@ -22,4 +22,4 @@ <f:checkbox/> </f:entry> -</j:jelly> +</j:jelly> \ No newline at end of file
src/main/resources/com/xebialabs/deployit/ci/workflow/XLDeployDeployStep/config.jelly+2 −2 modified@@ -7,6 +7,6 @@ <f:textbox default="Applications/" /> </f:entry> <f:entry title="${%Environment}" field="environmentId" help="/plugin/deployit-plugin/help-environment.html"> - <f:textbox default="Environments/"/> + <f:textbox default="Environments/" checkMethod="post" /> </f:entry> -</j:jelly> +</j:jelly> \ No newline at end of file
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-44w7-gh9c-4qvrghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-10305ghsaADVISORY
- www.securityfocus.com/bid/108045ghsavdb-entryx_refsource_BIDWEB
- github.com/jenkinsci/xldeploy-plugin/commit/5acf9d797fe0afb4defa7c1d5e198103fcdb6989ghsaWEB
- jenkins.io/security/advisory/2019-04-17/ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.