Recipe Name:
Portability Flaw: Avoid locale dependent comparisons: equals after case conversion
Description:
This comparison is sensitive to the system's locale. Ignore the case or add a locale.
Level:
warning
Language:
  • java
Tags:
  • security
  • Java basic
  • quality
  • SEI CERT
Documentation

Performing a comparison using equals after changing the casing of strings is locale dependent. This comparison may lead to unexpected results.

When performing changes to the casing of a string, a locale needs to be provided to map lowercase and uppercase characters. If no locale is provided, the JVM will use its default locale. In practice this means that characters may change in an unexpected way when changing their case, depending on the locale used to create the string. When comparing the resulting strings for equality, they might not be considered equal even though they would be expected to be equal.

For example, in the Turkish locale the character "\u0130" indicates the "latin capital letter i with dot above". When comparing using the default locale, it will not be considered equal to the character 'i', however, it can be interpreted by code as such. This can lead to validation routine bypassing, enabling other types of attacks.

To resolve the issue, either provide a locale to the case changing operation or remove the case changing operation and use equalsIgnoreCase instead.

Before
public static void processTag(String tag) {
  if (tag.toUpperCase().equals("SCRIPT")) {
    return;
  }
  // Process tag
}
After (Using explicit locale)
public static void processTag(String tag) {
  if (tag.toUpperCase(Locale.ROOT).equals("SCRIPT")) {
    return;
  }
  // Process tag
}
After (Using equalsIgnoreCase)
public static void processTag(String tag) {
  if (tag.equalsIgnoreCase("SCRIPT")) {
    return;
  }
  // Process tag
}
References
Recipe
id: scw:java:equals-after-case-conversion
version: 10
metadata:
  name: 'Portability Flaw: Avoid locale dependent comparisons: equals after case conversion'
  shortDescription: This comparison is sensitive to the system's locale. Ignore the case or add a locale.
  level: warning
  language: java
  newCodeOnly: false
  cweCategory: 474
  enabled: true
  comment: ""
  descriptionFile: descriptions/Portability_Flaw__Avoid_locale_dependent_comparisons__equals_after_case_conversion.html
  tags: security;Java basic;quality;SEI CERT
search:
  methodcall:
    name: equals
    type: java.lang.String
    "on":
      methodcall:
        anyOf:
        - name:
            matches: (toUpperCase|toLowerCase)
          type: java.lang.String
          without:
            args:
              any: {}
availableFixes:
- name: Use equalsIgnoreCase
  actions:
  - rewrite:
      to: '{{{ qualifier.qualifier }}}.equalsIgnoreCase({{{ arguments.0 }}})'
- name: Add ROOT locale
  actions:
  - rewrite:
      to: '{{{ qualifier.qualifier }}}.{{{ qualifier.methodName }}}(java.util.Locale.ROOT).{{{ methodName }}}({{{ arguments.0 }}})'