LDAP – Entender Para Atacar

Escrito por  maxin

Quando realizamos testes internos, é comum o uso do LDAP (Lightweight Directory Access Protocol), mas pouco se entende sobre sua real função, utilidade e o quão poderoso esse protocolo pode ser. E como nosso lema é: “Entenda para atacar”, vamos explorar o que é o LDAP, sua origem, a versão anterior, suas nuances e muito mais.

O que é DAP e LDAP?

O DAP (Directory Access Protocol) foi um protocolo criado com base no X.500 (sobre o qual falaremos na seção de Diretórios) e no modelo OSI. Como sabemos, o modelo OSI não é amplamente utilizado na prática, sendo o TCP/IP o padrão dominante. Por conta disso, o DAP apresentava problemas de performance, pois era lento e pesado.

Para resolver essas limitações, surgiu o LDAP, com o termo Lightweight adicionado ao nome. Ele manteve o uso do X.500 para a estrutura de diretórios, mas passou a utilizar o TCP/IP como base. Isso o tornou mais leve, rápido e eficiente que seu antecessor, sendo utilizado até hoje.

O que são os diretórios?

Como mencionado anteriormente, o LDAP utiliza o padrão X.500 para a construção de seus diretórios. Esse padrão foi desenvolvido pela ITU-T (International Telecommunication Union – Telecommunication Standardization Sector). A ideia principal é que exista apenas uma DIT (Directory Information Tree), onde todas as entradas são organizadas — como grupos, usuários, computadores, unidades organizacionais (OUs) e outros. Cada entrada contém os atributos e os valores correspondentes ao objeto representado.

Nesta imagem, podemos perceber como o diretório é estruturado: ele se assemelha a uma árvore, tendo a hierarquia como base. O DC=com representa a raiz desse diretório, e tudo o que estiver abaixo são considerados seus “filhos”. Utilizando o usuário D3v como exemplo, podemos visualizar que os “parentes” desse objeto são, ascendentemente, Pentest, hakai e com.

Algo bastante comum nesse tipo de estrutura são os DNs (Distinguished Names), que nos permitem identificar o caminho completo até um objeto dentro do diretório. Por exemplo:
DN: DC=com,DC=hakai,OU=Red Team,CN=Oliveira. Com essa DN, sabemos exatamente onde o usuário Oliveira está localizado dentro da árvore.

Agora que entendemos o que é o LDAP e como funciona um diretório, uma dúvida natural pode surgir:
“O que podemos fazer com esse diretório e seus objetos?”. Para responder essa pergunta, precisamos conhecer as operações do LDAP.

Operações LDAP

Existem diversas operações que podem ser realizadas com o LDAP, conforme definido na RFC 4511, mais especificamente na seção 4.1.1 (Message Envelope).

Neste post, comentaremos sobre as principais operações do LDAP: Bind, Add, Delete, Modify e Search.

Operação Bind

A operação Bind é utilizada para a autenticação do usuário. Existem duas formas principais de autenticação: Simple e SASL (Simple Authentication and Security Layer). A autenticação está detalhada na RFC 4511, seção 4.2 (Bind Operation), onde é possível visualizar exatamente como essa operação é especificada.

A Simple Authentication, como o nome sugere, é utilizada para autenticação básica, por meio de usuário e senha.

ldapsearch -H ldap://hakai.com -x -D "[email protected]" -w "P@ssw0rd" -b "OU=Pentest,DC=hakai,DC=com"

Utilizando a ferramenta ldapsearch com a opção -x (que ativa a Simple Authentication) e passando a flag -D com o DN do usuário que será usado — neste caso, podemos utilizar [email protected] ou CN=maxin,OU=Pentest,DC=hakai,DC=com — estabelecemos uma conexão com autenticação simples.

A partir desse ponto, e para o restante deste post, utilizaremos o Wireshark para analisar a comunicação. No exemplo a seguir, veremos como a autenticação é realizada.

Diferentemente da Simple Authentication, o SASL é utilizado para autenticação por meio de protocolos externos, como o GSSAPI, que é empregado na autenticação Kerberos.

Para utilizar um protocolo externo no ldapsearch, usamos a opção -Y.

ldapsearch -Y GSSAPI -H ldap://dc01.hakai.com -b "dc=hakai,dc=com"

Podemos observar que, em comparação com a Simple Authentication, a comunicação utilizando um protocolo externo é muito mais complexa e requer o envio de uma quantidade maior de informações.

Operação de Adição

A operação Add é utilizada para adicionar uma nova entrada no diretório. Podemos incluir usuários, grupos, computadores, entre outros objetos, dependendo das permissões do usuário que estiver realizando a ação.

Neste exemplo, adicionaremos uma nova máquina ao domínio. Para isso, é necessário criar um arquivo LDIF (LDAP Data Interchange Format), que será usado pelo comando ldapadd.

Após criar o arquivo LDIF, podemos executar o ldapadd, utilizando um usuário com privilégios de administrador para realizar a operação.

Operação de Modificação

A operação Modify é utilizada para modificar entradas no diretório. Podemos alterar objetos no LDAP dependendo das permissões do usuário em questão. Assim como na operação Add, essa operação também é feita por meio de um arquivo LDIF.

Essa operação é muito importante, mas pouco utilizada. Caso seu usuário tenha permissão sobre outro usuário ou faça parte de um grupo especial, é possível usar a operação modify para alterar entradas no diretório LDAP. Por exemplo, se um atacante obtiver acesso a uma conta pertencente ao grupo Account Operators, poderá utilizar essa operação para adicionar usuários a outros grupos no Active Directory.

Neste exemplo, adicionaremos o usuário Oliveira ao grupo Administrators.

Usaremos a ferramenta ldapmodify para realizar essa ação, passando o arquivo LDIF criado anteriormente.

Operação de remoção

A operação Delete permite excluir uma entrada no diretório. Diferentemente das operações anteriores, o Delete não requer um arquivo LDIF, pois é uma ação direta onde especificamos o DN da entrada que será removida.

Neste exemplo, iremos excluir o computador criado anteriormente na operação Add.

Operação de Busca

A operação Search é a mais utilizada no LDAP, permitindo pesquisar todas as informações do Active Directory.

Para entender a Search Operation, precisamos compreender os elementos necessários para sua construção. Na seção 4.5.1 (Search Request) da RFC 4511, vemos exatamente como ela é definida.

Essa operação é dividida em vários componentes: baseObject, scope, derefAliases, sizeLimit, timeLimit, typesOnly, filter e attributes. Neste post, focaremos nos elementos baseObject, scope, filter e attributes.

Base Object & Scope

O base object define o ponto na “árvore” a partir do qual a pesquisa começará. Por exemplo, se definirmos o base object como OU=Pentest,DC=hakai,DC=com, todas as informações coletadas serão a partir da unidade organizacional (OU) Pentest. Nesse caso, a pesquisa não retornará dados relacionados a outras OUs.

Geralmente, o base object é definido no topo da “árvore”, como DC=hakai,DC=com, para que a pesquisa colete todas as informações possíveis.

Já o scope determina o quão profundo iremos a partir do ponto definido pelo base object — por isso, os dois conceitos se complementam. Como mostrado na imagem anterior, o scope possui três opções:

  • baseObject (0)
  • singleLevel (1)
  • wholeSubtree (2)

Cada uma dessas opções define o nível de profundidade da pesquisa. Para ilustrar melhor, observe a seguinte imagem:

Neste caso, definimos o baseObject como DC=hakai,DC=com.

Se definirmos o scope como baseObject (destaque em vermelho), o escopo se limita exatamente ao ponto base definido. Ao escolher singleLevel (destaque em azul), o scope abrange apenas um nível abaixo do baseObject. A última a opção wholeSubtree (destaque em verde) permite visualizar toda a árvore a partir da base definida.

A opção mais utilizada para o escopo costuma ser a wholeSubtree, mas é importante conhecer todas as opções disponíveis. Neste exemplo, usaremos novamente o ldapsearch, mas agora sem definir explicitamente o escopo.

ldapsearch -H ldap://hakai.com -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "objectClass=user" cn

Podemos observar no Wireshark que as informações enviadas correspondem exatamente ao que a RFC específica.

No comando ldapsearch, a opção -b define o baseObject e a opção -s define o scope. Como não passamos nenhum valor para o escopo, por padrão é utilizado o wholeSubtree, conforme mostrado na imagem.

Attributes & Filter

Attributes

Nesta seção, começaremos falando sobre os atributos, que são a base para entender os filtros no LDAP.

Todos os objetos no Active Directory possuem atributos. Para visualizar esses atributos, podemos realizar uma busca simples usando o ldapsearch.

ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn=maxin"

A seguir, apresentamos alguns atributos do objeto maxin. Com base nesses atributos, podemos aplicar o que chamamos de Attribute Selection para filtrar quais informações queremos visualizar. É importante destacar que, no comando ldapsearch, a seleção dos atributos deve ser feita na parte final do comando.

Capturando essa pesquisa feita no LDAP via wireshark, podemos ver os atributos selecionados.

É interessante entendermos o que acontece quando fazemos uma pesquisa simples no LDAP sem definir explicitamente o Filter e o Attribute Selection.

ldapsearch -H ldap://hakai.com -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com"

Caso nenhum filtro seja especificado, o ldapsearch utiliza, por padrão, o filtro objectClass=*. Isso significa que a busca retorna todas as classes de objetos presentes no Active Directory, ou seja, praticamente todos os objetos. Já o campo attributes, quando definido como 0, é interpretado como *, o que faz com que todos — ou quase todos — os atributos dos objetos selecionados pelo filtro sejam retornados.

Agora que entendemos o funcionamento básico do Attribute Selection, podemos avançar para o Filter. Nas operações de search, o filtro é um dos elementos mais importantes, pois determina exatamente quais tipos de informações serão coletadas. E esse filtro é baseado nos atributos dos objetos.

Filter

Consultando novamente a RFC 4511, na parte referente aos filtros, encontramos algo bastante interessante.

A RFC define diversos tipos de filtro, incluindo os chamados operadores booleanosAND (0), OR (1) e NOT (2) — sobre os quais falaremos mais adiante neste post. Além deles, temos outros filtros como:

  • equalityMatch (3)
  • substrings (4)
  • greaterOrEqual (5)
  • lessOrEqual (6)
  • entre outros.

Para entender melhor como cada tipo de filtro funciona, comecemos por um dos mais básicos: o equalityMatch (3).

No exemplo a seguir, iremos filtrar todos os objetos cujo atributo CN seja igual a “maxin”. Como existe apenas um objeto com esse nome, o retorno será único — o próprio objeto maxin.

ldapsearch -H ldap://hakai.com -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn=maxin"

Ao capturar essa operação no Wireshark, podemos ver exatamente o que a RFC descreve: a utilização do equalityMatch (3), o campo attributeDesc (que indica qual atributo está sendo filtrado) e o assertionValue (o valor definido na consulta).

Outro tipo de filtro bastante útil é o substrings (4). Um exemplo prático seria: cn=ad*. Neste caso, estamos utilizando o atributo CN com um curinga (*) para buscar todos os objetos cujo nome comece com “ad”. Embora este exemplo use o atributo CN, o substrings pode ser aplicado com diversos outros atributos conforme a necessidade.

ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn=ad*" cn

Esse filtro nos retornará todos os objetos cujo CN comece com “ad”. É importante observar que o Windows (e, por consequência, o Active Directory) é case insensitive, ou seja, não diferencia letras maiúsculas de minúsculas. Por isso, nomes com letras maiúsculas aparecerão no resultado.

Ao capturar essa operação no Wireshark, conseguimos visualizar claramente como a busca foi realizada.

Existem dois filtros adicionais que merecem destaque: greaterOrEqual (5) e lessOrEqual (6). Vamos entender como eles funcionam e como podem ser utilizados em consultas mais avançadas.

Ambos seguem o mesmo padrão de uso, é necessário fornecer um valor como número, letra ou símbolo.

ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn>=a" cn

O que exatamente o greaterOrEqual está fazendo nesse caso? Ao utilizarmos esse filtro com a letra “a”, o LDAP converte essa letra para o valor 97, que é seu código decimal na tabela ASCII. A partir disso, ele retorna todos os objetos cujos atributos comecem com 97 em diante — ou seja, a, b (98), c (99), d (100) e assim por diante. Quanto menor o número fornecido (em termos de ASCII), maior será o alcance da busca.

Neste próximo exemplo, utilizamos o símbolo “!”, que corresponde ao número 33 na tabela ASCII. Essa consulta retornou 711 linhas, em comparação com o exemplo anterior, onde usamos a letra “a” (valor 97 na tabela ASCII) e obtivemos apenas 455 linhas. Isso demonstra como valores mais baixos na tabela ASCII ampliam o escopo da busca. 

ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn>=!" cn

Entendendo o funcionamento do greaterOrEqual, o uso do lessOrEqual torna-se uma consequência lógica, já que ambos seguem o mesmo padrão de operação. O “truque” agora é inverter a lógica: utilizar uma letra ou símbolo com um valor alto na tabela ASCII, para limitar os resultados — ou seja, fazer o caminho inverso e filtrar apenas objetos com valores menores ou iguais ao definido.

ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" "cn<=a" cn

Search Extensions

Como mencionado anteriormente, o ldapsearch, por padrão, não retorna todos os atributos disponíveis dos objetos. Por exemplo, o atributo ntSecurityDescriptor é extremamente importante, pois contém informações sobre DACL e SACL, que definem permissões e auditorias. No entanto, se tentarmos buscá-lo diretamente, como no exemplo abaixo:

"cn=maxin" ntSecurityDescriptor

Não teremos nenhum retorno para esse atributo. Mas isso não significa que ele não possa ser acessado.

Para isso, utilizamos uma funcionalidade chamada Search Extensions no ldapsearch, ativada com a flag -E.

-E '1.2.840.113556.1.4.801=::MAMCAQc=' 'cn=maxin' ntSecurityDescriptor

Vamos por partes para entender o motivo pelo qual essa busca funciona. A opção de Search Extensions aceita apenas OIDs (Object Identifiers). O OID 1.2.840.113556.1.4.801 é interpretado como uma extensão chamada SD_FLAGS.

O SD_FLAGS define quais partes do ntSecurityDescriptor serão retornadas. Os principais componentes e seus valores são:

  • DACL (Discretionary Access Control List): 1
  • SACL (System Access Control List): 2
  • Owner: 4
  • Group: 8

Se quisermos retornar DACL, SACL e Owner, precisamos somar os valores: 1 + 2 + 4 = 7. O valor MAMCAQc= que aparece na extensão é um blob em base64 que representa exatamente esse número (7), porém codificado e seguindo a estrutura ASN.1, exigida pelo SD_FLAGS.

Operações booleanas

As operações booleanas no LDAP permitem construir filtros mais complexos e específicos, utilizando os operadores AND, OR e NOT. Vamos entender melhor como cada um deles funciona na prática.

A operação AND é representada pelo símbolo &. Assim como a operação OR (que veremos a seguir), é necessário combinar duas ou mais condições, cada uma envolvida por parênteses. A estrutura fica assim: &(condição1)(condição2).

Neste exemplo, realizamos uma verificação simples. Aqui estamos verificando se o objeto pertence à classe user e se o CN é igual a maxin. Ambas as condições precisam ser verdadeiras para que o objeto seja retornado.

ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" '(&(objectClass=user)(cn=maxin))'

Podemos melhorar esse filtro utilizando o que aprendemos na seção anterior, com o operador greaterOrEqual. Estamos verificando todos os objetos da classe user que retornam na busca de “cn>=!”

ldapsearch -H ldap://hakai.com -LLL -x -D "[email protected]" -w "P@ssw0rd" -b "DC=hakai,DC=com" '(&(objectClass=user)(cn>=!))'

A operação OR segue a mesma estrutura do AND, mas com uma diferença fundamental: basta que uma das condições seja verdadeira para que o objeto seja retornado.

Para utilizar o OR, usamos o símbolo “|", da seguinte forma: |(condição1)(condição2). Normalmente, o OR é combinado com outras operações booleanas para compor filtros mais precisos. Por exemplo:

'(&(objectClass=user)(|(cn=maxin)(cn=luriel)))'

Neste caso, utilizamos um AND em conjunto com um OR. Veja que, após a condição (&(objectClass=user), abrimos outro parêntese — esse contém a operação OR, com duas condições internas. Aqui, como ambas as condições dentro do OR são verdadeiras (os CNs maxin e Luriel existem) e a condição do AND também é satisfeita, os dois objetos são retornados.

Ao analisar essa busca no Wireshark, podemos observar um padrão interessante: o filtro começa com o AND (0), e dentro dele há um OR (1) — exatamente como foi estruturado no comando. Isso mostra claramente como o filtro é interpretado pelo LDAP.

A operação NOT, representada pelo símbolo “!", é a última operação booleana disponível no LDAP. Como o nome sugere, essa operação nega a condição fornecida. Diferentemente do AND e do OR, o NOT requer apenas uma única condição, ou seja, apenas um par de parênteses é necessário.

Neste comando, estamos buscando todos os objetos da classe user que não tenham o CN=maxin. O resultado será uma lista com todos os usuários, exceto o objeto maxin.

'(&(objectClass=user)(!(cn=maxin)))' cn

Na imagem acima, podemos visualizar claramente no Wireshark a presença da operação AND (0) e, dentro dela, a operação NOT (2) aplicada à condição.

Esses foram exemplos simples para demonstrar o uso dos Boolean Operators no LDAP. Apesar da simplicidade dos casos apresentados, essas operações têm uma aplicação muito mais ampla, especialmente quando integradas a mecanismos de autenticação web-based, onde podem ser usadas para construir ou manipular lógicas de acesso com base em filtros LDAP.

Conclusão

Quando nos deparamos com um ambiente de Active Directory, entender o funcionamento do LDAP é essencial. Mesmo que não o utilizemos diretamente, ele quase sempre está atuando nos bastidores. Por isso, conhecer o protocolo, saber como ele opera e como extrair informações de forma consciente é de extrema importância. Ferramentas amplamente utilizadas como o BloodHound, por exemplo, fazem uso intensivo do LDAP para coletar dados e realizar correlações — como o atributo ntSecurityDescriptor, citado anteriormente, que permite identificar permissões entre objetos e o que um usuário pode ou não fazer no domínio. Outro exemplo é o Certipy, utilizado no reconhecimento e exploração de vulnerabilidades no Active Directory Certificate Services (AD CS). Assim como o BloodHound, o Certipy também utiliza o LDAP para buscar e estruturar informações críticas.

Esses exemplos deixam claro o quão poderoso é o protocolo. Embora seja comumente lembrado apenas por sua função de pesquisa, o LDAP permite também adicionar, modificar e remover entradas no diretório — funcionalidades que, se mal utilizadas ou exploradas, podem comprometer seriamente um ambiente.

Ressaltamos sempre a importância de entender como os sistemas — protocolos, serviços etc. — realmente funcionam para, só então, atacá-los. Nada adiantará se não entendermos o que está acontecendo e simplesmente tentarmos atacar. Nesse cenário, acabamos apenas executando ferramentas sem compreender o que de fato está acontecendo — e, provavelmente, sem obter nenhum resultado relevante. Entenda primeiro e ataque depois!

Espero que este blog post tenha lhe ajudado, de alguma forma, a entender mais sobre o protocolo LDAP e tudo o que realmente pode ser feito com ele. Além disso, este post também foi uma forma de mostrar como a curiosidade e a vontade de entender podem levar a uma compreensão significativa sobre qualquer tema. Espero que esta demonstração sirva como um combustível para todos que estão lendo e incentive a fazer o mesmo.

Logo da Hakai.