2011-03-10 101 views
11

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

我注意到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 

试图使用PHP版本5.3.2-1ubuntu4.7从Ubuntu的10.04回购

$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); 

以上未能全部绑定到Active Directory服务器。从日志错误:

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 

而且我也有在/ etc/LDAP。 conf修复'TLS_REQCERT永不' - 即使此修复程序针对的是不同的错误,但它提供了相当明确的错误消息。

+0

您的示例工作得很好,在OS X上使用PHP 5.3.3-dev,所以我不认为问题出现在您的代码中,如果这有帮助... – dearlbry 2011-03-10 18:51:34

+0

它有助于一些,因为我尝试在不同的机器上运行它,针对相同的ldap服务器,并且SSL绑定工作。这是一个php 5.2的centos 5服务器。调试输出也有一些TLS行,在ubuntu安装时缺少。什么是Ubuntu的错误,仍然没有我 – 2011-03-11 10:52:36

+0

我试图升级到10.10,其中PHP版本=> 5.3.3-1ubuntu9.3 - 我仍然得到excact与上述相同的结果。 – 2011-03-11 12:16:18

回答

1

由于我的代码与CentOS一起工作正常,我得出结论,问题不是特定的编程。到目前为止,我还没有能够在我的Ubuntu环境中运行它,但我认为这是我服务器软件中的一个错误。

0

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

+0

缺少CA密钥的解决方法是设置/etc/ldap.conf指令:TLS_REQCERT从不 - 这里不是问题 – 2011-03-11 09:44:23

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) 
[email protected]($_LDAP_HOST, $_LDAP_PORT, $errno, $errstr, 1); 
@fclose($sock); 
if($errno!=0) continue; 

// then do a "connect"... (the real connect happens with bind) 
[email protected]_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从不”

# 
# 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库问题。尝试更新到新版本:)
+0

您的方法与我在“SSL绑定尝试”中尝试的方法完全相同 - 它不会在我的服务器环境中不适合我。 – 2011-03-14 09:21:46

4

你有没有看到关于一些证书存储,这是否缺少权限PHP.net页面的意见:

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

bleathem 27- 2008年2月10:30 每个人都在发布关于获取ldaps的信息://在WAMP/AD堆栈中工作,我很难找到如何在RHEL 5.1中使用(所有的股票转售)。良好的旧strace做了这个窍门,并帮我找到了问题......原来,php正在/ etc/pki/CA中查找CA文件,并且我没有在该文件夹上拥有正确的权限。 chmod'ing它到755解决了我的“无法联系LDAP服务器”消息。

所以也许这就是你的问题。如果不是的话,你应该让strace或wireshark试图捕捉系统调用和网络传输,并找出出了什么问题。其中一个将显示清楚。

+0

strace中没有任何内容表示访问文件的权限问题。我试图在运行PHP 5.2的Centos 5上运行我的代码,该代码在相同的ldap服务器上运行正常。但是,发布的Ubuntu PHP版本在10.04版本中为我提供了桌面和服务器版本+ 10.10桌面版本的问题。 这几乎就像ssl的支持完全从图书馆丢失..任何好方法来检查? – 2011-03-15 09:56:33

1

我终于能够通过阅读下面的PHP错误线程,让我的Windows机器上工作的事情: http://bugs.php.net/bug.php?id=48866

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

TLS仍然不完全工作(它给了我一个“无法启动TLS:无法联系LDAP服务器”的消息),但SSL确实出现了工作,我能为一个帐户更新密码在我的测试脚本中。

我猜测ldap.conf文件只是需要在我的Web服务器上设置类似,我应该希望在业务(我只是不知道如果已经存在那个我需要修改或者如果我需要创建一个额外的)。我会看看我能否在这方面报告。

+0

我只能用一个小小的打嗝就能在我的网络服务器上工作。首先,我发现ldap.conf文件直接位于/ etc(在/ etc/openldap中有另一个,但我认为这只是一个示例文件),并添加了TLS_REQCERT从不声明并重新启动Apache。然而,这仍然给我提出了问题,但它似乎不同于我的Windows机器(当我试图运行脚本时它会挂起)。这个问题是由于我的csf防火墙阻塞了传出的636端口。一旦我打开它,它工作正常。不确定Ubuntu有什么问题,但是我在RHEL/CentOS上运行。 – 2011-03-28 19:54:30

+0

在Windows上,您应该也可以这样做:putenv('LDAPTLS_REQCERT = never');而不是ldap.conf修复。我也能够在CentOS上工作,所以我怀疑为某个地方的Ubuntu构建了apache/ldap/php中的一个bug。 – 2011-03-29 07:45:36