Skip to content

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, click Test/Push events.
  • You should see the entire Redis content as result, otherwise, if you got errors, click Test/Push eventsagain 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, set nameserver 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: