代客击败OS X钥匙串访问控制列表零日漏洞

我们如何使用测试驱动的开发来复制攻击向量并针对它加强代码

抬起头,我们已经搬家了! 如果您想继续了解Square的最新技术内容,请访问我们的新家https://developer.squareup.com/blog

Dan Federman 撰写

世界着火了

6月17日,The Register报告说,iOS和OS X钥匙串中存在一个零日漏洞,该漏洞破坏了钥匙串中存储的安全数据。 该文章声称,写入到钥匙串的数据可以被恶意应用程序读取。 在Square,我们编写可转移资金的iOS代码。 安全始终是我们的头等大事,我们非常重视这些要求。 如果这篇文章的主张是正确的,那么世界就着火了。

在太平洋标准时间上午8点过后,当我们意识到该漏洞时,我们立即打开了描述该攻击的文件。 我们发现该攻击的工作方式如下:

  1. 恶意应用会在钥匙串中搜索良性应用编写的密钥。 恶意应用程序可以看到密钥存在,但无法读取关联的秘密值。
  2. 恶意应用会删除良性应用编写的密钥。
  3. 然后,恶意应用将密钥重新添加到不包含值的钥匙串中,并将自身和良性应用添加到密钥的访问控制列表(ACL)中,从而允许恶意应用读取良性应用随后写入这些密钥的所有机密信息。

在这一点上,我们可以松一口气:访问控制列表仅存在于Mac OS X上,而不存在于iOS上,因此尽管出现了头条新闻,但我们的应用程序并不容易受到攻击。 但是,两周前,我们刚刚开放了开源的Valet,这是一个跨平台的钥匙链包装器,其OS X组件很容易受到攻击。 这是我们无法接受的,因此我们加强了代客抵御这种攻击的能力。

有毒的工具

Apple的钥匙串仅提供三种用于更新钥匙串的工具:添加,更新和删除。 但是我们现在知道在OS X上,更新本质上是不安全的。 我们不能相信钥匙串中的现有钥匙没有受损的访问控制列表。 因此,我们仅需添加和删除即可。

解决方案似乎很明显。 更改钥匙串中某项的值时,与其更新现有的钥匙串条目,不如删除现有的项然后添加一个新项。

但是Apple的文档非常清楚:请勿删除然后添加-始终更新。 为什么? 因为“ [删除钥匙串项目时,您将丢失用户或其他应用程序添加的所有访问控制和信任设置。”虽然听起来不祥,但这正是我们想要的效果。 更好的是,此警告不适用于Valet,后者使用安全的共享访问组而不是ACL在OS X和iOS上共享钥匙串值。

检验假设

我们的第一步是编写一个单元测试来检验我们的假设。 我们希望测试尽可能地模仿攻击。 因此,我们首先在ACL中具有多个应用程序的钥匙串中插入一个钥匙。 使用与测试VALValet相同的基本查询来添加此泄露密钥,以便我们的代客能够读取和写入该泄露密钥。

  VALValet * valet = [[VALValet分配] initWithIdentifier:@“ MacOSVulnTest”可访问性:VALAccessibilityWhenUnlocked]; 

NSString * const vulnKey = @“ AccessControlListVulnTestKey”;
NSString * const vulnKeyValue = @“ AccessControlListVulnTestValue”;
  //将一个条目添加到带有访问控制列表的钥匙串中。 
NSMutableDictionary * keychainData = [valet.baseQuery mutableCopy];
keychainData [( __bridge id )kSecAttrAccount] = vulnKey;
keychainData [( __bridge id )kSecValueData] = [vulnKeyValue dataUsingEncoding:NSUTF8StringEncoding];

SecAccessRef accessList = NULL;
SecTrustedApplicationReftrustedAppSelf = NULL;
SecTrustedApplicationReftrustedAppSystemUIServer = NULL;
XCTAssertEqual(SecTrustedApplicationCreateFromPath(NULL, trustedAppSelf),errSecSuccess);
XCTAssertEqual(SecTrustedApplicationCreateFromPath(“ / System / Library / CoreServices / SystemUIServer.app”, and trustedAppSystemUIServer),errSecSuccess);
XCTAssertEqual(SecAccessCreate(( __bridge CFStringRef)@“访问控制列表”,
__bridge CFArrayRef)@ [( __bridge id )trustedAppSelf,( __bridge id )trustedAppSystemUIServer],
accessList),
errSecSuccess);

keychainData [( __bridge id )kSecAttrAccess] =__bridge id )accessList;
XCTAssertEqual(SecItemAdd(( __bridge CFDictionaryRef)keychainData,NULL),errSecSuccess);

一旦运行了此代码,我们就会在钥匙串中看到受损的价值。

现在我们有了一个折衷的值,我们在OS X上更改了Valet的代码,以调用SecItemDelete,然后调用setObject:forKey:上的SecItemAdd。 然后,我们在测试中添加了以下几行:

  //使用Valet更新易受攻击的钥匙串值,并看到我们已删除了现有的钥匙链项目(而不是对其进行更新),因此不再易受攻击。 
NSString * const vulnKeyOtherValue = @“ AccessControlListVulnOtherTestValue”;
[valet setString:vulnKeyOtherValue forKey:vulnKey];

运行该代码后,我们看到该值不再受到损害。

然后,我们添加了几行以编程方式测试确实在setString:forKey上删除了受到破坏的钥匙串条目。 我们在Mac OS X Yosemite机器上本地运行了测试-测试通过了,我们进行了推广!

尚未明确

然后发生了令人惊讶的事情。 我们的测试在CI中失败。 具体来说,以编程方式测试已删除受到破坏的钥匙串条目的行失败了。 我们的开发环境和CI环境之间的最大区别是Travis CI(我们为公共GitHub项目提供的CI解决方案)运行的是Mac OS X Mavericks(10.9),而不是Yosemite(10.10)。 因此,我们在办公室中发现了一台尚未升级到优胜美地的机器,并在本地进行了测试,真是太糟糕了! 我们的测试在同一地点失败。

经过一些试验和一些谷歌搜索,我们发现在优胜美地之前,SecItemDelete实际上并没有删除与ACL关联的钥匙串项目,尽管返回代码表明它已经成功。 这意味着我们的补丁无法在10.9上运行,因为该漏洞是在有人将ACL恶意添加到您的钥匙串项目时发生的。

我们短暂地在10.9机器上使用SecKeychainItemDelete(确实删除了带有ACL的项目)进行了试验,但是随后发现SecKeychainItem *和SecItem *不能很好地配合使用。 因此,我们没有使用SecKeychainItem *重写Valet for 10.9的所有内容,而是将Valet的最低版本提高到10.10。

修补!

在意识到该漏洞后仅8个小时,我们就成功修补了Valet。 在Mac OS X上使用Valet的开发人员不会受到黑客的攻击。

以为我们错过了什么? 通过提出问题或针对Valet开通PR,让我们知道。 如果您有兴趣为10.9创建SecKeychainItem *解决方案,我们将很高兴收到您的来信!