使用PHP安全绑定到Active Directory的问题

时间:2011-03-10 10:45:24

标签: php ldap ssl

我似乎无法使用php安全地绑定到Active Directory。未加密的连接工作正常。使用其他客户端能够安全地绑定,例如,使用LDAPAdmin通过SSL连接。这里有什么问题?是否有一些我缺少的LDAP SSL模块?如何使用PHP安全绑定到服务器?

我从phpinfo()注意到cURL支持ldap / ldaps - 利用它在php中执行安全绑定有什么好的例子?这是一个可行的解决方法吗?

的phpinfo();

ldap
LDAP Support    enabled
RCS Version     $Id: ldap.c 293036 2010-01-03 09:23:27Z sebastian $
Total Links     0/unlimited
API Version     3001
Vendor Name     OpenLDAP
Vendor Version  20421
SASL Support    Enabled 

尝试使用Ubuntu 10.04 repo中的PHP Version 5.3.2-1ubuntu4.7绑定到Active Directory服务器

$username = 'user';
$password = 'passwd';
$account_suffix = '@example.com';
$hostnameSSL = 'ldaps://ldap.example.com:636';
$hostnameTLS = 'ldap.example.com';
$portTLS = 389;

ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);

// Attempting fix from http://www.php.net/manual/en/ref.ldap.php#77553
putenv('LDAPTLS_REQCERT=never');

####################
# SSL bind attempt #
####################
// Attempting syntax from http://www.php.net/manual/en/function.ldap-bind.php#101445
$con =  ldap_connect($hostnameSSL);
if (!is_resource($con)) trigger_error("Unable to connect to $hostnameSSL",E_USER_WARNING);

// Options from http://www.php.net/manual/en/ref.ldap.php#73191
if (!ldap_set_option($con, LDAP_OPT_PROTOCOL_VERSION, 3))
{
    trigger_error("Failed to set LDAP Protocol version to 3, TLS not supported",E_USER_WARNING);
}
ldap_set_option($con, LDAP_OPT_REFERRALS, 0);

if (ldap_bind($con,$username . $account_suffix, $password)) die('All went well using SSL');
ldap_close($con);

####################
# TLS bind attempt #
####################
$con =  ldap_connect($hostnameTLS,$portTLS);
ldap_set_option($con, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($con, LDAP_OPT_REFERRALS, 0);
$encrypted = (ldap_start_tls($con));
if ($encrypted) ldap_bind($con,$username . $account_suffix, $password); // Unecrypted works, but don't want logins sent in cleartext
ldap_close($con);

#####################
# SASL bind attempt #
#####################
$con =  ldap_connect($hostnameTLS,$portTLS);
ldap_set_option($con, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($con, LDAP_OPT_REFERRALS, 0);
ldap_sasl_bind($con, NULL, $password, 'DIGEST-MD5', NULL, $username. $account_suffix);
ldap_close($con);

以上所有都失败了。来自日志的错误:

ldap_create
ldap_url_parse_ext(ldaps://ldap.example.com:636)
ldap_bind_s
ldap_simple_bind_s
ldap_sasl_bind_s
ldap_sasl_bind
ldap_send_initial_request
ldap_new_connection 1 1 0
ldap_int_open_connection
ldap_connect_to_host: TCP ldap.example.com:636
ldap_new_socket: 27
ldap_prepare_socket: 27
ldap_connect_to_host: Trying 1.1.1.1:636
ldap_pvt_connect: fd: 27 tm: -1 async: 0
ldap_open_defconn: successful
ldap_send_server_request
ldap_result ld 0x215380c0 msgid 1
wait4msg ld 0x215380c0 msgid 1 (infinite timeout)
wait4msg continue ld 0x215380c0 msgid 1 all 1
** ld 0x215380c0 Connections:
* host: ldap.example.com  port: 636  (default)
  refcnt: 2  status: Connected
  last used: Thu Mar 10 11:15:53 2011


** ld 0x215380c0 Outstanding Requests:
 * msgid 1,  origid 1, status InProgress
   outstanding referrals 0, parent count 0
  ld 0x215380c0 request count 1 (abandoned 0)
** ld 0x215380c0 Response Queue:
   Empty
  ld 0x215380c0 response count 0
ldap_chkResponseList ld 0x215380c0 msgid 1 all 1
ldap_chkResponseList returns ld 0x215380c0 NULL
ldap_int_select
read1msg: ld 0x215380c0 msgid 1 all 1
ldap_err2string
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP Warning:  ldap_bind() [<a href='function.ldap-bind'>function.ldap-bind</a>]: Unable to bind to server: Can't contact LDAP server in /..test.php on line 28
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP Stack trace:
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP   1. {main}() /..test.php:0
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP   2. ldap_bind() /..test.php:28
ldap_free_request (origid 1, msgid 1)
ldap_free_connection 1 1
ldap_free_connection: actually freed
ldap_create
ldap_err2string
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP Warning:  ldap_start_tls() [<a href='function.ldap-start-tls'>function.ldap-start-tls</a>]: Unable to start TLS: Not Supported in /..test.php on line 37
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP Stack trace:
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP   1. {main}() /..test.php:0
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP   2. ldap_start_tls() /..test.php:37
ldap_create
ldap_sasl_interactive_bind_s: user selected: DIGEST-MD5
ldap_err2string
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP Warning:  ldap_sasl_bind() [<a href='function.ldap-sasl-bind'>function.ldap-sasl-bind</a>]: Unable to bind to server: Not Supported in /..test.php on line 47
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP Stack trace:
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP   1. {main}() /..test.php:0
[Thu Mar 10 11:15:53 2011] [error] [client ::1] PHP   2. ldap_sasl_bind() /..test.php:47

看ssl回复:

>> openssl s_client -connect my.example.com:636 -prexit

(...)
SSL handshake has read 5732 bytes and written 443 bytes
---
New, TLSv1/SSLv3, Cipher is RC4-MD5
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : RC4-MD5
    Session-ID: 111111111111111111111111
    Session-ID-ctx: 
    Master-Key: AAAAAAAAAAAAAAAAAAAAA
    Key-Arg   : None
    Start Time: 1299071105
    Timeout   : 300 (sec)
    Verify return code: 20 (unable to get local issuer certificate)

'strace php test.php'的结果:

    write(2, "  refcnt: 2  status: Connected\n", 31  refcnt: 2  status: Connected
    ) = 31
    write(2, "  last used: Tue Mar 15 10:59:19"..., 39  last used: Tue Mar 15 10:59:19 2011

    ) = 39
    write(2, "\n", 1
    )                       = 1
    write(2, "** ld 0x954e0b8 Outstanding Requ"..., 38** ld 0x954e0b8 Outstanding Requests:
    ) = 38
    write(2, " * msgid 1,  origid 1, status In"..., 41 * msgid 1,  origid 1, status InProgress
    ) = 41
    write(2, "   outstanding referrals 0, pare"..., 43   outstanding referrals 0, parent count 0
    ) = 43
    write(2, "  ld 0x954e0b8 request count 1 ("..., 45  ld 0x954e0b8 request count 1 (abandoned 0)
    ) = 45
    write(2, "** ld 0x954e0b8 Response Queue:\n", 32** ld 0x954e0b8 Response Queue:
    ) = 32
    write(2, "   Empty\n", 9   Empty
    )               = 9
    write(2, "  ld 0x954e0b8 response count 0\n", 32  ld 0x954e0b8 response count 0
    ) = 32
    write(2, "ldap_chkResponseList ld 0x954e0b"..., 48ldap_chkResponseList ld 0x954e0b8 msgid 1 all 1
    ) = 48
    write(2, "ldap_chkResponseList returns ld "..., 47ldap_chkResponseList returns ld 0x954e0b8 NULL
    ) = 47
    write(2, "ldap_int_select\n", 16ldap_int_select
    )       = 16
    poll([{fd=3, events=POLLIN|POLLPRI|POLLERR|POLLHUP}], 1, -1) = 1 ([{fd=3, revents=POLLIN}])
    write(2, "read1msg: ld 0x954e0b8 msgid 1 a"..., 37read1msg: ld 0x954e0b8 msgid 1 all 1
    ) = 37
    read(3, "", 8)                          = 0
    write(2, "ldap_err2string\n", 16ldap_err2string
    )       = 16
    write(2, "PHP Warning:  ldap_bind(): Unabl"..., 158PHP Warning:  ldap_bind(): Unable to bind to server: Can't contact LDAP server in

我确实使用'TLS_REQCERT never'修复了/etc/ldap.conf - 即使此修复程序是针对不同的错误,也会提供相当明确的错误消息。

6 个答案:

答案 0 :(得分:4)

您是否在PHP.net页面上看到关于某些证书存储上缺少权限的评论:

http://de3.php.net/manual/en/function.ldap-connect.php

  

bleathem 27-Feb-2008 10:30   每个人都在发布关于获取ldaps://在WAMP / AD堆栈中工作的问题,我很难找到如何在RHEL 5.1(带有所有股票rpms)中使用它。好老的strace做了诀窍并帮助我找到了问题...原来php正在/ etc / pki / CA中寻找CA文件,而我对该文件夹没有正确的权限。 chmod'ing it to 755解决了我的“无法联系LDAP服务器”的消息。

所以也许这就是你的问题。如果不是,你应该尝试使用strace或wireshark来捕获系统调用和网络传输,并找出问题所在。其中一个将清楚地显示出来。

答案 1 :(得分:3)

这就是我的表现:

<?php
    $username = ''; // username to check
    $password = ''; // password to check

/**
 * Is it an Active Directory?
 *
 * <pre>
 * true = yes
 *        set the following values:
 *        SDB_AUTH_LDAP_HOST
 *        SDB_AUTH_LDAP_SSL
 *        SDB_AUTH_LDAP_BASE
 *        SDB_AUTH_LDAP_SEARCH
 *        SDB_AUTH_LDAP_USERDOMAIN
 * false = no, you have to supply an hostname
 *         and configure the following values:
 *         SDB_AUTH_LDAP_HOST
 *         SDB_AUTH_LDAP_PORT
 *         SDB_AUTH_LDAP_SSL
 *         SDB_AUTH_LDAP_BASE
 *         SDB_AUTH_LDAP_SEARCH
 *         SDB_AUTH_LDAP_USERDOMAIN
 * </pre>
 * @see SDB_AUTH_LDAP_HOST
 */
define('SDB_AUTH_IS_AD', true);
/**
 * Domain name of the LDAP Host or of the AD-Domain
 */
define('SDB_AUTH_LDAP_HOST', 'your-domain.tld');
/**
 * LDAP Port?
 *
 * if {@link SDB_AUTH_IS_AD} = true, then the port will be read form DNS.
 */
define('SDB_AUTH_LDAP_PORT', '389');
/**
 * Use LDAPS (true) oder LDAP (false) connection?
 */
define('SDB_AUTH_LDAP_SSL', false);
/**
 * LDAP Base
 */
define('SDB_AUTH_LDAP_BASE', 'CN=Users,DC=your-domain.tld,DC=de');
/**
 * LDAP Search, to find a user
 *
 * %s will be replaced by the username.<br>
 * z.B. CN=%s
 */
define('SDB_AUTH_LDAP_SEARCH', '(&(sAMAccountName=%s)(objectclass=user)(objectcategory=person))');
/**
 * Die LDAP Domain des Benutzers
 *
 * if the username doesnt contain a domain append this domain to it.<br>
 * in case this is empty, nothing will be appended.
 */
define('SDB_AUTH_LDAP_USERDOMAIN', 'your-domain.tld');
/**
 * Path to LDAP Search
 *
 * Will give back better error messages
 * ( leave empty in case you don't want to have it. )
 */
define('SDB_AUTH_LDAP_SEARCHBIN', '/usr/bin/ldapsearch');




        $ldap_error_codes=array(
        '525' => 'Username doesnt exist.',
        '52e' => 'Wrong password.',
        '530' => 'You cannot login at this time.',
        '531' => 'You cannot login from this host.',
        '532' => 'Your password was expired.',
        '533' => 'Your account has been deactivated.',
        '701' => 'Your account was expired.',
        '773' => 'Please set another password (at your workstation) before you login.',
        '775' => 'Your account has been locked.',
        );


  if(SDB_AUTH_LDAP_SSL) $dcs=dns_get_record("_ldaps._tcp.".SDB_AUTH_LDAP_HOST, DNS_SRV); else $dcs=dns_get_record("_ldap._tcp.".SDB_AUTH_LDAP_HOST, DNS_SRV);
  shuffle($dcs);

  $_LDAP_ATTRS=array('cn', 'sn', 'description', 'givenName', 'distinguishedName', 'displayName', 'memberOf', 'name', 'sAMAccountName', 'sAMAccountType', 'objectClass', 'objectCategory');
  if(SDB_AUTH_LDAP_USERDOMAIN!='' && strstr($username, '@')===false) {
        $username=$username.'@'.SDB_AUTH_LDAP_USERDOMAIN;
  }
  $status=array();
  $status['CN']='';
  $status['displayName']='';
  $status['description']='';
  $status['distinguishedName']='';
  $status['groups']=array();
  $status['RC']=array();
  $status['connected']=false;
  $status['user_exists']=false;
  $status['is_in_team']=false;

foreach($dcs as $_LDAP_HOST) {
$_LDAP_PORT=$_LDAP_HOST['port'];
$_LDAP_HOST=$_LDAP_HOST['target'];
// check connection first ( http://bugs.php.net/bug.php?id=15637 )
$sock=@fsockopen($_LDAP_HOST, $_LDAP_PORT, $errno, $errstr, 1);
@fclose($sock);
if($errno!=0) continue;

// then do a "connect"... ( the real connect happens with bind )
$ds=@ldap_connect(( SDB_AUTH_LDAP_SSL ? "ldaps://" : "ldap://" ).$_LDAP_HOST.":".$_LDAP_PORT."/");
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
// are we connected? actually, this will always return true
if(is_resource($ds)) {
    $status['connected']=true;
    // login sucessful? actually also connection test
    if(@ldap_bind($ds, $username, $password)) {
        // search
        $sr=ldap_search($ds, SDB_AUTH_LDAP_BASE, sprintf(SDB_AUTH_LDAP_SEARCH, $usernode), $_LDAP_ATTRS);
        // suche successful?
        if(is_resource($sr)) {

            // fetch entries
            $info = ldap_get_entries($ds, $sr);
            if(isset($info['count']) && $info['count']>0) {
                $status['user_exists']=true;
            }
            // close search result
            ldap_free_result($sr);
            $status['CN']=$info[0]['cn'][0];
            $status['description']=$info[0]['description'][0];
            $status['displayName']=$info[0]['displayname'][0];
            $status['distinguishedName']=$info[0]['distinguishedname'][0];
            // is the user in the dexteam?
            for($i=0; $i<$info[0]['memberof']['count']; $i++) {
                $status['groups'][]=$info[0]['memberof'][$i];
                // IS IN TEAM CHECK 
                if(substr($info[0]['memberof'][$i], 0, strlen('CN=DexTeam,'))=='CN=DexTeam,') $status['is_in_team']=true; 
            }

            $status['RC']['code']=ldap_errno($ds);
            $status['RC']['string']=ldap_error($ds);
            ldap_close($ds);
            break;
        }
        else {
            $status['RC']['code']=ldap_errno($ds);
            $status['RC']['string']=ldap_error($ds);
            ldap_close($ds);
            break;
        }
    }
    else {
        $status['RC']['code']=ldap_errno($ds);
        $status['RC']['string']=ldap_error($ds);
        // do we want better error messages?
        if(SDB_AUTH_LDAP_SEARCHBIN!='' && is_executable(SDB_AUTH_LDAP_SEARCHBIN)) {
            $status['RC']['ldapsearchrc']='';
            $status['RC']['ldapsearchtxt']=array();
            exec(SDB_AUTH_LDAP_SEARCHBIN.' -x -H '.escapeshellarg(( SDB_AUTH_LDAP_SSL ? "ldaps://" : "ldap://" ).$_LDAP_HOST.":".$_LDAP_PORT."/").' -D '.escapeshellarg($username).' -w '.escapeshellarg($password).' 2>&1', $status['RC']['ldapsearchtxt'], $status['RC']['ldapsearchrc']);
            if($status['RC']['ldapsearchrc']!=0) {
                if(preg_match("/data ([^, ]+),/", $status['RC']['ldapsearchtxt'][1], $matches)) {
                    if(isset($ldap_error_codes[$matches[1]])) {
                        $status['RC']['code']=$matches[1];
                        $status['RC']['string']=$ldap_error_codes[$matches[1]];
                    }
                }
                unset($status['RC']['ldapsearchrc']);
                unset($status['RC']['ldapsearchtxt']);
            }
        }
        ldap_close($ds);
        break;
    }
}
else {
    continue;
}
}

你启用了证书吗?我知道有一个问题,当certifiacte被拒绝。编辑“/etc/ldap/ldap.conf”并添加“TLS_REQCERT never”

#
# LDAP Defaults
#
# See ldap.conf(5) for details
# This file should be world readable but not world writable.
#BASE   dc=example,dc=com
#URI    ldap://ldap.example.com ldap://ldap-master.example.com:666
#SIZELIMIT      12
#TIMELIMIT      15
#DEREF          never
TLS_REQCERT never

然而,对我而言,它适用于ldap和ldaps:

  • 这可能是广告配置的配置问题。可能会降低某些安全限制......
  • 或者它也可能是php / ldap lib问题。尝试更新到更新版本:)

答案 2 :(得分:1)

通过阅读以下PHP错误线程,我终于能够在我的Windows机器上运行了: http://bugs.php.net/bug.php?id=48866

不幸的是,这是Windows特定的,但它让我至少在我的测试中朝着正确的方向前进(我知道它应该可以在我的Web服务器上使用PHP ...只要我正确配置了ldap.conf )。在使用PHP 5.3的Windows上,我需要将ldap.conf文件添加到我的C:驱动器的根目录中(我在网上看到的其他示例已经将它放在C:\ openldap \ sysconf中,但它无效)。

TLS仍然无法正常工作(它给我“无法启动tls:无法联系LDAP服务器”消息),但SSL似乎确实有效,我可以更新帐户的密码我的测试脚本。

我猜测ldap.conf文件只需要在我的网络服务器上进行类似设置,我希望能够开展业务(我只是不确定那个已经存在的那个是否需要修改或者如果我需要创建一个额外的)。我会看看我是否可以在这方面报道。

答案 3 :(得分:1)

由于我的代码在CentOS上运行良好,我得出的结论是问题不是特定于编程的。我还没有能够在我的Ubuntu环境中运行它,但我认为这是我服务器软件中的一个错误。

答案 4 :(得分:0)

您的Active Directory是否启用了LDAPS?如果是这样,请将CA密钥的受信任根目录添加到受信任的根密钥库中。

答案 5 :(得分:0)

从网络和SO上阅读并试用解决方案后,省下了我的一天,是使用没有指定端口的ldaps uri

因此,我不得不使用以下命令:ldaps://example.com:636,而不是这样:ldaps://example.com,它现在就像一种魅力。

我在Ubuntu 16.04上通过Nginx和php-fpm运行PHP7.3进行了设置。

完整的代码示例:

try{
    $ldapUri = "ldaps://example.com";
    $ldapUsername = 'username';
    $ldapPassword = 'password';
    $ldapConn = ldap_connect($ldapUri);
    if($ldapConn){
        ldap_set_option($ldapConn,LDAP_OPT_NETWORK_TIMEOUT,10);

        if(!ldap_set_option($ldapConn,LDAP_OPT_PROTOCOL_VERSION,3)){
           print 'Failed to set ldap protocol to version 3<br>';
        }
        ldap_set_option($ldapConn, LDAP_OPT_REFERRALS,0);
        $ldapBind = ldap_bind($ldapConn, $ldapUsername, $ldapPass);
        if ($ldapBind) {
           echo "LDAP bind successful...";
           //DO LDAP search and stuff
           ldap_unbind($ldapConn);
        } else {
           echo "LDAP bind failed...";
        }
    }
}catch(Exception $e){
    print($e->getMessage();
}