LDAP – Entender Para Atacar
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 booleanos — AND (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.