热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

GolangTLS双向认证中的DoS漏洞深度解析(CVE-2018-16875)

如果程序使用Go语言编写并涉及单向或双向TLS认证,可能会遭受CPU拒绝服务攻击(DoS)。本文深入分析了CVE-2018-16875漏洞,探讨其成因、影响及防范措施,为开发者提供全面的安全指导。

Golang TLS双向身份认证DoS漏洞分析(CVE-2018-16875)

一、前言

如果程序源代码使用 Go 语言编写,并且用到了单向或者双向TLS认证,那么就容易受到CPU拒绝服务(DoS)攻击。Go语言的 crypto/x509 标准库中的校验算法存在逻辑缺陷,攻击者可以精心构造输入数据,使校验算法在尝试验证客户端提供的TLS证书链时占用所有可用的CPU资源。

为了保护正常服务,大家应立即 升级 到G0 v1.10.6、v1.11.3或者更新版本。

二、研究背景

42Crunch的API Security平台后端采用的是微服务架构,而微服务使用Go语言编写。微服务之间通过 gRPC 相互通信,并且部署了REST API网关用于外部调用。为了确保安全性,我们遵循了“TLS everywhere”(处处部署TLS)原则,广泛采用了TLS双向认证机制。

Go的标准库原生支持SSL/TLS认证,也支持大量与连接处理、验证、身份认证等方面有关的x509和TLS原语。这种原生支持可以避免外部依赖,使用标准化的、经过精心维护和审核的TLS库也能降低安全风险。

因此42Crunch很有可能受此TLS漏洞影响,需要理解漏洞原理,保证42Crunch平台的安全性。

42Crunch 安全团队针细致分析了该CVE,如下文所示。

三、问题描述

这个DoS问题最早由Netflixx发现,Golang在issue跟踪日志中提到:

crypto/x509 包负责解析并验证X.509编码的密钥和证书,正常情况下会占用一定的资源来处理攻击者提供的证书链。

crypto/x509 包并没有限制验证每个证书链时所分配的工作量,攻击者有可能构造恶意输入,导致CPU拒绝服务。Go TLS服务器在接受客户端证书或者TLS客户端在验证证书时会受此漏洞影响。

该漏洞具体位于 crypto/x509 Certificate.Verify() 函数的调用路径中,该函数负责证书认证及验证。

四、漏洞分析

背景知识

为了便于漏洞分析,我们举个简单的例子:TLS客户端连接至TLS服务器,服务器验证客户端证书。

TLS服务器在 8080 端口监听TLS客户端请求,验证客户端证书是否由证书颁发机构(CA)颁发:

caPool := x509.NewCertPool()
ok := caPool.AppendCertsFromPEM(caCert)
if !ok {
        panic(errors.New("could not add to CA pool"))
}

tlsConfig := &tls.Config{
        ClientCAs:  caPool,
        ClientAuth: tls.RequireAndVerifyClientCert,
}

//tlsConfig.BuildNameToCertificate()
server := &http.Server{
        Addr:      ":8080",
        TLSConfig: tlsConfig,
}

server.ListenAndServeTLS(certWeb, keyWeb)

在标准的TLS验证场景中,TLS客户端会连接到TLS服务器的 8080 端口,然后向服务器提供证书的“trust chain”(信任链),其中包括客户端证书、root CA证书以及中间所有CA证书。TLS服务器处理TLS握手,验证客户端证书,检查客户端是否可信(即客户端证书是否由服务器信任的CA签名)。通常TLS握手过程如下图所示:

Golang TLS双向身份认证DoS漏洞分析(CVE-2018-16875)

分析Go语言的 crypto/x509 库,最终我们会进入 x509/tls/handshake_server.go:doFullHandshake() 函数代码段:

...
if c.config.ClientAuth >= RequestClientCert {
        if certMsg, ok = msg.(*certificateMsg); !ok {
                c.sendAlert(alertUnexpectedMessage)
                return unexpectedMessageError(certMsg, msg)
        }
        hs.finishedHash.Write(certMsg.marshal())

        if len(certMsg.certificates) == 0 {
                // The client didn't actually send a certificate
                switch c.config.ClientAuth {
                case RequireAnyClientCert, RequireAndVerifyClientCert:
                        c.sendAlert(alertBadCertificate)
                        return errors.New("tls: client didn't provide a certificate")
                }
        }

        pub, err = hs.processCertsFromClient(certMsg.certificates)
        if err != nil {
                return err
        }

        msg, err = c.readHandshake()
        if err != nil {
                return err
        }
}
...

根据代码,服务器会处理收到的客户端证书,然后调用 x509/tls/handshake_server.go:processCertsFromClient() 函数。如果需要验证客户端证书,服务器就会创建一个 VerifyOptions 结构,其中包含如下信息:

  • root CA池,即已配置的一系列可信CA(由服务器控制),用来验证客户端证书
  • 中间CA池,即服务端收到的一系列中间CA(由客户端控制)
  • 已签名的客户端证书(由客户端控制)
  • 其他字段(可选项)
if c.config.ClientAuth >= VerifyClientCertIfGiven && len(certs) > 0 {
        opts := x509.VerifyOptions{
                Roots:         c.config.ClientCAs,
                CurrentTime:   c.config.time(),
                Intermediates: x509.NewCertPool(),
                KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
        }

        for _, cert := range certs[1:] {
                opts.Intermediates.AddCert(cert)
        }

        chains, err := certs[0].Verify(opts)
        if err != nil {
                c.sendAlert(alertBadCertificate)
                return nil, errors.New("tls: failed to verify client's certificate: " + err.Error())
        }

        c.verifiedChains = chains
}

为了澄清问题机理,我们需要理解服务端如何管理证书池,以便通过高效的方式来验证证书。证书池实际上就是一个证书列表,可以根据实际需求通过3种不同的方式来访问。一种访问方式如下图所示:池中证书可以通过索引数组(这里为 Certs )来访问,以 CN , IssuerName , SubjectKeyId 字段作为哈希字段。

Golang TLS双向身份认证DoS漏洞分析(CVE-2018-16875)

验证过程

服务端使用 VerifyOptions 参数调用 Verify() 函数来处理客户端证书(即 chain:certs[0] 中的第一个证书)。

然后 Verify() 会根据客户端提供的证书链来处理待验证的客户端证书,但首先需要使用 buildChains() 函数建立并检查整条验证链:

var candidateChains [][]*Certificate
if opts.Roots.contains(c) {
        candidateChains = append(candidateChains, []*Certificate{c})
} else {
        if candidateChains, err = c.buildChains(make(map[int][][]*Certificate), []*Certificate{c}, &opts); err != nil {
                return nil, err
        }
}

buildChains() 函数会依次调用占用CPU资源的一些函数,递归处理这条链上的每个元素。

buildChains() 函数依赖于 findVerifiedParents() 函数,而后者可以通过 IssuerName 或者 AuthorityKeyId 映射访问证书池,识别上级证书,,然后返回候选证书索引,以便后续根据客户端控制的证书池来验证该证书。

在正常情况下,程序会提取 IssuerNameAuthorityKeyId ,并且认为这些值为唯一值,只会返回一个待验证的证书:

func (s *CertPool) findVerifiedParents(cert *Certificate) (parents []int, errCert *Certificate, err error) {
    if s == nil {
        return
    }
    var candidates []int

    if len(cert.AuthorityKeyId) > 0 {
        candidates = s.bySubjectKeyId[string(cert.AuthorityKeyId)]
    }
    if len(candidates) == 0 {
        candidates = s.byName[string(cert.RawIssuer)]
    }

    for _, c := range candidates {
        if err = cert.CheckSignatureFrom(s.certs[c]); err == nil {
            parents = append(parents, c)
        } else {
            errCert = s.certs[c]
        }
    }

    return
}

buildChains() 函数会在客户端发给TLS服务器的整条证书链上执行如下操作:

  • 在(服务端)root CA池上调用 findVerifiedParents(client_certificate) ,查找待验证证书的签发机构(判断是否为root CA),然后根据 AuthorityKeyId (如果不为 nil )或者原始的issuer值(如果为 nil )检查所有找到的证书的签名
  • 在(客户端提供的)中间CA池上调用 findVerifiedParents(client_certificate) ,查找已验证证书的签发机构(判断是否为中间CA),然后根据 AuthorityKeyId (如果不为 nil )或者原始的 issuer 值(如果为 nil )检查所有找到的证书的签名
  • 获取上一级中间签名节点
  • 在新发现的中间节点上调用 buildChains() ,然后重复前面描述的签名检查过程

Golang TLS双向身份认证DoS漏洞分析(CVE-2018-16875)

DoS攻击

攻击者可以构造一种非预期场景,其中所有的中间CA证书使用的都是同一个名称,并且 AuthKeyId 值为 nil ,这样当调用 buildChains()findVerifiedParent() 函数时,就会造成CPU DoS攻击效果。 findVerifiedParent() 函数会返回与该名称匹配的所有证书(这里返回的是整个证书池),然后检查所有证书的签名。检查完毕后,会再次递归调用 buildchains() 函数处理找到的上一级证书,最后处理到root CA为止。每一次检查过程实际上都会处理整个中间CA池,因此单单一个TLS连接就会耗尽所有可用的CPU资源。

Golang TLS双向身份认证DoS漏洞分析(CVE-2018-16875)

五、漏洞影响

攻击者可以精心构造一条证书链,使客户端证书校验过程耗尽服务端所有CPU资源,降低目标主机响应速度。只需要1个连接就能导致这种攻击效果。根据Go的调度程序规则,只有两个CPU核心会受到影响,CPU使用率达到100%,攻击者可以创建新连接,强制调度程序分配更多资源来校验签名,最终导致目标服务或目标主机无响应。

六、缓解措施

Go语言社区已经通过 如下措施 修复该问题:

findVerifiedParent()

如果向修复该漏洞,请立即 升级 到G0 v1.10.6、v1.11.3或者更新版本。


以上所述就是小编给大家介绍的《Golang TLS双向身份认证DoS漏洞分析(CVE-2018-16875)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 我们 的支持!


推荐阅读
  • 全面解析运维监控:白盒与黑盒监控及四大黄金指标
    本文深入探讨了白盒和黑盒监控的概念,以及它们在系统监控中的应用。通过详细分析基础监控和业务监控的不同采集方法,结合四个黄金指标的解读,帮助读者更好地理解和实施有效的监控策略。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 对于许多初学者而言,遇到总线错误(bus error)或段错误(segmentation fault/core dump)是极其令人困扰的。本文详细探讨了这两种错误的成因、表现形式及解决方法,并提供了实用的调试技巧。 ... [详细]
  • 本文深入探讨了MySQL中常见的面试问题,包括事务隔离级别、存储引擎选择、索引结构及优化等关键知识点。通过详细解析,帮助读者在面对BAT等大厂面试时更加从容。 ... [详细]
  • 解决TensorFlow CPU版本安装中的依赖问题
    本文记录了在安装CPU版本的TensorFlow过程中遇到的依赖问题及解决方案,特别是numpy版本不匹配和动态链接库(DLL)错误。通过详细的步骤说明和专业建议,帮助读者顺利安装并使用TensorFlow。 ... [详细]
  • 本文详细介绍了Grand Central Dispatch (GCD) 的核心概念和使用方法,探讨了任务队列、同步与异步执行以及常见的死锁问题。通过具体示例和代码片段,帮助开发者更好地理解和应用GCD进行多线程开发。 ... [详细]
  • 本月初,我们为大家推荐了一系列精选书单,助力大家提升技术水平。月底,我们将介绍几位行业大牛,帮助大家找到人生导师。InfoQ一直致力于为用户提供有价值的资源和支持。 ... [详细]
  • ElasticSearch 集群监控与优化
    本文详细介绍了如何有效地监控 ElasticSearch 集群,涵盖了关键性能指标、集群健康状况、统计信息以及内存和垃圾回收的监控方法。 ... [详细]
  • vivo Y5s配备了联发科Helio P65八核处理器,这款处理器采用12纳米工艺制造,具备两颗高性能Cortex-A75核心和六颗高效能Cortex-A55核心。此外,它还集成了先进的图像处理单元和语音唤醒功能,为用户提供卓越的性能体验。 ... [详细]
  • 深入解析 Android IPC 中的 Messenger 机制
    本文详细介绍了 Android 中基于消息传递的进程间通信(IPC)机制——Messenger。通过实例和源码分析,帮助开发者更好地理解和使用这一高效的通信工具。 ... [详细]
  • 优化Flask应用的并发处理:解决Mysql连接过多问题
    本文探讨了在Flask应用中通过优化后端架构来应对高并发请求,特别是针对Mysql 'too many connections' 错误的解决方案。我们将介绍如何利用Redis缓存、Gunicorn多进程和Celery异步任务队列来提升系统的性能和稳定性。 ... [详细]
  • 本文介绍了如何在PHP Magento模型中自定义主键,避免使用默认的自动递增主键,并提供了解决方案和代码示例。 ... [详细]
  • 本文介绍了一个基于 Java SpringMVC 和 SSM 框架的综合系统,涵盖了操作日志记录、文件管理、头像编辑、权限控制、以及多种技术集成如 Shiro、Redis 等,旨在提供一个高效且功能丰富的开发平台。 ... [详细]
  • 深入剖析JVM垃圾回收机制
    本文详细探讨了Java虚拟机(JVM)中的垃圾回收机制,包括其意义、对象判定方法、引用类型、常见垃圾收集算法以及各种垃圾收集器的特点和工作原理。通过理解这些内容,开发人员可以更好地优化内存管理和程序性能。 ... [详细]
  • 本文详细对比了Windows 7家庭高级版与旗舰版之间的主要区别,包括技术支持期限、硬件兼容性及特色功能等方面。 ... [详细]
author-avatar
dujiaolianglong
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有