Habilitando EPA em FreeTDS e go-mssqldb: Uma Aventura de 5 Minutos
Este artigo detalha a implementação do Extended Protection for Authentication (EPA) em FreeTDS e go-mssqldb para mitigar ataques NTLM relay em servidores MSSQL. O autor, um engenheiro de segurança da Yandex, explica o processo e os desafios enfrentados.
MundiX News·13 de maio de 2026·10 min de leitura·👁 10 views
128K+
Alcance em 30 dias
Яндекс
543,14
Classificação
276 726
Assinantes
Assinar
bulatgafurov
Há 23 minutos
Habilitando EPA em FreeTDS e go-mssqldb: Uma Aventura de 5 Minutos
10 min
1.1K
Blog da Empresa Yandex
Segurança da Informação
*
Gerenciamento de Produto
*
Infraestrutura de TI
*
Imagine: você perde o controle do SCCM — uma das ferramentas de gerenciamento de infraestrutura mais críticas. E o ponto de entrada é uma conexão MSSQL comum, onde ele armazena seus dados. Um invasor intercepta a autenticação NTLM e a redireciona para o servidor desejado — é assim que funciona o NTLM relay. Nós, da equipe de Engenharia de Segurança, decidimos não esperar a exploração dessa vulnerabilidade.
Meu nome é Bulat Gafurov, sou engenheiro de segurança da informação na Yandex. Neste artigo, contarei por que a solução padrão não foi suficiente e como adicionamos suporte ao mecanismo EPA em bibliotecas populares para mudar a proteção no lado do MSSQL para o modo
Require
, sem privar os serviços Linux e Windows do acesso aos dados.
De onde tudo começou
SCCM (System Center Configuration Manager) — uma ferramenta para gerenciar dispositivos Windows: permite instalar e atualizar aplicativos centralmente, gerenciar configurações e, essencialmente, é RCE as a Service em toda a infraestrutura.
Para armazenar configurações, direitos de acesso, inventário e dados para implantação, o SCCM usa MSSQL. Isso torna o banco de dados um alvo atraente: com acesso a ele, o invasor obtém controle sobre o SCCM e, por meio dele, acesso a todos os dispositivos gerenciados.
Em nosso caso, não apenas os servidores SCCM se conectaram ao MSSQL: era necessário obter dados para monitoramento e exportar o inventário para sistemas de terceiros. O acesso à rede ao banco de dados foi aberto a vários serviços — tanto no Windows quanto no Linux.
Vale ressaltar que o firewall, como qualquer segmentação, é uma medida muito eficaz para limitar as capacidades dos invasores, mas ao projetar sistemas, o firewall não pode ser o principal meio de garantir a segurança.
SCCM é um alvo bem conhecido para ataques NTLM relay, e as recomendações de proteção existem há muito tempo:
PREVENT14
. Nosso objetivo era nos proteger do NTLM relay sem quebrar as conexões de sistemas Linux e Windows.
Um detalhe curioso: o suporte ao mecanismo de proteção EPA em ferramentas de auditoria de segurança apareceu muito antes do que nas bibliotecas de clientes para Linux, incluindo o cliente oficial da Microsoft.
Há vários artigos que adicionam suporte EPA a ferramentas semelhantes:
A journey implementing Channel Binding on
MSSQLClient.py
(como o suporte EPA foi adicionado ao Impacket,
PR
).
Dissecting NTLM EPA with love & building a MitM proxy
(artigo sobre a análise da mensagem NTLM com EPA e a criação de um proxy MitM
Prox-Ez
).
Implementação de
Channel Binding
em Certipy.
Ldap3:
suporte a Channel Binding (o suporte foi adicionado há quase três anos).
Mas simplesmente mudar o EPA para o modo Require não podíamos: isso cortaria imediatamente todos os clientes Linux. Restavam duas opções:
Recusar TLS e usar
Service Binding
.
Escrever suporte EPA em bibliotecas populares — FreeTDS e
microsoft/go-mssqldb
.
A primeira opção não nos convinha: Service Binding nos força a abandonar o TLS. Portanto, seguimos o segundo caminho. Mas antes de mergulhar no código, vamos lembrar o que é NTLM relay, Channel Bindings e EPA.
Vamos relembrar a base do NTLM relay
NTLM relay é uma técnica em que um invasor intercepta as credenciais de autenticação do cliente e as envia para o servidor desejado para obter acesso ao serviço em nome da vítima (
mais detalhes sobre NTLM relay
).
A ideia principal: o invasor precisa do próprio fato da autenticação do cliente — não importa exatamente onde ele se conecta. Para isso, ataques MITM clássicos, bem como ataques de coerção — técnicas que permitem forçar a autenticação de um computador sob sua conta de máquina para um endereço IP ou nome de domínio DNS arbitrário, são adequados.
A coerção deve ser distinguida dos ataques de spoofing de protocolos de resolução de nomes. Estes últimos não dão liberdade na escolha da vítima e são quase sempre limitados a um único segmento L2. Mas, às vezes, é possível interceptar a autenticação de um usuário real, e não de uma conta de máquina. Mais detalhes sobre técnicas de coerção — em
The Ultimate Guide to Windows Coercion Techniques in 2025
.
Serviços que funcionam sob
SYSTEM
ou
Network Service
e se autenticam na rede sob a conta da máquina são especialmente vulneráveis. SCCM é um exemplo típico de tal serviço.
O que é Channel Binding e por que é necessário
Parece que o TLS configurado e a verificação do certificado do servidor protegem de forma confiável contra ataques MITM. Mas com NTLM relay, tudo é mais complicado. Ataques de retransmissão podem ser entre protocolos: o cliente se conecta ao invasor via SMB sem TLS, e este redireciona a autenticação para o servidor de destino via HTTPS. Nesse caso, o cliente não verifica nenhum certificado. E a presença de TLS no servidor de destino não dá nada no caso de um ataque de coerção, porque o cliente verifica o certificado do invasor.
É para proteger contra esses cenários que existe o Channel Binding (CB) — um mecanismo que fornece prova criptográfica de que ambas as partes estão se comunicando no mesmo canal TLS. O valor específico que o cliente passa para o servidor para verificação é chamado de Channel Binding Token (CBT).
Existem vários tipos de CB descritos em
RFC 5929
:
tls-server-end-point
— CBT é calculado como SHA-256 (às vezes SHA-384 ou SHA-512) do certificado folha da mensagem ServerHello;
tls-unique
— usa verify_data da mensagem TLS Client Finished;
tls-unique-for-telnet
— o mesmo, mas o TLS Server Finished é tomado.
Com o lançamento do TLS 1.3, o cálculo de
tls-unique
tornou-se impossível, então
RFC 9266
apareceu com o quarto tipo —
tls-exporter
. Ele usa Export Keying Material com um rótulo estático e um comprimento de 32 bytes.
A forma exata como CB protege contra um ataque é bem visível no diagrama abaixo: o invasor intercepta a conexão TLS, mas não pode alterar o CBT — e o servidor rejeita a autenticação.
Em que casos o Channel Binding pode não salvar
Há estudos que demonstram como contornar o CB sob certas condições:
Em
estudo da Almond Consulting
conseguiram contornar a verificação CB graças à funcionalidade STARTTLS no LDAPS. Durante um ataque de retransmissão, o invasor primeiro se autenticou por um canal não criptografado, estabeleceu uma conexão TLS sobre a existente, e o servidor não verificou o CB. Para exploração, o LDAP-signing desativado é necessário.
Em
estudo da CrowdStrike (Preempt)
conseguiram remover o MIC (assinatura) dos pacotes NTLM, após o que foi possível substituir o valor CB e o servidor aceitará a mensagem
NTLMSSP_AUTHENTICATE
.
Separatamente, vale a pena notar o ataque
3Shake
, em que o invasor poderia estabelecer duas sessões com as mesmas chaves mestras por meio do mecanismo de retomada de sessão no TLS 1.2. Com RFC 7627, isso foi corrigido e a chave mestra foi garantidamente diferente para sessões diferentes.
EPA (Extended Protection for Authentication)
Algumas palavras sobre EPA
EPA é um modo em que o servidor MSSQL (ou qualquer outro serviço: IIS, WinRM (over TLS), LDAP/LDAPS) exige a transmissão de Channel Binding ou Service Binding correto. A Microsoft fala sobre EPA em duas páginas em sua documentação:
visão geral do EPA
e
Microsoft Docs: Supporting Extended Protection for Authentication (EPA) in a service
.
Service Binding
(SB) é necessário caso não seja possível usar TLS ou verificar o Channel Binding. Exemplo: quando outro serviço termina o TLS, pode ser um balanceador de carga para IIS, ou um bastion, ou se o serviço não suportar TLS.
SB permite que o servidor obtenha o atributo
SECPKG_ATTR_CLIENT_SPECIFIED_TARGET
e entenda por qual nome o cliente se conectou originalmente.
Assim, se a autenticação do cliente for interceptada, o servidor verá o nome de outro serviço, entenderá que esses pacotes não foram direcionados a ele e interromperá a conexão.
O atributo
SECPKG_ATTR_CLIENT_SPECIFIED_TARGET
também é protegido por MIC.
No MSSQL EPA,
segundo a documentação
, pode estar em três modos:
Off
Allowed
Required
O modo Off desliga a verificação CB e SB, enquanto Required não permitirá a conexão sem CB ou SB.
Mas com o modo Allowed, tudo é muito mais interessante: é muito útil quando nem todos os clientes suportam EPA e permite não enviar CB para uma conexão bem-sucedida. Mas se o cliente enviou algum valor, o servidor certamente o verificará. Aqui, confiamos que todos os clientes, se possível, enviarão CB e não poderão ser retransmitidos.
Se a autenticação ocorrer por um canal não criptografado, digamos SMB ou WebDAV, o cliente pode especificar um valor nulo (00000000000000000000000000000000) como CB. E se o invasor decidir usar essa autenticação por um canal TLS, o servidor a rejeitará. Se o cliente não passar o CB em
InitializeSecurityContext
, SSPI (ou seja, apenas no Windows) sempre envia um CB nulo.
Adicionando suporte EPA ao cliente MSSQL
Escolha do cliente
Toda a nossa lógica de escolha do cliente pode ser entendida olhando para a seguinte tabela:
Nossa escolha recaiu sobre o FreeTDS, que pode ser usado em muitas linguagens onde ODBC é implementado, digamos Python com pyodbc, e no go-mssqldb para Golang (é claro, você pode pegar um cliente ODBC no Go, mas em uma parte da infraestrutura usamos o Telegraf, então decidimos adicionar suporte a ele também).
PRs correspondentes para projetos:
FreeTDS
e
microsoft/go-mssqldb
.
Um pouco sobre TDS e TLS
Tabular Data Stream (TDS) é um protocolo de camada de aplicação para interagir com o MSSQL Server, funciona sobre TCP (pacotes TLS-, NTLM-, SSPI- são mais frequentemente transmitidos dentro do TDS, mas nem sempre).
Há também uma diferença entre TDS 7.4 e 8.0. No TDS 7.4, o cliente envia uma mensagem Pre-Login sobre suporte (ou sua ausência) de criptografia e outras opções, o servidor responde a ele com opções suportadas ou obrigatórias. Se o cliente e o servidor concordarem que haverá criptografia, o cliente estabelecerá uma handshake TLS dentro do TDS.
Mas no TDS 8.0, a capacidade de
Strict
cryptografia foi adicionada. Quando o cliente e o servidor nos parâmetros de conexão
Encryption
estão definidos como
Strict
, o cliente estabelece imediatamente uma conexão TLS ao se conectar e, somente depois, envia pacotes TDS. Para TDS 8.0, se você usar TLS ≤ 1.2, então CB consideramos da mesma forma, mas se TLS 1.3 for usado, ainda não há informações públicas da Microsoft sobre como o MSSQL considera CBT.
TDS 7.4 suporta TLS não superior a 1.2.
Desmontando pacotes TDS no Wireshark
Para ver nosso CB, precisamos gravar nossa conexão no MSSQL e, em seguida, descriptografá-la.
Para descriptografar a conexão TLS, precisaremos das chaves, que podem ser obtidas pela variável de ambiente
SSLKEYLOGFILE
OpenSSL, GnuTLS. Se esta opção não for adequada para você, você pode tentar usar algum utilitário para capturar essas chaves no nível do kernel, digamos
ecapture
.
O Wireshark não analisa TDS em TLS, ele faz isso apenas para TDS sobre TCP, então escrevemos um dissecador simples que chama TDS:
lua
-- Get the TDS dissectorlocal tds_dissector = Dissector.get("tds")-- Create our custom dissectorlocal tls_to_tds_proto =Proto("tls_to_tds","TLS to TDS Decrypted Data")-- Define fields for our protocollocal f_tls_to_tds_data = ProtoField.bytes("tls_to_tds.data","Decrypted TDS Data")local f_tls_to_tds_length = ProtoField.uint32("tls_to_tds.length","Data Length", base.DEC)-- Add fields to protocoltls_to_tds_proto.fields ={f_tls_to_tds_data, f_tls_to_tds_length}-- Main dissector functionfunction tls_to_tds_proto.dissector(tvb, pinfo, tree)local length = tvb:len()if length ==0thenreturnend-- Create our protocol treelocal subtree = tree:add(tls_to_tds_proto,tvb()) subtree:add(f_tls_to_tds_length, length)-- subtree:add(f_tls_to_tds_data, tvb)-- Parse with TDS dissectorif tds_dissector thenlocal tds_subtree = subtree:add(tds_dissector, tvb) tds_dissector:call(tvb, pinfo, tds_subtree)else subtree:add_expert_info(PI_PROTOCOL, PI_WARN,"TDS dissector not available")endendmyproto_dissector_table = DissectorTable.get("tls.port")myproto_dissector_table:add(1433, tls_to_tds_proto)myproto_dissector_table:add(2433, tls_to_tds_proto)myproto_dissector_table:add_for_decode_as(tls_to_tds_proto)print("TLS to TDS dissector loaded successfully")
Isso será suficiente para lermos o CB no NTLM, mas para o Kerberos precisamos do arquivo keytab da conta sob a qual o MSSQL é executado.
Como passamos o CB na autenticação
A forma de passar o CBT depende do tipo de autenticação. É importante para nós que o invasor não possa influenciar o CBT nas mensagens do cliente, caso contrário, ele (o invasor) poderá calcular o CBT e substituí-lo. Ou seja, precisamos de uma verificação de integridade para o CBT. O MSSQL usa TLS-unique para gerar CB.
GSS-API
Se quisermos nos autenticar com máquinas unix via Kerberos, provavelmente usaremos GSS-API, que exige que montemos a estrutura correta
gss_channel_bindings_struct
.
em
N
indicaremos o comprimento de M em bytes, e M representará uma string sem um byte NULL no final
tls-unique
: e o valor
verify_data
.
Esta estrutura precisará ser passada como um parâmetro ao chamar
gss_init_sec_context
. O servidor fará o mesmo em seu lado, e as estruturas devem corresponder. Se você entender, dentro da chamada
gss_init_sec_context
após a formação da estrutura CB, um hash MD5 é calculado a partir dela. Deste hash, a chave de sessão e outras flags, o campo Checksum da estrutura Authenticator da mensagem AP-REQ (Authentication Protocol Request) é calculado.
Isso pode ser visto no seguinte commit em
github.com/krb5/krb5: checksum implementation of gss_channel_bindings_struct
.
Se quisermos ver o CB no Kerberos no Wireshark, precisamos olhar para o pacote TDS7-Login, nele o ap-req authenticator com o campo Bnd.
É assim que ficará no Wireshark quando abrirmos a mensagem TDS7-Login:
Mais detalhes sobre GSS-API:
GSS-API Programming Guide: Channel Binding
, sobre o uso de CB em GSS-API:
RFC 5554
.
NTLM
Para NTLM, precisamos montar a estrutura da mesma forma, mas calcular independentemente o hash MD5 a partir dela e colocá-lo no conjunto de atributos (os chamados
AV_PAIR
) NTLM Response na mensagem AUTH.
Mais detalhes sobre
AV_PAIR
:
na documentação do protocolo da Microsoft
.
O MIC garante a integridade do CBT, o invasor não poderá substituir o CBT até que tenha a capacidade de falsificar o MIC, ou seja, até que ele tenha o hash NTLMv2 ou a senha do usuário.
No Wireshark, o CB pode ser visto na mensagem
NTLMSSP_AUTH
, como mostrado na imagem a seguir:
WinSSPI
WinSSPI é uma implementação do GSS-API da Microsoft, que implementa e disponibiliza por meio da API para aplicativos no modo de usuário protocolos como NTLM, Kerberos, Negotiate, Schannel, CredSSP.
Aqui é um pouco diferente. A Microsoft exige que passemos uma estrutura e parâmetros ligeiramente diferentes para
InitializeSecurityContext
, que são diferentes de
gss_init_sec_context
.
Então, a Microsoft exige a estrutura
SEC_CHANNEL_BINDINGS
. Ela difere no fato de que a própria estrutura descreve apenas o tipo, o comprimento e o deslocamento dos dados, essa estrutura precisa ser passada no buffer e, em seguida, os próprios dados. Também definimos os parâmetros
Initiator
e
Acceptor
como 0 e preenchemos
cbApplicationDataLength
e
dwApplicationDataOffset
. No último, indicamos o tamanho da própria estrutura, o que significa que os próprios dados estarão localizados imediatamente após a estrutura.
Você precisa passar esse buffer para a função
InitializeSecurityContext
como parte de pInput, mas apenas após a primeira chamada, porque na primeira chamada pInput deve ser NULL.
Mais detalhes sobre InitializeSecurityContext
.
Conclusão
Analisamos como o mecanismo EPA funciona e adicionamos suporte a ele nas principais bibliotecas para Linux —
PR em FreeTDS
e
PR em microsoft/go-mssqldb
. Agora, nada impede que você ative o modo Required e proteja seus servidores MSSQL contra ataques NTLM relay.
Para sua conveniência — uma tabela resumida de clientes e seu suporte EPA:
Uma observação final: adicionar suporte Kerberos a
go-mssqldb
não foi possível até agora: não há um repositório
krb5
adequado no ecossistema Golang que estaria pronto para integração. Como solução alternativa, você pode considerar a conexão via ODBC.
Obrigado por ler. Desejamos que seu MSSQL permaneça sempre seguro.
Tags:
windows
security
ntlm relay
ntlm
ntlmrelay
epa
mssql
mssqlserver
microsoft
freetds
Hubs:
Blog da Empresa Yandex
Segurança da Informação
Gerenciamento de Produto
Infraestrutura de TI
🛡️⚡
Pare de pesquisar. Comece a hackear.
O MundiX é seu copiloto de pentest com IA: comandos exatos, análise de outputs e próximo passo na kill chain — em segundos.
Sem cartão para começar · Planos a partir de R$49/mês
128K+
Alcance em 30 dias
Яндекс
543,14
Classificação
276 726
Assinantes
Assinar
bulatgafurov
Há 23 minutos
Habilitando EPA em FreeTDS e go-mssqldb: Uma Aventura de 5 Minutos
10 min
1.1K
Blog da Empresa Yandex
Segurança da Informação
*
Gerenciamento de Produto
*
Infraestrutura de TI
*
Imagine: você perde o controle do SCCM — uma das ferramentas de gerenciamento de infraestrutura mais críticas. E o ponto de entrada é uma conexão MSSQL comum, onde ele armazena seus dados. Um invasor intercepta a autenticação NTLM e a redireciona para o servidor desejado — é assim que funciona o NTLM relay. Nós, da equipe de Engenharia de Segurança, decidimos não esperar a exploração dessa vulnerabilidade.
Meu nome é Bulat Gafurov, sou engenheiro de segurança da informação na Yandex. Neste artigo, contarei por que a solução padrão não foi suficiente e como adicionamos suporte ao mecanismo EPA em bibliotecas populares para mudar a proteção no lado do MSSQL para o modo
Require
, sem privar os serviços Linux e Windows do acesso aos dados.
De onde tudo começou
SCCM (System Center Configuration Manager) — uma ferramenta para gerenciar dispositivos Windows: permite instalar e atualizar aplicativos centralmente, gerenciar configurações e, essencialmente, é RCE as a Service em toda a infraestrutura.
Para armazenar configurações, direitos de acesso, inventário e dados para implantação, o SCCM usa MSSQL. Isso torna o banco de dados um alvo atraente: com acesso a ele, o invasor obtém controle sobre o SCCM e, por meio dele, acesso a todos os dispositivos gerenciados.
Em nosso caso, não apenas os servidores SCCM se conectaram ao MSSQL: era necessário obter dados para monitoramento e exportar o inventário para sistemas de terceiros. O acesso à rede ao banco de dados foi aberto a vários serviços — tanto no Windows quanto no Linux.
Vale ressaltar que o firewall, como qualquer segmentação, é uma medida muito eficaz para limitar as capacidades dos invasores, mas ao projetar sistemas, o firewall não pode ser o principal meio de garantir a segurança.
SCCM é um alvo bem conhecido para ataques NTLM relay, e as recomendações de proteção existem há muito tempo:
PREVENT14
. Nosso objetivo era nos proteger do NTLM relay sem quebrar as conexões de sistemas Linux e Windows.
Um detalhe curioso: o suporte ao mecanismo de proteção EPA em ferramentas de auditoria de segurança apareceu muito antes do que nas bibliotecas de clientes para Linux, incluindo o cliente oficial da Microsoft.
Há vários artigos que adicionam suporte EPA a ferramentas semelhantes:
A journey implementing Channel Binding on
MSSQLClient.py
(como o suporte EPA foi adicionado ao Impacket,
PR
).
Dissecting NTLM EPA with love & building a MitM proxy
(artigo sobre a análise da mensagem NTLM com EPA e a criação de um proxy MitM
Prox-Ez
).
Implementação de
Channel Binding
em Certipy.
Ldap3:
suporte a Channel Binding (o suporte foi adicionado há quase três anos).
Mas simplesmente mudar o EPA para o modo Require não podíamos: isso cortaria imediatamente todos os clientes Linux. Restavam duas opções:
Recusar TLS e usar
Service Binding
.
Escrever suporte EPA em bibliotecas populares — FreeTDS e
microsoft/go-mssqldb
.
A primeira opção não nos convinha: Service Binding nos força a abandonar o TLS. Portanto, seguimos o segundo caminho. Mas antes de mergulhar no código, vamos lembrar o que é NTLM relay, Channel Bindings e EPA.
Vamos relembrar a base do NTLM relay
NTLM relay é uma técnica em que um invasor intercepta as credenciais de autenticação do cliente e as envia para o servidor desejado para obter acesso ao serviço em nome da vítima (
mais detalhes sobre NTLM relay
).
A ideia principal: o invasor precisa do próprio fato da autenticação do cliente — não importa exatamente onde ele se conecta. Para isso, ataques MITM clássicos, bem como ataques de coerção — técnicas que permitem forçar a autenticação de um computador sob sua conta de máquina para um endereço IP ou nome de domínio DNS arbitrário, são adequados.
A coerção deve ser distinguida dos ataques de spoofing de protocolos de resolução de nomes. Estes últimos não dão liberdade na escolha da vítima e são quase sempre limitados a um único segmento L2. Mas, às vezes, é possível interceptar a autenticação de um usuário real, e não de uma conta de máquina. Mais detalhes sobre técnicas de coerção — em
The Ultimate Guide to Windows Coercion Techniques in 2025
.
Serviços que funcionam sob
SYSTEM
ou
Network Service
e se autenticam na rede sob a conta da máquina são especialmente vulneráveis. SCCM é um exemplo típico de tal serviço.
O que é Channel Binding e por que é necessário
Parece que o TLS configurado e a verificação do certificado do servidor protegem de forma confiável contra ataques MITM. Mas com NTLM relay, tudo é mais complicado. Ataques de retransmissão podem ser entre protocolos: o cliente se conecta ao invasor via SMB sem TLS, e este redireciona a autenticação para o servidor de destino via HTTPS. Nesse caso, o cliente não verifica nenhum certificado. E a presença de TLS no servidor de destino não dá nada no caso de um ataque de coerção, porque o cliente verifica o certificado do invasor.
É para proteger contra esses cenários que existe o Channel Binding (CB) — um mecanismo que fornece prova criptográfica de que ambas as partes estão se comunicando no mesmo canal TLS. O valor específico que o cliente passa para o servidor para verificação é chamado de Channel Binding Token (CBT).
Existem vários tipos de CB descritos em
RFC 5929
:
tls-server-end-point
— CBT é calculado como SHA-256 (às vezes SHA-384 ou SHA-512) do certificado folha da mensagem ServerHello;
tls-unique
— usa verify_data da mensagem TLS Client Finished;
tls-unique-for-telnet
— o mesmo, mas o TLS Server Finished é tomado.
Com o lançamento do TLS 1.3, o cálculo de
tls-unique
tornou-se impossível, então
RFC 9266
apareceu com o quarto tipo —
tls-exporter
. Ele usa Export Keying Material com um rótulo estático e um comprimento de 32 bytes.
A forma exata como CB protege contra um ataque é bem visível no diagrama abaixo: o invasor intercepta a conexão TLS, mas não pode alterar o CBT — e o servidor rejeita a autenticação.
Em que casos o Channel Binding pode não salvar
Há estudos que demonstram como contornar o CB sob certas condições:
Em
estudo da Almond Consulting
conseguiram contornar a verificação CB graças à funcionalidade STARTTLS no LDAPS. Durante um ataque de retransmissão, o invasor primeiro se autenticou por um canal não criptografado, estabeleceu uma conexão TLS sobre a existente, e o servidor não verificou o CB. Para exploração, o LDAP-signing desativado é necessário.
Em
estudo da CrowdStrike (Preempt)
conseguiram remover o MIC (assinatura) dos pacotes NTLM, após o que foi possível substituir o valor CB e o servidor aceitará a mensagem
NTLMSSP_AUTHENTICATE
.
Separatamente, vale a pena notar o ataque
3Shake
, em que o invasor poderia estabelecer duas sessões com as mesmas chaves mestras por meio do mecanismo de retomada de sessão no TLS 1.2. Com RFC 7627, isso foi corrigido e a chave mestra foi garantidamente diferente para sessões diferentes.
EPA (Extended Protection for Authentication)
Algumas palavras sobre EPA
EPA é um modo em que o servidor MSSQL (ou qualquer outro serviço: IIS, WinRM (over TLS), LDAP/LDAPS) exige a transmissão de Channel Binding ou Service Binding correto. A Microsoft fala sobre EPA em duas páginas em sua documentação:
visão geral do EPA
e
Microsoft Docs: Supporting Extended Protection for Authentication (EPA) in a service
.
Service Binding
(SB) é necessário caso não seja possível usar TLS ou verificar o Channel Binding. Exemplo: quando outro serviço termina o TLS, pode ser um balanceador de carga para IIS, ou um bastion, ou se o serviço não suportar TLS.
SB permite que o servidor obtenha o atributo
SECPKG_ATTR_CLIENT_SPECIFIED_TARGET
e entenda por qual nome o cliente se conectou originalmente.
Assim, se a autenticação do cliente for interceptada, o servidor verá o nome de outro serviço, entenderá que esses pacotes não foram direcionados a ele e interromperá a conexão.
O atributo
SECPKG_ATTR_CLIENT_SPECIFIED_TARGET
também é protegido por MIC.
No MSSQL EPA,
segundo a documentação
, pode estar em três modos:
Off
Allowed
Required
O modo Off desliga a verificação CB e SB, enquanto Required não permitirá a conexão sem CB ou SB.
Mas com o modo Allowed, tudo é muito mais interessante: é muito útil quando nem todos os clientes suportam EPA e permite não enviar CB para uma conexão bem-sucedida. Mas se o cliente enviou algum valor, o servidor certamente o verificará. Aqui, confiamos que todos os clientes, se possível, enviarão CB e não poderão ser retransmitidos.
Se a autenticação ocorrer por um canal não criptografado, digamos SMB ou WebDAV, o cliente pode especificar um valor nulo (00000000000000000000000000000000) como CB. E se o invasor decidir usar essa autenticação por um canal TLS, o servidor a rejeitará. Se o cliente não passar o CB em
InitializeSecurityContext
, SSPI (ou seja, apenas no Windows) sempre envia um CB nulo.
Adicionando suporte EPA ao cliente MSSQL
Escolha do cliente
Toda a nossa lógica de escolha do cliente pode ser entendida olhando para a seguinte tabela:
Nossa escolha recaiu sobre o FreeTDS, que pode ser usado em muitas linguagens onde ODBC é implementado, digamos Python com pyodbc, e no go-mssqldb para Golang (é claro, você pode pegar um cliente ODBC no Go, mas em uma parte da infraestrutura usamos o Telegraf, então decidimos adicionar suporte a ele também).
PRs correspondentes para projetos:
FreeTDS
e
microsoft/go-mssqldb
.
Um pouco sobre TDS e TLS
Tabular Data Stream (TDS) é um protocolo de camada de aplicação para interagir com o MSSQL Server, funciona sobre TCP (pacotes TLS-, NTLM-, SSPI- são mais frequentemente transmitidos dentro do TDS, mas nem sempre).
Há também uma diferença entre TDS 7.4 e 8.0. No TDS 7.4, o cliente envia uma mensagem Pre-Login sobre suporte (ou sua ausência) de criptografia e outras opções, o servidor responde a ele com opções suportadas ou obrigatórias. Se o cliente e o servidor concordarem que haverá criptografia, o cliente estabelecerá uma handshake TLS dentro do TDS.
Mas no TDS 8.0, a capacidade de
Strict
cryptografia foi adicionada. Quando o cliente e o servidor nos parâmetros de conexão
Encryption
estão definidos como
Strict
, o cliente estabelece imediatamente uma conexão TLS ao se conectar e, somente depois, envia pacotes TDS. Para TDS 8.0, se você usar TLS ≤ 1.2, então CB consideramos da mesma forma, mas se TLS 1.3 for usado, ainda não há informações públicas da Microsoft sobre como o MSSQL considera CBT.
TDS 7.4 suporta TLS não superior a 1.2.
Desmontando pacotes TDS no Wireshark
Para ver nosso CB, precisamos gravar nossa conexão no MSSQL e, em seguida, descriptografá-la.
Para descriptografar a conexão TLS, precisaremos das chaves, que podem ser obtidas pela variável de ambiente
SSLKEYLOGFILE
OpenSSL, GnuTLS. Se esta opção não for adequada para você, você pode tentar usar algum utilitário para capturar essas chaves no nível do kernel, digamos
ecapture
.
O Wireshark não analisa TDS em TLS, ele faz isso apenas para TDS sobre TCP, então escrevemos um dissecador simples que chama TDS:
-- Get the TDS dissector
local tds_dissector = Dissector.get("tds")
-- Create our custom dissector
local tls_to_tds_proto = Proto("tls_to_tds", "TLS to TDS Decrypted Data")
-- Define fields for our protocol
local f_tls_to_tds_data = ProtoField.bytes("tls_to_tds.data", "Decrypted TDS Data")
local f_tls_to_tds_length = ProtoField.uint32("tls_to_tds.length", "Data Length", base.DEC)
-- Add fields to protocol
tls_to_tds_proto.fields = {f_tls_to_tds_data, f_tls_to_tds_length}
-- Main dissector function
function tls_to_tds_proto.dissector(tvb, pinfo, tree)
local length = tvb:len()
if length == 0 then return end
-- Create our protocol tree
local subtree = tree:add(tls_to_tds_proto, tvb())
subtree:add(f_tls_to_tds_length, length)
-- subtree:add(f_tls_to_tds_data, tvb)
-- Parse with TDS dissector
if tds_dissector then
local tds_subtree = subtree:add(tds_dissector, tvb)
tds_dissector:call(tvb, pinfo, tds_subtree)
else
subtree:add_expert_info(PI_PROTOCOL, PI_WARN, "TDS dissector not available")
end
end
myproto_dissector_table = DissectorTable.get("tls.port")
myproto_dissector_table:add(1433, tls_to_tds_proto)
myproto_dissector_table:add(2433, tls_to_tds_proto)
myproto_dissector_table:add_for_decode_as(tls_to_tds_proto)
print("TLS to TDS dissector loaded successfully")
Isso será suficiente para lermos o CB no NTLM, mas para o Kerberos precisamos do arquivo keytab da conta sob a qual o MSSQL é executado.
Como passamos o CB na autenticação
A forma de passar o CBT depende do tipo de autenticação. É importante para nós que o invasor não possa influenciar o CBT nas mensagens do cliente, caso contrário, ele (o invasor) poderá calcular o CBT e substituí-lo. Ou seja, precisamos de uma verificação de integridade para o CBT. O MSSQL usa TLS-unique para gerar CB.
GSS-API
Se quisermos nos autenticar com máquinas unix via Kerberos, provavelmente usaremos GSS-API, que exige que montemos a estrutura correta
gss_channel_bindings_struct
.
em
N
indicaremos o comprimento de M em bytes, e M representará uma string sem um byte NULL no final
tls-unique
: e o valor
verify_data
.
Esta estrutura precisará ser passada como um parâmetro ao chamar
gss_init_sec_context
. O servidor fará o mesmo em seu lado, e as estruturas devem corresponder. Se você entender, dentro da chamada
gss_init_sec_context
após a formação da estrutura CB, um hash MD5 é calculado a partir dela. Deste hash, a chave de sessão e outras flags, o campo Checksum da estrutura Authenticator da mensagem AP-REQ (Authentication Protocol Request) é calculado.
Isso pode ser visto no seguinte commit em
github.com/krb5/krb5: checksum implementation of gss_channel_bindings_struct
.
Se quisermos ver o CB no Kerberos no Wireshark, precisamos olhar para o pacote TDS7-Login, nele o ap-req authenticator com o campo Bnd.
É assim que ficará no Wireshark quando abrirmos a mensagem TDS7-Login:
Mais detalhes sobre GSS-API:
GSS-API Programming Guide: Channel Binding
, sobre o uso de CB em GSS-API:
RFC 5554
.
NTLM
Para NTLM, precisamos montar a estrutura da mesma forma, mas calcular independentemente o hash MD5 a partir dela e colocá-lo no conjunto de atributos (os chamados
AV_PAIR
) NTLM Response na mensagem AUTH.
Mais detalhes sobre
AV_PAIR
:
na documentação do protocolo da Microsoft
.
O MIC garante a integridade do CBT, o invasor não poderá substituir o CBT até que tenha a capacidade de falsificar o MIC, ou seja, até que ele tenha o hash NTLMv2 ou a senha do usuário.
No Wireshark, o CB pode ser visto na mensagem
NTLMSSP_AUTH
, como mostrado na imagem a seguir:
WinSSPI
WinSSPI é uma implementação do GSS-API da Microsoft, que implementa e disponibiliza por meio da API para aplicativos no modo de usuário protocolos como NTLM, Kerberos, Negotiate, Schannel, CredSSP.
Aqui é um pouco diferente. A Microsoft exige que passemos uma estrutura e parâmetros ligeiramente diferentes para
InitializeSecurityContext
, que são diferentes de
gss_init_sec_context
.
Então, a Microsoft exige a estrutura
SEC_CHANNEL_BINDINGS
. Ela difere no fato de que a própria estrutura descreve apenas o tipo, o comprimento e o deslocamento dos dados, essa estrutura precisa ser passada no buffer e, em seguida, os próprios dados. Também definimos os parâmetros
Initiator
e
Acceptor
como 0 e preenchemos
cbApplicationDataLength
e
dwApplicationDataOffset
. No último, indicamos o tamanho da própria estrutura, o que significa que os próprios dados estarão localizados imediatamente após a estrutura.
Você precisa passar esse buffer para a função
InitializeSecurityContext
como parte de pInput, mas apenas após a primeira chamada, porque na primeira chamada pInput deve ser NULL.
Mais detalhes sobre InitializeSecurityContext
.
Conclusão
Analisamos como o mecanismo EPA funciona e adicionamos suporte a ele nas principais bibliotecas para Linux —
PR em FreeTDS
e
PR em microsoft/go-mssqldb
. Agora, nada impede que você ative o modo Required e proteja seus servidores MSSQL contra ataques NTLM relay.
Para sua conveniência — uma tabela resumida de clientes e seu suporte EPA:
Uma observação final: adicionar suporte Kerberos a
go-mssqldb
não foi possível até agora: não há um repositório
krb5
adequado no ecossistema Golang que estaria pronto para integração. Como solução alternativa, você pode considerar a conexão via ODBC.
Obrigado por ler. Desejamos que seu MSSQL permaneça sempre seguro.
Tags:
windows
security
ntlm relay
ntlm
ntlmrelay
epa
mssql
mssqlserver
microsoft
freetds
Hubs:
Blog da Empresa Yandex
Segurança da Informação
Gerenciamento de Produto
Infraestrutura de TI
📤 Compartilhar & Baixar
🧰 Ferramentas recomendadas
Divulgação: alguns links são patrocinados. Podemos receber comissão se você comprar — sem custo extra para você. Só indicamos o que faz sentido para a comunidade.