轻量级目录访问协议(LDAP)广泛应用于企业内部的身份认证和资源目录管理。当应用程序需要查询目录服务器以验证用户凭据或检索组织架构信息时,通常会构建LDAP过滤语句。如果应用程序将用户的输入直接拼接到这些查询语句中,而没有进行严格的过滤和转义,就会产生LDAP注入漏洞。这种漏洞允许攻击者通过修改查询逻辑,绕过身份验证或获取未经授权的目录数据。

LDAP注入攻击的底层原理是什么?
要理解LDAP注入,首先需要了解LDAP查询的基本语法。LDAP过滤器通常使用前缀表达式(波兰表示法),例如查找名为John的用户,过滤语句可能是(cn=John)。如果需要组合多个条件,会使用逻辑运算符如&(AND)、|(OR)和!(NOT)。例如,查找名为John且密码为123456的用户,语句为(&(cn=John)(userPassword=123456))。
在典型的Web应用中,开发者可能会接收用户输入的用户名和密码,然后直接将它们嵌入到这个过滤语句中。假设有一个登录表单,用户输入用户名为admin,密码为123456。后端代码可能会这样构建查询字符串:String filter = "(&(cn=" + username + ")(userPassword=" + password + "))";。这种拼接方式看似直观,却埋下了巨大的安全隐患。
// 存在漏洞的代码示例
String username = request.getParameter("username");
String password = request.getParameter("password");
// 直接拼接用户输入,存在LDAP注入风险
String filter = "(&(cn=" + username + ")(userPassword=" + password + "))";
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration results = ctx.search("ou=users,dc=ipipp,dc=com", filter, controls);
问题在于,如果用户在用户名输入框中输入*,构建出的语句就会变成(&(cn=*)(userPassword=123456))。由于*在LDAP中代表通配符,这会匹配所有用户。如果攻击者输入更复杂的逻辑表达式,就能完全改变查询的语义,这就是LDAP注入的核心原理。攻击者利用这种特性,可以操控目录服务器的响应结果。
攻击者如何构造LDAP注入Payload?
攻击者在构造Payload时,主要目标是篡改过滤器的逻辑结构,使其永远返回真值,从而绕过登录验证或提取敏感信息。最经典的绕过手法是利用布尔逻辑和通配符。例如,在用户名输入框中输入*)(uid=*))(|(uid=*。
如果应用程序将其拼接为(&(cn=*)(uid=*))(|(uid=*))(userPassword=任意密码)),这个复杂的语句实际上被解析为多个独立的过滤条件。由于(uid=*)匹配目录中的所有条目,整个逻辑表达式结果为真,攻击者无需知道正确的密码即可成功登录系统,获取目标账号的权限。
除了绕过认证,LDAP注入还可用于信息收集。攻击者可以通过逐字符提取的方式,利用(attribute=初始字符*)这样的过滤器,结合服务器的响应时间或错误信息,推断出目录中特定属性的值,例如邮箱地址、电话号码或组织架构信息。这种盲注方式虽然耗时,但能造成严重的数据泄露,给企业带来巨大的安全风险。
如何从代码层面有效防御LDAP注入?
防御LDAP注入的最有效手段是采用参数化查询机制,这与防御SQL注入的思路一致。大多数现代LDAP库都支持参数化绑定,开发者应该定义查询模板,并将用户输入作为参数传递给模板,而不是直接进行字符串拼接。这样可以确保用户输入始终被视为字面量,而不会被解释为LDAP语法的一部分。
// 安全的参数化查询示例
String username = request.getParameter("username");
String password = request.getParameter("password");
// 使用参数化查询,防止注入
String filter = "(&(cn={0})(userPassword={1}))";
Object[] filters = new Object[]{username, password};
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration results = ctx.search("ou=users,dc=ipipp,dc=com", filter, filters, controls);
如果使用的底层库不支持参数化查询,开发者必须对所有用户输入进行严格的字符转义。LDAP保留字符包括*、(、)、、NUL等。转义规则是在这些特殊字符前加上反斜杠,例如将(转义为28,将)转义为29,将*转义为2a。通过这种十六进制转义,可以中和恶意Payload的逻辑控制能力,使其失去破坏力。
此外,实施最小权限原则也是降低漏洞危害的重要措施。应用程序连接LDAP服务器的账号应当只具备完成其业务功能所需的最小权限。例如,如果应用只需要读取用户信息,就不应授予写入或修改目录树的权限。结合输入验证、字符转义和最小权限原则,可以构建起坚固的防御体系,确保目录服务的安全稳定运行。