VYPR
Moderate severityNVD Advisory· Published Apr 18, 2019· Updated Aug 4, 2024

CVE-2019-10304

CVE-2019-10304

Description

A cross-site request forgery vulnerability in Jenkins XebiaLabs XL Deploy Plugin in the Credential#doValidateUserNamePassword form validation method allows attackers to initiate a connection to an attacker-specified server.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Jenkins XebiaLabs XL Deploy Plugin has a CSRF flaw in Credential#doValidateUserNamePassword, allowing attackers to force a connection to an attacker-specified server.

Vulnerability

A cross-site request forgery (CSRF) vulnerability exists in the Jenkins XebiaLabs XL Deploy Plugin. The Credential#doValidateUserNamePassword form validation method did not require a POST request, making it susceptible to CSRF attacks [1]. This allows an attacker to trick an authenticated Jenkins user into initiating a connection to an attacker-specified server.

Exploitation

To exploit this vulnerability, an attacker must craft a malicious web page or link that, when visited by an authenticated Jenkins user, triggers a forged request to the vulnerable endpoint. The form validation method would then execute a connection to a server controlled by the attacker. No additional privileges beyond convincing a user to click a link are required [2][3].

Impact

A successful CSRF attack enables the attacker to force the Jenkins server to connect to an arbitrary server, potentially exposing internal network resources or allowing further reconnaissance. The connection itself may be used to probe internal services or leak information through side channels.

Mitigation

The vulnerability is fixed in XebiaLabs XL Deploy Plugin version 7.5.4 and later [2][4]. The fix introduces the @RequirePOST annotation on the vulnerable method, ensuring that only POST requests are accepted, which prevents CSRF exploitation [2]. Users are advised to update their plugin or apply the workaround of ensuring that their Jenkins instance is not accessible to untrusted users.

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.

PackageAffected versionsPatched versions
com.xebialabs.deployit.ci:deployit-pluginMaven
< 7.5.57.5.5

Affected products

3

Patches

1
5acf9d797fe0

Merge pull request #62 from FellipeAvanci/XLINT-629

https://github.com/jenkinsci/xldeploy-pluginXebiaLabs CI ServersMay 16, 2019via ghsa
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

6

News mentions

0

No linked articles in our index yet.