Maintainer can leak masked webhook secrets by manipulating URL masking
HackerOne report #1976206 by theluci
on 2023-05-07, assigned to @nmalcolm:
Report | Attachments | How To Reproduce
Report
Hello,
I recently submitted a report #1915507 which was closed as duplicate of #391685 (closed).
The fix to #391685 (closed) can be bypassed.
Summary
There is an option to mask parts of a webhook URL to treat it as a secret value.
https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#mask-sensitive-portions-of-webhook-urls
When this feature is used any secret string in the configured URL will be masked in the UI and in any logs in the UI. The values work the same as other tokens in that they are not even accessible by the user configuring it after it is first configured. It should not be possible for the initial user or any other users to retrieve these values.
The docs states this about the secret
Sensitive portions do not get logged and are encrypted at rest in the database.
However, there is a way to leak masked webhook secrets by bypassing the validation logic. (See Vulnerability)
Vulnerability
The fix changed validation logic as following,
def reset_url_variables
interpolated_url_was = interpolated_url(decrypt_url_was, url_variables_were)
return if url_variables_were.empty? || interpolated_url_was == interpolated_url
self.url_variables = {} if url_changed? && url_variables_were.to_a.intersection(url_variables.to_a).any?
end
To bypass the validation logic
-
attacker
mask the victim-url such ashttps://victim-url?token={TOKEN}
becomes{masked-url}?token={TOKEN}
and clicks save.
This will not reset url_variables
as url_changed?
returns false.
-
attacker
now changes the url such as{masked-url}?token={TOKEN}
becomeshttps://attacker-url?token={TOKEN}
-
attacker
masks the attacker-url with the same mask {masked-url}. So, that url finally becomes{masked-url}?token={TOKEN}
and clicks save.
This will not reset url_variables
as interpolated_url_was == interpolated_url
condition is satisfied.
-
attacker
can now click test to receive masked webhook secrets.
Steps to reproduce
victim
is the owner of a project victim-project
attacker
is a maintainer in victim-project
As victim
,
-
victim
goes to hisvictim-project
webhook settings,https://gitlab.com/<victim-group>/<victim-project>/-/hooks
-
victim
configures a webhook with a secret token and mask the secret token. For example, Put the URL like thishttps://example.com?token=secret-token
- Click to add a mask, add the value
secret-token
with the replacement TOKEN. Click save for the webhook
As attacker
-
attacker
goes tovictim-project
webhook settings,https://gitlab.com/<victim-group>/<victim-project>/-/hooks
- Scroll to the bottom of the page to the list of configured hooks and edit the webhook.
-
attacker
mask the victim-url as {masked-url}. Keep the token={TOKEN} part. Like this,{masked-url}?token={TOKEN}
and clicks save
-
attacker
add the attacker-url (i used https://webhook.site to catch the request). Keep the token={TOKEN} part. Like this,https://attacker-url?token={TOKEN}
and mask the attacker-url using the same mask, {masked-url} as shown below
-
attacker
click save -
attacker
click test - Request is sent to the attacker-url and containing the secret token.
POC
Impact
Maintainers can leak secret masked values that should not be accessible after configuration.
Output of checks
This bug happens on GitLab.com (Probably on instance too)
Impact
Maintainers can leak secret masked values that should not be accessible after configuration.
Attachments
Warning: Attachments received through HackerOne, please exercise caution!
How To Reproduce
Please add reproducibility information to this section: