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

Using STARTTLS breaks LDAP authentication #12047

Closed
oschlueter opened this Issue Oct 5, 2018 · 2 comments

Comments

Projects
None yet
3 participants
@oschlueter
Copy link

oschlueter commented Oct 5, 2018

Abstract

I discovered that when I configure the LDAP Auth Provider to use STARTTLS authentication is broken, i.e. I can log into the Neo4j instance with any existing user account using arbitrary passwords.
Unencrypted LDAP connections (without STARTTLS) and LDAPS connections are not affected.

  • Neo4j version: 3.4.4-enterprise (also reproduced with 3.4.7-enterprise)
  • Operating system: Alpine (via Docker)
  • API/Driver: ?

Steps to reproduce

  1. Create docker-compose.yml:
version: '3.4'

services:
  openldap:
    image: osixia/openldap:1.2.2
    ports:
      - 1389:389
      - 1636:636
    environment:
      - LDAP_BASE_DN=
      - LDAP_READONLY_USER=true
      - LDAP_READONLY_USER_USERNAME=bind
      - LDAP_READONLY_USER_PASSWORD=bind
      - LDAP_TLS_VERIFY_CLIENT=try
      - LDAP_TLS_CRT_FILENAME=openldap.crt
      - LDAP_TLS_KEY_FILENAME=openldap.key
      - LDAP_TLS_CA_CRT_FILENAME=ca.crt
    volumes:
      - $PWD/bootstrap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/bootstrap.ldif
      - $PWD/certs:/container/service/slapd/assets/certs
    command: --copy-service

  neo4j:
    image: neo4j:3.4.4-enterprise
    ports:
      - 7474:7474
      - 7687:7687
    environment:
      - NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
    volumes:
      - $PWD/neo4j.conf:/conf/neo4j.conf
      - $PWD/logs:/logs
      - $PWD/certs/truststore.jks:/etc/neo4j/truststore.jks
  1. Pull Docker images: docker-compose pull
  2. Create bootstrap.ldif:
dn: ou=groups,dc=example,dc=org
objectClass: organizationalUnit
ou: groups
description: generic groups branch

dn: ou=people,dc=example,dc=org
objectClass: organizationalUnit
ou: people
description: generic people branch

dn: uid=alice,ou=people,dc=example,dc=org
uid: alice
cn: alice
sn: smith
objectClass: inetOrgPerson
userPassword: alice

dn: cn=everyone,ou=groups,dc=example,dc=org
objectClass: groupOfUniqueNames
cn: everyone
description: Contains every account 
uniqueMember: uid=alice,ou=people,dc=example,dc=org
  1. Create certificates and truststore
mkdir certs && cd certs && \
openssl genrsa -out ca.key 1024 && \
openssl req -new -x509 -key ca.key -out ca.crt -nodes -subj '/CN=CA' && \
openssl genrsa -out openldap.key 1024 && \
openssl req -new -key openldap.key -out openldap.csr -subj '/CN=openldap' && \
openssl x509 -req -in openldap.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out openldap.crt && \
openssl dhparam -out dhparam.pem 256 && \
keytool -keystore truststore.jks -alias CARoot -import -file ca.crt -storepass truststore -noprompt && \
cd ..
  1. Create neo4j.conf:
dbms.connector.https.enabled=false

dbms.security.auth_provider=ldap
dbms.security.auth_cache_ttl=0

dbms.security.ldap.host=ldap://openldap
dbms.security.ldap.use_starttls=true
dbms.security.ldap.authentication.user_dn_template=cn={0},ou=people,dc=example,dc=org

dbms.security.ldap.authorization.use_system_account=true
dbms.security.ldap.authorization.system_username=cn=bind,dc=example,dc=org
dbms.security.ldap.authorization.system_password=bind

dbms.security.ldap.authorization.user_search_base=ou=people,dc=example,dc=org
dbms.security.ldap.authorization.user_search_filter=(&(objectClass=*)(uid={0}))
dbms.security.ldap.authorization.group_membership_attributes=memberOf

dbms.security.ldap.authorization.group_to_role_mapping=\
          "cn=everyone,ou=groups,dc=example,dc=org"      = reader;    \
          "cn=everyone,ou=groups,dc=example,dc=org"      = publisher;    \
          "cn=everyone,ou=groups,dc=example,dc=org"  = architect;    \
          "cn=everyone,ou=groups,dc=example,dc=org"   = admin;

dbms.jvm.additional=-Djavax.net.ssl.trustStore=/etc/neo4j/truststore.jks
dbms.jvm.additional=-Djavax.net.ssl.trustStorePassword=truststore
  1. Create logs directory: mkdir logs
  2. docker-compose up
  3. Verify STARTTLS works and account alice exists:
LDAPTLS_REQCERT=never ldapwhoami -D uid=alice,ou=people,dc=example,dc=org -H ldap://localhost:1389 -w alice -ZZ

Corresponding LDAP log:

openldap_1  | 5bb760bb conn=1000 fd=12 ACCEPT from IP=192.168.32.1:51174 (IP=0.0.0.0:389)
openldap_1  | 5bb760bb conn=1000 op=0 EXT oid=1.3.6.1.4.1.1466.20037
openldap_1  | 5bb760bb conn=1000 op=0 STARTTLS
openldap_1  | 5bb760bb conn=1000 op=0 RESULT oid= err=0 text=
openldap_1  | 5bb760bb conn=1000 fd=12 TLS established tls_ssf=256 ssf=256
openldap_1  | 5bb760bb conn=1000 op=1 BIND dn="uid=alice,ou=people,dc=example,dc=org" method=128
openldap_1  | 5bb760bb conn=1000 op=1 BIND dn="uid=alice,ou=people,dc=example,dc=org" mech=SIMPLE ssf=0
openldap_1  | 5bb760bb conn=1000 op=1 RESULT tag=97 err=0 text=
openldap_1  | 5bb760bb conn=1000 op=2 EXT oid=1.3.6.1.4.1.4203.1.11.3
openldap_1  | 5bb760bb conn=1000 op=2 WHOAMI
openldap_1  | 5bb760bb conn=1000 op=2 RESULT oid= err=0 text=
openldap_1  | 5bb760bb conn=1000 op=3 UNBIND
openldap_1  | 5bb760bb conn=1000 fd=12 closed
  1. Open Neo4J in your browser at http://localhost:7474 and login using the account alice and the password bob

Expected behavior

Login fails with error message Neo.ClientError.Security.Unauthorized: The client is unauthorized due to authentication failure.

Actual behavior

Login succeeds.

@oschlueter

This comment has been minimized.

Copy link
Author

oschlueter commented Oct 5, 2018

Some additional insights from my LDAP log analysis

We can see why Neo4j permits access to any valid user by looking at the LDAP log (using an incorrect password), e.g.:

authentication bind:
slapd[1042]: conn=130293 fd=24 ACCEPT from IP=172.31.28.66:60278 (IP=0.0.0.0:389)
slapd[1042]: conn=130293 op=0 EXT oid=1.3.6.1.4.1.1466.20037
slapd[1042]: conn=130293 op=0 STARTTLS
slapd[1042]: conn=130293 op=0 RESULT oid= err=0 text=
slapd[1042]: conn=130293 fd=24 TLS established tls_ssf=256 ssf=256
slapd[1042]: conn=130293 fd=24 closed (connection lost)

authorization query:
slapd[1042]: conn=130294 fd=24 ACCEPT from IP=172.31.28.66:60280 (IP=0.0.0.0:389)
slapd[1042]: conn=130294 op=0 EXT oid=1.3.6.1.4.1.1466.20037
slapd[1042]: conn=130294 op=0 STARTTLS
slapd[1042]: conn=130294 op=0 RESULT oid= err=0 text=
slapd[1042]: conn=130294 fd=24 TLS established tls_ssf=256 ssf=256
slapd[1042]: conn=130294 op=1 BIND dn="cn=bind,dc=example,dc=org" method=128
slapd[1042]: conn=130294 op=1 BIND dn="cn=bind,dc=example,dc=org" mech=SIMPLE ssf=0
slapd[1042]: conn=130294 op=1 RESULT tag=97 err=0 text=
slapd[1042]: conn=130294 op=2 SRCH base="ou=people,dc=example,dc=org" scope=2 deref=3 filter="(&(objectClass=*)(uid=alice))"
slapd[1042]: conn=130294 op=2 SRCH attr=memberOf
slapd[1042]: conn=130294 op=2 SEARCH RESULT tag=101 err=0 nentries=1 text=
slapd[1042]: conn=130294 op=3 UNBIND
slapd[1042]: conn=130294 fd=24 closed

Compare this to a properly functioning setup (e.g. our company's TeamCity) with an incorrect password:

authorization query:
slapd[1042]: conn=123422 fd=24 ACCEPT from IP=172.31.19.107:42984 (IP=0.0.0.0:636)
slapd[1042]: conn=123422 fd=24 TLS established tls_ssf=256 ssf=256
slapd[1042]: conn=123422 op=0 BIND dn="cn=bind,dc=example,dc=org" method=128
slapd[1042]: conn=123422 op=0 BIND dn="cn=bind,dc=example,dc=org" mech=SIMPLE ssf=0
slapd[1042]: conn=123422 op=0 RESULT tag=97 err=0 text=
slapd[1042]: conn=123422 op=1 SRCH base="ou=people,dc=example,dc=org" scope=2 deref=3 filter="(&(uid=alice)(memberOf=cn=teamcity,ou=groups,dc=example,dc=org))"
slapd[1042]: conn=123422 op=1 SRCH attr=uid distinguishedName
slapd[1042]: conn=123422 op=1 SEARCH RESULT tag=101 err=0 nentries=1 text=
slapd[1042]: conn=123422 op=2 UNBIND
slapd[1042]: conn=123422 fd=24 closed

authentication bind:
slapd[1042]: conn=123423 fd=24 ACCEPT from IP=172.31.19.107:42986 (IP=0.0.0.0:636)
slapd[1042]: conn=123423 fd=24 TLS established tls_ssf=256 ssf=256
slapd[1042]: conn=123423 op=0 BIND dn="uid=alice,ou=people,dc=example,dc=org" method=128
slapd[1042]: conn=123423 op=0 RESULT tag=97 err=49 text=
slapd[1042]: conn=123423 fd=24 closed (connection lost)

The authentication bind performed by TeamCity clearly shows the account alice used as BIND DN and the result code returned is 49 (invalid credentials) as expected. However the authentication bind performed by Neo4j shows no actual BIND operation and the last result code returned is 0 (success) from the STARTTLS operation.

The primary difference here is TeamCity connecting via LDAPS whereas Neo4j connects via regular LDAP connection and establishes a STARTLS upgrade afterwards, an example of this using ldapwhoami looks like this:

ldapwhoami -H ldap://openldap.internal.example.de -x -D uid=alice,ou=people,dc=example,dc=org -W -ZZ

slapd[1042]: conn=131150 fd=24 ACCEPT from IP=172.31.30.126:53224 (IP=0.0.0.0:389)
slapd[1042]: conn=131150 op=0 EXT oid=1.3.6.1.4.1.1466.20037
slapd[1042]: conn=131150 op=0 STARTTLS
slapd[1042]: conn=131150 op=0 RESULT oid= err=0 text=
slapd[1042]: conn=131150 fd=24 TLS established tls_ssf=256 ssf=256
slapd[1042]: conn=131150 op=1 BIND dn="uid=alice,ou=people,dc=example,dc=org" method=128
slapd[1042]: conn=131150 op=1 BIND dn="uid=alice,ou=people,dc=example,dc=org" mech=SIMPLE ssf=0
slapd[1042]: conn=131150 op=1 RESULT tag=97 err=0 text=
slapd[1042]: conn=131150 op=2 EXT oid=1.3.6.1.4.1.4203.1.11.3
slapd[1042]: conn=131150 op=2 WHOAMI
slapd[1042]: conn=131150 op=2 RESULT oid= err=0 text=
slapd[1042]: conn=131150 op=3 UNBIND
slapd[1042]: conn=131150 fd=24 closed

Here we can see the STARTLS command executed with result code 0 prior to a BIND operation with the account alice (and correct credentials in this case). Therefore I assume Neo4j interprets the intial result code of the STARTTLS negotiation as result of a successful BIND operation.

@SaschaPeukert

This comment has been minimized.

Copy link
Contributor

SaschaPeukert commented Oct 11, 2018

Thank you so much for reporting this @oschlueter!
The fix for that problem ( 46de5d0 ) will go in all upcoming releases from 3.4.9 onwards.

Cheers,
Sascha

Neo4j Cypher Team

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment