CVE-2022-24832 (published 2022-04-11T21:15Z)

GoCD is an open source a continuous delivery server. The bundled gocd-ldap-authentication-plugin included with the GoCD Server fails to correctly escape special characters when using the username to construct LDAP queries. While this does not directly allow arbitrary LDAP data exfiltration, it can allow an existing LDAP-authenticated GoCD user with malicious intent to construct and execute malicious queries, allowing them to deduce facts about other users or entries within the LDAP database (e.g alternate fields, usernames, hashed passwords etc) through brute force mechanisms. This only affects users who have a working LDAP authorization configuration enabled on their GoCD server, and only is exploitable by users authenticating using such an LDAP configuration. This issue has been fixed in GoCD 22.1.0, which is bundled with gocd-ldap-authentication-plugin v2.2.0-144.

Severity: MEDIUM
CVSS v2 base score: 4.9 (AV:N/AC:M/Au:S/C:P/I:P/A:N)
CVSS v3 base score: 6.8, MEDIUM (CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N)

Weakness information:
cwe_id: CWE-74
cwe_name: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
description: The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
extended_description: Software or other automated logic has certain assumptions about what constitutes data and control respectively. It is the lack of verification of these assumptions for user-controlled input that leads to injection problems. Injection problems encompass a wide variety of issues -- all mitigated in very different ways and usually attempted in order to alter the control flow of the process. For this reason, the most effective way to discuss these weaknesses is to note the distinct features that classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed.
         

Fix information:
repo_url: https://github.com/gocd/gocd-ldap-authentication-plugin
commit_details: repo_url: https://github.com/gocd/gocd-ldap-authentication-plugin
author: Chad Wilson
author_date: 2022-03-06 14:25:28+08:00
msg: Escape/encode values when building search filters.
repository: repo_url: https://github.com/gocd/gocd-ldap-authentication-plugin
repo_name: gocd/gocd-ldap-authentication-plugin
description: LDAP authentication plugin for GoCD
repo_language: Java
owner: gocd
file_changes: filename: ApacheDsLdapClient.java
change_type: ModificationType.MODIFY
diff: @@ -25,6 +25,7 @@ import org.apache.directory.api.ldap.codec.api.LdapApiServiceFactory;
 import org.apache.directory.api.ldap.extras.controls.ppolicy_impl.PasswordPolicyDecorator;
 import org.apache.directory.api.ldap.model.entry.Entry;
 import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.api.ldap.model.filter.FilterEncoder;
 import org.apache.directory.api.ldap.model.message.*;
 import org.apache.directory.api.ldap.model.name.Dn;
 import org.apache.directory.ldap.client.api.LdapConnectionConfig;
@@ -95,7 +96,7 @@ public class ApacheDsLdapClient implements LdapClient {
     }
 
     @Override
-    public <T> List<T> search(final String filter, final Object[] filterArgs, final Mapper<T> mapper, final int maxResultCount) {
+    public <T> List<T> search(final String filter, final String[] filterArgs, final Mapper<T> mapper, final int maxResultCount) {
         final List<T> searchResults = new ArrayList<>();
         for (String searchBase : ldapConfiguration.getSearchBases()) {
             int resultsToFetch = resultsToFetch(maxResultCount, searchResults.size());
@@ -109,7 +110,7 @@ public class ApacheDsLdapClient implements LdapClient {
                         .setScope(SearchScope.SUBTREE)
                         .addAttributes("*")
                         .setSizeLimit(resultsToFetch)
-                        .setFilter(format(filter, filterArgs))
+                        .setFilter(FilterEncoder.format(filter, filterArgs))
                         .setTimeLimit(ldapConfiguration.getSearchTimeout())
                         .setBase(new Dn(searchBase));
 
@@ -124,11 +125,11 @@ public class ApacheDsLdapClient implements LdapClient {
 
     @Override
     public void validate() {
-        final String filter = format(ldapConfiguration.getUserSearchFilter(), "test");
+        final String filter = FilterEncoder.format(ldapConfiguration.getUserSearchFilter(), "test");
         ldapConnectionTemplate.searchFirst(ldapConfiguration.getSearchBases().get(0), filter, SearchScope.SUBTREE, entry -> entry);
     }
 
-    public List<Entry> search(final String filter, final Object[] filterArgs, final int maxResultCount) {
+    public List<Entry> search(final String filter, final String[] filterArgs, final int maxResultCount) {
         return search(filter, filterArgs, resultWrapper -> (Entry) resultWrapper.getResult(), maxResultCount);
     }
 

programming_language: Java
filename: LdapClient.java
change_type: ModificationType.MODIFY
diff: @@ -24,7 +24,7 @@ import java.util.List;
 public interface LdapClient {
     <T> T authenticate(String username, String password, Mapper<T> mapper);
 
-    <T> List<T> search(String userSearchFilter, Object[] filterArgs, Mapper<T> mapper, int maxResult);
+    <T> List<T> search(String userSearchFilter, String[] filterArgs, Mapper<T> mapper, int maxResult);
 
     void validate() throws NamingException;
 }

programming_language: Java
filename: JNDILdapClient.java
change_type: ModificationType.MODIFY
diff: @@ -71,7 +71,7 @@ public class JNDILdapClient implements LdapClient {
         }
     }
 
-    public <T> List<T> search(String filter, Object[] filterArgs, Mapper<T> mapper, int maxResult) {
+    public <T> List<T> search(String filter, String[] filterArgs, Mapper<T> mapper, int maxResult) {
         List<T> results = new ArrayList<>();
         DirContext dirContext = getDirContext(ldapConfiguration, ldapConfiguration.getManagerDn(), ldapConfiguration.getPassword());
 

programming_language: Java
filename: ApacheDsLdapClientTest.java
change_type: ModificationType.RENAME
diff: @@ -38,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.*;
 
-class LdapTest {
+class ApacheDsLdapClientTest {
 
     @Test
     void shouldBeAbleToSearchUsers() throws ParseException {
@@ -65,6 +65,26 @@ class LdapTest {
         assertThat(searchRequest.getTimeLimit()).isEqualTo(10);
     }
 
+    @Test
+    void shouldEscapeSearchFilterValues() throws ParseException {
+        final LdapConfiguration ldapConfiguration = new LdapConfigurationBuilder()
+                .withSearchTimeout(10)
+                .withSearchBases("ou=foo,dc=bar")
+                .build();
+
+        final LdapConnectionTemplate ldapConnectionTemplate = mock(LdapConnectionTemplate.class);
+        final ApacheDsLdapClient ldap = new ApacheDsLdapClient(ldapConfiguration, ldapConnectionTemplate);
+        final ArgumentCaptor<SearchRequest> argumentCaptor = ArgumentCaptor.forClass(SearchRequestImpl.class);
+
+        when(ldapConnectionTemplate.search(argumentCaptor.capture(), ArgumentMatchers.<EntryMapper<Entry>>any())).thenReturn(Collections.singletonList(new DefaultEntry()));
+
+        String injectionUserName = "*)(objectclass=*";
+        ldap.search("(uid={0})", new String[]{injectionUserName}, 1);
+
+        assertThat(argumentCaptor.getValue().getFilter())
+                .isEqualTo(FilterParser.parse("(uid=\\2A\\29\\28objectclass=\\2A)"));
+    }
+
     @Test
     void shouldAbleToFetchResultsFromMultipleSearchBase() {
         final LdapConfiguration ldapConfiguration = new LdapConfigurationBuilder()

programming_language: Scala