Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】如何解密TFS的秘密变量

0
0
【技术分享】如何解密TFS的秘密变量

2017-08-02 18:20:44

阅读:128次
点赞(0)
收藏
来源: lowleveldesign.org





【技术分享】如何解密TFS的秘密变量

作者:興趣使然的小胃






【技术分享】如何解密TFS的秘密变量

译者:興趣使然的小胃

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


什么是 TFS(Team Foundation Server)?

Team Foundation Server 提供一组协作软件开发工具,它与你现有的 IDE 或编辑器集成,从而使你的跨功能团队可以在所有大小的软件项目上高效工作。

引用自https://www.visualstudio.com/zh-hans/tfs/


一、前言

TFS的每个生成(build)及发布(release)定义中都包含一组自定义变量,并且会在随后的build/release管道中使用这些变量作为参数用于PowerShell/批处理脚本、配置文件转换或者其他子任务。从一个任务中访问这些变量类似于访问进程环境变量。由于TFS使用了非常详细的日志记录,因此我们经常可以在build日志中发现明文形式的具体变量值。这也是微软使用秘密变量的原因之一。

TFS的生成配置面板示例如下所示,其中有个秘密变量amiprotected(注意图中高亮的小锁图标):


【技术分享】如何解密TFS的秘密变量

当秘密变量被保存之后,我们再也无法通过网页面板读取这个值(当你点击小锁图标时,文本框会被清空)。

当我们将秘密变量传递给PowerShell脚本并打印这个变量时,对应的输出日志如下所示:


【技术分享】如何解密TFS的秘密变量

现在让我们来探索一下这些秘密变量具体存储的位置及过程。


二、秘密变量存储的过程

为了分析TFS存储秘密变量的具体过程,我一开始检查的是TFS的数据库服务器。不同的团队集合都有独立的数据库与之对应。每个数据库会将表(table)分组为名字易于理解的schema,比如Build、Release或者TfsWorkItemTracking。此外,默认的dbo schema中也包含一些表。我们的秘密变量属于生成定义的一部分,因此我们可以来检查一下Build这个schema。tbl_Definition看上去像是个可行的方法。这个表的前几列如下所示:


【技术分享】如何解密TFS的秘密变量

其中,Variables这一列以JSON格式列出了所有的变量。不幸的是,我无法了解关于我们的秘密变量的更多信息(除了得知它是个秘密变量之外)。因此,我开始搜索是否存在名字为“tbl_Variables”或者“tbl_Secrets”的表,但一无所获。抱着最后一丝希望,我使用了procmon这个工具,这个工具也是我在实在没办法的时候一直使用的一个工具。利用这个工具,我跟踪了秘密变量保存时的路径,在这个路径中发现了一些有趣的信息:


【技术分享】如何解密TFS的秘密变量

我选择其中一条记录,检查它的调用栈:


【技术分享】如何解密TFS的秘密变量

现在我需要使用一个小技巧,来解码procmon跟踪路径中的frame信息。最后一个frame指向的是TeamFoundationStrongBoxService.AddStream方法。这个类的名称非常特别,因此我特意在数据库中查找了一下相关信息,很快就找到了一些较为有趣的表。

tbl_StrongBoxDrawer表:


【技术分享】如何解密TFS的秘密变量

tbl_StrongBoxItem表:


【技术分享】如何解密TFS的秘密变量

以及tbl_SigningKey表:


【技术分享】如何解密TFS的秘密变量

正如你所看到的那样,tbl_StrongBoxDrawer表中的某一行引用了build的定义,同时对秘密变量进行了分组。tbl_StrongBoxItem表以加密形式存储了秘密变量所对应的所有值(包括我们当前的秘密变量)。最后,tbl_SigningKey表存储了某些私钥,这些私钥可能会在加密过程中使用。

现在是启动dnspy工具、开始更为细致分析的时候了。AddStream方法包含将敏感信息添加到TFS数据库的逻辑处理流程。首先,秘密变量值经过AES加密(使用了自动生成的密钥以及随机初始化的向量)并保存在一个byte数组中。其次,AES密钥经过签名密钥的加密,并被保存到另一个byte数组中。最后,这两个表以及初始化向量会被保存为与秘密向量有关的加密值。对于本文使用的例子来说,这个加密值如下所示:


【技术分享】如何解密TFS的秘密变量

而签名密钥会以RSA CSP数据块的形式保存,如下所示:


【技术分享】如何解密TFS的秘密变量

当你从数据库中提取这段数据后,你可以使用如下示例代码进一步解压这段数据:

byte[]aesKey; using(RSACryptoServiceProviderrsaCryptoServiceProvider=newRSACryptoServiceProvider(2048, newCspParameters{Flags=CspProviderFlags.UseMachineKeyStore})){ varcspBlob="...hexstring..."; rsaCryptoServiceProvider.ImportCspBlob(Hex.FromHexString(cspBlob)); varencryptedAesKey="...hexstring..."; aesKey=rsaCryptoServiceProvider.Decrypt(Hex.FromHexString(encryptedAesKey),true); } variv=Hex.FromHexString("...hexstring..."); varcipher=Hex.FromHexString("...hexstring..."); using(varaes=Aes.Create()){ vartransform=aes.CreateDecryptor(aesKey,iv); using(varencryptedStream=newMemoryStream(cipher)){ using(varcryptoStream=newCryptoStream(encryptedStream,transform,CryptoStreamMode.Read)){ using(vardecryptedStream=newMemoryStream()){ cryptoStream.CopyTo(decryptedStream); Console.WriteLine(Hex.PrettyPrint(decryptedStream.ToArray())); } } } }

如上所述,tbl_SigningKey表包含非常敏感的数据,因此你必须确保只有经过认证的用户才能访问这个表。TFS在这里竟然没有使用任何DPAPI或者其他加密机制,这让我非常惊讶。


三、揭开秘密变量的神秘面纱

前文中,我们利用存在数据库中相关数据成功解密了秘密变量,但实际上还有更为简单的方法。如果你可以修改build/release任务管道,那么你就可以很方便地创建一个PowerShell任务,如下所示:


【技术分享】如何解密TFS的秘密变量

由于字符串中第一个字符与剩余字符之间有一个空格,因此输出不会被屏蔽,你可以以在日志中看到明文形式的密码:


【技术分享】如何解密TFS的秘密变量

因此,你可以得出结论,如果人们能够修改build/release任务管道,或者人们可以修改任务管道中使用的脚本内容,那么他们就能读取秘密变量的值。


四、从TFS内存中提取秘密变量

最后,让我们研究一下TFS服务器进程(w3wp.exe)的内存,看看我们能从中获得什么信息。我将这个进程的全部内存导出到一个文件中,然后使用WinDbg打开这个文件(WinDbg已事先加载了SOSEX以及SOS扩展)。先来列出StrongBoxItemInfo的所有实例信息:

0:000>!mx*.StrongBoxItemInfo AppDomain0000000001eb8b50(/LM/W3SVC/2/ROOT/tfs-1-131555514123625000) --------------------------------------------------------- module:Microsoft.TeamFoundation.Framework.Server class:Microsoft.TeamFoundation.Framework.Server.StrongBoxItemInfo module:Microsoft.TeamFoundation.Client class:Microsoft.TeamFoundation.Framework.Client.StrongBoxItemInfo … 0:000>.foreach(addr{!DumpHeap-short-mt000007fe92560f80}){!mdtaddr} ... 0000000203419dc8(Microsoft.TeamFoundation.Framework.Server.StrongBoxItemInfo) <drawerid>k__BackingField:(System.Guid){c2808c4d-0c0d-43e5-b4b2-e743f5121cdd}VALTYPE(MT=000007feef467350,ADDR=0000000203419df8) <itemkind>k__BackingField:0x00(String)(Microsoft.TeamFoundation.Framework.Server.StrongBoxItemKind) <lookupkey>k__BackingField:000000020341a1f8(System.String)Length=24,String="9/variables/amiprotected" <signingkeyid>k__BackingField:(System.Guid){c2808c4d-0c0d-43e5-b4b2-e743f5121cdd}VALTYPE(MT=000007feef467350,ADDR=0000000203419e08) <expirationdate>k__BackingField:(System.Nullable`1[[System.DateTime,mscorlib]])VALTYPE(MT=000007feef4c13b8,ADDR=0000000203419e18) <credentialname>k__BackingField:NULL(System.String) <encryptedcontent>k__BackingField:000000020341a3f0(System.Byte[],Elements:312) <value>k__BackingField:NULL(System.String) <fileid>k__BackingField:0xffffffff(System.Int32) … LookupKey这个字段包含变量的名称。为了将其与build/release定义结合起来,我们需要找到Id等于c2808c4d-0c0d-43e5-b4b2-e743f5121cdd的StrongBoxDrawerName实例。但我们的主要任务是解密EncryptedContent字段的值,为了完成这个任务,我们需要得到签名密钥(id: c2808c4d-0c0d-43e5-b4b2-e743f5121cdd)。TFS会在缓存中保存最近使用的签名密钥,因此如果我们运气不错的话,我们还是有可能在缓存中找到这个签名密钥。这个过程稍显漫长,首先,我们需要列出TeamFoundationSigningService+SigningServiceCache类的所有实例:
0:000>!mx*.TeamFoundationSigningService+SigningServiceCache module:Microsoft.TeamFoundation.Framework.Server class:Microsoft.TeamFoundation.Framework.Server.TeamFoundationSigningService+SigningServiceCache 0:000>!DumpHeap-mt000007fe932d99e0 AddressMTSize ... 0000000203645ce0000007fe932d99e096

然后,一路跟踪对象树,找到与我们有关的那个私钥:

Microsoft.TeamFoundation.Framework.Server.TeamFoundationSigningService+SigningServiceCache |-m_cacheData(00000002036460a0) |-m_cache(0000000203646368) |-m_dictionary(0000000203646398) 0:000>!mdt0000000203646398-e:2-start:0-count:2 … [1](…)VALTYPE(MT=000007fe969d7160,ADDR=0000000100d6d510) key:(System.Guid){c2808c4d-0c0d-43e5-b4b2-e743f5121cdd}VALTYPE(MT=000007feefdc7350,ADDR=0000000100d6d520) value:000000020390d440 |-Value(000000020390d410) |-<Value>k__BackingField(000000020390d3f0) |-m_Item1(000000020390d2b0) 0:000>!mdt000000020390d2b0 000000020390d2b0(Microsoft.TeamFoundation.Framework.Server.SigningServiceKey) <keydata>k__BackingField:000000020390ce00(System.Byte[],Elements:1172) <keytype>k__BackingField:0x0(RSAStored)(Microsoft.TeamFoundation.Framework.Server.SigningKeyType)

其实一开始我们可以直接搜索SigningServiceKey类的实例。但如果这么做,我们需要打印出所有的GC根,逐一检查,核实其Id是否就是我们正在查找的那个Id。我发现这种自上而下的方法会更为容易一些(但这个方法还是稍显乏味)。


五、总结

希望这篇文章能对读者有所帮助。根据本文分析,我们认为还是有办法能够安全地保存TFS秘密变量中的敏感数据,但首先要做到以下几点:

1、只有授权用户可以修改build/release管道(包括使用秘密变量的任务的内容)。

2、只有授权用户可以访问TFS数据库中的tbl_SigningKey表。

此外,我们也可以使用精心配置的TFS代理,配合密钥库或者某些本地DPAPI加密块来执行所有的敏感任务。



【技术分享】如何解密TFS的秘密变量
【技术分享】如何解密TFS的秘密变量
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://lowleveldesign.org/2017/07/04/decrypting-tfs-secret-variables/

Viewing all articles
Browse latest Browse all 12749