Проблемы с безопасным связыванием с Active Directory с помощью PHP

Кажется, я не могу использовать php для надежного привязки к Active Directory. Незашифрованные соединения работают нормально. Использование других клиентов может надежно связываться, например. используя LDAPAdmin через SSL. В чем проблема? Есть ли какой-то модуль SSL LDAP, который мне не хватает? Как безопасно привязываться к серверу с помощью 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 

Попытка привязки к серверу Active Directory с использованием PHP версии 5.3.2-1ubuntu4.7 из Ubuntu 10.04 repo

$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

И у меня есть исправление /etc/ldap.conf с 'TLS_REQCERT never' - хотя это исправление для другой ошибки, что дает довольно ясное сообщение об ошибке.

Ответы

Ответ 1

Поскольку мой код отлично работает с CentOS, я пришел к выводу, что проблема не в программировании. Я пока не смог запустить его в среде Ubuntu, но я предполагаю, что это ошибка в моем серверном программном обеспечении.

Ответ 2

Вы видели комментарий на странице 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 искал файл CA в /etc/pki/CA, и у меня не было прав на эту папку. chmod'ing до 755 решил мое сообщение "Не могу связаться с LDAP-сервером".

Так что, возможно, это и ваша проблема. Если нет, вы должны дать либо strace, либо wirehark попытку поймать системные вызовы и сетевые передачи и выяснить, что пошло не так. Один из двух четко покажет это.

Ответ 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 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. Попробуйте обновить до более новых версий:)

Ответ 4

Наконец-то я смог заставить все работать на моей машине Windows, прочитав следующий поток ошибок PHP: http://bugs.php.net/bug.php?id=48866

К сожалению, это зависит от Windows, но в моем тестировании мне по крайней мере нужно идти в правильном направлении (я знаю, что он должен работать с PHP сейчас на моем веб-сервере... пока у меня ldap.conf настроен правильно). В Windows с PHP 5.3 мне нужно было добавить файл ldap.conf в корень моего диска C: (другие примеры, которые я видел в Интернете, помещали его в C:\openldap\sysconf, который не работал).

TLS все еще не работает точно (это дает мне сообщение "не удалось запустить tls: не удается связаться с сервером LDAP" ), но SSL, похоже, работает, и я смог обновить пароль для учетной записи в мой тест script.

Я предполагаю, что файл ldap.conf просто нужно настроить аналогично на моем веб-сервере, и я, надеюсь, буду в бизнесе (я просто не уверен, что тот, который уже есть тот, который мне нужно изменить или если мне нужно создать дополнительный). Я посмотрю, смогу ли я сообщить об этом фронте.

Ответ 5

Поддерживается ли в вашей Active Directory LDAPS? Если это так, получите доверенный корень ключа CA в доверенное корневое хранилище ключей.