由用户控制的来源构建的 LDAP 查询¶
ID: java/ldap-injection
Kind: path-problem
Security severity: 9.8
Severity: error
Precision: high
Tags:
- security
- external/cwe/cwe-090
Query suites:
- java-code-scanning.qls
- java-security-extended.qls
- java-security-and-quality.qls
如果使用字符串连接构建 LDAP 查询,并且连接的组件包含用户输入,则用户很可能能够运行恶意 LDAP 查询。
建议¶
如果必须在 LDAP 查询中包含用户输入,则应转义该输入,以避免恶意用户提供更改查询含义的特殊字符。如果可能,请使用框架帮助程序方法构建 LDAP 查询,例如使用 Spring 的 LdapQueryBuilder
和 LdapNameBuilder
,而不是字符串连接。或者,使用适当的 LDAP 编码方法转义用户输入,例如:OWASP ESAPI 中的 encodeForLDAP
或 encodeForDN
,Spring LDAP 中的 LdapEncoder.filterEncode
或 LdapEncoder.nameEncode
,或 UnboundID 库中的 Filter.encodeValue
。
示例¶
在以下示例中,代码接受来自用户的“组织名称”和“用户名”,并使用它们查询 LDAP。
第一个示例将未验证且未编码的用户输入直接连接到 DN(专有名称)和用于 LDAP 查询的搜索过滤器中。恶意用户可以提供特殊字符来更改这些查询的含义,并搜索完全不同的值集。LDAP 查询使用 Java JNDI API 执行。
第二个示例使用 OWASP ESAPI 库对用户值进行编码,然后再将它们包含在 DN 和搜索过滤器中。这确保了恶意用户无法更改查询的含义。
import javax.naming.directory.DirContext;
import org.owasp.esapi.Encoder;
import org.owasp.esapi.reference.DefaultEncoder;
public void ldapQueryBad(HttpServletRequest request, DirContext ctx) throws NamingException {
String organizationName = request.getParameter("organization_name");
String username = request.getParameter("username");
// BAD: User input used in DN (Distinguished Name) without encoding
String dn = "OU=People,O=" + organizationName;
// BAD: User input used in search filter without encoding
String filter = "username=" + userName;
ctx.search(dn, filter, new SearchControls());
}
public void ldapQueryGood(HttpServletRequest request, DirContext ctx) throws NamingException {
String organizationName = request.getParameter("organization_name");
String username = request.getParameter("username");
// ESAPI encoder
Encoder encoder = DefaultEncoder.getInstance();
// GOOD: Organization name is encoded before being used in DN
String safeOrganizationName = encoder.encodeForDN(organizationName);
String safeDn = "OU=People,O=" + safeOrganizationName;
// GOOD: User input is encoded before being used in search filter
String safeUsername = encoder.encodeForLDAP(username);
String safeFilter = "username=" + safeUsername;
ctx.search(safeDn, safeFilter, new SearchControls());
}
第三个示例使用 Spring LdapQueryBuilder
来构建 LDAP 查询。除了简化复杂搜索参数的构建之外,它还提供了对搜索过滤器中任何不安全字符的正确转义。DN 是使用 LdapNameBuilder
构建的,它还提供了正确的转义。
import static org.springframework.ldap.query.LdapQueryBuilder.query;
import org.springframework.ldap.support.LdapNameBuilder;
public void ldapQueryGood(@RequestParam String organizationName, @RequestParam String username) {
// GOOD: Organization name is encoded before being used in DN
String safeDn = LdapNameBuilder.newInstance()
.add("O", organizationName)
.add("OU=People")
.build().toString();
// GOOD: User input is encoded before being used in search filter
LdapQuery query = query()
.base(safeDn)
.where("username").is(username);
ldapTemplate.search(query, new AttributeCheckAttributesMapper());
}
第四个示例使用 UnboundID
类、Filter
和 DN
来构建一个安全的过滤器和基本 DN。
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.RDN;
import com.unboundid.ldap.sdk.Filter;
public void ldapQueryGood(HttpServletRequest request, LDAPConnection c) {
String organizationName = request.getParameter("organization_name");
String username = request.getParameter("username");
// GOOD: Organization name is encoded before being used in DN
DN safeDn = new DN(new RDN("OU", "People"), new RDN("O", organizationName));
// GOOD: User input is encoded before being used in search filter
Filter safeFilter = Filter.createEqualityFilter("username", username);
c.search(safeDn.toString(), SearchScope.ONE, safeFilter);
}
第五个示例展示了如何使用 Apache LDAP API 构建一个安全的过滤器和 DN。
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.name.Rdn;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal;
public void ldapQueryGood(HttpServletRequest request, LdapConnection c) {
String organizationName = request.getParameter("organization_name");
String username = request.getParameter("username");
// GOOD: Organization name is encoded before being used in DN
Dn safeDn = new Dn(new Rdn("OU", "People"), new Rdn("O", organizationName));
// GOOD: User input is encoded before being used in search filter
String safeFilter = equal("username", username);
SearchRequest searchRequest = new SearchRequestImpl();
searchRequest.setBase(safeDn);
searchRequest.setFilter(safeFilter);
c.search(searchRequest);
}
参考¶
OWASP:LDAP 注入预防备忘单。
OWASP ESAPI:OWASP ESAPI。
Spring LdapQueryBuilder 文档:LdapQueryBuilder。
Spring LdapNameBuilder 文档:LdapNameBuilder。
UnboundID:了解和防御 LDAP 注入攻击。
常见弱点枚举:CWE-90。