[Bypass for CVE-2023-1965] SAML RelayState parameter still allows for arbitrary redirection

Please read the process on how to fix security issues before starting to work on the issue. Vulnerabilities must be fixed in a security mirror.

HackerOne report #1972880 by joaxcar on 2023-05-04, assigned to H1 Triage:

Report | Attachments | How To Reproduce

Report

Summary

GitLab version 15.11.1 contained a fix for CVE-2023-1965 titled Account takeover through open redirect for Group SAML accounts
"
. The issue was that GitLab allowed any URL in the RelayState parameter to propagate through the application after a SAML authentication and then redirect the user to this URL.

In the fix this code was added

def saml_redirect_path  
    return params['RelayState'] if params['RelayState'].to_s.start_with?("/")

    begin  
      parsed_uri = URI.parse(params['RelayState'].to_s)  
    rescue URI::InvalidURIError  
      parsed_uri = nil  
    end

    if parsed_uri.present? &&  
        (parsed_uri.scheme =~ /\Ahttps?\z/i) &&  
        (parsed_uri.host == Gitlab.config.gitlab.host) &&  
        (parsed_uri.port == Gitlab.config.gitlab.port)  
      params['RelayState']  
    else  
      group_path([@]unauthenticated_group)  
    end  
  end  

Where we can see that unless the RelayState parameter starts with a slash (a relative path) it will be returned as is. If not it will go through this check to see that it is pointing to the configured gitlab origin. The URL is later used by redirect_to to redirect.

The problem here is that an URL can start with double slash // and will then in a lot of cases (for example by redirect_to) count as a new URL (reusing the current scheme) and not a relative URL. So given the RelayState of //evil.com the fix will be bypassed and the redirect will again end up outside of Gitlab.

CVSS

The original researcher was able to show an impact of account takeover through this by leaking tokens after the redirect. There is nothing in the fix that prevents that from happening with this bypass, thus I will keep the CVSS at the same level. I have not managed to recreate it myself, but the Gitlab team will have access to the other report and will be able to confirm this.

Steps to reproduce

SAML

First, you need to register a SAML provider. I have tested this with Auth0 and Okta and they both work. Auth0 is a bit easier to set up. Follow this guide here to use Auth0 for SAML
https://auth0.com/docs/authenticate/protocols/saml/saml-sso-integrations/configure-auth0-saml-identity-provider#manually-configure-sso-integrations
The only addition to this guide is that you need to add this config during the step of setting up the SAML web app addon

{
  "nameIdentifierFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"  
}

When configuring the SAML service you need an "Application callback URL" this one will look like this https://gitlab.com/groups/testagain2/-/saml/callback with your group name

In Gitlab
  1. Create a new group (Ultimate license)
  2. Go to https://gitlab.com/groups/testgroup/-/saml
  3. Activate SAML and enter the IDP and Fingerprint given to you by Auth0
  4. Now visit the link under "GitLab single sign-on URL" it will look like this https://gitlab.com/groups/testgroup/-/saml/sso?token=TOKEN
  5. Make sure to log all requests, either in the network tab or in Burp
  6. Click "Sign in"
  7. First there will be a POST request to Gitlab, then there will be a redirect to Auth0 with an URL containing a SAML request and a RelayState
  8. Copy this URL and open it in a new tab instead. Replace RelayState value with //example.com
  9. Log in to Auth0 in the prompt.
  10. You will be redirected to GitLab and then directly redirected to example.com
Easy way out

Use my test Auth0 for an easier time
IDP: https://joaxcar.eu.auth0.com/samlp/iUZCIvymBh4JCXyuJDUQ4ydcFRpPJCWb
Fingerprint: 43:9F:EF:A3:A2:7C:C9:FC:28:09:CC:FA:2E:0C:57:E5:55:8B:D7:E5

And login with the user
Username: testuser@joaxcar.com
Password: TestUserPassword.
(note the dot in the password)

Impact

Bypass of CVE-2023-1965 leading to open redirect capable of account takeover

What is the current bug behavior?

RelayState is not checked for // URLs

What is the expected correct behavior?

Check for double slashes in the URL, or validate it in opposite order

Relevant logs and/or screenshots

In this screenshot here you can see that the GitLab endpoint gets a RelayState sent to it containing //example.com and that the next request is for example.com

image.png

Output of checks

This bug happens on GitLab.com

Impact

Bypass of CVE-2023-1965 leading to open redirect capable of account takeover

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

How To Reproduce

Please add reproducibility information to this section:

Implementation approach

Based on the previous fix Account takeover through open redirect for Grou... (#406235 - closed) we'd need to add an allow-list of URLs that are accepted as RelayState parameters to avoid this problem

Edited by Adil Farrukh