Skip to content

ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, $option) can't be overridden #17776

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

Closed
robert-scheck opened this issue Feb 12, 2025 · 9 comments

Comments

@robert-scheck
Copy link

Description

The following code:

<?php
  ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 6);

  echo 'Test LDAP_OPT_X_TLS_ALLOW' . PHP_EOL;
  if (!ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_ALLOW)) {
    echo 'Unable to set LDAP_OPT_X_TLS_ALLOW!' . PHP_EOL;
  }
  $ldapconn1 = ldap_connect('ldaps://localhost:636');
  ldap_set_option($ldapconn1, LDAP_OPT_PROTOCOL_VERSION, 3);
  $ldapbind1 = ldap_bind($ldapconn1, 'CN=Administrator,CN=Users,DC=samdom,DC=example,DC=com', 'Passw0rd');
  if ($ldapbind1) {
    echo 'LDAP bind succeeded (expected)' . PHP_EOL;
  } else {
    echo 'LDAP bind failed (unexpected)' . PHP_EOL;
  }
  ldap_unbind($ldapconn1);

  echo PHP_EOL;

  echo 'Test LDAP_OPT_X_TLS_DEMAND' . PHP_EOL;
  if (!ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_DEMAND)) {
    echo 'Unable to set LDAP_OPT_X_TLS_DEMAND!' . PHP_EOL;
  }
  $ldapconn2 = ldap_connect('ldaps://localhost:636');
  ldap_set_option($ldapconn2, LDAP_OPT_PROTOCOL_VERSION, 3);
  $ldapbind2 = ldap_bind($ldapconn2, 'CN=Administrator,CN=Users,DC=samdom,DC=example,DC=com', 'Passw0rd');
  if ($ldapbind2) {
    echo 'LDAP bind succeeded (unexpected)' . PHP_EOL;
  } else {
    echo 'LDAP bind failed (expected)' . PHP_EOL;
  }
  ldap_unbind($ldapconn2);
EOF

Resulted in this output:

Test LDAP_OPT_X_TLS_ALLOW
TLS certificate verification: Error, unable to get local issuer certificate
TLS certificate verification: Error, unable to verify the first certificate
LDAP bind succeeded (expected)

Test LDAP_OPT_X_TLS_DEMAND
TLS certificate verification: Error, unable to get local issuer certificate
TLS certificate verification: Error, unable to verify the first certificate
TLS: unable to get peer certificate.
LDAP bind succeeded (unexpected)

But I expected this output instead:

Test LDAP_OPT_X_TLS_ALLOW
TLS certificate verification: Error, unable to get local issuer certificate
TLS certificate verification: Error, unable to verify the first certificate
LDAP bind succeeded (expected)

Test LDAP_OPT_X_TLS_DEMAND
TLS certificate verification: Error, unable to get local issuer certificate
TLS: can't connect: error:0A000086:SSL routines::certificate verify failed (unable to get local issuer certificate).
PHP Warning:  ldap_bind(): Unable to bind to server: Can't contact LDAP server in /tmp/ldap.php on line 26
LDAP bind failed (expected)

Full reproducer:

  1. docker run --rm -it docker.io/smblds/smblds:latest /bin/sh
  2. rm -f /root/.ldaprc
  3. apk update
  4. apk add php84-cli php84-ldap
cat > /tmp/ldap.php <<\EOF
<?php
  ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 6);

  echo 'Test LDAP_OPT_X_TLS_ALLOW' . PHP_EOL;
  if (!ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_ALLOW)) {
    echo 'Unable to set LDAP_OPT_X_TLS_ALLOW!' . PHP_EOL;
  }
  $ldapconn1 = ldap_connect('ldaps://localhost:636');
  ldap_set_option($ldapconn1, LDAP_OPT_PROTOCOL_VERSION, 3);
  $ldapbind1 = ldap_bind($ldapconn1, 'CN=Administrator,CN=Users,DC=samdom,DC=example,DC=com', 'Passw0rd');
  if ($ldapbind1) {
    echo 'LDAP bind succeeded (expected)' . PHP_EOL;
  } else {
    echo 'LDAP bind failed (unexpected)' . PHP_EOL;
  }
  ldap_unbind($ldapconn1);

  echo PHP_EOL;

  echo 'Test LDAP_OPT_X_TLS_DEMAND' . PHP_EOL;
  if (!ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_DEMAND)) {
    echo 'Unable to set LDAP_OPT_X_TLS_DEMAND!' . PHP_EOL;
  }
  $ldapconn2 = ldap_connect('ldaps://localhost:636');
  ldap_set_option($ldapconn2, LDAP_OPT_PROTOCOL_VERSION, 3);
  $ldapbind2 = ldap_bind($ldapconn2, 'CN=Administrator,CN=Users,DC=samdom,DC=example,DC=com', 'Passw0rd');
  if ($ldapbind2) {
    echo 'LDAP bind succeeded (unexpected)' . PHP_EOL;
  } else {
    echo 'LDAP bind failed (expected)' . PHP_EOL;
  }
  ldap_unbind($ldapconn2);
EOF
  1. php84 /tmp/ldap.php
  2. If I reverse the two tests, both tests will fail for LDAP bind instead of both succeeding

If I'm not overlooking something, ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, $option) can't be overridden, but I also don't receive any failure for ldap_set_option(). And $ldapconn = ldap_connect('ldaps://localhost:636'); ldap_set_option($ldapconn, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_ALLOW); doesn't work.

PHP Version

PHP 8.4.3 (with OpenLDAP 2.6.8)

Operating System

Alpine Linux 3.21.2

@cmb69
Copy link
Member

cmb69 commented Feb 13, 2025

@remicollet
Copy link
Member

remicollet commented Feb 26, 2025

ldap2.php.txt

So from @cmb69 link, setting LDAP_OPT_X_TLS_NEWCTX is enough (attached script)

Quick try, and got expected result

Test LDAP_OPT_X_TLS_ALLOW
Set LDAP_OPT_X_TLS_ALLOW OK
Set LDAP_OPT_X_TLS_NEWCTX OK
TLS certificate verification: Error, unable to get local issuer certificate
TLS certificate verification: Error, unable to verify the first certificate
LDAP bind succeeded (expected)

Test LDAP_OPT_X_TLS_DEMAND

Warning: Undefined variable $ldapconn2 in /work/build/phpmaster/bugldap.php on line 28
Set LDAP_OPT_X_TLS_DEMAND OK
Set LDAP_OPT_X_TLS_NEWCTX OK
TLS certificate verification: Error, unable to get local issuer certificate
TLS: can't connect: error:0A000086:SSL routines::certificate verify failed (unable to get local issuer certificate).

Warning: ldap_bind(): Unable to bind to server: Can't contact LDAP server in /work/build/phpmaster/bugldap.php on line 40

So it is only about adding LDAP_OPT_X_TLS_NEWCTX constant

@robert-scheck
Copy link
Author

robert-scheck commented Feb 26, 2025

  • LDAP_OPT_X_TLS_NEWCTX is unfortunately not documented at https://www.php.net/ldap_set_option
  • Why does ldap_set_option() not fail by return code, if it doesn't do what it's supposed to do?
  • Wouldn't it be advisable, that LDAP_OPT_X_TLS_NEWCTX is automatically set by PHP ldap extension internally?
    • Setting LDAP_OPT_X_TLS_NEWCTX automatically internally makes sense to me given that ldap_set_option() doesn't ensure what it's supposed to do (and due to the OpenLDAP API it might be even never able to throw an error)
  • Current situation is IMHO likely to cause a MITM vulnerability for the PHP developer when working with two LDAP servers and different options, like in my reproducer

@remicollet
Copy link
Member

LDAP_OPT_X_TLS_NEWCTX is unfortunately not documented at https://www.php.net/ldap_set_option

It doesn't exists ;)

Proposal in PR #17939

Wouldn't it be advisable, that LDAP_OPT_X_TLS_NEWCTX is automatically set by PHP ldap extension internally?

This is another way...
Will try it

remicollet added a commit to remicollet/php-src that referenced this issue Feb 26, 2025
@remicollet
Copy link
Member

Wouldn't it be advisable, that LDAP_OPT_X_TLS_NEWCTX is automatically set by PHP ldap extension internally?

Second proposal in PR #17940

@robert-scheck
Copy link
Author

Wouldn't it be advisable, that LDAP_OPT_X_TLS_NEWCTX is automatically set by PHP ldap extension internally?

Second proposal in PR #17940

Thank you, very much appreciated!

@robert-scheck
Copy link
Author

robert-scheck commented Feb 26, 2025

I am sorry, but while commuting I came to the conclusion that my report covers only LDAPS (ldaps://), but not STARTTLS (ldap://). Given this issue came from actual code using only LDAPS, I didn't think about STARTTLS before. Unfortunately, STARTTLS is affected the same way, here is a full reproducer:

<?php                                                                                                                                                                                                                                                                                                                                            
  ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 6);                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                                                 
  echo 'Test LDAPS with LDAP_OPT_X_TLS_ALLOW' . PHP_EOL;                                                                                                                                                                                                                                                                                         
  if (!ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_ALLOW)) {                                                                                                                                                                                                                                                               
    echo 'Unable to set LDAP_OPT_X_TLS_ALLOW!' . PHP_EOL;                                                                                                                                                                                                                                                                                        
  }                                                                                                                                                                                                                                                                                                                                              
  $ldapconn1 = ldap_connect('ldaps://localhost:636');                                                                                                                                                                                                                                                                                            
  ldap_set_option($ldapconn1, LDAP_OPT_PROTOCOL_VERSION, 3);                                                                                                                                                                                                                                                                                     
  $ldapbind1 = ldap_bind($ldapconn1, 'CN=Administrator,CN=Users,DC=samdom,DC=example,DC=com', 'Passw0rd');                                                                                                                                                                                                                                       
  if ($ldapbind1) {                                                                                                                                                                                                                                                                                                                              
    echo 'LDAP bind succeeded (expected)' . PHP_EOL;                                                                                                                                                                                                                                                                                             
  } else {                                                                                                                                                                                                                                                                                                                                       
    echo 'LDAP bind failed (unexpected)' . PHP_EOL;                                                                                                                                                                                                                                                                                              
  }                                                                                                                                                                                                                                                                                                                                              
  ldap_unbind($ldapconn1);                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                 
  echo PHP_EOL;                                                                                                                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                                                                                                                                 
  echo 'Test LDAPS with LDAP_OPT_X_TLS_DEMAND' . PHP_EOL;                                                                                                                                                                                                                                                                                             
  if (!ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_DEMAND)) {                                                                                                                                                                                                                                                              
    echo 'Unable to set LDAP_OPT_X_TLS_DEMAND!' . PHP_EOL;                                                                                                                                                                                                                                                                                       
  }                                                                                                                                                                                                                                                                                                                                              
  $ldapconn2 = ldap_connect('ldaps://localhost:636');                                                                                                                                                                                                                                                                                            
  ldap_set_option($ldapconn2, LDAP_OPT_PROTOCOL_VERSION, 3);                                                                                                                                                                                                                                                                                     
  $ldapbind2 = ldap_bind($ldapconn2, 'CN=Administrator,CN=Users,DC=samdom,DC=example,DC=com', 'Passw0rd');                                                                                                                                                                                                                                       
  if ($ldapbind2) {                                                                                                                                                                                                                                                                                                                              
    echo 'LDAP bind succeeded (unexpected)' . PHP_EOL;                                                                                                                                                                                                                                                                                           
  } else {                                                                                                                                                                                                                                                                                                                                       
    echo 'LDAP bind failed (expected)' . PHP_EOL;                                                                                                                                                                                                                                                                                                
  }                                                                                                                                                                                                                                                                                                                                              
  ldap_unbind($ldapconn2);                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                 
  echo PHP_EOL;                                                                                                                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                                                                                                                                 
  echo 'Test STARTTLS with LDAP_OPT_X_TLS_ALLOW' . PHP_EOL;                                                                                                                                                                                                                                                                                      
  if (!ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_ALLOW)) {                                                                                                                                                                                                                                                               
    echo 'Unable to set LDAP_OPT_X_TLS_ALLOW!' . PHP_EOL;                                                                                                                                                                                                                                                                                        
  }                                                                                                                                                                                                                                                                                                                                              
  $ldapconn3 = ldap_connect('ldap://localhost:389');                                                                                                                                                                                                                                                                                             
  ldap_set_option($ldapconn3, LDAP_OPT_PROTOCOL_VERSION, 3);                                                                                                                                                                                                                                                                                     
  ldap_start_tls($ldapconn3);                                                                                                                                                                                                                                                                                                                    
  $ldapbind3 = ldap_bind($ldapconn3, 'CN=Administrator,CN=Users,DC=samdom,DC=example,DC=com', 'Passw0rd');                                                                                                                                                                                                                                       
  if ($ldapbind3) {                                                                                                                                                                                                                                                                                                                              
    echo 'LDAP bind succeeded (expected)' . PHP_EOL;                                                                                                                                                                                                                                                                                             
  } else {                                                                                                                                                                                                                                                                                                                                       
    echo 'LDAP bind failed (unexpected)' . PHP_EOL;                                                                                                                                                                                                                                                                                              
  }                                                                                                                                                                                                                                                                                                                                              
  ldap_unbind($ldapconn3);                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                 
  echo PHP_EOL;                                                                                                                                                                                                                                                                                                                                  
                                                                                                                                                                                                                                                                                                                                                 
  echo 'Test STARTTLS with LDAP_OPT_X_TLS_DEMAND' . PHP_EOL;                                                                                                                                                                                                                                                                                     
  if (!ldap_set_option(null, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_DEMAND)) {                                                                                                                                                                                                                                                              
    echo 'Unable to set LDAP_OPT_X_TLS_DEMAND!' . PHP_EOL;                                                                                                                                                                                                                                                                                       
  }                                                                                                                                                                                                                                                                                                                                              
  $ldapconn4 = ldap_connect('ldap://localhost:389');                                                                                                                                                                                                                                                                                             
  ldap_set_option($ldapconn4, LDAP_OPT_PROTOCOL_VERSION, 3);                                                                                                                                                                                                                                                                                     
  ldap_start_tls($ldapconn4);                                                                                                                                                                                                                                                                                                                    
  $ldapbind4 = ldap_bind($ldapconn4, 'CN=Administrator,CN=Users,DC=samdom,DC=example,DC=com', 'Passw0rd');                                                                                                                                                                                                                                       
  if ($ldapbind4) {                                                                                                                                                                                                                                                                                                                              
    echo 'LDAP bind succeeded (unexpected)' . PHP_EOL;                                                                                                                                                                                                                                                                                           
  } else {                                                                                                                                                                                                                                                                                                                                       
    echo 'LDAP bind failed (expected)' . PHP_EOL;                                                                                                                                                                                                                                                                                                
  }                                                                                                                                                                                                                                                                                                                                              
  ldap_unbind($ldapconn4);

remicollet added a commit to remicollet/php-src that referenced this issue Feb 27, 2025
remicollet added a commit to remicollet/php-src that referenced this issue Feb 27, 2025
remicollet added a commit to remicollet/php-src that referenced this issue Feb 27, 2025
remicollet added a commit to remicollet/php-src that referenced this issue Apr 10, 2025
remicollet added a commit that referenced this issue Apr 10, 2025
* PHP-8.3:
  NEWS for GH-17940
  Fix #17776 LDAP_OPT_X_TLS_REQUIRE_CERT can't be overridden
remicollet added a commit that referenced this issue Apr 10, 2025
* PHP-8.4:
  NEWS for GH-17940
  NEWS for GH-17940
  Fix #17776 LDAP_OPT_X_TLS_REQUIRE_CERT can't be overridden
remicollet added a commit to remicollet/php-src that referenced this issue May 15, 2025
…ap_start_tls()

Regresion introduced in fix for phpGH-17776

- ensure TLS string options are properly inherited
  workaround to openldap issue https://bugs.openldap.org/show_bug.cgi?id=10337

- fix ldaps/start_tls tests using LDAPNOINIT in ldaps/tls tests
remicollet added a commit that referenced this issue May 15, 2025
…start_tls() Regresion introduced in fix for GH-17776

- ensure TLS string options are properly inherited
  workaround to openldap issue https://bugs.openldap.org/show_bug.cgi?id=10337

- fix ldaps/start_tls tests using LDAPNOINIT in ldaps/tls tests
remicollet added a commit that referenced this issue May 15, 2025
* PHP-8.3:
  NEWS
  Fix GH-18529: ldap no longer respects TLS_CACERT from ldaprc in ldap_start_tls() Regresion introduced in fix for GH-17776
remicollet added a commit that referenced this issue May 15, 2025
* PHP-8.4:
  NEWS
  NEWS
  Fix GH-18529: ldap no longer respects TLS_CACERT from ldaprc in ldap_start_tls() Regresion introduced in fix for GH-17776
@arderyp
Copy link

arderyp commented May 20, 2025

for some reason, this fix broke my code on Ubuntu 20.4. I can fix it by adding ldap_set_option($ds, LDAP_OPT_X_TLS_CACERTFILE, '/etc/ssl/certs/ca-certificates.crt'), but this was not needed before. Additionally, this fix did not break my RHEL instalation running the same code and php/php-ldap versions. Neither openssl.cafile nor openssl.capath are defined in php.ini on either my ubuntu or my RHEL servers. Any thoughts why the sudden breakage and new requirement for setting LDAP_OPT_X_TLS_CACERTFILE in the ubuntu environment only?

@remicollet
Copy link
Member

remicollet commented May 21, 2025

@arderyp this regression is reported as #18529 (fixed)

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

No branches or pull requests

4 participants