Skip to content

Commit d5086d8

Browse files
committed
Update LDAP encoders
This commit updates: * LDAP encoder documentation; * LDAP encoding in encodeForDN and encodeForLDAP; * LDAP tests for encodeForDN and encodeForLDAP
1 parent b703ff9 commit d5086d8

File tree

3 files changed

+144
-12
lines changed

3 files changed

+144
-12
lines changed

src/main/java/org/owasp/esapi/Encoder.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,32 +444,73 @@ public interface Encoder {
444444
/**
445445
* Encode data for use in LDAP queries. Wildcard (*) characters will be encoded.
446446
*
447+
* This encoder operates according to RFC 4515, Section 3. RFC 4515 says the following character ranges
448+
* are valid: 0x01-0x27, 0x2B-0x5B and 0x5D-0x7F. Characters outside the ranges are hex encoded, and they
449+
* include 0x00 (NUL), 0x28 (LPAREN), 0x29 (RPAREN), 0x2A (ASTERISK), and 0x5C (ESC). The encoder will also
450+
* encode 0x2F (FSLASH), which is required by Microsoft Active Directory.
451+
*
452+
* NB: At ESAPI 2.5.3, {@code encodeForLDAP} began strict conformance with RFC 4515. Characters above 0x7F
453+
* are converted to UTF-8, and then the byte sequences are hex encoded according to the RFC.
454+
*
447455
* @param input
448456
* the text to encode for LDAP
449457
*
450458
* @return input encoded for use in LDAP
459+
*
460+
* @see <a href="https://www.ietf.org/rfc/rfc4515.txt">RFC 4515, Lightweight Directory Access Protocol
461+
* (LDAP): String Representation of Search Filters</a>
462+
*
463+
* @since ESAPI 1.3
451464
*/
452465
String encodeForLDAP(String input);
453466

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

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

src/main/java/org/owasp/esapi/reference/DefaultEncoder.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ public String encodeForLDAP(String input, boolean encodeWildcards) {
311311
// According to Microsoft docs [1,2], the forward slash ('/') MUST be escaped.
312312
// According to RFC 4515 Section 3 [3], the forward slash (and other characters) MAY be escaped.
313313
// Since Microsoft is a MUST, escape forward slash for all implementations. Also see discussion at [4].
314+
// Characters above 0x7F are converted to UTF-8 and then hex encoded in the default case.
314315
// [1] https://docs.microsoft.com/en-us/windows/win32/adsi/search-filter-syntax
315316
// [2] https://social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx
316317
// [3] https://tools.ietf.org/search/rfc4515#section-3
@@ -343,7 +344,18 @@ public String encodeForLDAP(String input, boolean encodeWildcards) {
343344
sb.append("\\00");
344345
break;
345346
default:
346-
sb.append(c);
347+
if (c >= 0x80) {
348+
try {
349+
final byte[] u = String.valueOf(c).getBytes("UTF-8");
350+
for (byte b : u) {
351+
sb.append(String.format("\\%02x", b));
352+
}
353+
} catch (UnsupportedEncodingException ex) {
354+
// UTF-8 is always supported
355+
}
356+
} else {
357+
sb.append(c);
358+
}
347359
}
348360
}
349361
return sb.toString();
@@ -365,6 +377,9 @@ public String encodeForDN(String input) {
365377
for (int i = 0; i < input.length(); i++) {
366378
char c = input.charAt(i);
367379
switch (c) {
380+
case '\0':
381+
sb.append("\\00");
382+
break;
368383
case '\\':
369384
sb.append("\\\\");
370385
break;
@@ -390,7 +405,18 @@ public String encodeForDN(String input) {
390405
sb.append("\\;");
391406
break;
392407
default:
393-
sb.append(c);
408+
if (c >= 0x80) {
409+
try {
410+
final byte[] u = String.valueOf(c).getBytes("UTF-8");
411+
for (byte b : u) {
412+
sb.append(String.format("\\%02x", b));
413+
}
414+
} catch (UnsupportedEncodingException ex) {
415+
// UTF-8 is always supported
416+
}
417+
} else {
418+
sb.append(c);
419+
}
394420
}
395421
}
396422
// add the trailing backslash if needed

src/test/java/org/owasp/esapi/reference/EncoderTest.java

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -528,46 +528,111 @@ public void testMySQLANSIModeQuoteInjection() {
528528

529529
/**
530530
* Test of encodeForLDAP method, of class org.owasp.esapi.Encoder.
531+
*
532+
* Additional tests: https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
531533
*/
532534
public void testEncodeForLDAP() {
533535
System.out.println("encodeForLDAP");
534536
Encoder instance = ESAPI.encoder();
535537
assertEquals(null, instance.encodeForLDAP(null));
536-
assertEquals("No special characters to escape", "Hi This is a test #��", instance.encodeForLDAP("Hi This is a test #��"));
537-
assertEquals("Zeros", "Hi \\00", instance.encodeForLDAP("Hi \u0000"));
538-
assertEquals("LDAP Christams Tree", "Hi \\28This\\29 = is \\2a a \\5c test # � � �", instance.encodeForLDAP("Hi (This) = is * a \\ test # � � �"));
538+
assertEquals("No special characters to escape", "Hi This is a test", instance.encodeForLDAP("Hi This is a test"));
539+
assertEquals("No special characters to escape", "Hi This is a test \u0007f", instance.encodeForLDAP("Hi This is a test \u0007f"));
540+
assertEquals("Special characters to escape", "Hi This is a test \\c2\\80", instance.encodeForLDAP("Hi This is a test \u0080"));
541+
assertEquals("Special characters to escape", "Hi This is a test \\c3\\bf", instance.encodeForLDAP("Hi This is a test \u00FF"));
542+
assertEquals("Special characters to escape", "Hi This is a test \\df\\bf", instance.encodeForLDAP("Hi This is a test \u07FF"));
543+
assertEquals("Special characters to escape", "Hi This is a test \\e0\\a0\\80", instance.encodeForLDAP("Hi This is a test \u0800"));
544+
assertEquals("Special characters to escape", "Hi This is a test \\e0\\a3\\bf", instance.encodeForLDAP("Hi This is a test \u08FF"));
545+
assertEquals("Special characters to escape", "Hi This is a test \\e7\\bf\\bf", instance.encodeForLDAP("Hi This is a test \u7FFF"));
546+
assertEquals("Special characters to escape", "Hi This is a test \\e8\\80\\80", instance.encodeForLDAP("Hi This is a test \u8000"));
547+
assertEquals("Special characters to escape", "Hi This is a test \\e8\\bf\\bf", instance.encodeForLDAP("Hi This is a test \u8FFF"));
548+
assertEquals("Special characters to escape", "Hi This is a test \\ef\\bf\\bf", instance.encodeForLDAP("Hi This is a test \uFFFF"));
549+
assertEquals("Special characters to escape", "Hi This is a test #\\ef\\bf\\bd\\ef\\bf\\bd", instance.encodeForLDAP("Hi This is a test #��"));
550+
assertEquals("NUL", "Hi \\00", instance.encodeForLDAP("Hi \u0000"));
551+
assertEquals("LPAREN", "Hi \\28", instance.encodeForLDAP("Hi ("));
552+
assertEquals("RPAREN", "Hi \\29", instance.encodeForLDAP("Hi )"));
553+
assertEquals("ASTERISK", "Hi \\2a", instance.encodeForLDAP("Hi *"));
554+
assertEquals("SLASH", "Hi \\2f", instance.encodeForLDAP("Hi /"));
555+
assertEquals("ESC", "Hi \\5c", instance.encodeForLDAP("Hi \\"));
556+
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 # � � �"));
539557
assertEquals("Hi \\28This\\29 =", instance.encodeForLDAP("Hi (This) ="));
540558
assertEquals("Forward slash for \\2fMicrosoft\\2f \\2fAD\\2f", instance.encodeForLDAP("Forward slash for /Microsoft/ /AD/"));
559+
assertEquals("RFC 4515, Section 4", "(cn=Babs Jensen)", "(cn=" + instance.encodeForLDAP("Babs Jensen") + ")");
541560
}
542561

543562
/**
544563
* Test of encodeForLDAP method with without encoding wildcard characters, of class org.owasp.esapi.Encoder.
564+
*
565+
* Additional tests: https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
545566
*/
546567
public void testEncodeForLDAPWithoutEncodingWildcards() {
547568
System.out.println("encodeForLDAPWithoutEncodingWildcards");
548569
Encoder instance = ESAPI.encoder();
549570
assertEquals(null, instance.encodeForLDAP(null, false));
550-
assertEquals("No special characters to escape", "Hi This is a test #��", instance.encodeForLDAP("Hi This is a test #��", false));
551-
assertEquals("Zeros", "Hi \\00", instance.encodeForLDAP("Hi \u0000", false));
552-
assertEquals("LDAP Christams Tree", "Hi \\28This\\29 = is * a \\5c test # � � �", instance.encodeForLDAP("Hi (This) = is * a \\ test # � � �", false));
571+
assertEquals("No special characters to escape", "Hi This is a test", instance.encodeForLDAP("Hi This is a test"));
572+
assertEquals("No special characters to escape", "Hi This is a test \u0007f", instance.encodeForLDAP("Hi This is a test \u0007f", false));
573+
assertEquals("Special characters to escape", "Hi This is a test \\c2\\80", instance.encodeForLDAP("Hi This is a test \u0080", false));
574+
assertEquals("Special characters to escape", "Hi This is a test \\c3\\bf", instance.encodeForLDAP("Hi This is a test \u00FF", false));
575+
assertEquals("Special characters to escape", "Hi This is a test \\df\\bf", instance.encodeForLDAP("Hi This is a test \u07FF", false));
576+
assertEquals("Special characters to escape", "Hi This is a test \\e0\\a0\\80", instance.encodeForLDAP("Hi This is a test \u0800", false));
577+
assertEquals("Special characters to escape", "Hi This is a test \\e0\\a3\\bf", instance.encodeForLDAP("Hi This is a test \u08FF", false));
578+
assertEquals("Special characters to escape", "Hi This is a test \\e7\\bf\\bf", instance.encodeForLDAP("Hi This is a test \u7FFF", false));
579+
assertEquals("Special characters to escape", "Hi This is a test \\e8\\80\\80", instance.encodeForLDAP("Hi This is a test \u8000", false));
580+
assertEquals("Special characters to escape", "Hi This is a test \\e8\\bf\\bf", instance.encodeForLDAP("Hi This is a test \u8FFF", false));
581+
assertEquals("Special characters to escape", "Hi This is a test \\ef\\bf\\bf", instance.encodeForLDAP("Hi This is a test \uFFFF", false));
582+
assertEquals("Special characters to escape", "Hi This is a test #\\ef\\bf\\bd\\ef\\bf\\bd", instance.encodeForLDAP("Hi This is a test #��", false));
583+
assertEquals("NUL", "Hi \\00", instance.encodeForLDAP("Hi \u0000", false));
584+
assertEquals("LPAREN", "Hi \\28", instance.encodeForLDAP("Hi (", false));
585+
assertEquals("RPAREN", "Hi \\29", instance.encodeForLDAP("Hi )", false));
586+
assertEquals("ASTERISK", "Hi *", instance.encodeForLDAP("Hi *", false));
587+
assertEquals("SLASH", "Hi \\2f", instance.encodeForLDAP("Hi /", false));
588+
assertEquals("ESC", "Hi \\5c", instance.encodeForLDAP("Hi \\", false));
589+
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));
553590
assertEquals("Forward slash for \\2fMicrosoft\\2f \\2fAD\\2f", instance.encodeForLDAP("Forward slash for /Microsoft/ /AD/"));
591+
assertEquals("RFC 4515, Section 4", "(&(objectClass=Person)(|(sn=Jensen)(cn=Babs J*)))",
592+
"(&(objectClass=" + instance.encodeForLDAP("Person") + ")(|(sn=" + instance.encodeForLDAP("Jensen") + ")(cn=" + instance.encodeForLDAP("Babs J*", false) + ")))");
593+
assertEquals("RFC 4515, Section 4", "(o=univ*of*mich*)",
594+
"(o=" + instance.encodeForLDAP("univ*of*mich*", false) + ")");
554595
}
555596

556597
/**
557598
* Test of encodeForDN method, of class org.owasp.esapi.Encoder.
599+
*
600+
* Additional tests: https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
558601
*/
559602
public void testEncodeForDN() {
560603
System.out.println("encodeForDN");
561604
Encoder instance = ESAPI.encoder();
562605
assertEquals(null, instance.encodeForDN(null));
563-
assertEquals("No special characters to escape", "Hello�", instance.encodeForDN("Hello�"));
564-
assertEquals("leading #", "\\# Hello�", instance.encodeForDN("# Hello�"));
565-
assertEquals("leading space", "\\ Hello�", instance.encodeForDN(" Hello�"));
566-
assertEquals("trailing space", "Hello�\\ ", instance.encodeForDN("Hello� "));
606+
assertEquals("No special characters to escape", "Hello", instance.encodeForDN("Hello"));
607+
assertEquals("No special characters to escape", "Hello \u0007f", instance.encodeForDN("Hello \u0007f"));
608+
assertEquals("Special characters to escape", "Hello \\c2\\80", instance.encodeForDN("Hello \u0080"));
609+
assertEquals("Special characters to escape", "Hello \\c3\\bf", instance.encodeForDN("Hello \u00FF"));
610+
assertEquals("Special characters to escape", "Hello \\df\\bf", instance.encodeForDN("Hello \u07FF"));
611+
assertEquals("Special characters to escape", "Hello \\e0\\a0\\80", instance.encodeForDN("Hello \u0800"));
612+
assertEquals("Special characters to escape", "Hello \\e0\\a3\\bf", instance.encodeForLDAP("Hello \u08FF"));
613+
assertEquals("Special characters to escape", "Hello \\e7\\bf\\bf", instance.encodeForDN("Hello \u7FFF"));
614+
assertEquals("Special characters to escape", "Hello \\e8\\80\\80", instance.encodeForDN("Hello \u8000"));
615+
assertEquals("Special characters to escape", "Hello \\e8\\bf\\bf", instance.encodeForDN("Hello \u8FFF"));
616+
assertEquals("Special characters to escape", "Hello \\ef\\bf\\bf", instance.encodeForDN("Hello \uFFFF"));
617+
assertEquals("Special characters to escape", "Hello\\ef\\bf\\bd", instance.encodeForDN("Hello�"));
618+
assertEquals("NUL", "Hi \\00", instance.encodeForDN("Hi \u0000"));
619+
assertEquals("DQUOTE", "Hi \\\"", instance.encodeForDN("Hi \""));
620+
assertEquals("PLUS", "Hi \\+", instance.encodeForDN("Hi +"));
621+
assertEquals("COMMA", "Hi \\,", instance.encodeForDN("Hi ,"));
622+
assertEquals("SLASH", "Hi \\/", instance.encodeForDN("Hi /"));
623+
assertEquals("SEMI", "Hi \\;", instance.encodeForDN("Hi ;"));
624+
assertEquals("LANGLE", "Hi \\<", instance.encodeForDN("Hi <"));
625+
assertEquals("RANGLE", "Hi \\>", instance.encodeForDN("Hi >"));
626+
assertEquals("ESC", "Hi \\\\", instance.encodeForDN("Hi \\"));
627+
assertEquals("leading #", "\\# Hello\\ef\\bf\\bd", instance.encodeForDN("# Hello�"));
628+
assertEquals("leading space", "\\ Hello\\ef\\bf\\bd", instance.encodeForDN(" Hello�"));
629+
assertEquals("trailing space", "Hello\\ef\\bf\\bd\\ ", instance.encodeForDN("Hello� "));
567630
assertEquals("less than greater than", "Hello\\<\\>", instance.encodeForDN("Hello<>"));
568631
assertEquals("only 3 spaces", "\\ \\ ", instance.encodeForDN(" "));
569632
assertEquals("Christmas Tree DN", "\\ Hello\\\\ \\+ \\, \\\"World\\\" \\;\\ ", instance.encodeForDN(" Hello\\ + , \"World\" ; "));
570633
assertEquals("Forward slash for \\/Microsoft\\/ \\/AD\\/", instance.encodeForDN("Forward slash for /Microsoft/ /AD/"));
634+
assertEquals("RFC 4514, Section 4", "CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net",
635+
"CN=" + instance.encodeForDN("James \"Jim\" Smith, III") + ",DC=" + instance.encodeForDN("example") + ",DC=" + instance.encodeForDN("net"));
571636
}
572637

573638
/**

0 commit comments

Comments
 (0)