【问题标题】:Problems with secure bind to Active Directory using PHP使用 PHP 安全绑定到 Active Directory 的问题
【发布时间】:2011-03-10 10:45:24
【问题描述】:

我似乎无法使用 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 存储库的 PHP 版本 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 - 即使此修复是针对不同的错误,它会提供相当清晰的错误消息。

【问题讨论】:

  • 你的例子对我来说工作得很好,在 OS X 上使用 PHP 5.3.3-dev,所以我认为问题不在于你的代码,如果有帮助的话......
  • 它对一些人有帮助,因为我试图在另一台机器上运行它,针对同一个 ldap 服务器,并且 SSL 绑定有效。这是一个带有 php 5.2 的 centos 5 服务器。调试输出也有一些 TLS 行,这些在 ubuntu 设置中是缺失的。 ubuntu 上的错误是什么,我仍然无法理解
  • 我尝试升级到 10.10,它附带 PHP 版本 => 5.3.3-1ubuntu9.3 - 我仍然得到与上面完全相同的结果。
  • 您的问题是整个互联网上最有用的手册!你让我今天一整天都感觉很好。不过可以改变一件事。 putenv('LDAPTLS_REQCERT=never');对我的 Windows 机器上的 sasl 完全没有影响。
  • 这个 Q 和问题已经过时了,但我花了一整天的时间让它在 ubuntu 16.04 和 php 7.3 上运行。最后使它起作用的是使用没有端口的主机名。所以就像这样:ldaps://ldap.example.com。我希望这对某人有所帮助,我也会在下面添加它作为答案。

标签: php ldap ssl


【解决方案1】:

您是否在 PHP.net 页面上看到有关缺少执行此操作的某些证书​​存储的权限的评论:

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

bleathem 2008 年 2 月 27 日 10:30 每个人都在发布关于让 ldaps:// 在 WAMP/AD 堆栈中工作的帖子,我很难找到如何在 RHEL 5.1 中运行它(w/所有库存 rpm)。老 strace 成功了,帮我找到了问题……原来 php 正在 /etc/pki/CA 中寻找 CA 文件,而我对该文件夹没有正确的权限。将其更改为 755 解决了我的“无法联系 LDAP 服务器”消息。

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

【讨论】:

  • strace 中没有任何内容表明访问文件存在权限问题。我试图在运行 PHP 5.2 的 Centos 5 上运行我的代码,它运行良好,针对同一个 ldap 服务器。但是附带的 Ubuntu PHP 版本在 10.04 中给我带来了桌面和服务器版本 + 10.10 桌面版本的问题。库中几乎完全缺少对 ssl 的支持。有什么好的方法来检查吗?
【解决方案2】:

这就是我的做法:

<?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;
}
}

您是否启用了证书?我知道有问题,当证书被拒绝时。编辑“/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 问题。尝试更新到新版本:)

【讨论】:

  • 您的方法与我在“SSL 绑定尝试”中尝试的方法完全相同——这在我的服务器环境中对我不起作用。
【解决方案3】:

通过阅读以下 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 文件只需要在我的网络服务器上进行类似的设置,我应该希望能够开展业务(我只是不确定已经存在的文件是否是我需要修改或如果我需要创建一个额外的)。我会看看我是否可以在这方面报告。

【讨论】:

  • 我能够在我的网络服务器上运行一些小问题。首先,我找到了直接位于 /etc 中的 ldap.conf 文件(在 /etc/openldap 中还有一个文件,但我认为这只是一个示例文件)并添加了 TLS_REQCERT never 声明并重新启动了 Apache。但是,这仍然给我带来了问题,但它似乎与我的 Windows 机器不同(当我尝试运行脚本时它会挂起)。这个问题是由于我的 csf 防火墙阻止了传出的 636 端口。一旦我打开它,它工作得很好。不确定 Ubuntu 有什么问题,但我可以在 RHEL/CentOS 上运行它。
  • 在 Windows 上,你应该也可以这样做: putenv('LDAPTLS_REQCERT=never');而不是 ldap.conf 修复。我也能够让它在 CentOS 上运行,所以我怀疑在某个地方为 Ubuntu 构建的 apache/ldap/php 中存在错误..
【解决方案4】:

您的 Active Directory 是否启用了 LDAPS?如果是这样,将 CA 密钥的可信根放入可信根密钥库。

【讨论】:

  • 缺少 CA 密钥的解决方法是设置 /etc/ldap.conf 指令:从不使用 TLS_REQCERT - 这不是这里的问题
【解决方案5】:

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

【讨论】:

    【解决方案6】:

    在阅读并尝试了整个网络和 SO 的解决方案后,节省了我一天的时间是使用没有指定端口的 ldaps uri

    所以我不得不使用这个:ldaps://example.com 而不是这个:ldaps://example.com:636,它现在就像一个魅力。

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

    完整的代码示例:

    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();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-11
      • 1970-01-01
      • 2016-05-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多