CodeQL 文档

由用户控制的来源构建的 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

单击以在 CodeQL 存储库中查看查询

如果使用字符串连接构建 LDAP 查询,并且连接的组件包含用户输入,则用户很可能能够运行恶意 LDAP 查询。

建议

如果必须在 LDAP 查询中包含用户输入,则应转义该输入,以避免恶意用户提供更改查询含义的特殊字符。如果可能,请使用框架帮助程序方法构建 LDAP 查询,例如使用 Spring 的 LdapQueryBuilderLdapNameBuilder,而不是字符串连接。或者,使用适当的 LDAP 编码方法转义用户输入,例如:OWASP ESAPI 中的 encodeForLDAPencodeForDN,Spring LDAP 中的 LdapEncoder.filterEncodeLdapEncoder.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 类、FilterDN 来构建一个安全的过滤器和基本 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);
}

参考

  • ©GitHub, Inc.
  • 条款
  • 隐私