CVE-2019-10908 (published 2019-04-07T14:29Z)

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.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 9.8, CRITICAL (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-335
cwe_name: Incorrect Usage of Seeds in Pseudo-Random Number Generator (PRNG)
description: The product uses a Pseudo-Random Number Generator (PRNG) but does not correctly manage seeds.
extended_description: 
		   
		   PRNGs are deterministic and, while their output appears
		   random, they cannot actually create entropy. They rely on
		   cryptographically secure and unique seeds for entropy so
		   proper seeding is critical to the secure operation of the
		   PRNG.
		   
		   Management of seeds could be broken down into two main areas:
		   
		   
			 
		   (1) protecting seeds as cryptographic material (such as a cryptographic key);
			 
			 
		   (2) whenever possible, using a uniquely generated seed from
		   a cryptographically secure source
			 
		   
			 
			   PRNGs require a seed as input to generate a stream of
			   numbers that are functionally indistinguishable from
			   random numbers.  While the output is, in many cases,
			   sufficient for cryptographic uses, the output of any
			   PRNG is directly determined by the seed provided as
			   input. If the seed can be ascertained by a third party,
			   the entire output of the PRNG can be made known to
			   them. As such, the seed should be kept secret and
			   should ideally not be able to be guessed. For example,
			   the current time may be a poor seed. Knowing the
			   approximate time the PRNG was seeded greatly reduces
			   the possible key space.
			 
			 
			   Seeds do not necessarily need to be unique, but reusing seeds may open up attacks if the seed is discovered.
			 
		 
         

Fix information:
repo_url: https://github.com/airsonic/airsonic
commit_details: repo_url: https://github.com/airsonic/airsonic
author: jvoisin
author_date: 2019-03-28 21:47:57+01:00
msg: 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.
repository: repo_url: https://github.com/airsonic/airsonic
repo_name: airsonic/airsonic
description:  :satellite: :cloud: :notes:Airsonic, a Free and Open Source community driven media server (fork of Subsonic and Libresonic)
repo_language: Java
owner: airsonic
file_changes: filename: RecoverController.java
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,6 @@ 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;
@@ -22,6 +21,7 @@ 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;
@@ -37,6 +37,10 @@ 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
@@ -69,7 +73,13 @@ public class RecoverController {
             } 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);

programming_language: Java