CVE-2019-16303: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG) (CWE-338)
value: A class generated by the Generator in JHipster before 6.3.0 and JHipster Kotlin through 1.1.0 produces code that uses an insecure source of randomness (apache.commons.lang3 RandomStringUtils). This allows an attacker (if able to obtain their own password reset URL) to compute the value for all other password resets for other accounts, thus allowing privilege escalation or account takeover.
CWE description: The product uses a Pseudo-Random Number Generator (PRNG) in a security context, but the PRNG's algorithm is not cryptographically strong.
CVSS v3 base score: 9.8
Published: 2019-09-14T00:15Z
Repository: https://github.com/jhipster/generator-jhipster
Language: Other
Files changed: generators/server/templates/src/main/java/package/security/PersistentTokenRememberMeServices.java.ejs, generators/server/templates/src/main/java/package/service/UserService.java.ejs, generators/server/templates/src/main/java/package/service/util/RandomUtil.java.ejs, generators/server/templates/src/test/java/package/service/UserServiceIT.java.ejs
Commit date: 2019-09-13 15:11:35 +0200
Commit message: use new SecureRandom which uses non blocking /dev/urandom

Diff: diff --git a/generators/server/templates/src/main/java/package/security/PersistentTokenRememberMeServices.java.ejs b/generators/server/templates/src/main/java/package/security/PersistentTokenRememberMeServices.java.ejs
index fc7bb2798..da564c42a 100644
--- a/generators/server/templates/src/main/java/package/security/PersistentTokenRememberMeServices.java.ejs
+++ b/generators/server/templates/src/main/java/package/security/PersistentTokenRememberMeServices.java.ejs
@@ -125,11 +125,11 @@ public class PersistentTokenRememberMeServices extends
 
                 // Token also matches, so login is valid. Update the token value, keeping the *same* series number.
                 log.debug("Refreshing persistent login token for user '{}', series '{}'", login, token.getSeries());<% if (databaseType === 'sql' || databaseType === 'mongodb' || databaseType === 'couchbase') { %>
                 token.setTokenDate(LocalDate.now());<%}%><% if (databaseType === 'cassandra') { %>
                 token.setTokenDate(new Date());<%}%>
-                token.setTokenValue(RandomUtil.getInstance().generateTokenData());
+                token.setTokenValue(RandomUtil.generateTokenData());
                 token.setIpAddress(request.getRemoteAddr());
                 token.setUserAgent(request.getHeader("User-Agent"));
                 try {
                     <% if (databaseType === 'sql') { %>persistentTokenRepository.saveAndFlush(token);<% } else { %>persistentTokenRepository.save(token);<% } %>
                     <%_ if (databaseType === 'sql' || databaseType === 'mongodb' || databaseType === 'couchbase') { _%>
@@ -154,15 +154,15 @@ public class PersistentTokenRememberMeServices extends
         String login = successfulAuthentication.getName();
 
         log.debug("Creating new persistent login for user {}", login);
         PersistentToken token = userRepository.findOneByLogin(login).map(u -> {
             PersistentToken t = new PersistentToken();
-            t.setSeries(RandomUtil.getInstance().generateSeriesData());<% if (databaseType === 'sql' || databaseType === 'mongodb') { %>
+            t.setSeries(RandomUtil.generateSeriesData());<% if (databaseType === 'sql' || databaseType === 'mongodb') { %>
             t.setUser(u);<% } else { %>
             t.setLogin(login);<% } %><% if (databaseType === 'cassandra') { %>
             t.setUserId(u.getId());<% } %>
-            t.setTokenValue(RandomUtil.getInstance().generateTokenData());
+            t.setTokenValue(RandomUtil.generateTokenData());
             t.setTokenDate(<% if (databaseType === 'cassandra') { %>new Date()<% } else { %>LocalDate.now()<% } %>);
             t.setIpAddress(request.getRemoteAddr());
             t.setUserAgent(request.getHeader("User-Agent"));
             return t;
         }).orElseThrow(() -> new UsernameNotFoundException("User " + login + " was not found in the database"));
diff --git a/generators/server/templates/src/main/java/package/service/UserService.java.ejs b/generators/server/templates/src/main/java/package/service/UserService.java.ejs
index ae56a0447..aa255a3b8 100644
--- a/generators/server/templates/src/main/java/package/service/UserService.java.ejs
+++ b/generators/server/templates/src/main/java/package/service/UserService.java.ejs
@@ -200,11 +200,11 @@ public class UserService {
 
     public <% if (reactive) { %>Mono<% } else { %>Optional<% } %><<%= asEntity('User') %>> requestPasswordReset(String mail) {
         return userRepository.findOneByEmailIgnoreCase(mail)
             .filter(<%= asEntity('User') %>::getActivated)
             .<% if (reactive) { %>flatMap<% } else { %>map<% } %>(user -> {
-                user.setResetKey(RandomUtil.getInstance().generateResetKey());
+                user.setResetKey(RandomUtil.generateResetKey());
                 user.setResetDate(Instant.now());
             <%_ if (!reactive) { _%>
                 <%_ if (databaseType === 'mongodb' || databaseType === 'couchbase' || databaseType === 'cassandra') { _%>
                 userRepository.save(user);
                 <%_ } _%>
@@ -250,11 +250,11 @@ public class UserService {
         <%_ } _%>
         newUser.setLangKey(userDTO.getLangKey());
         // new user is not active
         newUser.setActivated(false);
         // new user gets registration key
-        newUser.setActivationKey(RandomUtil.getInstance().generateActivationKey());
+        newUser.setActivationKey(RandomUtil.generateActivationKey());
         <%_ if (['sql', 'mongodb'].includes(databaseType)) { _%>
         Set<Authority> authorities = new HashSet<>();
         authorityRepository.findById(AuthoritiesConstants.USER).ifPresent(authorities::add);
         <%_ } else { _%>
         Set<String> authorities = new HashSet<>();
@@ -308,11 +308,11 @@ public class UserService {
                 <%_ } _%>
                 newUser.setLangKey(userDTO.getLangKey());
                 // new user is not active
                 newUser.setActivated(false);
                 // new user gets registration key
-                newUser.setActivationKey(RandomUtil.getInstance().generateActivationKey());
+                newUser.setActivationKey(RandomUtil.generateActivationKey());
                 Set<<% if (['sql', 'mongodb'].includes(databaseType)) { %>Authority<% } else { %>String<% } %>> authorities = new HashSet<>();
                 <%_ if (['sql', 'mongodb'].includes(databaseType)) { _%>
                 return authorityRepository.findById(AuthoritiesConstants.USER)
                     .map(authorities::add)
                     .thenReturn(newUser)
@@ -360,13 +360,13 @@ public class UserService {
         if (userDTO.getLangKey() == null) {
             user.setLangKey(Constants.DEFAULT_LANGUAGE); // default language
         } else {
             user.setLangKey(userDTO.getLangKey());
         }
-        String encryptedPassword = passwordEncoder.encode(RandomUtil.getInstance().generatePassword());
+        String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword());
         user.setPassword(encryptedPassword);
-        user.setResetKey(RandomUtil.getInstance().generateResetKey());
+        user.setResetKey(RandomUtil.generateResetKey());
         user.setResetDate(Instant.now());
         user.setActivated(true);
     <%_ if (!reactive) { _%>
         <%_ if (['sql', 'mongodb'].includes(databaseType)) { _%>
         if (userDTO.getAuthorities() != null) {
diff --git a/generators/server/templates/src/main/java/package/service/util/RandomUtil.java.ejs b/generators/server/templates/src/main/java/package/service/util/RandomUtil.java.ejs
index 72d9a7e28..22d3e3766 100644
--- a/generators/server/templates/src/main/java/package/service/util/RandomUtil.java.ejs
+++ b/generators/server/templates/src/main/java/package/service/util/RandomUtil.java.ejs
@@ -28,73 +28,62 @@ import java.security.SecureRandom;
  */
 public final class RandomUtil {
 
     private static final int DEF_COUNT = 20;
 
-    private static final RandomUtil INSTANCE = new RandomUtil();
-
-    public static RandomUtil getInstance() {
-        return INSTANCE;
-    }
-
-    private final SecureRandom secureRandom;
+    private static final SecureRandom secureRandom = new SecureRandom(new SecureRandom().generateSeed(128));
 
     private RandomUtil() {
-        try {
-            secureRandom = SecureRandom.getInstanceStrong();
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException("Unable to find secure algorithms.", e); //NOSONAR
-        }
     }
 
-    private String generateRandomAlphanumericString() {
+    private static String generateRandomAlphanumericString() {
         return RandomStringUtils.random(DEF_COUNT, 0, 0, true, true, null, secureRandom);
     }
 
     /**
      * Generate a password.
      *
      * @return the generated password.
      */
-    public String generatePassword() {
+    public static String generatePassword() {
         return generateRandomAlphanumericString();
     }
 
     /**
      * Generate an activation key.
      *
      * @return the generated activation key.
      */
-    public String generateActivationKey() {
+    public static String generateActivationKey() {
         return generateRandomAlphanumericString();
     }
 
     /**
      * Generate a reset key.
      *
      * @return the generated reset key.
      */
-    public String generateResetKey() {
+    public static String generateResetKey() {
         return generateRandomAlphanumericString();
     }
     <%_ if (authenticationType === 'session' && !reactive) { _%>
 
     /**
      * Generate a unique series to validate a persistent token, used in the
      * authentication remember-me mechanism.
      *
      * @return the generated series data.
      */
-    public String generateSeriesData() {
+    public static String generateSeriesData() {
         return generateRandomAlphanumericString();
     }
 
     /**
      * Generate a persistent token, used in the authentication remember-me mechanism.
      *
      * @return the generated token data.
      */
-    public String generateTokenData() {
+    public static String generateTokenData() {
         return generateRandomAlphanumericString();
     }
     <%_ } _%>
 }
diff --git a/generators/server/templates/src/test/java/package/service/UserServiceIT.java.ejs b/generators/server/templates/src/test/java/package/service/UserServiceIT.java.ejs
index b86a411f2..cc7d5fed3 100644
--- a/generators/server/templates/src/test/java/package/service/UserServiceIT.java.ejs
+++ b/generators/server/templates/src/test/java/package/service/UserServiceIT.java.ejs
@@ -293,11 +293,11 @@ public class UserServiceIT <% if (databaseType === 'cassandra') { %>extends Abst
     <%_ if (databaseType === 'sql') { _%>
     @Transactional
     <%_ } _%>
     public void assertThatResetKeyMustNotBeOlderThan24Hours() {
         Instant daysAgo = Instant.now().minus(25, ChronoUnit.HOURS);
-        String resetKey = RandomUtil.getInstance().generateResetKey();
+        String resetKey = RandomUtil.generateResetKey();
         user.setActivated(true);
         user.setResetDate(daysAgo);
         user.setResetKey(resetKey);
         userRepository.save<% if (databaseType === 'sql') { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %>;
 
@@ -327,11 +327,11 @@ public class UserServiceIT <% if (databaseType === 'cassandra') { %>extends Abst
     @Transactional
     <%_ } _%>
     public void assertThatUserCanResetPassword() {
         String oldPassword = user.getPassword();
         Instant daysAgo = Instant.now().minus(2, ChronoUnit.HOURS);
-        String resetKey = RandomUtil.getInstance().generateResetKey();
+        String resetKey = RandomUtil.generateResetKey();
         user.setActivated(true);
         user.setResetDate(daysAgo);
         user.setResetKey(resetKey);
         userRepository.save<% if (databaseType === 'sql') { %>AndFlush<% } %>(user)<% if (reactive) { %>.block()<% } %>;
 

Vulnerable code:                 token.setTokenValue(RandomUtil.getInstance().generateTokenData());
            t.setSeries(RandomUtil.getInstance().generateSeriesData());<% if (databaseType === 'sql' || databaseType === 'mongodb') { %>
            t.setTokenValue(RandomUtil.getInstance().generateTokenData());
                user.setResetKey(RandomUtil.getInstance().generateResetKey());
        newUser.setActivationKey(RandomUtil.getInstance().generateActivationKey());
                newUser.setActivationKey(RandomUtil.getInstance().generateActivationKey());
        String encryptedPassword = passwordEncoder.encode(RandomUtil.getInstance().generatePassword());
        user.setResetKey(RandomUtil.getInstance().generateResetKey());
    private static final RandomUtil INSTANCE = new RandomUtil();

    public static RandomUtil getInstance() {
        return INSTANCE;
    }

    private final SecureRandom secureRandom;
        try {
            secureRandom = SecureRandom.getInstanceStrong();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Unable to find secure algorithms.", e); //NOSONAR
        }
    private String generateRandomAlphanumericString() {
    public String generatePassword() {
    public String generateActivationKey() {
    public String generateResetKey() {
    public String generateSeriesData() {
    public String generateTokenData() {
        String resetKey = RandomUtil.getInstance().generateResetKey();
        String resetKey = RandomUtil.getInstance().generateResetKey();
Fixed code:                 token.setTokenValue(RandomUtil.generateTokenData());
            t.setSeries(RandomUtil.generateSeriesData());<% if (databaseType === 'sql' || databaseType === 'mongodb') { %>
            t.setTokenValue(RandomUtil.generateTokenData());
                user.setResetKey(RandomUtil.generateResetKey());
        newUser.setActivationKey(RandomUtil.generateActivationKey());
                newUser.setActivationKey(RandomUtil.generateActivationKey());
        String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword());
        user.setResetKey(RandomUtil.generateResetKey());
    private static final SecureRandom secureRandom = new SecureRandom(new SecureRandom().generateSeed(128));
    private static String generateRandomAlphanumericString() {
    public static String generatePassword() {
    public static String generateActivationKey() {
    public static String generateResetKey() {
    public static String generateSeriesData() {
    public static String generateTokenData() {
        String resetKey = RandomUtil.generateResetKey();
        String resetKey = RandomUtil.generateResetKey();