Skip to content

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

Closed

Description

@robert-scheck

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

Activity

  1. remicollet commented on Feb 26, 2025

    @remicollet
    Member

    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

  2. robert-scheck commented on Feb 26, 2025

    @robert-scheck
    Author
    • 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
  3. remicollet commented on Feb 26, 2025

    @remicollet
    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

  4. remicollet commented on Feb 26, 2025

    @remicollet
    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

  5. robert-scheck commented on Feb 26, 2025

    @robert-scheck
    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!

  6. robert-scheck commented on Feb 26, 2025

    @robert-scheck
    Author

    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);
  7. added a commit that references this issue on Apr 10, 2025
    2f5b56f
  8. 1 remaining item

  9. added 3 commits that reference this issue on Apr 10, 2025
    bcee2af
    e65fbe1
    389de7c
  10. added 4 commits that reference this issue on May 15, 2025
  11. arderyp commented on May 20, 2025

    @arderyp

    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?

  12. remicollet commented on May 21, 2025

    @remicollet
    Member

    @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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions