CVE-2024-38820 is a fascinating vulnerability that builds on the patch for an earlier issue—CVE-2022-22968—in the popular Spring Framework. The original bug and its fix seemed routine: DataBinder’s disallowedFields property needed to be case-insensitive to better protect against sneaky attacks during data binding. But as so often happens, the devil is in the details.
Thanks to the nuance of how Java turns strings to lowercase, security researchers discovered a loophole that leaves some fields exposed under certain locales. In this post, we’ll break down what went wrong, how it can be exploited, and what developers need to change going forward.
But…
toLowerCase() can behave differently depending on the system’s locale—for example, Turkish has weird exceptions for the dotless ‘i’.
Let’s see how things looked in Spring’s DataBinder
public void setDisallowedFields(String... fields) {
for (String field : fields) {
this.disallowedFields.add(field.toLowerCase());
}
}
At first glance, this seems fine: make sure all disallowedFields are lowercase, and compare everything in lowercase. But what if your app runs in Turkish?
The Turkish Locale Trap
In the Turkish language, the lowercase of “I” isn't “i”, and the uppercase of “i” isn’t “I”. This causes a mismatch between the expected and actual field names.
Example
Locale.setDefault(new Locale("tr", "TR"));
String field = "Id";
System.out.println(field.toLowerCase()); // Prints "ıd", not "id"
So if you block “id” in the disallowedFields, but the attacker submits “Id” in a Turkish locale, the match will fail.
How Can This Be Exploited?
Suppose you want to block a sensitive field like “id” or “password” from being bound.
In your controller
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields("id"); // You think 'Id', 'ID', etc. are all blocked
}
But… an attacker submits a POST request with
Content-Type: application/x-www-form-urlencoded
Id=12345
If your server is running with Turkish locale, toLowerCase() will convert "Id" to "ıd" (with a dotless 'i'), and since "id" (with a Latin 'i') is in the disallowed list, the match fails—leaving your field unprotected.
Try this piece of code to see the bug in action
import java.util.Locale;
public class LocaleLowerTest {
public static void main(String[] args) {
Locale.setDefault(new Locale("tr", "TR"));
String s = "Id";
System.out.println("toLowerCase(TR): " + s.toLowerCase());
System.out.println("toLowerCase(EN): " + s.toLowerCase(Locale.ENGLISH));
}
}
Expected output:
toLowerCase(TR): ıd
toLowerCase(EN): id
How to Fix It
The safest approach is to force the locale when you convert strings to lowercase, use Locale.ENGLISH:
field.toLowerCase(Locale.ENGLISH)
So, the patched line should be
this.disallowedFields.add(field.toLowerCase(Locale.ENGLISH));
This way, no matter what the server’s default locale is, string comparisons will behave in a predictable, secure way.
Spring Framework GitHub Issue:
https://github.com/spring-projects/spring-framework/issues/30982
CVE-2024-38820 NVD Entry:
https://nvd.nist.gov/vuln/detail/CVE-2024-38820
CVE-2022-22968 (related fix):
https://nvd.nist.gov/vuln/detail/CVE-2022-22968
StackOverflow on Turkish Locale & Lowercase:
https://stackoverflow.com/a/22907647/390872
Oracle Docs on String.toLowerCase():
Never assume toLowerCase() (or toUpperCase()) works the same everywhere.
- Always specify Locale.ENGLISH (or another constant, not the system default) for security checks involving case changes.
Review and test existing locale-sensitive string normalizations in security-critical code!
If you use Spring, upgrade as soon as the patch with the forced locale is available. Double-check your custom code for the same pattern.
_Stay safe, and remember: a “simple” fix often has unexpected edges you need to smooth out for real-world security!_
Timeline
Published on: 10/18/2024 06:15:03 UTC
Last modified on: 11/05/2024 21:35:09 UTC