The vulnerability to detect for this challenge was an SSRF exploitable with a workaround using a DNS rebinding (TOCTOU). The challenge resolution does not necessarily require knowledge of the language (Ruby) or the web framework (Roda). Indeed, the problem being rather focused on logical and universal concepts.

Note: This article is also available in french 🇫🇷. The challenge was announced in this tweet 🐦.

Explication

The main problem is that the application resolves the domain name twice: once to perform the security check and the second time to perform the request itself. This double resolution induces a race condition.

The first DNS lookup takes place within the trusted?() function when Resolv.getaddress is called to check if the domain included in the requested URL belongs to one of the servers hosting the internal web sites.
The second DNS resolution is performed at the time the HTTP request is made (http.get(url)).

An attacker can therefore bypass the protection mechanism by performing a DNS rebinding attack. More specifically, a TOCTOU (Time-Of-Check to Time-Of-Use) DNS rebinding, which is a form of race condition.

The principle of the attack is based on the fact that the attacker controls a domain name and the authoritative DNS server in order to give dynamically different answers according to certain criteria. For example, for a given sub-domain, the server will respond with an IP A to the first DNS request it receives and an IP B to the second request or an IP A to the first request and an IP B after a time T following the first request and originating from the same IP address.

A simple way to conduct this attack is to host an instance of the 1u.ms service or to use the public online version.

By specifying the following subdomain, the application will get the value 10.10.0.200 on the first DNS request, allowing the security check to be validated, and then get 42.42.42.42, or any other IP address the attacker wants to point the server to, on the second DNS request when the HTTP request is made. This allows the attacker to bypass the security mechanism while controlling the target of the HTTP request.

make-10.10.0.200-rebind-42.42.42.42-rr.1u.ms

DNS rebinding

In order to use the SSRF, simply use the following URL:

http://localhost:9292/admin/proxy?url=http://make-10.10.0.200-rebind-42.42.42.42-rr.1u.ms

If the attack does not work when performed several times in a row, it is probably because the DNS client used by the application has cached the DNS entry and is therefore no longer resolved on each HTTP request. However, 1u.ms provides for the addition of prefixes or suffixes to generate different subdomains with the same behavior.

noraj01-make-10.10.0.200-rebind-42.42.42.42-rr.1u.ms

Fixed code

Countermeasures to protect against this attack can be tricky to implement.

  • Blocking private addresses is not always possible or effective.
  • Using/allowing HTTPS exclusively will cause SSL exchanges to fail in an attack.
  • Placing an authentication system on all local services will protect the data even if the attacker manages to carry out his attack.
  • Specify the address of the DNS server used to resolve queries and configure a cache system on this server. However, the cache must define a minimum retention time (e.g. 300 seconds), otherwise if the cache relies on the TTL returned by the authority, the attacker will still be able to send a TTL of 0 or 1 second. systemd-resolved, dnsmasq, bind use the TTL, but a --min-cache-ttl option or equivalent is available to apply a minimum retention time.
  • It is possible to implement an application-level cache system using a Redis server:
    1. Check if the entry already exists in Redis
    2. If it exists, use it, if it doesn’t, go to the next step
    3. Solve the subdomain once
    4. Store it in Redis with a certain lifetime

The simplest and most universal solution will probably be to use a DNS cache.

Example of a dnsmasq configuration with a minimum cache of 30 seconds.

➜ grep -Ev '^#|^$' /etc/dnsmasq.conf
port=5353
listen-address=127.0.0.153
cache-size=1000
min-cache-ttl=30

DNS cache

Here is the fixed code using the previously configured DNS cache:

Fixed code

Diff

The source code is available on the Github repository Acceis/vulnerable-code-snippets.

About the author

Article written by Alexandre ZANNI aka noraj, Penetration Testing Engineer at ACCEIS.