EMBARGOED CVE-2024-10270 org.keycloak/keycloak-services: Keycloak Denial of Service (#216)

Closes #CVE-2024-10270

Signed-off-by: Douglas Palmer <dpalmer@redhat.com>
This commit is contained in:
Douglas Palmer 2024-11-14 00:47:34 -08:00 committed by GitHub
parent 3da16eed1f
commit c4160df1e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 80 additions and 18 deletions

View File

@ -19,36 +19,86 @@ package org.keycloak.utils;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public class SearchQueryUtils {
public static final Pattern queryPattern = Pattern.compile("\\s*(?:(?<name>[^\"][^: ]+|.)|\"(?<nameEsc>(?:\\\\.|[^\\\\\"])+)\"):(?:(?<value>[^\"][^ ]*)|\"(?<valueEsc>(?:\\\\.|[^\\\\\"])+)\")\\s*");
public static final Pattern escapedCharsPattern = Pattern.compile("\\\\(.)");
public static Map<String, String> getFields(final String query) {
Matcher matcher = queryPattern.matcher(query);
Map<String, String> ret = new HashMap<>();
while (matcher.find()) {
String name = matcher.group("name");
if (name == null) {
name = unescape(matcher.group("nameEsc"));
char[] chars = query.trim().toCharArray();
for (int i = 0; i < chars.length; i++) {
boolean inQuotes = false;
boolean internal = false;
String name = "";
while (i < chars.length && chars[i] != ':') {
if (chars[i] == '\\') {
if (chars[i+1] == '\"') {
i++;
}
else if (chars[i+1] == '\\') {
i+=2;
continue;
}
}
else if (chars[i] == '\"') {
if(!inQuotes && name.length() > 0) {
internal = true;
}
else if(internal) {
internal = false;
}
else {
inQuotes = !inQuotes;
i++;
continue;
}
}
else if(chars[i] == ' ' && !inQuotes) {
break;
}
name += chars[i];
i++;
}
String value = matcher.group("value");
if (value == null) {
value = unescape(matcher.group("valueEsc"));
if(i == chars.length || chars[i] == ' ') {
continue;
}
i++;
inQuotes = false;
internal = false;
String value = "";
while (i < chars.length) {
if (chars[i] == '\\') {
if (chars[i+1] == '\"') {
i++;
}
else if (chars[i+1] == '\\') {
i+=2;
continue;
}
}
else if (chars[i] == '\"') {
if(!inQuotes && value.length() > 0) {
internal = true;
}
else if(internal) {
internal = false;
}
else {
inQuotes = !inQuotes;
i++;
continue;
}
}
else if(chars[i] == ' ' && !inQuotes) {
break;
}
value += chars[i];
i++;
}
ret.put(name, value);
}
return ret;
}
public static String unescape(final String escaped) {
return escapedCharsPattern.matcher(escaped).replaceAll("$1");
}
}

View File

@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
@ -78,4 +79,15 @@ public class SearchQueryUtilsTest {
assertEquals(expected, actual);
}
@Test
public void testReDoS() {
long start = System.currentTimeMillis();
int count = 50000;
for (int i = 0; i < count; i++) {
SearchQueryUtils.getFields(" ".repeat(1443) + "\n\n".repeat(1443) + 0);
}
long end = System.currentTimeMillis() - start;
System.out.println("took: " + end + " milliseconds");
}
}