Php 如何防止LDAP注入

Php 如何防止LDAP注入,php,ldap,code-injection,Php,Ldap,Code Injection,我们正在构建一个通过php利用LDAP的应用程序,我开始思考,您可以通过注入LDAP来做些什么,更好的是,如何防止LDAP注入 在大多数情况下,它对LDAP使用只读帐户。由于LDAP在写操作方面很差,所以更新只发生在应用程序中可以使用其他帐户的非常小的部分 即使如此,查询语言和更新语言也是完全分离的 为了防止显示不需要的信息,请将所有用户输入视为受污染的数据,并确保受污染的数据在被解析、清理、正确转义和复制到干净的变量之前从未被使用 类似地,您可能只考虑从响应中选择期望的数据,并返回用于显示的数

我们正在构建一个通过php利用LDAP的应用程序,我开始思考,您可以通过注入LDAP来做些什么,更好的是,如何防止LDAP注入

在大多数情况下,它对LDAP使用只读帐户。由于LDAP在写操作方面很差,所以更新只发生在应用程序中可以使用其他帐户的非常小的部分

即使如此,查询语言和更新语言也是完全分离的


为了防止显示不需要的信息,请将所有用户输入视为受污染的数据,并确保受污染的数据在被解析、清理、正确转义和复制到干净的变量之前从未被使用


类似地,您可能只考虑从响应中选择期望的数据,并返回用于显示的数据。

要考虑的一个项目是LDAP绑定用户名(DN),但没有密码被认为是匿名绑定。因此,如果您测试所传递的凭据是否可以通过LDAP绑定以验证用户,如果他们传递了一个空白密码,而您按原样传递了该密码,则可能会错误地让某人进入

构造LDAP筛选器时,必须确保筛选器值的处理符合:

任何带有ACII的控制字符 代码<32以及字符 在LDAP筛选器中具有特殊意义 “*”、“(“,”)和“\”(反斜杠) 转换为表示形式 指一个反斜杠后跟两个十六进制 表示十六进制的数字 字符的值

Zend_Ldap
例如,使用以下例程

//[...]
$val = str_replace(array('\\', '*', '(', ')'), array('\5c', '\2a', '\28', '\29'), $val);
for ($i = 0; $i<strlen($val); $i++) {
    $char = substr($val, $i, 1);
    if (ord($char)<32) {
        $hex = dechex(ord($char));
        if (strlen($hex) == 1) $hex = '0' . $hex;
        $val = str_replace($char, '\\' . $hex, $val);
    }
}
//[...]
/[…]
$val=str\u replace(数组('\\'、'*'、'('、')')、数组('\5c'、'\2a'、'\28'、'\29')、$val);

对于PHP5.6+中的($i=0;$i),您应该使用函数来过滤值和RDN。例如:

// Escaping an LDAP filter for ldap_search ...
$username = ldap_escape($username, null, LDAP_ESCAPE_FILTER);
$filter = "(sAMAccountName=$username)";

// Escaping a DN to be used in an ldap_add, or a rename...
$rdn = ldap_escape('Smith, John', null, LDAP_ESCAPE_DN);
$dn = "cn=$rdn,dc=example,dc=local";
此外,如果您在搜索中接受用户输入的属性名称,您应该验证它是否是可接受的OID或属性名称。您可以使用以下函数来完成此操作:

/**
 * Validate an attribute is an OID or a valid string attribute name.
 *
 * @param string
 * @return bool
 */
function isValidAttributeFormat($value)
{
    $matchOid = '/^[0-9]+(\.[0-9]+?)*?$/';
    $matchDescriptor = '/^\pL([\pL\pN-]+)?$/iu';

    return preg_match($matchOid, $value) 
        || preg_match($matchDescriptor, $value);
}

$attribute = 'sAMAccountName';
$value = 'foo';

if (!isValidAttributeFormat($attribute)) {
    throw new \InvalidArgumentException(sprintf('Invalid attribute name: %s', $attribute));
}

$value = ldap_escape($value, null, LDAP_ESCAPE_FILTER);
$filter = "($attribute=$value)";

“为了防止显示不需要的信息,请将所有用户输入视为受污染的数据,并确保受污染的数据在被解析、清理、正确转义和复制到干净变量之前从未被使用。”问题是如何着手做这件事?谢谢,我理解,这不是真正的注射。@Chris:同意,不是真正的注射,而是在同一个基本问题空间内。在阅读了这个答案后,我立即测试了我们正在开发的一个应用程序,但这个特殊问题没有被发现,谢谢!我该如何清理password,因为这些特殊字符是允许的?这正是上面的代码所做的…它转换特殊字符并对它们进行编码。这就像URL一样,想象一下,如果有人在参数中添加了一个-,那么您就可以转换它。仅仅因为浏览器/ldap密码是智能的,并且试图为您修复此错误,并不意味着仍然可以发送特殊字符。已尝试此解决方案,但无法正常工作。必须将十六进制字符数组替换为:数组(“\x5c”、“\x2a”、“\x28”、“\x29”),才能使其正常工作。@KevinRoth:这不太可能。
数组(“\x5c”、“\x2a”、“\x28”、“\x29”)
实际上与
数组(“\\\”、“*”、“(“,”)相同
而RFC2254要求将这些字符替换为
'\5c'
'\2a'
'\28'
'\29'
。@KevinRoth:是的,您不应该替换用于绑定LDAP的密码中的字符。当您构造LDAP筛选器进行查询时,这种转义必须发生,以避免出现错误DAP注入(相当于SQL注入的LDAP)。虽然这段代码可以回答这个问题,但提供关于如何和/或为什么解决问题的附加上下文将提高答案的长期价值。
/**
 * Validate an attribute is an OID or a valid string attribute name.
 *
 * @param string
 * @return bool
 */
function isValidAttributeFormat($value)
{
    $matchOid = '/^[0-9]+(\.[0-9]+?)*?$/';
    $matchDescriptor = '/^\pL([\pL\pN-]+)?$/iu';

    return preg_match($matchOid, $value) 
        || preg_match($matchDescriptor, $value);
}

$attribute = 'sAMAccountName';
$value = 'foo';

if (!isValidAttributeFormat($attribute)) {
    throw new \InvalidArgumentException(sprintf('Invalid attribute name: %s', $attribute));
}

$value = ldap_escape($value, null, LDAP_ESCAPE_FILTER);
$filter = "($attribute=$value)";