File tomcat-8.0.53-CVE-2021-30640.patch of Package tomcat.23111
Index: apache-tomcat-8.0.53-src/java/org/apache/catalina/realm/JNDIRealm.java
===================================================================
--- apache-tomcat-8.0.53-src.orig/java/org/apache/catalina/realm/JNDIRealm.java
+++ apache-tomcat-8.0.53-src/java/org/apache/catalina/realm/JNDIRealm.java
@@ -1626,8 +1626,11 @@ public class JNDIRealm extends RealmBase
if (username == null || userPatternFormatArray[curUserPattern] == null)
return null;
- // Form the dn from the user pattern
- String dn = userPatternFormatArray[curUserPattern].format(new String[] { username });
+ // Form the DistinguishedName from the user pattern.
+ // Escape in case username contains a character with special meaning in
+ // an attribute value.
+ String dn = userPatternFormatArray[curUserPattern].format(
+ new String[] { doAttributeValueEscaping(username) });
try {
user = getUserByPattern(context, username, attrIds, dn);
@@ -1668,7 +1671,9 @@ public class JNDIRealm extends RealmBase
return null;
// Form the search filter
- String filter = userSearchFormat.format(new String[] { username });
+ // Escape in case username contains a character with special meaning in
+ // a search filter.
+ String filter = userSearchFormat.format(new String[] { doFilterEscaping(username) });
// Set up the search controls
SearchControls constraints = new SearchControls();
@@ -1959,8 +1964,13 @@ public class JNDIRealm extends RealmBase
if ((roleFormat == null) || (roleName == null))
return list;
- // Set up parameters for an appropriate search
- String filter = roleFormat.format(new String[] { doRFC2254Encoding(dn), username, userRoleId });
+ // Set up parameters for an appropriate search filter
+ // The dn is already attribute value escaped but the others are not
+ // This is a filter so all input will require filter escaping
+ String filter = roleFormat.format(new String[] {
+ doFilterEscaping(dn),
+ doFilterEscaping(doAttributeValueEscaping(username)),
+ doFilterEscaping(doAttributeValueEscaping(userRoleId)) });
SearchControls controls = new SearchControls();
if (roleSubtree)
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
@@ -1974,7 +1984,9 @@ public class JNDIRealm extends RealmBase
Name name = np.parse(dn);
String nameParts[] = new String[name.size()];
for (int i = 0; i < name.size(); i++) {
- nameParts[i] = name.get(i);
+ // May have been returned with \<char> escaping rather than
+ // \<hex><hex>. Make sure it is \<hex><hex>.
+ nameParts[i] = convertToHexEscape(name.get(i));
}
base = roleBaseFormat.format(nameParts);
} else {
@@ -1995,7 +2007,7 @@ public class JNDIRealm extends RealmBase
Attributes attrs = result.getAttributes();
if (attrs == null)
continue;
- String dname = getDistinguishedName(context, roleBase, result);
+ String dname = getDistinguishedName(context, base, result);
String name = getAttributeValue(roleName, attrs);
if (name != null && dname != null) {
groupMap.put(dname, name);
@@ -2028,14 +2040,19 @@ public class JNDIRealm extends RealmBase
Map<String, String> newThisRound = new HashMap<>(); // Stores the groups we find in this iteration
for (Entry<String, String> group : newGroups.entrySet()) {
- filter = roleFormat.format(new String[] { group.getKey(), group.getValue(), group.getValue() });
+ // Group key is already value escaped if required
+ // Group value is not value escaped
+ // Everything needs to be filter escaped
+ filter = roleFormat.format(new String[] {
+ doFilterEscaping(group.getKey()),
+ doFilterEscaping(doAttributeValueEscaping(group.getValue())),
+ doFilterEscaping(doAttributeValueEscaping(group.getValue())) });
if (containerLog.isTraceEnabled()) {
- containerLog.trace("Perform a nested group search with base "+ roleBase + " and filter " + filter);
+ containerLog.trace("Perform a nested group search with base "+ roleBase +
+ " and filter " + filter);
}
-
- results = searchAsUser(context, user, roleBase, filter, controls,
- isRoleSearchAsUser());
+ results = searchAsUser(context, user, base, filter, controls, isRoleSearchAsUser());
try {
while (results.hasMore()) {
@@ -2477,12 +2494,14 @@ public class JNDIRealm extends RealmBase
sslContext = SSLContext.getDefault();
}
return sslContext.getSocketFactory();
- } catch (NoSuchAlgorithmException | KeyManagementException e) {
- List<String> allowedProtocols = Arrays
- .asList(getSupportedSslProtocols());
- throw new IllegalArgumentException(
- sm.getString("jndiRealm.invalidSslProtocol", protocol,
- allowedProtocols), e);
+ } catch (NoSuchAlgorithmException e) {
+ List<String> allowedProtocols = Arrays.asList(getSupportedSslProtocols());
+ throw new IllegalArgumentException(sm.getString("jndiRealm.invalidSslProtocol",
+ protocol, allowedProtocols), e);
+ } catch (KeyManagementException e) {
+ List<String> allowedProtocols = Arrays.asList(getSupportedSslProtocols());
+ throw new IllegalArgumentException(sm.getString("jndiRealm.invalidSslProtocol",
+ protocol, allowedProtocols), e);
}
}
@@ -2698,6 +2717,27 @@ public class JNDIRealm extends RealmBase
* @return String the escaped/encoded result
*/
protected String doRFC2254Encoding(String inString) {
+ return doFilterEscaping(inString);
+ }
+
+
+ /**
+ * Given an LDAP search string, returns the string with certain characters
+ * escaped according to RFC 2254 guidelines.
+ * The character mapping is as follows:
+ * char -> Replacement
+ * ---------------------------
+ * * -> \2a
+ * ( -> \28
+ * ) -> \29
+ * \ -> \5c
+ * \0 -> \00
+ *
+ * @param inString string to escape according to RFC 2254 guidelines
+ *
+ * @return String the escaped/encoded result
+ */
+ protected String doFilterEscaping(String inString) {
StringBuilder buf = new StringBuilder(inString.length());
for (int i = 0; i < inString.length(); i++) {
char c = inString.charAt(i);
@@ -2782,6 +2822,148 @@ public class JNDIRealm extends RealmBase
}
}
+ /**
+ * Implements the necessary escaping to represent an attribute value as a
+ * String as per RFC 4514.
+ *
+ * @param input The original attribute value
+ * @return The string representation of the attribute value
+ */
+ protected String doAttributeValueEscaping(String input) {
+ int len = input.length();
+ StringBuilder result = new StringBuilder();
+
+ for (int i = 0; i < len; i++) {
+ char c = input.charAt(i);
+ switch (c) {
+ case ' ': {
+ if (i == 0 || i == (len -1)) {
+ result.append("\\20");
+ } else {
+ result.append(c);
+ }
+ break;
+ }
+ case '#': {
+ if (i == 0 ) {
+ result.append("\\23");
+ } else {
+ result.append(c);
+ }
+ break;
+ }
+ case '\"': {
+ result.append("\\22");
+ break;
+ }
+ case '+': {
+ result.append("\\2B");
+ break;
+ }
+ case ',': {
+ result.append("\\2C");
+ break;
+ }
+ case ';': {
+ result.append("\\3B");
+ break;
+ }
+ case '<': {
+ result.append("\\3C");
+ break;
+ }
+ case '>': {
+ result.append("\\3E");
+ break;
+ }
+ case '\\': {
+ result.append("\\5C");
+ break;
+ }
+ case '\u0000': {
+ result.append("\\00");
+ break;
+ }
+ default:
+ result.append(c);
+ }
+
+ }
+
+ return result.toString();
+ }
+
+
+ protected static String convertToHexEscape(String input) {
+ if (input.indexOf('\\') == -1) {
+ // No escaping present. Return original.
+ return input;
+ }
+ // +6 allows for 3 escaped characters by default
+ StringBuilder result = new StringBuilder(input.length() + 6);
+ boolean previousSlash = false;
+ for (int i = 0; i < input.length(); i++) {
+ char c = input.charAt(i);
+ if (previousSlash) {
+ switch (c) {
+ case ' ': {
+ result.append("\\20");
+ break;
+ }
+ case '\"': {
+ result.append("\\22");
+ break;
+ }
+ case '#': {
+ result.append("\\23");
+ break;
+ }
+ case '+': {
+ result.append("\\2B");
+ break;
+ }
+ case ',': {
+ result.append("\\2C");
+ break;
+ }
+ case ';': {
+ result.append("\\3B");
+ break;
+ }
+ case '<': {
+ result.append("\\3C");
+ break;
+ }
+ case '=': {
+ result.append("\\3D");
+ break;
+ }
+ case '>': {
+ result.append("\\3E");
+ break;
+ }
+ case '\\': {
+ result.append("\\5C");
+ break;
+ }
+ default:
+ result.append('\\');
+ result.append(c);
+ }
+ previousSlash = false;
+ } else {
+ if (c == '\\') {
+ previousSlash = true;
+ } else {
+ result.append(c);
+ }
+ }
+ }
+ if (previousSlash) {
+ result.append('\\');
+ }
+ return result.toString();
+ }
// ------------------------------------------------------ Private Classes
Index: apache-tomcat-8.0.53-src/webapps/docs/changelog.xml
===================================================================
--- apache-tomcat-8.0.53-src.orig/webapps/docs/changelog.xml
+++ apache-tomcat-8.0.53-src/webapps/docs/changelog.xml
@@ -146,6 +146,10 @@
set using a canonical path which in turn meant resource URLs were not
being constructed as expected. (markt)
</fix>
+ <fix>
+ <bug>65224</bug>: Ensure the correct escaping of attribute values and
+ search filters in the JNDIRealm. (markt)
+ </fix>
</changelog>
</subsection>
<subsection name="Coyote">