Skip to content

Commit

Permalink
Merge pull request #790 from noloader/search-filter
Browse files Browse the repository at this point in the history
Update LDAP encoders
  • Loading branch information
xeno6696 authored Oct 13, 2023
2 parents 707fb49 + fea91b3 commit dd0bbb2
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 12 deletions.
41 changes: 41 additions & 0 deletions src/main/java/org/owasp/esapi/Encoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -444,32 +444,73 @@ public interface Encoder {
/**
* Encode data for use in LDAP queries. Wildcard (*) characters will be encoded.
*
* This encoder operates according to RFC 4515, Section 3. RFC 4515 says the following character ranges
* are valid: 0x01-0x27, 0x2B-0x5B and 0x5D-0x7F. Characters outside the ranges are hex encoded, and they
* include 0x00 (NUL), 0x28 (LPAREN), 0x29 (RPAREN), 0x2A (ASTERISK), and 0x5C (ESC). The encoder will also
* encode 0x2F (FSLASH), which is required by Microsoft Active Directory.
*
* NB: At ESAPI 2.5.3, {@code encodeForLDAP} began strict conformance with RFC 4515. Characters above 0x7F
* are converted to UTF-8, and then the byte sequences are hex encoded according to the RFC.
*
* @param input
* the text to encode for LDAP
*
* @return input encoded for use in LDAP
*
* @see <a href="https://www.ietf.org/rfc/rfc4515.txt">RFC 4515, Lightweight Directory Access Protocol
* (LDAP): String Representation of Search Filters</a>
*
* @since ESAPI 1.3
*/
String encodeForLDAP(String input);

/**
* Encode data for use in LDAP queries. You have the option whether or not to encode wildcard (*) characters.
*
* This encoder operates according to RFC 4515, Section 3. RFC 4515 says the following character ranges
* are valid: 0x01-0x27, 0x2B-0x5B and 0x5D-0x7F. Characters outside the ranges are hex encoded, and they
* include 0x00 (NUL), 0x28 (LPAREN), 0x29 (RPAREN), 0x2A (ASTERISK), and 0x5C (ESC). The encoder will also
* encode 0x2F (FSLASH), which is required by Microsoft Active Directory.
*
* NB: At ESAPI 2.5.3, {@code encodeForLDAP} began strict conformance with RFC 4515. Characters above 0x7F
* are converted to UTF-8, and then the byte sequences are hex encoded according to the RFC.
*
* @param input
* the text to encode for LDAP
* @param encodeWildcards
* whether or not wildcard (*) characters will be encoded.
*
* @return input encoded for use in LDAP
*
* @see <a href="https://www.ietf.org/rfc/rfc4515.txt">RFC 4515, Lightweight Directory Access Protocol
* (LDAP): String Representation of Search Filters</a>
*
* @since ESAPI 1.3
*/
String encodeForLDAP(String input, boolean encodeWildcards);

/**
* Encode data for use in an LDAP distinguished name.
*
* This encoder operates according to RFC 4514, Section 3. RFC 4514 says the following character ranges
* are valid: 0x01-0x21, 0x23-0x2A, 0x2D-0x3A, 0x3D, 0x3F-0x5B, 0x5D-0x7F. Characters outside the ranges
* are hex encoded, and they include 0x00 (NUL), 0x22 (DQUOTE), 0x2B (PLUS), 0x2C (COMMA),
* 0x3B (SEMI), 0x3C (LANGLE), 0x3E (RANGLE) and 0x5C (ESC). The encoder will also encode 0x2F (FSLASH),
* which is required by Microsoft Active Directory. The leading and trailing characters in a distinguished
* name string will also have 0x20 (SPACE) and 0x23 (SHARP) encoded.
*
* NB: At ESAPI 2.5.3, {@code encodeForDN} began strict conformance with RFC 4514. Characters above 0x7F
* are converted to UTF-8, and then the byte sequences are hex encoded according to the RFC.
*
* @param input
* the text to encode for an LDAP distinguished name
*
* @return input encoded for use in an LDAP distinguished name
*
* @see <a href="https://www.ietf.org/rfc/rfc4514.txt">RFC 4514, Lightweight Directory Access Protocol
* (LDAP): String Representation of Distinguished Names</a>
*
* @since ESAPI 1.3
*/
String encodeForDN(String input);

Expand Down
30 changes: 28 additions & 2 deletions src/main/java/org/owasp/esapi/reference/DefaultEncoder.java
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ public String encodeForLDAP(String input, boolean encodeWildcards) {
// According to Microsoft docs [1,2], the forward slash ('/') MUST be escaped.
// According to RFC 4515 Section 3 [3], the forward slash (and other characters) MAY be escaped.
// Since Microsoft is a MUST, escape forward slash for all implementations. Also see discussion at [4].
// Characters above 0x7F are converted to UTF-8 and then hex encoded in the default case.
// [1] https://docs.microsoft.com/en-us/windows/win32/adsi/search-filter-syntax
// [2] https://social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx
// [3] https://tools.ietf.org/search/rfc4515#section-3
Expand Down Expand Up @@ -343,7 +344,18 @@ public String encodeForLDAP(String input, boolean encodeWildcards) {
sb.append("\\00");
break;
default:
sb.append(c);
if (c >= 0x80) {
try {
final byte[] u = String.valueOf(c).getBytes("UTF-8");
for (byte b : u) {
sb.append(String.format("\\%02x", b));
}
} catch (UnsupportedEncodingException ex) {
// UTF-8 is always supported
}
} else {
sb.append(c);
}
}
}
return sb.toString();
Expand All @@ -365,6 +377,9 @@ public String encodeForDN(String input) {
for (int i = 0; i < input.length(); i++) {
char c = input.charAt(i);
switch (c) {
case '\0':
sb.append("\\00");
break;
case '\\':
sb.append("\\\\");
break;
Expand All @@ -390,7 +405,18 @@ public String encodeForDN(String input) {
sb.append("\\;");
break;
default:
sb.append(c);
if (c >= 0x80) {
try {
final byte[] u = String.valueOf(c).getBytes("UTF-8");
for (byte b : u) {
sb.append(String.format("\\%02x", b));
}
} catch (UnsupportedEncodingException ex) {
// UTF-8 is always supported
}
} else {
sb.append(c);
}
}
}
// add the trailing backslash if needed
Expand Down
87 changes: 77 additions & 10 deletions src/test/java/org/owasp/esapi/reference/EncoderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -528,46 +528,113 @@ public void testMySQLANSIModeQuoteInjection() {

/**
* Test of encodeForLDAP method, of class org.owasp.esapi.Encoder.
*
* Additional tests: https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
*/
public void testEncodeForLDAP() {
System.out.println("encodeForLDAP");
Encoder instance = ESAPI.encoder();
assertEquals(null, instance.encodeForLDAP(null));
assertEquals("No special characters to escape", "Hi This is a test #��", instance.encodeForLDAP("Hi This is a test #��"));
assertEquals("Zeros", "Hi \\00", instance.encodeForLDAP("Hi \u0000"));
assertEquals("LDAP Christams Tree", "Hi \\28This\\29 = is \\2a a \\5c test # � � �", instance.encodeForLDAP("Hi (This) = is * a \\ test # � � �"));
assertEquals("No special characters to escape", "Hi This is a test", instance.encodeForLDAP("Hi This is a test"));
assertEquals("No special characters to escape", "Hi This is a test \u0007f", instance.encodeForLDAP("Hi This is a test \u0007f"));
assertEquals("Special characters to escape", "Hi This is a test \\c2\\80", instance.encodeForLDAP("Hi This is a test \u0080"));
assertEquals("Special characters to escape", "Hi This is a test \\c3\\bf", instance.encodeForLDAP("Hi This is a test \u00FF"));
assertEquals("Special characters to escape", "Hi This is a test \\df\\bf", instance.encodeForLDAP("Hi This is a test \u07FF"));
assertEquals("Special characters to escape", "Hi This is a test \\e0\\a0\\80", instance.encodeForLDAP("Hi This is a test \u0800"));
assertEquals("Special characters to escape", "Hi This is a test \\e0\\a3\\bf", instance.encodeForLDAP("Hi This is a test \u08FF"));
assertEquals("Special characters to escape", "Hi This is a test \\e7\\bf\\bf", instance.encodeForLDAP("Hi This is a test \u7FFF"));
assertEquals("Special characters to escape", "Hi This is a test \\e8\\80\\80", instance.encodeForLDAP("Hi This is a test \u8000"));
assertEquals("Special characters to escape", "Hi This is a test \\e8\\bf\\bf", instance.encodeForLDAP("Hi This is a test \u8FFF"));
assertEquals("Special characters to escape", "Hi This is a test \\ef\\bf\\bf", instance.encodeForLDAP("Hi This is a test \uFFFF"));
assertEquals("Special characters to escape", "Hi This is a test #\\ef\\bf\\bd\\ef\\bf\\bd", instance.encodeForLDAP("Hi This is a test #��"));
assertEquals("NUL", "Hi \\00", instance.encodeForLDAP("Hi \u0000"));
assertEquals("LPAREN", "Hi \\28", instance.encodeForLDAP("Hi ("));
assertEquals("RPAREN", "Hi \\29", instance.encodeForLDAP("Hi )"));
assertEquals("ASTERISK", "Hi \\2a", instance.encodeForLDAP("Hi *"));
assertEquals("SLASH", "Hi \\2f", instance.encodeForLDAP("Hi /"));
assertEquals("ESC", "Hi \\5c", instance.encodeForLDAP("Hi \\"));
assertEquals("LDAP Christams Tree", "Hi \\28This\\29 = is \\2a a \\5c test # \\ef\\bf\\bd \\ef\\bf\\bd \\ef\\bf\\bd", instance.encodeForLDAP("Hi (This) = is * a \\ test # � � �"));
assertEquals("Hi \\28This\\29 =", instance.encodeForLDAP("Hi (This) ="));
assertEquals("Forward slash for \\2fMicrosoft\\2f \\2fAD\\2f", instance.encodeForLDAP("Forward slash for /Microsoft/ /AD/"));
assertEquals("RFC 4515, Section 4", "(cn=Babs Jensen)", "(cn=" + instance.encodeForLDAP("Babs Jensen") + ")");
}

/**
* Test of encodeForLDAP method with without encoding wildcard characters, of class org.owasp.esapi.Encoder.
*
* Additional tests: https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
*/
public void testEncodeForLDAPWithoutEncodingWildcards() {
System.out.println("encodeForLDAPWithoutEncodingWildcards");
Encoder instance = ESAPI.encoder();
assertEquals(null, instance.encodeForLDAP(null, false));
assertEquals("No special characters to escape", "Hi This is a test #��", instance.encodeForLDAP("Hi This is a test #��", false));
assertEquals("Zeros", "Hi \\00", instance.encodeForLDAP("Hi \u0000", false));
assertEquals("LDAP Christams Tree", "Hi \\28This\\29 = is * a \\5c test # � � �", instance.encodeForLDAP("Hi (This) = is * a \\ test # � � �", false));
assertEquals("No special characters to escape", "Hi This is a test", instance.encodeForLDAP("Hi This is a test"));
assertEquals("No special characters to escape", "Hi This is a test \u0007f", instance.encodeForLDAP("Hi This is a test \u0007f", false));
assertEquals("Special characters to escape", "Hi This is a test \\c2\\80", instance.encodeForLDAP("Hi This is a test \u0080", false));
assertEquals("Special characters to escape", "Hi This is a test \\c3\\bf", instance.encodeForLDAP("Hi This is a test \u00FF", false));
assertEquals("Special characters to escape", "Hi This is a test \\df\\bf", instance.encodeForLDAP("Hi This is a test \u07FF", false));
assertEquals("Special characters to escape", "Hi This is a test \\e0\\a0\\80", instance.encodeForLDAP("Hi This is a test \u0800", false));
assertEquals("Special characters to escape", "Hi This is a test \\e0\\a3\\bf", instance.encodeForLDAP("Hi This is a test \u08FF", false));
assertEquals("Special characters to escape", "Hi This is a test \\e7\\bf\\bf", instance.encodeForLDAP("Hi This is a test \u7FFF", false));
assertEquals("Special characters to escape", "Hi This is a test \\e8\\80\\80", instance.encodeForLDAP("Hi This is a test \u8000", false));
assertEquals("Special characters to escape", "Hi This is a test \\e8\\bf\\bf", instance.encodeForLDAP("Hi This is a test \u8FFF", false));
assertEquals("Special characters to escape", "Hi This is a test \\ef\\bf\\bf", instance.encodeForLDAP("Hi This is a test \uFFFF", false));
assertEquals("Special characters to escape", "Hi This is a test #\\ef\\bf\\bd\\ef\\bf\\bd", instance.encodeForLDAP("Hi This is a test #��", false));
assertEquals("NUL", "Hi \\00", instance.encodeForLDAP("Hi \u0000", false));
assertEquals("LPAREN", "Hi \\28", instance.encodeForLDAP("Hi (", false));
assertEquals("RPAREN", "Hi \\29", instance.encodeForLDAP("Hi )", false));
assertEquals("ASTERISK", "Hi *", instance.encodeForLDAP("Hi *", false));
assertEquals("SLASH", "Hi \\2f", instance.encodeForLDAP("Hi /", false));
assertEquals("ESC", "Hi \\5c", instance.encodeForLDAP("Hi \\", false));
assertEquals("LDAP Christams Tree", "Hi \\28This\\29 = is * a \\5c test # \\ef\\bf\\bd \\ef\\bf\\bd \\ef\\bf\\bd", instance.encodeForLDAP("Hi (This) = is * a \\ test # � � �", false));
assertEquals("Forward slash for \\2fMicrosoft\\2f \\2fAD\\2f", instance.encodeForLDAP("Forward slash for /Microsoft/ /AD/"));
assertEquals("RFC 4515, Section 4", "(&(objectClass=Person)(|(sn=Jensen)(cn=Babs J*)))",
"(&(objectClass=" + instance.encodeForLDAP("Person") + ")(|(sn=" + instance.encodeForLDAP("Jensen") + ")(cn=" + instance.encodeForLDAP("Babs J*", false) + ")))");
assertEquals("RFC 4515, Section 4", "(o=univ*of*mich*)",
"(o=" + instance.encodeForLDAP("univ*of*mich*", false) + ")");
}

/**
* Test of encodeForDN method, of class org.owasp.esapi.Encoder.
*
* Additional tests: https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
*/
public void testEncodeForDN() {
System.out.println("encodeForDN");
Encoder instance = ESAPI.encoder();
assertEquals(null, instance.encodeForDN(null));
assertEquals("No special characters to escape", "Hello�", instance.encodeForDN("Hello�"));
assertEquals("leading #", "\\# Hello�", instance.encodeForDN("# Hello�"));
assertEquals("leading space", "\\ Hello�", instance.encodeForDN(" Hello�"));
assertEquals("trailing space", "Hello�\\ ", instance.encodeForDN("Hello� "));
assertEquals("No special characters to escape", "Hello", instance.encodeForDN("Hello"));
assertEquals("No special characters to escape", "Hello \u0007f", instance.encodeForDN("Hello \u0007f"));
assertEquals("Special characters to escape", "Hello \\c2\\80", instance.encodeForDN("Hello \u0080"));
assertEquals("Special characters to escape", "Hello \\c3\\bf", instance.encodeForDN("Hello \u00FF"));
assertEquals("Special characters to escape", "Hello \\df\\bf", instance.encodeForDN("Hello \u07FF"));
assertEquals("Special characters to escape", "Hello \\e0\\a0\\80", instance.encodeForDN("Hello \u0800"));
assertEquals("Special characters to escape", "Hello \\e0\\a3\\bf", instance.encodeForLDAP("Hello \u08FF"));
assertEquals("Special characters to escape", "Hello \\e7\\bf\\bf", instance.encodeForDN("Hello \u7FFF"));
assertEquals("Special characters to escape", "Hello \\e8\\80\\80", instance.encodeForDN("Hello \u8000"));
assertEquals("Special characters to escape", "Hello \\e8\\bf\\bf", instance.encodeForDN("Hello \u8FFF"));
assertEquals("Special characters to escape", "Hello \\ef\\bf\\bf", instance.encodeForDN("Hello \uFFFF"));
assertEquals("Special characters to escape", "Hello\\ef\\bf\\bd", instance.encodeForDN("Hello�"));
assertEquals("NUL", "Hi \\00", instance.encodeForDN("Hi \u0000"));
assertEquals("DQUOTE", "Hi \\\"", instance.encodeForDN("Hi \""));
assertEquals("PLUS", "Hi \\+", instance.encodeForDN("Hi +"));
assertEquals("COMMA", "Hi \\,", instance.encodeForDN("Hi ,"));
assertEquals("SLASH", "Hi \\/", instance.encodeForDN("Hi /"));
assertEquals("SEMI", "Hi \\;", instance.encodeForDN("Hi ;"));
assertEquals("LANGLE", "Hi \\<", instance.encodeForDN("Hi <"));
assertEquals("RANGLE", "Hi \\>", instance.encodeForDN("Hi >"));
assertEquals("ESC", "Hi \\\\", instance.encodeForDN("Hi \\"));
assertEquals("leading #", "\\# Hello\\ef\\bf\\bd", instance.encodeForDN("# Hello�"));
assertEquals("leading space", "\\ Hello\\ef\\bf\\bd", instance.encodeForDN(" Hello�"));
assertEquals("trailing space", "Hello\\ef\\bf\\bd\\ ", instance.encodeForDN("Hello� "));
assertEquals("less than greater than", "Hello\\<\\>", instance.encodeForDN("Hello<>"));
assertEquals("only 3 spaces", "\\ \\ ", instance.encodeForDN(" "));
assertEquals("Christmas Tree DN", "\\ Hello\\\\ \\+ \\, \\\"World\\\" \\;\\ ", instance.encodeForDN(" Hello\\ + , \"World\" ; "));
assertEquals("Forward slash for \\/Microsoft\\/ \\/AD\\/", instance.encodeForDN("Forward slash for /Microsoft/ /AD/"));
assertEquals("RFC 4514, Section 4", "CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net",
"CN=" + instance.encodeForDN("James \"Jim\" Smith, III") + ",DC=" + instance.encodeForDN("example") + ",DC=" + instance.encodeForDN("net"));
assertEquals("RFC 4514, Section 4", "CN=Lu\\c4\\8di\\c4\\87",
"CN=" + instance.encodeForDN("\u004C\u0075\u010D\u0069\u0107"));
}

/**
Expand Down

0 comments on commit dd0bbb2

Please sign in to comment.