CVE-2025-27497 Fix Denial of Service (Dos) using alias loop
| | |
| | | * |
| | | * Copyright 2008-2010 Sun Microsystems, Inc. |
| | | * Portions Copyright 2011-2016 ForgeRock AS. |
| | | * Portions Copyright 2024 3A Systems, LLC. |
| | | * Portions Copyright 2024-2025 3A Systems, LLC. |
| | | */ |
| | | package org.opends.server.workflowelement.localbackend; |
| | | |
| | | import java.util.HashSet; |
| | | import java.util.Set; |
| | | import java.util.concurrent.atomic.AtomicBoolean; |
| | | |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | |
| | | /** The filter for the search. */ |
| | | private SearchFilter filter; |
| | | |
| | | /** Service object to detect dereferencing recursion */ |
| | | private final Set<DN> dereferencingDNs = new HashSet<>(); |
| | | |
| | | /** |
| | | * Creates a new operation that may be used to search for entries in a local |
| | | * backend of the Directory Server. |
| | |
| | | ) { |
| | | final Entry baseEntry=DirectoryServer.getEntry(baseDN); |
| | | if (baseEntry!=null && baseEntry.isAlias()) { |
| | | setBaseDN(baseEntry.getAliasedDN()); |
| | | processSearch(executePostOpPlugins); |
| | | return; |
| | | final DN aliasedDn = baseEntry.getAliasedDN(); |
| | | if(!dereferencingDNs.contains(aliasedDn)) { //detect recursive search |
| | | dereferencingDNs.add(aliasedDn); |
| | | setBaseDN(aliasedDn); |
| | | try { |
| | | processSearch(executePostOpPlugins); |
| | | } catch (StackOverflowError error) { |
| | | throw new Exception(error); |
| | | } |
| | | dereferencingDNs.remove(aliasedDn); |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2024 3A Systems, LLC. |
| | | * Copyright 2024-2025 3A Systems, LLC. |
| | | */ |
| | | package org.openidentityplatform.opendj; |
| | | |
| | |
| | | import org.opends.server.DirectoryServerTestCase; |
| | | import org.opends.server.TestCaseUtils; |
| | | |
| | | import org.opends.server.types.Entry; |
| | | import org.testng.annotations.BeforeClass; |
| | | import org.testng.annotations.Test; |
| | | |
| | |
| | | "objectclass: extensibleobject", |
| | | "cn: President", |
| | | "aliasedobjectname: cn=John Doe, o=MyCompany, o=test", |
| | | "", |
| | | |
| | | "dn: ou=employees,o=test", |
| | | "objectClass: top", |
| | | "objectClass: organizationalUnit", |
| | | "ou: employees", |
| | | "description: All employees", |
| | | "", |
| | | "dn: uid=jdoe,ou=employees,o=test", |
| | | "objectClass: alias", |
| | | "objectClass: top", |
| | | "objectClass: extensibleObject", |
| | | "aliasedObjectName: uid=jdoe,ou=researchers,o=test", |
| | | "uid: jdoe", |
| | | "", |
| | | "dn: ou=researchers,o=test", |
| | | "objectClass: top", |
| | | "objectClass: organizationalUnit", |
| | | "ou: researchers", |
| | | "description: All reasearchers", |
| | | "", |
| | | "dn: uid=jdoe,ou=researchers,o=test", |
| | | "objectClass: alias", |
| | | "objectClass: top", |
| | | "objectClass: extensibleObject", |
| | | "aliasedObjectName: uid=jdoe,ou=employees,o=test", |
| | | "uid: jdoe", |
| | | |
| | | "", |
| | | "dn: ou=students,o=test", |
| | | "objectClass: top", |
| | | "objectClass: organizationalUnit", |
| | | "ou: students", |
| | | "description: All students", |
| | | "", |
| | | "dn: uid=janedoe,ou=students,o=test", |
| | | "objectClass: alias", |
| | | "objectClass: top", |
| | | "objectClass: extensibleObject", |
| | | "aliasedObjectName: uid=janedoe,ou=researchers,o=test", |
| | | "uid: janedoe", |
| | | "", |
| | | "dn: uid=janedoe,ou=researchers,o=test", |
| | | "objectClass: alias", |
| | | "objectClass: top", |
| | | "objectClass: extensibleObject", |
| | | "aliasedObjectName: uid=janedoe,ou=employees,o=test", |
| | | "uid: janedoe", |
| | | "", |
| | | "dn: uid=janedoe,ou=employees,o=test", |
| | | "objectClass: alias", |
| | | "objectClass: top", |
| | | "objectClass: extensibleObject", |
| | | "aliasedObjectName: uid=janedoe,ou=students,o=test", |
| | | "uid: janedoe", |
| | | "" |
| | | ); |
| | | |
| | |
| | | } |
| | | |
| | | public HashMap<String,SearchResultEntry> search(SearchScope scope,DereferenceAliasesPolicy policy) throws SearchResultReferenceIOException, LdapException { |
| | | final SearchRequest request =Requests.newSearchRequest("ou=Area1,o=test", scope,"(objectclass=*)") |
| | | return search("ou=Area1,o=test", scope, policy); |
| | | } |
| | | |
| | | public HashMap<String,SearchResultEntry> search(String dn, SearchScope scope,DereferenceAliasesPolicy policy) throws SearchResultReferenceIOException, LdapException { |
| | | final SearchRequest request =Requests.newSearchRequest(dn, scope,"(objectclass=*)") |
| | | .setDereferenceAliasesPolicy(policy); |
| | | System.out.println("---------------------------------------------------------------------------------------"); |
| | | System.out.println(request); |
| | |
| | | // It returns ou=Area1,o=test. |
| | | @Test |
| | | public void test_base_search() throws SearchResultReferenceIOException, LdapException { |
| | | HashMap<String,SearchResultEntry> res=search(SearchScope.BASE_OBJECT,DereferenceAliasesPolicy.IN_SEARCHING); |
| | | HashMap<String,SearchResultEntry> res=search(SearchScope.BASE_OBJECT, DereferenceAliasesPolicy.IN_SEARCHING); |
| | | |
| | | assertThat(res.containsKey("ou=Area1,o=test")).isTrue(); |
| | | assertThat(res.containsKey("o=MyCompany,o=test")).isFalse(); |
| | |
| | | assertThat(res.containsKey("cn=John Doe,o=MyCompany,o=test")).isTrue(); |
| | | } |
| | | |
| | | // Dereferencing recursion avoidance test. |
| | | @Test |
| | | public void test_alias_recursive() throws LdapException, SearchResultReferenceIOException { |
| | | HashMap<String, SearchResultEntry> res = search("uid=jdoe,ou=employees,o=test", SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.ALWAYS); |
| | | |
| | | assertThat(res.containsKey("uid=jdoe,ou=employees,o=test")).isTrue(); |
| | | assertThat(res.containsKey("uid=jdoe,ou=researchers,o=test")).isFalse(); |
| | | } |
| | | |
| | | @Test |
| | | public void test_alias_recursive_loop() throws LdapException, SearchResultReferenceIOException { |
| | | HashMap<String, SearchResultEntry> res = search("uid=janedoe,ou=students,o=test", SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.ALWAYS); |
| | | |
| | | assertThat(res.containsKey("uid=janedoe,ou=students,o=test")).isTrue(); |
| | | assertThat(res.containsKey("uid=janedoe,ou=researches,o=test")).isFalse(); |
| | | assertThat(res.containsKey("uid=janedoe,ou=employees,o=test")).isFalse(); |
| | | } |
| | | |
| | | @Test(expectedExceptions = LdapException.class) |
| | | public void test_stackoverflow() throws Exception { |
| | | |
| | | String entryTemplate = "dn: uid={uid},ou=employees,o=test\n" + |
| | | "objectClass: alias\n" + |
| | | "objectClass: top\n" + |
| | | "objectClass: extensibleObject\n" + |
| | | "aliasedObjectName: uid={alias},ou=employees,o=test \n" + |
| | | "uid: {uid}\n"; |
| | | final String firstDn = "uid=jdoe0,ou=employees,o=test"; |
| | | for(int i = 0; i < 10000; i++) { |
| | | String entryStr = entryTemplate.replace("{uid}", "jdoe" + i).replace("{alias}", "jdoe" + (i + 1)); |
| | | Entry entry = TestCaseUtils.makeEntry(entryStr); |
| | | TestCaseUtils.addEntry(entry); |
| | | } |
| | | search(firstDn, SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.ALWAYS); |
| | | } |
| | | } |