CVE-2020-5222 (published 2020-01-30T21:15Z)

Opencast before 7.6 and 8.1 enables a remember-me cookie based on a hash created from the username, password, and an additional system key. This means that an attacker getting access to a remember-me token for one server can get access to all servers which allow log-in using the same credentials without ever needing the credentials. This problem is fixed in Opencast 7.6 and Opencast 8.1

Severity: MEDIUM
CVSS v2 base score: 6.5 (AV:N/AC:L/Au:S/C:P/I:P/A:P)
CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-798
cwe_name: Use of Hard-coded Credentials
description: The product contains hard-coded credentials, such as a password or cryptographic key.
extended_description: 
		   There are two main variations:
		   
			 Inbound: the product contains an authentication mechanism that checks the input credentials against a hard-coded set of credentials. In this variant, a default administration account is created, and a simple password is hard-coded into the product and associated with that account. This hard-coded password is the same for each installation of the product, and it usually cannot be changed or disabled by system administrators without manually modifying the program, or otherwise patching the product. It can also be difficult for the administrator to detect.
			 Outbound: the product connects to another system or component, and it contains hard-coded credentials for connecting to that component. This variant applies to front-end systems that authenticate with a back-end service. The back-end service may require a fixed password that can be easily discovered. The programmer may simply hard-code those back-end credentials into the front-end product.
		   
         
         

Fix information:
repo_url: https://github.com/opencast/opencast
commit_details: repo_url: https://github.com/opencast/opencast
author: Lars Kiesow
author_date: 2020-01-13 20:33:34+01:00
msg: Fix Remember-Me Cookie

The security configuration in etc/security/mh_default_org.xml enables a
remember-me cookie based on a hash created from the username, password,
and an additional system key. Opencast has hard-coded this system key in
the large XML file and never mentions to change this, basically ensuring
that all systems use the same key:

    <sec:remember-me key="opencast" user-service-ref="userDetailsService" />

This means that an attacker getting access to a remember-me token for
one server can get access to all servers which allow log-in using the
same credentials without ever needing the credentials. For example, a
remember-me token obtained from develop.opencast.org can be used on
stable.opencast.org without actually knowing the log-in credentials.

Such an attack will usually not work on different installations –
assuming that safe, unique passwords are used – but it is basically
guaranteed to work to get access to all machines of one cluster if a
token from one machine is compromised.

This patch now makes Opencast automatically generate a safe key based on
unique system properties, creating a safe, zero-configuration variant of
the remember-me token.

This patch additionally switches from using MD5 for the tokens to using
SHA-512 instead to also be more resilient against brute-force attacks in
case an attacker gets access to such a token.
repository: repo_url: https://github.com/opencast/opencast
repo_name: opencast/opencast
description: The free and open source solution for automated video capture and distribution at scale.
repo_language: Java
owner: opencast
file_changes: filename: mh_default_org.xml
change_type: ModificationType.MODIFY
diff: @@ -326,7 +326,7 @@
     -->
 
     <!-- Enables "remember me" functionality -->
-    <sec:remember-me key="opencast" user-service-ref="userDetailsService" />
+    <sec:remember-me services-ref="rememberMeServices" />
 
     <!-- Set the request cache -->
     <sec:request-cache ref="requestCache" />
@@ -344,6 +344,15 @@
 
   </sec:http>
 
+  <bean id="rememberMeServices" class="org.opencastproject.kernel.security.SystemTokenBasedRememberMeService">
+    <property name="userDetailsService" ref="userDetailsService"/>
+    <!-- All following settings are optional -->
+    <property name="tokenValiditySeconds" value="1209600"/>
+    <property name="cookieName" value="oc-remember-me"/>
+    <!-- The following key will be augmented by system properties. Thus, leaving this untouched is okay -->
+    <property name="key" value="opencast"/>
+  </bean>
+
   <!-- ############################# -->
   <!-- # Authentication Filters    # -->
   <!-- ############################# -->

programming_language: Shell
filename: SystemTokenBasedRememberMeService.java
change_type: ModificationType.ADD
diff: @@ -0,0 +1,136 @@
+/**
+ * Licensed to The Apereo Foundation under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ *
+ * The Apereo Foundation licenses this file to you under the Educational
+ * Community License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License
+ * at:
+ *
+ *   http://opensource.org/licenses/ecl2.txt
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ *
+ */
+
+package org.opencastproject.kernel.security;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.codec.Hex;
+import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * This implements a zero-configuration version Spring Security's token based remember-me service. While the key can
+ * still be augmented by configuration, it is generally generated based on seldom changing but unique system
+ * properties like hostname, IP address, file system information and Linux kernel.
+ */
+public class SystemTokenBasedRememberMeService extends TokenBasedRememberMeServices {
+  private Logger logger = LoggerFactory.getLogger(SystemTokenBasedRememberMeService.class);
+  private String key;
+
+  @Deprecated
+  public SystemTokenBasedRememberMeService() {
+    super();
+    setKey(null);
+  }
+
+  public SystemTokenBasedRememberMeService(String key, UserDetailsService userDetailsService) {
+    super(key, userDetailsService);
+    setKey(key);
+  }
+
+  /**
+   * Set a new key to be used when generating remember-me tokens.
+   *
+   * Note that the key passed to this method will be augmented by seldom changing but generally unique system
+   * properties like hostname, IP address, file system information and Linux kernel. Hence, even setting no custom
+   * key should be save.
+   */
+  @Override
+  public void setKey(String key) {
+    // Start with a user key if provided
+    StringBuilder keyBuilder = new StringBuilder(Objects.toString(key, ""));
+
+    // This will give us the hostname and IP address as something which should be unique per system.
+    // For example: lk.elan-ev.de/10.10.10.31
+    try {
+      keyBuilder.append(InetAddress.getLocalHost());
+    } catch (UnknownHostException e) {
+      // silently ignore this
+    }
+
+    // Gather additional system properties as key
+    // This requires a proc-fs which should generally be available under Linux.
+    // But even without, we have fallbacks above and below.
+    for (String procFile: Arrays.asList("/proc/version", "/proc/partitions")) {
+      try (FileInputStream fileInputStream = new FileInputStream(new File(procFile))) {
+        keyBuilder.append(IOUtils.toString(fileInputStream, StandardCharsets.UTF_8));
+      } catch (IOException e) {
+        // ignore this
+      }
+    }
+
+    // If we still have no proper key, just generate a random one.
+    // This will work just fine with the single drawback that restarting Opencast invalidates all remember-me tokens.
+    // But it should be a sufficiently good fallback.
+    key = keyBuilder.toString();
+    if (key.isEmpty()) {
+      logger.warn("Could not generate semi-persistent remember-me key. Will generate a non-persistent random one.");
+      key = Double.toString(Math.random());
+    }
+    logger.debug("Remember me key before hashing: {}", key);
+
+    // Use a SHA-512 hash as key to have a more sane key.
+    try {
+      MessageDigest digest = MessageDigest.getInstance("SHA-512");
+      key = new String(Hex.encode(digest.digest(key.getBytes())));
+    } catch (NoSuchAlgorithmException e) {
+      logger.warn("No SHA-512 algorithm available!");
+    }
+    logger.debug("Calculated remember me key: {}", key);
+    this.key = key;
+    super.setKey(key);
+  }
+
+  @Override
+  public String getKey() {
+    return this.key;
+  }
+
+  /**
+   * Calculates the digital signature to be put in the cookie. Default value is
+   * SHA-512 ("username:tokenExpiryTime:password:key")
+   */
+  @Override
+  protected String makeTokenSignature(long tokenExpiryTime, String username, String password) {
+    String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
+    MessageDigest digest;
+    try {
+      digest = MessageDigest.getInstance("SHA-512");
+    } catch (NoSuchAlgorithmException e) {
+      throw new IllegalStateException("No SHA-512 algorithm available!");
+    }
+
+    return new String(Hex.encode(digest.digest(data.getBytes())));
+  }
+}

programming_language: Java