jjzjj

php - 当密码包含“&”号时,php ldap_bind失败

coder 2024-04-29 原文

我的active directory上有一个用于身份验证的函数,它工作正常,除了一些包含特殊字符的密码,例如:
<>
总是返回“无效凭据”
我在网上看过好几篇文章,但似乎没有一篇是正确的。
我使用的是php version 5.3.8-zs(zend服务器)
以下是我的类构造函数中的设置:

ldap_set_option($this->_link, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($this->_link, LDAP_OPT_REFERRALS, 0); 

以及登录功能:
public function login($userlogin,$userpassword){
$return=array();
$userlogin.="@".$this->_config["domaine"];
$ret=@ldap_bind($this->_link , $userlogin ,utf8_decode($userpassword));
if(!$ret){
    $return[]=array("status"=>"error","message"=>"Erreur d'authentification ! <br/> Veuillez vérifier votre nom et mot de passe SVP","ldap_error"=>ldap_error($this->_link));
}
if($ret)
{
    $return[]=array("status"=>"success","message"=>"Authentification réussie");
}
return json_encode($return);
}

任何帮助都将不胜感激。

最佳答案

我们发现自己处于一个类似的环境中,我们的企业环境ldap身份验证设置对于我们的一个内部托管web应用程序似乎工作得很好,直到用户的密码中有特殊字符,比如?*想使用该应用程序,然后很明显,有些东西实际上没有按预期工作。
当受影响的用户密码中出现特殊字符时,我们发现我们的身份验证请求失败到ldap_bind(),并且常见的错误是如Invalid credentialsNDS error: failed authentication (-669)(从LDAP的扩展错误日志记录)输出到日志。事实上,是NDS error: failed authentication (-669)错误导致了失败的绑定很可能是由于密码中存在特殊字符——通过Novell支持文章[AA],其中最显著的部分摘录如下:
管理密码包含LDAP无法理解的字符,即:“\
和“$”
已经对一系列修复进行了广泛的测试,包括通过html_entity_decode()utf8_encode()等来“清除”密码,并确保web应用程序和各种服务器之间没有密码的错误编码,但是无论做什么来调整或保护输入文本,以确保其原始的UTF-8编码保持完整,ldap_bind()当密码中存在一个或多个特殊字符时,总是失败(我们没有用用户名中的特殊字符测试用例)。但也有人报告说,当用户名包含特殊字符而不是密码或密码之外的字符时,也会出现类似的问题。
我们还使用在命令行上运行的最小php脚本直接测试了ldap_bind(),该脚本使用硬编码用户名和密码对密码中包含特殊字符(包括问号、星号和感叹号)的测试ldap帐户进行了测试,试图尽可能多地消除失败的潜在原因,但是绑定操作仍然失败。因此,很明显,这不是密码在Web应用程序和服务器之间错误编码的问题,所有这些设计和构建都是符合UTF-8的端到端的。相反,它似乎有一个潜在的问题与CC及其特殊字符的处理。同一个测试脚本成功地绑定了另一个密码中没有任何特殊字符的帐户,进一步证实了我们的怀疑。
早期版本的ldap身份验证的工作方式如下,这是大多数php ldap身份验证教程建议的:

$success = false; // could we authenticate the user?

if(!defined("LDAP_OPT_DIAGNOSTIC_MESSAGE")) {
    define("LDAP_OPT_DIAGNOSTIC_MESSAGE", 0x0032); // needed for more detailed logging
}

if(($ldap = ldap_connect($ldap_server)) !== false && is_resource($ldap)) {
    ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);

    if(@ldap_bind($ldap, sprintf("cn=%s,ou=Workers,o=Internal", $username), $password)) {
        $success = true; // login succeeded...
    } else {
        error_log(sprintf("* Unable to authenticate user (%s) against LDAP!", $username));
        error_log(sprintf(" * LDAP Error: %s", ldap_error($ldap)));

        if(ldap_get_option($ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, $extended_error)) {
            error_log(sprintf(" * LDAP Extended Error: %s\n", $extended_error));
            unset($extended_error);
        }
    }
}
@ldap_close($ldap); unset($ldap);

很明显,我们使用的仅仅是ldap_bind()ldap_connect()的简单解决方案将不允许用户使用复杂的密码进行身份验证,我们必须找到一个替代方案。
我们选择了基于使用系统帐户(创建时具有搜索用户所需的权限,但有意配置为有限的只读帐户)第一次绑定到LDAP服务器的解决方案。然后,我们使用ldap_bind()搜索具有所提供用户名的用户帐户,然后使用ldap_search()将用户提供的密码与ldap中存储的密码进行比较。这个解决方案已经被证明是可靠和有效的,我们现在能够验证用户的特殊字符在他们的密码和那些没有!
新的LDAP进程的工作方式如下:
if(!defined("LDAP_OPT_DIAGNOSTIC_MESSAGE")) {
    define("LDAP_OPT_DIAGNOSTIC_MESSAGE", 0x0032); // needed for more detailed logging
}

$success = false; // could we authenticate the user?

if(($ldap = @ldap_connect($ldap_server)) !== false && is_resource($ldap)) {
    ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);

    // instead of trying to bind the user, we bind to the server...
    if(@ldap_bind($ldap, $ldap_username, $ldap_password)) {
        // then we search for the given user...
        if(($search = ldap_search($ldap, "ou=Workers,o=Internal", sprintf("(&(uid=%s))", $username))) !== false && is_resource($search)) {
            // they should be the first and only user found for the search...
            if((ldap_count_entries($ldap, $search) == 1) && ($entry = ldap_first_entry($ldap, $search)) !== false && is_resource($entry)) {
                // we ensure this is the case by obtaining the user identifier (UID) from the search which must match the provided $username...
                if(($uid = ldap_get_values($ldap, $entry, "uid")) !== false && is_array($uid)) {
                    // ensure that just one entry was returned by ldap_get_values() and ensure the obtained value matches the provided $username (excluding any case-differences)
                    if((isset($uid["count"]) && $uid["count"] == 1) && (isset($uid[0]) && is_string($uid[0]) && (strcmp(mb_strtolower($uid[0], "UTF-8"), mb_strtolower($username, "UTF-8")) === 0))) {
                        // once we have compared the provied $username with the discovered username, we get the DN for the user, which we need to provide to ldap_compare()...
                        if(($dn = ldap_get_dn($ldap, $entry)) !== false && is_string($dn)) {
                            // we then use ldap_compare() to compare the user's password with the provided $password
                            // ldap_compare() will respond with a boolean true/false depending on if the comparison succeeded or not, and -1 on error...
                            if(($comparison = ldap_compare($ldap, $dn, "userPassword", $password)) !== -1 && is_bool($comparison)) {
                                if($comparison === true) {
                                    $success = true; // user successfully authenticated, if we don't get this far, it failed...
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    if(!$success) { // if not successful, either an error occurred or the credentials were actually incorrect
        error_log(sprintf("* Unable to authenticate user (%s) against LDAP!", $username));
        error_log(sprintf(" * LDAP Error: %s", ldap_error($ldap)));
        if(ldap_get_option($ldap, LDAP_OPT_DIAGNOSTIC_MESSAGE, $extended_error)) {
            error_log(sprintf(" * LDAP Extended Error: %s\n", $extended_error));
            unset($extended_error);
        }
    }
}
@ldap_close($ldap);
unset($ldap);

一个最初的担忧是,这种新的方法,所有额外的步骤将需要更长的时间来执行,但事实证明(至少在我们的环境中),运行一个简单的ldap_compare()和使用ldap_bind()ldap_search()的更复杂的解决方案之间的时间差实际上是微不足道的,平均可能要长0.1s。
请注意,只有在验证和测试用户输入数据(即用户名和密码输入)以确保没有提供空的或无效的用户名或密码字符串之后,才能运行早期和后期版本的LDAP验证代码。并且不存在无效字符(如空字节)。此外,用户证书应该理想地通过来自应用的安全HTTPS请求来发送,并且安全LDAP协议可以用于在认证过程中进一步保护用户凭证。我们发现直接使用ldap,通过ldap_compare()连接到服务器,而不是通过ldaps://...连接,然后切换到tls连接被证明是更可靠的。如果您的设置有所不同,则需要相应地调整上面的代码,以包含对ldap://...的支持。
最后,值得注意的是,ldap协议要求根据rfc(https://www.novell.com/support/kb/doc.php?id=3335671)使用utf-8对密码进行编码,因此,如果在请求期间使用的任何系统从初始输入一直到身份验证都没有将用户的凭据作为utf-8进行处理,这也可能是一个值得调查的领域。
希望这个解决方案将证明对其他人是有用的,他们已经努力解决这个问题与许多其他报告的解决方案,但仍然发现用户无法验证的情况。可能需要针对您自己的LDAP环境调整代码,特别是ldap_start_tls()中使用的DN,因为这将取决于LDAP目录的配置方式以及您支持应用程序的用户组。很高兴,它在我们的环境中运行得非常好,希望这个解决方案在其他地方也能被证明是有用的。

关于php - 当密码包含“&”号时,php ldap_bind失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12858493/

有关php - 当密码包含“&”号时,php ldap_bind失败的更多相关文章

  1. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  2. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  3. ruby - 检查字符串是否包含散列中的任何键并返回它包含的键的值 - 2

    我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案

  4. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  5. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

  6. ruby - 正则表达式在哪个位置失败? - 2

    我需要一个非常简单的字符串验证器来显示第一个符号与所需格式不对应的位置。我想使用正则表达式,但在这种情况下,我必须找到与表达式相对应的字符串停止的位置,但我找不到可以做到这一点的方法。(这一定是一种相当简单的方法……也许没有?)例如,如果我有正则表达式:/^Q+E+R+$/带字符串:"QQQQEEE2ER"期望的结果应该是7 最佳答案 一个想法:你可以做的是标记你的模式并用可选的嵌套捕获组编写它:^(Q+(E+(R+($)?)?)?)?然后你只需要计算你获得的捕获组的数量就可以知道正则表达式引擎在模式中停止的位置,你可以确定匹配结束

  7. ruby - 使用 rbenv 和 ruby​​-build 构建 ruby​​ 失败,出现 undefined symbol : SSLv2_method - 2

    我正在尝试在配备ARMv7处理器的SynologyDS215j上安装ruby​​2.2.4或2.3.0。我用了optware-ng安装gcc、make、openssl、openssl-dev和zlib。我根据README中的说明安装了rbenv(版本1.0.0-19-g29b4da7)和ruby​​-build插件。.这些是随optware-ng安装的软件包及其版本binutils-2.25.1-1gcc-5.3.0-6gconv-modules-2.21-3glibc-opt-2.21-4libc-dev-2.21-1libgmp-6.0.0a-1libmpc-1.0.2-1libm

  8. ruby-on-rails - 使用包含多个关联和单独的条件 - 2

    我的Gallery模型中有以下查询:media_items.includes(:photo,:video).rank(:position_in_gallery)我的图库模型有_许多媒体项,每个都有一个照片或视频关联。到目前为止,一切正常。它返回所有media_items包括它们的photo或video关联,由media_item的position_in_gallery属性排序。但是我现在需要将此查询返回的照片限制为仅具有is_processing属性的照片,即nil。是否可以进行相同的查询,但条件是返回的照片等同于:.where(photo:'photo.is_processingIS

  9. ruby - 我怎样才能只写一次 "Text"并同时检查 path_info 是否包含 'A' ? - 2

    -if!request.path_info.include?'A'%{:id=>'A'}"Text"-else"Text"“文本”写了两次。我怎样才能只写一次并同时检查path_info是否包含“A”? 最佳答案 有两种方法可以做到这一点。使用部分,或使用content_forblock:如果“文本”较长,或者是一个重要的子树,您可以将其提取到一个部分。这会使您的代码变干一点。在给出的示例中,这似乎有点矫枉过正。在这种情况下更好的方法是使用content_forblock,如下所示:-if!request.path_info.inc

  10. Ruby,使用包含 TK GUI 的 ocra 部署一个 exe - 2

    Ocra无法处理需要“tk”的应用程序require'tk'puts'nope'用奥克拉http://github.com/larsch/ocra不起作用(如链接中的一个问题所述)问题:https://github.com/larsch/ocra/issues/29(Ocra是1.9的"new"rubyscript2exe,本质上它用于将rb脚本部署为可执行文件)唯一的问题似乎是缺少tcl的DLL文件我不认为这是一个问题据我所知,问题是缺少tk的DLL文件如果它们是已知的,则可以在执行ocra时将它们包括在内有没有办法知道tk工作所需的DLL依赖项? 最佳答

随机推荐