响应Sucuri安全

A few days ago, a company called Sucuri Security posted a blog article that purported to follow up on a paper called The Most Dangerous Code In The World. This paper, which is relatively well known, talks about how many TLS-using implementations in popular programming languages fail to do appropriate verifications of TLS certificates, leading them to accept invalid certificates.

几天前,一家名为Sucuri Security的公司发布了一篇博客文章,据称该文章是对《世界上最危险的代码》的跟进。 相对知名的本文讨论了流行的编程语言中有多少使用TLS的实现未能对TLS证书进行适当的验证,从而导致它们接受无效的证书。

This is a very real problem with very real consequences, and the state of TLS certificate verification does need to be addressed. As a result, following up on that paper is an excellent idea. However, Sucuri Security’s follow-up on this is really quite alarmingly flawed: it demonstrates a severe failure to understand the tools they are using, including TLS itself. When I pointed out some errors to them they edited the post to “account” for them, but their edits both fail to correctly resolve their problems and also introduced further errors.

这是一个非常实际的问题,具有非常实际的后果,并且确实需要解决TLS证书验证的状态。 结果,跟进该论文是一个好主意。 但是,Sucuri Security在此问题上的后续行动确实存在相当惊人的缺陷:表明无法充分理解他们正在使用的工具(包括TLS本身),这是严重的失败。 当我向他们指出一些错误时,他们将帖子编辑为“帐户”,但是他们的编辑既无法正确解决问题,又引入了更多错误。

Some of their errors lead to them giving advice that is actively user harmful: that is, it discourages users from doing the safe thing. As a result, I’d like to publicly correct their article to ensure that as many users as possible are making themselves as safe as possible.

他们的某些错误导致他们提出对用户有害的建议:也就是说,这会阻止用户做安全的事情。 因此,我想公开更正其文章,以确保尽可能多的用户使自己尽可能安全。

I’m going to focus on their analysis of Python, and specifically of the Requests library of which I am a core maintainer. Someone with a stronger PHP background is welcome to discuss their findings there.

我将专注于他们对Python的分析,尤其是对我是核心维护者的Requests库的分析。 欢迎具有更强PHP背景的人在那里讨论他们的发现。

One final note before I begin: I have archived the page as it appeared at the time of writing this article here. This is the second revision of their article, which means some of their original mistakes cannot be found in it. This Reddit comment includes some quotes I took from the original article, which is the best source I’m able to provide of their original wording.

开始之前的最后一点说明:我已将页面存档,如在此处撰写本文时所显示的那样。 这是他们文章的第二次修订,这意味着找不到其中的一些原始错误。 Reddit的注释中包括我从原始文章中引用的一些引语,这是我能够提供其原始措辞的最佳来源。

Let’s get started.

让我们开始吧。

过期和自签名证书 (Expired and Self-Signed Certificates)

The “findings” of Sucuri Security for Python 2.7.6 appear to be pretty devastating. Their original chart can be found in their blog post.

Sucuri Security for Python 2.7.6的“发现”似乎是毁灭性的。 他们的原始图表可以在他们的博客文章中找到。

Their findings are that Requests does not reject expired or self-signed certificates when used with Python 2.7.6.

他们的发现是与Python 2.7.6一起使用时,Requests 不会拒绝过期或自签名的证书。

That finding is nonsense. The blog post claims that the following URLs were used to test the implementations:

这个发现是胡说八道。 该博客文章声称使用以下URL来测试实现:

This does not dovetail with their actual test code, which notes two expired cert hosts instead of one. The extra one is “https://expired.badssl.com/”, which will become relevant shortly.

这与它们的实际测试代码不符,后者记录了两个已过期的证书主机,而不是一个。 多余的是“ https://expired.badssl.com/”,不久将成为相关内容。

Regardless, the claim in the post as written is that Requests fails to reject the certificates presented by https://qvica1g3-e.quovadisglobal.com and https://self-signed.badssl.com (and based on the tests, possibly also https://expired.badssl.com/).

无论如何,帖子中的书面要求是:请求未能拒绝https://qvica1g3-e.quovadisglobal.com和https://self-signed.badssl.com提交的证书(基于测试,可能是也是https://expired.badssl.com/)。

Python 2.7.6 is an interesting version for them to have chosen to use, because at the time of writing the most recently released version of Python 2.7 is Python 2.7.11. I presume, then, that they chose 2.7.6 because it is the version installed by default on Ubuntu 14.04. That means also that I presume they’re using the version of Requests and OpenSSL installed in Ubuntu 14.04: that is Requests 2.2.1 and OpenSSL 1.0.1f at time of writing.

Python 2.7.6是一个供他们选择使用的有趣版本,因为在编写本文时,最新发行的Python 2.7版本是Python 2.7.11。 然后,我认为他们选择了2.7.6,因为它是Ubuntu 14.04上默认安装的版本。 这也意味着我假设他们正在使用Ubuntu 14.04中安装的Requests和OpenSSL版本 :在撰写本文时即请求2.2.1和OpenSSL 1.0.1f。

A quick check on a freshly installed Ubuntu 14.04 system reveals the first untruth in the post (click to enlarge):

快速检查一下新安装的Ubuntu 14.04系统,发现帖子中的第一个不真实之处(单击放大):

Requests rejecting an expired cert

Given the URL that the post claims to be using to test expired certs, Requests correctly rejects the expired certificate. This is using an old Requests on an old OS with an old OpenSSL. This means that the methodology claimed in the post does not correspond to the one that was actually used by the author. You can very easily test this yourself by spinning up a cloud VM and running the code I ran (the datetime is there to prove when I did the test).

给定帖子声称用于测试过期证书的URL,Requests会正确拒绝过期证书 。 这是在具有旧OpenSSL的旧OS上使用旧请求。 这意味着该帖子中主张的方法与作者实际使用的方法不符。 您可以通过旋转云VM并运行我运行的代码来轻松地自己测试( datetime时间可以证明我何时进行测试)。

However, as we spotted before, there is a second URL that is associated with expired certs. That URL is on the same host as the self-signed certificate that Requests also apparently erroneously allowed: badssl.com.

但是,正如我们之前发现的那样,还有另一个URL与过期的证书相关联。 该URL与“请求”显然也允许的自签名证书位于同一主机上:badssl.com。

Here’s the thing: badssl.com is a great website, but they have one well-known flaw. That flaw is that they serve all their domains from the same server, despite those subdomains having different hostnames. That means that they need to use the Server Name Indication extension to ensure that they present the correct certificate chain for the host the client actually wants to talk to.

事情是这样的:badssl.com是一个很棒的网站,但是它们有一个众所周知的缺陷。 缺点是,尽管这些子域具有不同的主机名,但它们仍从同一服务器为所有域提供服务。 这意味着他们需要使用服务器名称指示扩展名来确保为客户端实际要与之交谈的主机提供正确的证书链。

The general behaviour of all servers is that, if the client does not present the SNI extension, they will serve a fallback chain of the default host on that server. In the case of badssl, that default host is https://badssl.com.

所有服务器的一般行为是,如果客户端未提供SNI扩展名,则它们将在该服务器上为默认主机的后备链提供服务。 如果是badssl,则默认主机为https://badssl.com。

Here’s the thing: if you go to check https://badssl.com’s certificate chain yourself, you’ll notice that it presents a certificate that is valid for *.badssl.com.

事情是这样的:如果您自己检查https://badssl.com的证书链,您会注意到它提供的证书*.badssl.com有效

Fallback cert for badssl.com

That certificate, for *.badssl.com, is obviously valid for expired.badssl.com and self-signed.badssl.com. The kicker is this: the Python SSL library did not expose the hooks to configure SNI before version 2.7.9. Put another way: on older versions of Python, Requests cannot send the SNI extension, which means that BadSSL serves its fallback cert chain which is valid for the host in question. Requests is correctly validating the cert chain: the cert chain is valid!

该证书的*.badssl.com 显然expired.badssl.comself-signed.badssl.com有效。 关键是这样的:Python SSL库未公开2.7.9版之前的钩子来配置SNI。 换句话说,在旧版本的Python上,请求无法发送SNI扩展名,这意味着BadSSL提供了其后备证书链,该链对所讨论的主机有效 。 请正确验证证书链:该证书链是有效的!

This mistake demonstrates a severe misunderstanding on the part of the post authors: they literally never bothered to check whether the cert chain being validate by the tools they were testing is the same as the one they saw in their browser. This is because, as far as I can tell, they didn’t understand that they could possibly be different.

这个错误表明了帖子作者的严重误解:他们从不真正检查由他们测试的工具验证的证书链是否与他们在浏览器中看到的证书链相同。 据我所知,这是因为他们不了解他们可能会有所不同。

Worse, the authors doubled down on this error. You’ll note below that I notified them of their error 3 days before this post went up (that is, on the 1st of April) by commenting on their article. They rejected my comment, presumably because they did not want the criticism on their blog post (and fair enough, they don’t have to host my dissenting opinions on their forum).

更糟糕的是,作者将这个错误加倍了。 您会在下面注意到,在这篇文章发表之前的3天(即4月1日),我通过评论他们的文章通知了他们他们的错误。 他们拒绝了我的评论,大概是因为他们不想在博客上发表批评(而且很公平,他们不必在论坛上发表我的反对意见)。

My rejected comment on their blog post

That comment linked to my Reddit comment, in which I pointed out the following things.

该评论链接到我的Reddit评论 ,其中指出了以下几点。

  1. Since Requests 2.6.0 (released more than one year ago), Requests has emitted warnings when it is unable to fully configure TLS. This warning would have fired on Python 2.7.6, and would have directed the user to a page that instructs them to install additional dependencies.
  2. Since Requests 2.9.0 (released in December), Requests will specifically call out an inability to configure SNI. This warning would also have fired on Python 2.7.6.
  1. 从Requests 2.6.0(一年多以前发布)开始,Requests在无法完全配置TLS时发出警告。 该警告将在Python 2.7.6上触发,并将用户定向到指示他们安装其他依赖项的页面。
  2. 从Requests 2.9.0(于12月发布)开始,Requests将明确指出无法配置SNI。 在Python 2.7.6上也会触发此警告。

They clearly read my comment though, because they changed their post to read this:

他们清楚地阅读了我的评论,因为他们将帖子更改为阅读以下内容:

When using Requests with Python below 2.7.9, you should install additional libraries (ndg-httpsclient and libffi); without these libraries, Requests fails to reject self-signed or expired certificates.

在2.7.9以下版本的Python中使用Requests时,应安装其他库(ndg-httpsclient和libffi); 没有这些库,请求将无法拒绝自签名或过期的证书。

Their revision is still wrong! Firstly, and I cannot stress this enough: Requests does reject both self-signed and expired certificates! The certificate chain they’re testing with is valid for the site in question. The fact that they failed to understand this when directed to a comment that explicitly highlighted that point is really quite mind-boggling to me.

他们的修改仍然错误的 ! 首先,我对此压力不够大:请求的确会拒绝自签名证书和过期证书! 他们正在测试的证书链对所涉及的站点有效 。 当他们针对明确强调了这一点的评论时,他们不理解这一事实,这对我而言确实是令人难以置信的。

Secondly, their list of additional libraries is wrong. The list is pyopenssl, ndg-httpsclient, and pyasn1. That is, their comment is scaremongering and their recommended fix is wrong. This is utterly bizarre.

其次,他们的其他库列表是错误的。 列表是pyopensslndg-httpsclientpyasn1 。 也就是说,他们的评论令人scar目,建议的修复方法是错误的。 这绝对是奇怪的。

And, to head off comments at the pass: let me point out that being unable to do SNI does not introduce a security risk. Requests is still validating certificate chains. If the server presents the wrong chain, one of two things will be true: either the chain will be valid for the domain in question (as with badssl.com) or it will not be. If it is, then Requests should accept it (the chain is valid!). If it isn’t valid, then Requests will refuse to connect. Put another way: in the absence of SNI, Requests fails closed.

并且,一开始就拒绝发表评论:让我指出,无法执行SNI不会带来安全风险。 请求仍在验证证书链。 如果服务器提供了错误的链,则以下两项将是正确的:该链对所讨论的域有效(与badssl.com一样),否则将无效。 如果是,则请求应接受它(链条有效!)。 如果无效,则请求将拒绝连接。 换句话说,在没有SNI的情况下,请求无法关闭

RC4 (RC4)

Really fast: they list Requests as supporting RC4 on Python 2.7.6. Again, their interest seems not to have been piqued by the fact that that support apparently went away in newer Python versions.

速度非常快:他们在Python 2.7.6上将请求列为支持RC4。 同样,他们的兴趣似乎并没有因为在新的Python版本中这种支持显然消失而被激怒。

Older versions of Requests used to defer to the Python implementation for our default list of cipher suites. Newer versions (since 2.6.0) now unconditionally override Python to use our own list. That means you can avoid this weakness by either using a Requests that is no more than 1 year old, or by using a Python that is no more than 1 year old.

较早版本的“请求”用于遵照我们默认的密码套件列表的Python实现。 现在,较新的版本(自2.6.0开始)无条件覆盖Python以使用我们自己的列表。 这意味着您可以通过使用不超过1年的Requests或不超过1年的Python来避免这种弱点。

Note that this is the beginning of a theme: if you don’t upgrade your software, you don’t get much security!

请注意,这是主题的开始:如果不升级软件,则不会获得太大的安全性!

DH480 (DH480)

The next thing they beat up on us for is support for weak Diffie-Hellman keys:

他们为我们而殴打的下一件事是对弱Diffie-Hellman密钥的支持:

There is a third-party library, Requests, that improves the situation for some versions (e.g., 3.3.0 in the test). However, it does not solve all problems: weak DH keys are still allowed.

有一个第三方库Requests可以改善某些版本的情况(例如,测试中的3.3.0)。 但是,它不能解决所有问题:仍然允许使用弱DH密钥。

This criticism is somewhat valid: Requests can in some circumstances accept weak Diffie-Hellman keys. However, the author doesn’t seem intrigued by the idea that Requests’ weak Diffie-Hellman problem goes away in Python 3.4.3 (in fact, they don’t even mention it).

这种批评在某种程度上是正确的:在某些情况下,请求可以接受弱Diffie-Hellman密钥。 但是,作者似乎并不对在Python 3.4.3中Requests的Diffie-Hellman弱问题消失的想法很感兴趣(实际上,他们甚至没有提到它)。

The reason it goes away actually has nothing to do with Python, and everything to do with another very important dependency the author does not talk about at all: OpenSSL. The reason the problem went away in Python 3.4.3 is almost certainly because with that version the author was using OpenSSL 1.0.2 or later. This is because OpenSSL started rejecting weak Diffie-Hellman keys in 1.0.2 without input from callers.

它消失的原因实际上与Python无关,而与作者根本没有谈论的另一个非常重要的依赖关系无关:OpenSSL。 在Python 3.4.3中问题消失的原因几乎可以肯定是因为该版本的作者使用的是OpenSSL 1.0.2或更高版本。 这是因为OpenSSL开始拒绝1.0.2中的弱Diffie-Hellman密钥,而没有来自调用者的输入。

This indicates a further problem with the methodology here: the author did not consider whether changing OpenSSL versions will change the risk profile of the user (hint: it will). This means the author failed to give the best advice that could have been given: specifically, upgrade your OpenSSL. Doing that alone will help fix an enormous number of security problems. The author really should be normalising their OpenSSL version in all these tests, otherwise they may incorrectly attribute security fixes to languages and libraries rather than to OpenSSL versions. This, as a result, also calls into question most of the rest of their results.

这表明这里的方法存在进一步的问题:作者没有考虑更改OpenSSL版本是否会改变用户的风险状况(提示:它将)。 这意味着作者未能给出本该提供的最佳建议:具体地说, 升级您的OpenSSL 。 单独执行此操作将有助于修复大量的安全问题。 作者确实应该在所有这些测试中标准化其OpenSSL版本,否则他们可能会错误地将安全修复程序归因于语言和库,而不是归因于OpenSSL版本。 结果,这也使其余大部分结果受到质疑。

Regardless, it is very difficult for Requests to enforce support for strong Diffie Hellman keys. Both the Python standard library and the Python cryptography library do not expose the bindings for checking the temporary keys used in a TLS connection, which means that we cannot programmatically determine if they’re strong or not. This is not an excuse: we should move mountains to try to expose that support. But it’s an explanation as to why we don’t.

无论如何,请求很难对强力Diffie Hellman密钥实施支持。 Python标准库和Python加密库都没有公开用于检查TLS连接中使用的临时密钥的绑定,这意味着我们无法以编程方式确定它们是否牢固。 这不是借口:我们应该动大山,以尝试获得这种支持。 但这是对我们为什么不这样做的一种解释。

In the short term, if weak DH keys scare you, upgrade your OpenSSL.

在短期内,如果弱DH密钥吓到您,请升级您的OpenSSL

吊销 (Revocation)

The other thing the author gets high and mighty about is checking for certificate revocation. As they say in their post:

作者非常崇高的另一件事是检查证书吊销。 正如他们在帖子中所说的:

PHP, Python, and Go perform no revocation checks by default, neither does the cURL library. If the certificate was compromised and revoked by the owner, you will never know about it.

默认情况下,PHP,Python和Go不会执行吊销检查,cURL库也不会执行吊销检查。 如果证书被所有者盗用并吊销了,您将一无所知。

Yup, that’s true. And I’d argue it’s a good thing. Rather than go into the problems myself, I will direct you to Adam Langley of Google, who has not once, not twice, but three times written about the uselessness of revocation checking.

是的,是的。 我认为这是一件好事。 我不会直接自己解决问题,而是将您带到Google的亚当·兰利,他没有一次不是两次 ,但有3次关于吊销检查的无用性。

The TL;DR is that, in the absence of OCSP Must Staple, revocation checking is next-to-useless: an attacker capable of mounting a MITM attack on your connection is also capable of DoSing your revocation check. Requests is open to working with the OpenSSL team to implement OCSP Must Staple, but until OpenSSL includes the functionality it would be extremely difficult for us to implement and enforce.

TL; DR是,在没有OCSP Must Staple的情况下,撤销检查几乎是无用的:能够在您的连接上发起MITM攻击的攻击者也能够对您的撤销检查进行DoSing。 请求可以与OpenSSL团队一起实施OCSP Must Staple,但是在OpenSSL包含该功能之前,对我们来说,实施和强制实施将极为困难。

漏洞披露 (Vulnerability Disclosure)

A final point. While I’ve demonstrated above that most of the problems the author believes he has found in Requests are not present, that doesn’t change the fact that the author believed them to be true. That makes it all the more galling that the author did not report a single vulnerability to the Requests team.

最后一点。 尽管我已经在上面证明了作者认为他在请求中发现的大多数问题都不存在,但这并不能改变作者认为它们是真实的事实。 这使得作者没有向Requests团队报告单个漏洞的情况更加令人胆怯。

That is, the author believed that there were versions of Requests in the wild that did not validate expired or self-signed certs, and rather than inform the Requests team of that using our documented reporting policy instead decided to take to the web to take some pot shots at us. To be clear: we received no emails, bug reports, tweets, or any other form of communication from the author either before or after the publication of their post.

也就是说,作者认为有些野蛮的Requests版本无法验证过期或自签名证书,因此他们没有使用我们的书面报告政策告知Requests团队而是决定上网进行一些向我们开枪。 需要说明的是:在帖子发表之前或之后,我们都没有收到作者的电子邮件,错误报告,推文或任何其他形式的通讯。

This kind of behaviour is the very worst kind of opportunism. If the problems the author believed existed actually did exist, the public disclosure of those problems would have put our users at risk for as long as it would have taken us and our distribution partners to provide them with fixes. Such an action is at best naive, and at worst actively callous.

这种行为是最糟糕的机会主义。 如果作者认为存在的问题确实存在,那么只要我们和我们的发行伙伴向他们提供修复程序,对这些问题的公开披露将使我们的用户面临风险。 这样的行动充其量是天真的,而在最坏的情况下是积极的。

摘要 (Summary)

The public disclosure of (admittedly nonexistent) vulnerabilities in Requests, combined with the demonstrable lack of understanding shown in this post, does not paint Sucuri Security in a good light. The most charitable explanation of their behaviour is that they fail to understand both TLS and good security reporting policy, which is a less than desirable pair of traits in a website security company. Less charitably, this feels like an opportunistic lunge for publicity by writing an alarmist piece about cert valdation with no consideration for actual user security or even the minor detail of being correct.

公开披露请求中的漏洞(公认不存在),以及此帖子中所示的明显缺乏了解,并不能很好地说明Sucuri Security。 关于他们行为的最慈善的解释是,他们既不了解TLS,又不了解良好的安全报告策略,这是网站安全公司所不希望的。 不那么慈善,这感觉像是机会主义的宣传,因为写了一篇关于证书认证的警告文章,没有考虑实际用户的安全性,甚至没有考虑正确性的次要细节。

This doesn’t say anything good about Sucuri Security.

这对Sucuri Security来说并没有什么好说的。

翻译自: https://www.pybloggers.com/2016/04/in-response-to-sucuri-security/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值