Skip to content

Escape Username in LDAP search filters #96

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
htmlcov
18 changes: 13 additions & 5 deletions nginx-ldap-auth-daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@
''''[ -z $LOG ] && export LOG=/dev/stdout # '''
''''which python >/dev/null && exec python -u "$0" "$@" >> $LOG 2>&1 # '''

# Copyright (C) 2014-2015 Nginx, Inc.
# Copyright (C) 2014-2022 Nginx, Inc.

import sys
import os
import signal
import base64
import ldap
from ldap.filter import escape_filter_chars
import argparse

import sys, os, signal, base64, ldap, argparse
if sys.version_info.major == 2:
from Cookie import BaseCookie
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
Expand All @@ -23,6 +30,7 @@
# -----------------------------------------------------------------------------
# Requests are processed in separate thread
import threading

if sys.version_info.major == 2:
from SocketServer import ThreadingMixIn
elif sys.version_info.major == 3:
Expand Down Expand Up @@ -88,9 +96,9 @@ def do_GET(self):
except:
self.auth_failed(ctx)
return True

ctx['user'] = user

ctx['pass'] = passwd
ctx['user'] = ldap.filter.escape_filter_chars(user)

# Continue request processing
return False
Expand Down Expand Up @@ -228,7 +236,7 @@ def do_GET(self):
ldap_obj.bind_s(ctx['binddn'], ctx['bindpasswd'], ldap.AUTH_SIMPLE)

ctx['action'] = 'preparing search filter'
searchfilter = ctx['template'] % { 'username': ctx['user'] }
searchfilter = ctx['template'] % {'username': ctx['user']}

self.log_message(('searching on server "%s" with base dn ' + \
'"%s" with filter "%s"') %
Expand Down
6 changes: 5 additions & 1 deletion t/README → t/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
To run tests use supplied Dockerfile.test:

docker build -f Dockerfile.test -t my-tag
```shell
docker build -t my-tag -f Dockerfile.test .
```

If you desire to use a container with Python3, you can supply an appropriate
build argument:

```shell
docker build -f Dockerfile.test -t my-tag --build-arg PYTHON_VERSION=3 .
docker run my-tag
```

To run without Docker:

Expand Down
50 changes: 42 additions & 8 deletions t/ldap-auth.t
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ http {
proxy_pass http://backend/;
}

location /query-injection {
auth_request /auth-query-injection;

error_page 401 =200 /login;

proxy_pass http://backend/;

}

location /login {
proxy_pass http://backend/login;

Expand Down Expand Up @@ -221,6 +230,24 @@ http {
proxy_set_header Cookie nginxauth=$cookie_nginxauth;
}

location = /auth-query-injection {
internal;

proxy_pass http://127.0.0.1:8888;

proxy_pass_request_body off;
proxy_set_header Content-Length "";

proxy_set_header X-Ldap-URL "ldap://127.0.0.1:8083";
proxy_set_header X-Ldap-BaseDN "ou=Users,dc=test,dc=local";
proxy_set_header X-Ldap-BindDN "cn=root,dc=test,dc=local";
proxy_set_header X-Ldap-BindPass "secret";

proxy_set_header X-CookieName "nginxauth";
proxy_set_header Cookie nginxauth=$cookie_nginxauth;

proxy_set_header X-Ldap-Template '(|(&(memberOf=superadmin)(cn=%(username)s))(&(memberOf=admin)(cn=%(username)s)))';
}
}
}

Expand Down Expand Up @@ -321,7 +348,7 @@ EOF
$t->write_file_expand("initial.ldif", <<'EOF');
dn: dc=test,dc=local
dc: test
description: BlaBlaBla
description: Test-OU
objectClass: dcObject
objectClass: organization
o: Example, Inc.
Expand All @@ -333,7 +360,7 @@ objectclass: organizationalunit

dn: cn=user1,ou=Users,dc=test,dc=local
objectclass: inetOrgPerson
cn: User number one
cn: User1
sn: u1
uid: user1
userpassword: user1secret
Expand All @@ -343,7 +370,7 @@ ou: Users

dn: cn=user2,ou=Users,dc=test,dc=local
objectclass: inetOrgPerson
cn: User number one
cn: User2
sn: u2
uid: user2
userpassword: user2secret
Expand All @@ -353,7 +380,7 @@ ou: Users

dn: cn=user3,ou=Users,dc=test,dc=local
objectclass: inetOrgPerson
cn: User number one
cn: User3
sn: u3
uid: user3
userpassword: user3secret
Expand All @@ -378,13 +405,13 @@ objectclass: organizationalunit

dn: ou=more,ou=Users,dc=test,dc=local
dc: test
description: BlaBlaBla
description: Test-OU
objectClass: dcObject
objectClass: organizationalUnit

dn: cn=user4, ou=more, ou=Users,dc=test,dc=local
objectclass: inetOrgPerson
cn: User number one
cn: User4
sn: u4
uid: user4
userpassword: user4secret
Expand Down Expand Up @@ -441,7 +468,7 @@ $t->run_daemon('/bin/sh', "$d/auth_daemon.sh");
$t->waitforsocket('127.0.0.1:' . port(8888))
or die "Can't start auth daemon";

$t->plan(21);
$t->plan(22);

$t->run();

Expand Down Expand Up @@ -500,10 +527,17 @@ like(http_get_auth('/ref1', 'user4', 'user4secret'), qr!LOGIN PAGE!,
'server2 user via referral on server1');

# unknown user on referred server, result is empty dn
like(http_get_auth('/ref1', 'userx', 'blah'), qr!LOGIN PAGE!,
like(http_get_auth('/ref1', 'unknow_user', 'unknowpassword'), qr!LOGIN PAGE!,
'unknown user with referral on server1');


# LDAP Query Injection result in 401
like(http_get_auth('/query-injection', 'user1))(|(cn=user1', 'user1secret'), qr!LOGIN PAGE!,
'Injection Attempt in Username will be escaped and blocked.');




###############################################################################

sub http_get_auth {
Expand Down