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
-
attackermask 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.
-
attackernow changes the url such as{masked-url}?token={TOKEN}becomeshttps://attacker-url?token={TOKEN} -
attackermasks 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.
-
attackercan 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,
-
victimgoes to hisvictim-projectwebhook settings,https://gitlab.com/<victim-group>/<victim-project>/-/hooks -
victimconfigures 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-tokenwith the replacement TOKEN. Click save for the webhook
As attacker
-
attackergoes tovictim-projectwebhook 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.
-
attackermask the victim-url as {masked-url}. Keep the token={TOKEN} part. Like this,{masked-url}?token={TOKEN}and clicks save
-
attackeradd 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
-
attackerclick save -
attackerclick 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:



