在.NET中禁用SSL回退并仅使用TLS进行出站连接?(Poodle缓解)

111
我正在尝试减轻我们对Poodle SSL 3.0 Fallback攻击的漏洞。我们的管理员已经开始禁用SSL,转而使用TLS来处理服务器入站连接。我们还建议我们的团队在其Web浏览器中禁用SSL。现在我正在查看我们的.NET代码库,该代码库通过System.Net.HttpWebRequest与各种服务启动HTTPS连接。我认为,如果它们允许从TLS到SSL的回退,这些连接可能会受到MITM攻击的威胁。到目前为止,这是全新的漏洞,因此我尚未看到Microsoft关于如何在.NET中减轻这种漏洞的任何指导。请有人再次检查以验证我的正确性。
  1. System.Net.Security.SslStream类用于.NET中的安全通信,其允许的协议是通过System.Net.ServicePointManager.SecurityProtocol属性在每个AppDomain中进行全局设置的。

  2. .NET 4.5中此属性的默认值为Ssl3 | Tls(虽然我找不到支持这一说法的文档)。SecurityProtocolType是一个带有Flags属性的枚举,因此它是这两个值的按位OR。您可以使用以下代码在您的环境中检查此内容:

    Console.WriteLine(System.Net.ServicePointManager.SecurityProtocol.ToString());

  3. 在应用程序中发起任何连接之前,应将其更改为Tls或Tls12:

    System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls;

  4. 重要提示:由于该属性支持多个按位标志,因此我认为在握手期间,SslStream不会自动回退到其他未指定的协议。否则,支持多个标志的意义何在?

TLS 1.0与1.1/1.2更新:

根据Google安全专家Adam Langley的说法,如果没有正确实现,TLS 1.0后来被发现容易受到POODLE攻击, 因此您应该考虑完全转向TLS 1.2。

.NET Framework 4.7及以上版本更新:

正如Von Lemongargle教授所暗示的那样,在.NET Framework 4.7版本及以上,无需使用此技巧,因为默认设置将允许操作系统选择最安全的TLS协议版本。有关更多信息,请参见使用.NET Framework进行传输层安全性(TLS)的最佳实践


1
关于上面的第二点:请参见https://referencesource.microsoft.com/#System/net/System/Net/SecureProtocols/SslEnumTypes.cs,c7e989c42228556a SslProtocols“Default = Ssl3 | Tls”。 - Dai Bok
1
@Dai Bok 现在的默认选项是 SystemDefault。请参考 https://learn.microsoft.com/en-us/dotnet/api/system.net.securityprotocoltype?view=netframework-4.8。 - MarwaAhmad
如果是这种情况,我的链接中的源代码参考不反映默认值(48 | 192)。如果设置为无(0),它应该恢复到系统默认值。这对我来说有点可疑,我可能会在进行更改之前进行测试,因为这可能会导致错误配置...并关闭错误的.NET框架上的协议... - Dai Bok
6个回答

140

我们正在做同样的事情。要仅支持TLS 1.2而不支持SSL协议,可以这样做:

System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

SecurityProtocolType.Tls只包括TLS 1.0,不包括所有的TLS版本。

另外提一下:如果您想检查您的网站是否允许SSL连接,可以在此处进行检查(我认为这不会受到上述设置的影响,我们必须编辑注册表以强制IIS对传入连接使用TLS): https://www.ssllabs.com/ssltest/index.html

要在IIS中禁用SSL 2.0和3.0,请参见此页面:https://www.sslshopper.com/article-how-to-disable-ssl-2.0-in-iis-7.html


1
为了让其他人受益,您提到的注册表编辑也可以通过以下步骤完成:http://serverfault.com/a/637263/98656。在Windows Server 2003到2012 R2中,协议由HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\Schannel\Protocols下的标志控制。要禁用SSLv3,请在上述位置创建一个名为“SSL 3.0”的子键,在其中创建一个名为“Server”的子键,在其中创建一个名为“Enabled”的DWORD值,并将其设置为0。您还应以同样的方式禁用SSL 2.0。 - Jordan Rieger
4
如果你只是在服务器上运行简单的网站,并且只接受浏览器发起的入站连接,那么修改注册表就足够了。只有当你要从.NET代码发起outbound连接(例如从自定义代码连接到Web服务或API)时,才需要设置System.Net.ServicePointManager.SecurityProtocol。请注意,翻译后保持原意并使得内容更加通俗易懂,不会添加任何解释或其他信息。 - Jordan Rieger
7
注意:似乎SecurityProtocolType.Tls11SecurityProtocolType.Tls12枚举值仅适用于ASP.net 4.5及以上版本。如果TLS 1.0被淘汰了,不确定在运行2.0的旧代码库上我们需要做什么。 - Sam
看起来这种方法仍然启用了SSLv3回退。 - Kraang Prime
1
@AnishV 你需要将这行代码放在应用程序初始化之前,任何启动出站 SSL/TLS 连接的代码之前。 - Jordan Rieger
显示剩余17条评论

25
@Eddie Loeffen的答案似乎是这个问题中最受欢迎的答案,但它会产生一些不良的长期影响。如果您查看System.Net.ServicePointManager.SecurityProtocol的文档页面这里,备注部分暗示协商阶段应该只涉及到此事(强制协议在未来也是不好的实践,因为TLS 1.2也将被破解)。但是,如果他们协商不了呢,我们就无法得到这个答案。
经过研究,发现在协商阶段需要使用ALPN协议才能获得TLS1.2。我们以此为起点,尝试使用更新版本的.NET Framework来确定支持的开始位置。我们发现.NET 4.5.2不支持协商到TLS 1.2,但.NET 4.6支持。
因此,即使强制使用TLS1.2现在可以完成工作,我建议您升级到.NET 4.6。由于这是2016年6月PCI DSS问题,时间紧迫,但新的框架是更好的答案。
更新: 根据评论,我构建了这个:
ServicePointManager.SecurityProtocol = 0;    
foreach (SecurityProtocolType protocol in SecurityProtocolType.GetValues(typeof(SecurityProtocolType)))
    {
        switch (protocol)
        {
            case SecurityProtocolType.Ssl3:
            case SecurityProtocolType.Tls:
            case SecurityProtocolType.Tls11:
                break;
            default:
                ServicePointManager.SecurityProtocol |= protocol;
            break;
        }
    }
为了验证概念,我将SSL3和TLS1.2进行或运算,并运行代码以连接到仅支持TLS 1.0和TLS 1.2(1.1已禁用)的服务器。使用进行或运算的协议看起来可以正常连接。但如果改为SSL3和TLS 1.1,则无法连接。我的验证使用System.Net中的HttpWebRequest,并只调用GetResponse()。例如,我尝试过以下内容但失败了:
        HttpWebRequest request = WebRequest.Create("https://www.contoso.com/my/web/resource") as HttpWebRequest;
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls11;
        request.GetResponse();

虽然这样做是有效的:

        HttpWebRequest request = WebRequest.Create("https://www.contoso.com/my/web/resource") as HttpWebRequest;
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls12;
        request.GetResponse();

这种方法的优点在于,与强制使用TLS 1.2相比,如果.NET Framework升级,枚举中有更多条目,它们将被支持并保持原样。与仅使用.NET 4.6相比,它的缺点在于4.6使用ALPN,如果未指定限制,则应支持新协议。

编辑于2019年4月29日 - Microsoft在去年发表了这篇文章。其中提供了对各个版本的.NET Framework如何进行推荐的概述。


3
有趣。看起来随着MSDN转移到“.NET Framework **(当前版本)”,文档页面已经更新,现在解释说“故意没有列出此属性的默认值”。也许一个更加面向未来的被接受的回答版本是,首先查询该属性以检索允许的协议列表,然后删除您的应用程序认为不安全的协议(即小于TLS 1.2的任何协议),而不是只显式添加您认为安全的协议。这将不会排除将来出现的新版本。 - Jordan Rieger
能让程序从系统生成的列表中删除协议会很好,但我并没有看到在当前API中实现这一点的方法。唯一的选择似乎是强制特定的协议,但我不知道为什么有人会希望这样做。不幸的是,没有ALPN支持,这似乎是让TLS1.2连接正常工作的唯一方法。 - Prof Von Lemongargle
1
应该可以通过所有的SecurityProtocolType枚举进行循环,查看哪些在ServicePointManager.SecurityProtocol标志枚举中存在(它是所有已设置标志的逻辑OR,因此可以用AND测试每一个),然后构建一个新的列表,删除不需要的。然后将它们合并为一个枚举,并使用该枚举设置属性。 - Jordan Rieger
7
有没有其他人觉得.NET不会自动协商其能力范围内的最高协议版本,这很疯狂?! - Raman
@Raman 我知道这条评论有点老,但确实,为什么会这样呢?当v1.3及以后版本发布时,我们如何避免追踪每个硬编码TLS版本的实例呢? - k3davis
显示剩余2条评论

7

我不得不将整数转换为其等价的类型,以便规避因为我仍然使用.NET 4.0而导致的问题。

System.Net.ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;
/* Note the property type  
   [System.Flags]
   public enum SecurityProtocolType
   {
     Ssl3 = 48,
     Tls = 192,
     Tls11 = 768,
     Tls12 = 3072,
   } 
*/

这个可以工作,但是.NET 4.0不支持TLS 1.2,然而.NET 4.5及更高版本支持TLS 1.2。因此,您可以将此代码添加到您的.NET 4.0应用程序并构建它,或者进行按位OR以添加对TLS 1.2协商的支持,但您需要在.NET 4.5或更高版本上部署该代码。https://blogs.perficient.com/microsoft/2016/04/tsl-1-2-and-net-support/ - rboy
此外,请注意,.NET 4.0代码可以在包括.NET 4.5和.NET 4.6在内的更高版本的.NET上正常运行。参见https://dev59.com/55Dea4cB1Zd3GeqPcoTC。 - rboy
整数转换适用于发送TLS 1.2。在.NET 4.0中,Tls12枚举值不存在。 - CZahrobsky
两个不同的点。请看我的评论。你可以放置这个值,但如果你的运行时 .NET 版本是 4.0,它将失败。你可以使用此标志进行编译,但你的运行时 .NET 版本需要是 4.5 或更高版本,因为 .NET 4.0 上的基础组件不支持 TLS 1.2 所需的密码(请参见链接)。 - rboy
2
有几个针对旧版.NET运行时的热修复程序可添加TLS 1.2支持,例如https://support.microsoft.com/en-us/help/3154518/support-for-tls-system-default-versions-included-in-the--net-framework。CZahrobsky也可能在Win10秋季创作者更新中使用了此修补程序。 - silent tone

6

@watson

在Windows表单中,可以在类的顶部放置以下内容:

  static void Main(string[] args)
    {
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
       //other stuff here
    }

由于Windows是单线程的,因此您只需要将它放在服务调用的正上方(因为无法确定您将在哪个线程上)。如果这是一个服务,那就是你所需要的全部了。

using System.Security.Principal 

还需要。


5

如果您想了解.NET支持哪些协议,可以尝试使用HttpClient访问https://www.howsmyssl.com/

// set proxy if you need to
// WebRequest.DefaultWebProxy = new WebProxy("http://localhost:3128");

File.WriteAllText("howsmyssl-httpclient.html", new HttpClient().GetStringAsync("https://www.howsmyssl.com").Result);

// alternative using WebClient for older framework versions
// new WebClient().DownloadFile("https://www.howsmyssl.com/", "howsmyssl-webclient.html");

结果令人担忧:
您的客户端正在使用非常古老的TLS 1.0版本,可能容易受到BEAST攻击,并且没有最佳密码套件可用。添加像AES-GCM和SHA256来代替MD5-SHA-1等更多现代密码套件是不可用的。正如Eddie在上面解释的那样,您可以手动启用更好的协议。
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11; 

我不知道为什么它默认使用了不安全的协议。这似乎是一个糟糕的设置选择,等同于一个重大的安全漏洞(我敢打赌很多应用程序不会更改默认设置)。我们如何报告此问题?

1
我发现最简单的解决方法是添加两个注册表项,如下所示(在具有管理员权限的命令提示符中运行):
reg add HKLM\SOFTWARE\Microsoft\.NETFramework\v4.0.30319 /v SchUseStrongCrypto /t REG_DWORD /d 1 /reg:32

reg add HKLM\SOFTWARE\Microsoft\.NETFramework\v4.0.30319 /v SchUseStrongCrypto /t REG_DWORD /d 1 /reg:64

这些条目似乎会影响.NET CLR在作为客户端建立安全连接时选择协议的方式。
这里有关于此注册表条目的更多信息:

https://learn.microsoft.com/en-us/security-updates/SecurityAdvisories/2015/2960358#suggested-actions

不仅更简单,而且假设它适用于您的情况,比基于代码的解决方案更加健壮,后者需要开发人员跟踪协议和开发并更新所有相关代码。希望类似的环境更改可以针对TLS 1.3及更高版本进行,只要.NET保持足够愚笨,不会自动选择最高可用协议。
注意:尽管根据上面的文章,这只应禁用RC4,人们不认为这会改变.NET客户端是否允许使用TLS1.2+,但由于某种原因确实会产生这种影响。
注意:如@Jordan Rieger在评论中指出的那样,这不是POODLE的解决方案,因为它没有禁用旧协议,而只是允许客户端使用新协议(例如,当修补过的服务器禁用旧协议时)。然而,通过MITM攻击,显然受损的服务器将向客户端提供旧协议,客户端将愉快地使用它。
TODO:尝试使用这些注册表项禁用客户端使用TLS1.0和TLS1.1,但我不知道.NET http客户端库是否尊重这些设置。

https://learn.microsoft.com/en-us/windows-server/security/tls/tls-registry-settings#tls-10

https://learn.microsoft.com/en-us/windows-server/security/tls/tls-registry-settings#tls-11


注册表设置作为代码的替代方案会很好,但这些设置是否真正禁用了TLS 1.0和1.1,只允许客户端使用TLS 1.2及以上进行连接?根据链接,它似乎只禁用了TLS中的RC4。我认为Poodle攻击比那更广泛。 - Jordan Rieger
@JordanRieger 这些注册表条目允许 .NET 客户端连接到已禁用较旧协议以缓解 POODLE 的服务器。如果没有这些条目,客户端将会抛出错误,因为.NET客户端傻乎乎地坚持使用较旧的协议,即使服务器要求使用更新的协议。如果您的服务器仍允许使用旧协议进行连接,则所有赌注都无效。理论上来说,旧协议应该被禁用。我想知道是否可以使用相同的注册表条目在客户端上禁用这些协议(例如 https://www.nartac.com/Products/IISCrypto)? - Raman
在nartac操作的注册表条目中,有单独的设置用于(充当)客户端和(充当)服务器。我不记得它们的界面是否有区别。你知道哪些版本的.net支持这个注册表修改吗? - Prof Von Lemongargle
@Raman 我提出问题的目的是为了在客户端连接到您无法控制的服务器时减轻POODLE漏洞。该漏洞将允许中间人攻击者将协议降级到旧的TLS或SSL版本,从而可以被黑客攻击。仅禁用RC4是不够的。这些注册表设置可能对某些情况有用,但并不适用于我提出的场景。 - Jordan Rieger
1
@JordanRieger 同意。我更新了文本,并加入了一些额外的观察和想法。 - Raman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接