SSRF: DNS rebinding protection bypass when allowing an IP address in Outbound Requests setting
HackerOne report #1055816 by yvvdwf
on 2020-12-10, assigned to @rchan-gitlab:
Report | Attachments | How To Reproduce
Report
Hi,
While examining validate! function, I found that this implementation contradicts the specification of Ountbound Requests
in administrator setting area. This contradiction leads to disabling DNS rebinding attack protection, thus exposing local networks. Let's me detail.
Outbound Requests
setting allows requests to certain domains and local IP addresses that hooks and services may access while maintain DNS rebinding attack protection with the others. For example, in the following setting, we want to allow to access to only 192.168.0.120
and prohibit from accessing the other local IP addresses.
{{Image: Please see outbound-requests-setting.png
}}
However this is not the case in the implementation of validate!
function: Once the admin allows a local IP address, attackers may access any other local IPs.
Steps to reproduce
As Administrator:
Go to Admin Area/Settings/Network
, then Outbound Requests
section. Set the parameters in this section as in the image, Ex:
- Allow requests to the local network from web hooks and services: No
- Allow requests to the local network from system hooks: No
- Local IP addresses and domain names that hooks and services may access:
192.168.0.120
- Enforce DNS rebinding attack protection: YES
- Click
Save changes
As attacker
In an existing project, or create a new project, go to Settings/Webhooks
. Set the parameters in Webhooks as the following:
- URL:
http://a.192.168.0.120.3times.127.0.0.1.1time.repeat.gitlab.dyn.yvvdwf.me:9121/scrape?target=unix:///var/opt/gitlab/redis/redis.socket&check-keys=*
- The other parameters are not important, you can ignore them.
- Click
Add webhook
- After being saved, in the
Project Hooks (1)
area, clickTest/Push events
. - You should see the entire Redis content as result, otherwise, if you got errors, click
Test/Push events
again to retry. - The interval between success and fail tests depends on DNS cache. You should change the local default DNS resolver to
8.8.8.8
, for example, setnameserver 8.8.8.8
as content of/etc/resolve.conf
{{Image: Please see retry-rquests-when-fail.png
}}
Note
Attacker can be aware about local IP 192.168.0.120
by automatically trying one by one. There are about 18M local IP addresses. If he can probe 3 IPs per seconds, he needs 18M/3/3600/24 = ~70 days. He can easily reduce this since nothing prevents him from creating 10 different accounts and 10 checking threads running in parallel for each account. This probing time increases if administrator add a port number together with IP addresses.
Impact
Once administrator allows only one local IP address, attackers may have full access to any other local IP addresses.
What is the current bug behavior?
I think that the bug is in validate! function:
address_info = get_address_info(uri, dns_rebind_protection)
return [uri, nil] unless address_info
ip_address = ip_address(address_info)
return [uri, nil] if domain_allowed?(uri) || ip_allowed?(ip_address, port: get_port(uri))
protected_uri_with_hostname = enforce_uri_hostname(ip_address, uri, dns_rebind_protection)
The checking ip_allowed?
should be done after enforce_uri_hostname
What is the expected correct behavior?
I think that the expected implementation could be as the following:
address_info = get_address_info(uri, dns_rebind_protection)
return [uri, nil] unless address_info
ip_address = ip_address(address_info)
-return [uri, nil] if domain_allowed?(uri) || ip_allowed?(ip_address, port: get_port(uri))
+return [uri, nil] if domain_allowed?(uri)
protected_uri_with_hostname = enforce_uri_hostname(ip_address, uri, dns_rebind_protection)
+return protected_uri_with_hostname if ip_allowed?(ip_address, port: get_port(uri))
Output of checks
Results of GitLab environment info
(For installations with omnibus-gitlab package run and paste the output of:
sudo gitlab-rake gitlab:env:info
)
System information
System:
Proxy: no
Current User: git
Using RVM: no
Ruby Version: 2.7.2p137
Gem Version: 3.1.4
Bundler Version:2.1.4
Rake Version: 13.0.1
Redis Version: 5.0.9
Git Version: 2.29.0
Sidekiq Version:5.2.9
Go Version: unknown
GitLab information
Version: 13.6.2-ee
Revision: 98aab73cbd5
Directory: /opt/gitlab/embedded/service/gitlab-rails
DB Adapter: PostgreSQL
DB Version: 11.9
URL: http://gitlab.example.com
HTTP Clone URL: http://gitlab.example.com/some-group/some-project.git
SSH Clone URL: git@gitlab.example.com:some-group/some-project.git
Elasticsearch: no
Geo: no
Using LDAP: no
Using Omniauth: yes
Omniauth Providers:
GitLab Shell
Version: 13.13.0
Repository storage paths:
- default: /var/opt/gitlab/git-data/repositories
GitLab Shell path: /opt/gitlab/embedded/service/gitlab-shell
Git: /opt/gitlab/embedded/bin/git
Impact
Once administrator allows only one local IP address, attackers have full access to any other local IP addresses.
Attachments
Warning: Attachments received through HackerOne, please exercise caution!
How To Reproduce
Please add reproducibility information to this section: