Skip to content

Commit 08aee47

Browse files
maximthomasvharseko
authored andcommitted
CVE-2025-27497 Fix Denial of Service (Dos) using alias loop
1 parent 688a9ff commit 08aee47

File tree

2 files changed

+116
-7
lines changed

2 files changed

+116
-7
lines changed

opendj-server-legacy/src/main/java/org/opends/server/workflowelement/localbackend/LocalBackendSearchOperation.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
*
1414
* Copyright 2008-2010 Sun Microsystems, Inc.
1515
* Portions Copyright 2011-2016 ForgeRock AS.
16-
* Portions Copyright 2024 3A Systems, LLC.
16+
* Portions Copyright 2024-2025 3A Systems, LLC.
1717
*/
1818
package org.opends.server.workflowelement.localbackend;
1919

20+
import java.util.HashSet;
21+
import java.util.Set;
2022
import java.util.concurrent.atomic.AtomicBoolean;
2123

2224
import org.forgerock.i18n.slf4j.LocalizedLogger;
@@ -67,6 +69,9 @@ public class LocalBackendSearchOperation
6769
/** The filter for the search. */
6870
private SearchFilter filter;
6971

72+
/** Service object to detect dereferencing recursion */
73+
private final Set<DN> dereferencingDNs = new HashSet<>();
74+
7075
/**
7176
* Creates a new operation that may be used to search for entries in a local
7277
* backend of the Directory Server.
@@ -207,9 +212,18 @@ private void processSearch(AtomicBoolean executePostOpPlugins) throws CanceledOp
207212
) {
208213
final Entry baseEntry=DirectoryServer.getEntry(baseDN);
209214
if (baseEntry!=null && baseEntry.isAlias()) {
210-
setBaseDN(baseEntry.getAliasedDN());
211-
processSearch(executePostOpPlugins);
212-
return;
215+
final DN aliasedDn = baseEntry.getAliasedDN();
216+
if(!dereferencingDNs.contains(aliasedDn)) { //detect recursive search
217+
dereferencingDNs.add(aliasedDn);
218+
setBaseDN(aliasedDn);
219+
try {
220+
processSearch(executePostOpPlugins);
221+
} catch (StackOverflowError error) {
222+
throw new Exception(error);
223+
}
224+
dereferencingDNs.remove(aliasedDn);
225+
return;
226+
}
213227
}
214228
}
215229

opendj-server-legacy/src/test/java/org/openidentityplatform/opendj/AliasTestCase.java

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
* Header, with the fields enclosed by brackets [] replaced by your own identifying
1212
* information: "Portions Copyright [year] [name of copyright owner]".
1313
*
14-
* Copyright 2024 3A Systems, LLC.
14+
* Copyright 2024-2025 3A Systems, LLC.
1515
*/
1616
package org.openidentityplatform.opendj;
1717

@@ -23,6 +23,7 @@
2323
import org.opends.server.DirectoryServerTestCase;
2424
import org.opends.server.TestCaseUtils;
2525

26+
import org.opends.server.types.Entry;
2627
import org.testng.annotations.BeforeClass;
2728
import org.testng.annotations.Test;
2829

@@ -60,6 +61,61 @@ public void startServer() throws Exception {
6061
"objectclass: extensibleobject",
6162
"cn: President",
6263
"aliasedobjectname: cn=John Doe, o=MyCompany, o=test",
64+
"",
65+
66+
"dn: ou=employees,o=test",
67+
"objectClass: top",
68+
"objectClass: organizationalUnit",
69+
"ou: employees",
70+
"description: All employees",
71+
"",
72+
"dn: uid=jdoe,ou=employees,o=test",
73+
"objectClass: alias",
74+
"objectClass: top",
75+
"objectClass: extensibleObject",
76+
"aliasedObjectName: uid=jdoe,ou=researchers,o=test",
77+
"uid: jdoe",
78+
"",
79+
"dn: ou=researchers,o=test",
80+
"objectClass: top",
81+
"objectClass: organizationalUnit",
82+
"ou: researchers",
83+
"description: All reasearchers",
84+
"",
85+
"dn: uid=jdoe,ou=researchers,o=test",
86+
"objectClass: alias",
87+
"objectClass: top",
88+
"objectClass: extensibleObject",
89+
"aliasedObjectName: uid=jdoe,ou=employees,o=test",
90+
"uid: jdoe",
91+
92+
"",
93+
"dn: ou=students,o=test",
94+
"objectClass: top",
95+
"objectClass: organizationalUnit",
96+
"ou: students",
97+
"description: All students",
98+
"",
99+
"dn: uid=janedoe,ou=students,o=test",
100+
"objectClass: alias",
101+
"objectClass: top",
102+
"objectClass: extensibleObject",
103+
"aliasedObjectName: uid=janedoe,ou=researchers,o=test",
104+
"uid: janedoe",
105+
"",
106+
"dn: uid=janedoe,ou=researchers,o=test",
107+
"objectClass: alias",
108+
"objectClass: top",
109+
"objectClass: extensibleObject",
110+
"aliasedObjectName: uid=janedoe,ou=employees,o=test",
111+
"uid: janedoe",
112+
"",
113+
"dn: uid=janedoe,ou=employees,o=test",
114+
"objectClass: alias",
115+
"objectClass: top",
116+
"objectClass: extensibleObject",
117+
"aliasedObjectName: uid=janedoe,ou=students,o=test",
118+
"uid: janedoe",
63119
""
64120
);
65121

@@ -70,7 +126,11 @@ public void startServer() throws Exception {
70126
}
71127

72128
public HashMap<String,SearchResultEntry> search(SearchScope scope,DereferenceAliasesPolicy policy) throws SearchResultReferenceIOException, LdapException {
73-
final SearchRequest request =Requests.newSearchRequest("ou=Area1,o=test", scope,"(objectclass=*)")
129+
return search("ou=Area1,o=test", scope, policy);
130+
}
131+
132+
public HashMap<String,SearchResultEntry> search(String dn, SearchScope scope,DereferenceAliasesPolicy policy) throws SearchResultReferenceIOException, LdapException {
133+
final SearchRequest request =Requests.newSearchRequest(dn, scope,"(objectclass=*)")
74134
.setDereferenceAliasesPolicy(policy);
75135
System.out.println("---------------------------------------------------------------------------------------");
76136
System.out.println(request);
@@ -125,7 +185,7 @@ public void test_base_find() throws SearchResultReferenceIOException, LdapExcept
125185
// It returns ou=Area1,o=test.
126186
@Test
127187
public void test_base_search() throws SearchResultReferenceIOException, LdapException {
128-
HashMap<String,SearchResultEntry> res=search(SearchScope.BASE_OBJECT,DereferenceAliasesPolicy.IN_SEARCHING);
188+
HashMap<String,SearchResultEntry> res=search(SearchScope.BASE_OBJECT, DereferenceAliasesPolicy.IN_SEARCHING);
129189

130190
assertThat(res.containsKey("ou=Area1,o=test")).isTrue();
131191
assertThat(res.containsKey("o=MyCompany,o=test")).isFalse();
@@ -308,4 +368,39 @@ public void test_sub_always() throws SearchResultReferenceIOException, LdapExcep
308368
assertThat(res.containsKey("cn=John Doe,o=MyCompany,o=test")).isTrue();
309369
}
310370

371+
// Dereferencing recursion avoidance test.
372+
@Test
373+
public void test_alias_recursive() throws LdapException, SearchResultReferenceIOException {
374+
HashMap<String, SearchResultEntry> res = search("uid=jdoe,ou=employees,o=test", SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.ALWAYS);
375+
376+
assertThat(res.containsKey("uid=jdoe,ou=employees,o=test")).isTrue();
377+
assertThat(res.containsKey("uid=jdoe,ou=researchers,o=test")).isFalse();
378+
}
379+
380+
@Test
381+
public void test_alias_recursive_loop() throws LdapException, SearchResultReferenceIOException {
382+
HashMap<String, SearchResultEntry> res = search("uid=janedoe,ou=students,o=test", SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.ALWAYS);
383+
384+
assertThat(res.containsKey("uid=janedoe,ou=students,o=test")).isTrue();
385+
assertThat(res.containsKey("uid=janedoe,ou=researches,o=test")).isFalse();
386+
assertThat(res.containsKey("uid=janedoe,ou=employees,o=test")).isFalse();
387+
}
388+
389+
@Test(expectedExceptions = LdapException.class)
390+
public void test_stackoverflow() throws Exception {
391+
392+
String entryTemplate = "dn: uid={uid},ou=employees,o=test\n" +
393+
"objectClass: alias\n" +
394+
"objectClass: top\n" +
395+
"objectClass: extensibleObject\n" +
396+
"aliasedObjectName: uid={alias},ou=employees,o=test \n" +
397+
"uid: {uid}\n";
398+
final String firstDn = "uid=jdoe0,ou=employees,o=test";
399+
for(int i = 0; i < 10000; i++) {
400+
String entryStr = entryTemplate.replace("{uid}", "jdoe" + i).replace("{alias}", "jdoe" + (i + 1));
401+
Entry entry = TestCaseUtils.makeEntry(entryStr);
402+
TestCaseUtils.addEntry(entry);
403+
}
404+
search(firstDn, SearchScope.WHOLE_SUBTREE, DereferenceAliasesPolicy.ALWAYS);
405+
}
311406
}

0 commit comments

Comments
 (0)