[security:high, CVE-2020-24660] Lack of URL normalization by Nginx may lead to authorization bypass when URL access rules are used
Environment
LemonLDAP::NG version: 2.0.8
Operating system: Debian Stretch, Debian Buster, probably RHEL
Web server: nginx/1.10.3, nginx/1.14.2
Summary
When using Nginx, regexp-based access rules may not be correctly enforced by the handler.
I am doing a CVE request for this bug
Logs
- Content of test vhost:
# cat /var/lib/lemonldap-ng/test/admin
SECRET ADMIN FILE
- Handler configuration:
- Proof of exploitation:
GET -S http://test1.example.com/admin/secretfile
GET http://test1.example.com/admin/secretfile
302 Moved Temporarily //AS EXPECTED
$ GET -S http://test1.example.com/%61dmin/secretfile
GET http://test1.example.com/%61dmin/secretfile
200 OK
SECRET ADMIN FILE //SHOULD BE PROTECTED
GET -S http://test1.example.com/x/../admin/secretfile
GET http://test1.example.com/x/../admin/secretfile
200 OK
SECRET ADMIN FILE //SHOULD BE PROTECTED
I have also successfully tested this in a reverse proxy configuration, which is a very common, if not the most common use case. I have also tested this without the "skip" keyword, in such a cas, a normal user may be granted access to admin-only resources.
Cause
The problem comes from the fact that the handler tests regexp against the REQUEST_URI variable. Unlike Apache, Nginx does not normalize REQUEST_URI. Because of this, it becomes extremely hard for an admin to write a regexp that correctly catches all of the possible URLs that can be used to target a protected resource (such as /admin).
Solutions
URI::Normalize
Nginx transmits the original URL in a X_ORIGINAL_URL header. We could use this fact to trigger special processing in the handler:
$self->env->{REQUEST_URI} = $self->env->{X_ORIGINAL_URI}
if ( $self->env->{X_ORIGINAL_URI} );
would change to
$self->env->{REQUEST_URI} = normalize_url($self->env->{X_ORIGINAL_URI})
if ( $self->env->{X_ORIGINAL_URI} );
Using normalize_url
from URI::Normalize which is not in distros but easily embeddable.
Nginx config
We could also make Nginx normalize the URL, with something like this:
location / {
...
# Save the normalized URI here
set $original_uri $uri$is_args$args;
...
}
location = /lmauth {
...
fastcgi_param X_ORIGINAL_URI $original_uri;
...
}
But that means each webserver we ever want to support will probably have it's own, distinct solution