Security issue with redirects added by Certbot's Nginx plugin

We received a report of a vulnerability in Certbot that affects some Nginx configurations modified by Certbot to redirect all traffic from HTTP to HTTPS. For this problem to be exploitable, a number of conditions must be met including there being a cache between your Nginx server and its users that is misconfigured or has a separate vulnerability. Despite these limiting factors, we wanted to make our users aware of the problem, describe what we’re planning on doing going forward, and provide instructions on how to mitigate it.

Problem Description

The way Certbot creates an HTTP to HTTPS redirect in Nginx is through the following configuration directive:

return 301 https://$host$request_uri;

The potential problem with this is it relies on the value of the Host header for where to redirect the request. If a directive like this is added to the default server block for HTTP traffic in your Nginx configuration, users can modify the Host header of their request to have it redirected to an arbitrary domain. On its own, this isn’t a problem because the redirect to an arbitrary domain is only sent to user who put that domain in the Host header of their request.

The problem occurs if an attacker can trick a cache somewhere to store this redirect from Nginx and serve it to users who attempt to connect to the server through normal means with unmodified Host headers. If this could be done, an attacker could redirect traffic from users of that cache to a domain of their choosing.

Due to the prevalence of virtual hosting, a cache that would do this would have to have its own bugs or be misconfigured. The redirect added by Certbot is not exploitable on its own, but when coupled with other software with its own bugs, exploits, or misconfiguration, it can become a security problem.

Our Plan Going Forward

Starting with Certbot 0.21.1, redirects created by the Nginx plugin will not have this problem. This is done by ensuring the Host header is set to an expected value before serving the redirect. In a future Certbot release, we plan to allow Certbot to automatically correct redirects created by previous versions of the client. The GitHub issue tracking this feature is https://github.com/certbot/certbot/issues/5502.

How to Mitigate the Problem

Despite the limited scope of these problems, any user can modify their configuration now to ensure that they are not affected.

Before this release, Certbot would have added a redirect in one of two ways.

In one case, Certbot added the line return 301 https://$host$request_uri; # managed by Certbot. These users should replace this line with return 404; # managed by Certbot. After installing Certbot 0.21.1, they should run the command certbot --nginx --reinstall --redirect -d <domains>, where domains is a comma-separated list of domains that should be redirected to https. If this list of domains isn’t already known, it can be found by inspecting the server_name directive of the relevant server block, or by running certbot certificates to check which domains Certbot was used to generate a certificate for.

In the other case, Certbot added the following block:

if ($scheme != "https") {
    return 301 https://$host$request_uri; # managed by Certbot
} # managed by Certbot

These users should delete the entire if block, make sure they have Certbot 0.21.1 installed, and then run certbot --nginx --reinstall --redirect -d <domains>, where domains is a comma-separated list of domains that should be redirected to https, with domains can be found as described above.

For users who prefer to implement the fix manually, their resulting server blocks should take the form as in the example given below. Namely, they should have two resulting server blocks; one that supports https requests with the certificate information, and one that merely redirects traffic from http to https, with an if-block for each relevant domain and a return 404; at the bottom of the server block to ensure that any caught requests are not inadertently redirected.

server {
    root /var/www/html;
    index index.html;
    server_name example.com example.org;

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
    if ($host = example.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = example.org) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    server_name example.com example.org;
    listen 80;
    listen [::]:80;
    return 404; # managed by Certbot
}
1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.