静态加密:存在代理混淆的安全漏洞


假设您有一个简单的 Web 应用程序,它在将数据存储到 SQL 数据库之前对其进行加密。

一个快速而粗糙的实现可能看起来像这样:

class User {
    public function __construct(
        public readonly string $username,
        public string $email,
        public string $fullName
    ) {}
}
 
class UserModel {
    public function __construct(protected Database $db)
    {}
 
    public function save(User $user): bool
    {
        return $this->db->upsert(
           'users', 
           [ // set
               'full_name' => aes128gcm_encrypt($user->fullName),
               'email' => aes128gcm_encrypt($user->email)
                       
// encryption details abstracted
           ], 
           [
// where
               'username' => $user->username
           ]
        );
    }
 
    public function fetch(string $username): User
    {
        $row = $this->db->fetch('users', ['username' => $username]);
        return new User(
            $username,
            aes128gcm_decrypt($row['email']),
            aes128gcm_decrypt($row['full_name'])
        );
    }
}

上述伪代码中的抽象aes128gcm函数是一种静态加密方法:在加密过程中从 KMS 获取密钥,并将加密的数据密钥存储在密文稍后解密时可以引用的位置。

假设出现这样一个情况:
Alice 和 Bob 使用同一家健康保险提供商,该提供商存储了双方的敏感医疗记录。Bob 是他和 Alice 共同使用的保险公司的数据库管理员。

数据都是在 Web 应用程序中加密的,因此 Bob 可以访问的所有数据都与随机数据无异。
他可以通过应用程序访问自己的帐户并查看自己的数据,但从数据库服务器上的有利位置看不到 Alice 的数据。


这是一个在很多情况下都有效的愚蠢简单攻击:

  • Bob 复制Alice 的加密数据,并覆盖数据库中的记录,然后访问保险提供商的网络应用程序。

Web 应用程序能够解密使用不同密钥加密的不同记录。如果您将为 Alice 加密的记录传递给应用程序以解密 Bob 的记录,并且您没有验证您的访问模式,则 Bob 可以通过执行此攻击来读取 Alice 的数据。

执行以下操作来证明这种混淆代理风险:

$model = new UserModel($db);
$model->save(new User('alice', 'alice@example.com', 'Alice McWonderland'));
$model->save(new User('bob', 'bob@example.com', 'Bob BurgerMeister'));
 
// Fetch Alice's data
$aliceData = $db->fetch('users', ['username' => 'alice']);
$bobData = $db->fetch('users', ['username' => 'bob']);
 
// This is the attack the database server can perfrom:
// Replace Bob's full_name with Alice's email
$db->upsert('users', [
    'full_name' => $alice['email']
], ['username' => 'bob']);
 
$badBob = $model->fetch('bob');

现在 Bob 的全名设置为 Alice 的电子邮件地址。

  • 现在想象一下有人对工资系统中的工资字段进行同样的攻击。

解决方法
使用 AAD 机制(标准 AEAD 接口的一部分):将密文绑定到其上下文。这可以是客户 ID、数据库表主键的每一行值,或者其他完全不同的东西。

上个月发布的CipherSweet 4.7.0 版现在只需要进行如下代码更改即可缓解应用程序中的混淆问题:

$multiRowEncryptor = new EncryptedMultiRows($engine);
  $multiRowEncryptor
+     ->setAutoBindContext(true)
+     ->setPrimaryKeyColumn('table2', 'id')
      ->addTextField('table1', 'field1')


这是对新的增强型 AAD功能的补充,它允许基于其他字段和/或字符串文字进行灵活而强大的上下文绑定。

(事实上​​,这个新的便捷功能实际上在底层使用了增强型 AAD。)

然而,这并不是免费的:用户必须在写入记录之前知道记录的序列/主键,以便在加密字段时将其用作 AAD。然而,这比期望 PHP 开发人员自己管理上下文绑定的复杂性要容易得多。

鉴于客户端加密项目普遍使用不安全的分组密码模式(或ECB,即完全没有分组密码模式),因此,它们中的大多数都很难解决混淆代理攻击。甚至我在 2018 年制作 CipherSweet 时一开始也没有做对。