假设您有一个简单的 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 时一开始也没有做对。