CVE-2019-10908: Incorrect Usage of Seeds in Pseudo-Random Number Generator (PRNG) (CWE-335)
value: In Airsonic 10.2.1, RecoverController.java generates passwords via org.apache.commons.lang.RandomStringUtils, which uses java.util.Random internally. This PRNG has a 48-bit seed that can easily be bruteforced, leading to trivial privilege escalation attacks.
CWE description: The product uses a Pseudo-Random Number Generator (PRNG) but does not correctly manage seeds.
CVSS v3 base score: 9.8
Published: 2019-04-07T14:29Z
Repository: https://github.com/airsonic/airsonic
Language: Java
Files changed: airsonic-main/src/main/java/org/airsonic/player/controller/RecoverController.java
Commit date: 2019-03-28 21:47:57 +0100
Commit message: Generate new passwords in a secure way

Previously, lost passwords were generated via
org.apache.commons.lang.RandomStringUtils,
which is using java.util.Random internally.

This PRNG is has a 48-bit seed, that can easily be bruteforced
if an attacker is able to get the PRNG's output, for example
but resetting their own account multiple times,
leading to trivial privileges escalation attacks.

This commit makes use of java.security.SecureRandom
instead.

Diff: diff --git a/airsonic-main/src/main/java/org/airsonic/player/controller/RecoverController.java b/airsonic-main/src/main/java/org/airsonic/player/controller/RecoverController.java
index e095b34..ecba670 100644
--- a/airsonic-main/src/main/java/org/airsonic/player/controller/RecoverController.java
+++ b/airsonic-main/src/main/java/org/airsonic/player/controller/RecoverController.java
@@ -2,11 +2,10 @@ package org.airsonic.player.controller;
 
 import de.triology.recaptchav2java.ReCaptcha;
 import org.airsonic.player.domain.User;
 import org.airsonic.player.service.SecurityService;
 import org.airsonic.player.service.SettingsService;
-import org.apache.commons.lang.RandomStringUtils;
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
@@ -20,10 +19,11 @@ import javax.mail.Transport;
 import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeMessage;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import java.security.SecureRandom;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
@@ -35,10 +35,14 @@ import java.util.Properties;
 public class RecoverController {
 
 
     private static final Logger LOG = LoggerFactory.getLogger(RecoverController.class);
 
+    private static final String SYMBOLS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
+    private final SecureRandom random = new SecureRandom();
+    private static final int PASSWORD_LENGTH = 32;
+
     @Autowired
     private SettingsService settingsService;
     @Autowired
     private SecurityService securityService;
 
@@ -67,11 +71,17 @@ public class RecoverController {
             } else if (user == null) {
                 map.put("error", "recover.error.usernotfound");
             } else if (user.getEmail() == null) {
                 map.put("error", "recover.error.noemail");
             } else {
-                String password = RandomStringUtils.randomAlphanumeric(8);
+                StringBuilder sb = new StringBuilder(PASSWORD_LENGTH);
+                for(int i=0; i<PASSWORD_LENGTH; i++) {
+                  int index = random.nextInt(SYMBOLS.length());
+                  sb.append(SYMBOLS.charAt(index));
+                }
+                String password = sb.toString();
+
                 if (emailPassword(password, user.getUsername(), user.getEmail())) {
                     map.put("sentTo", user.getEmail());
                     user.setLdapAuthenticated(false);
                     user.setPassword(password);
                     securityService.updateUser(user);

Vulnerable code: import org.apache.commons.lang.RandomStringUtils;
                String password = RandomStringUtils.randomAlphanumeric(8);
Fixed code: import java.security.SecureRandom;
    private static final String SYMBOLS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    private final SecureRandom random = new SecureRandom();
    private static final int PASSWORD_LENGTH = 32;

                StringBuilder sb = new StringBuilder(PASSWORD_LENGTH);
                for(int i=0; i<PASSWORD_LENGTH; i++) {
                  int index = random.nextInt(SYMBOLS.length());
                  sb.append(SYMBOLS.charAt(index));
                }
                String password = sb.toString();