CVE-2015-20110: Improper Restriction of Excessive Authentication Attempts (CWE-307)
value: JHipster generator-jhipster before 2.23.0 allows a timing attack against validateToken due to a string comparison that stops at the first character that is different. Attackers can guess tokens by brute forcing one character at a time and observing the timing. This of course drastically reduces the search space to a linear amount of guesses based on the token length times the possible characters.
CWE description: The product does not implement sufficient measures to prevent multiple failed authentication attempts within a short time frame, making it more susceptible to brute force attacks.
CVSS v3 base score: 7.5
Published: 2023-10-31T03:15Z
Repository: https://github.com/jhipster/generator-jhipster
Language: Java
Files changed: app/templates/src/main/java/package/security/xauth/_TokenProvider.java
Commit date: 2015-10-06 18:06:09 +0200
Commit message: fixed timing attack vulnerability in TokenProvider #2095

Diff: diff --git a/app/templates/src/main/java/package/security/xauth/_TokenProvider.java b/app/templates/src/main/java/package/security/xauth/_TokenProvider.java
index 1b94854c..1910ae43 100644
--- a/app/templates/src/main/java/package/security/xauth/_TokenProvider.java
+++ b/app/templates/src/main/java/package/security/xauth/_TokenProvider.java
@@ -49,8 +49,25 @@ public class TokenProvider {
     public boolean validateToken(String authToken, UserDetails userDetails) {
         String[] parts = authToken.split(":");
         long expires = Long.parseLong(parts[1]);
         String signature = parts[2];
         String signatureToMatch = computeSignature(userDetails, expires);
-        return expires >= System.currentTimeMillis() && signature.equals(signatureToMatch);
+        return expires >= System.currentTimeMillis() && constantTimeEquals(signature, signatureToMatch));
     }
+
+    /**
+     * String comparison that doesn't stop at the first character that is different but instead always
+     * iterates the whole string length to prevent timing attacks.
+     */
+    private boolean constantTimeEquals(String a, String b) {
+        if (a.length() != b.length()) {
+            return false;
+        } else {
+            int equal = 0;
+            for (int i = 0; i < a.length(); i++) {
+                equal |= a.charAt(i) ^ b.charAt(i);
+            }
+            return equal == 0;
+        }
+    }
+
 }

Vulnerable code:         return expires >= System.currentTimeMillis() && signature.equals(signatureToMatch);
Fixed code:         return expires >= System.currentTimeMillis() && constantTimeEquals(signature, signatureToMatch));

    /**
     * String comparison that doesn't stop at the first character that is different but instead always
     * iterates the whole string length to prevent timing attacks.
     */
    private boolean constantTimeEquals(String a, String b) {
        if (a.length() != b.length()) {
            return false;
        } else {
            int equal = 0;
            for (int i = 0; i < a.length(); i++) {
                equal |= a.charAt(i) ^ b.charAt(i);
            }
            return equal == 0;
        }
    }