如何获取crypto与crypto.subtle对象属性键以校验Web Crypto API完整性?
问题
我构建了一个用于校验Web Crypto API完整性的模块,校验通过后会冻结crypto和crypto.subtle对象以确保安全。原本的校验逻辑计划:
- 获取
crypto和crypto.subtle的属性键 - 检查是否包含所有预期属性、属性内容是否为未篡改的原生API
- 验证SubtleCrypto的所有元素都是原生函数
但实际测试中,Object.keys(crypto);和Object.keys(crypto.subtle);都返回空数组,因为这两个对象的属性都是非可枚举的;JSON.stringify()也无法获取属性,返回空JSON。
目前只能用手动创建预期键列表的次优方案,但原本依赖动态获取属性键的逻辑完全失效,导致校验失败,控制台只会输出警告而非保护启用信息。以下是原代码:
function check_crypto() { var l_cryptoapi; var l_protected = true; var l_crypt_keys; // If crypto isn't object Crypto or crypto.subtle isn't object SubtleCrypto, // someone has tampered with the API! if(crypto.toString() != '[object Crypto]') l_protected = false; else if((l_cryptoapi = crypto.subtle).toString() != '[object SubtleCrypto]') l_protected = false; else l_crypt_keys = Object.keys(crypto.subtle); // THIS DOES NOT WORK!!! // l_crypt_keys is empty so the next checks inevitably fail! if(l_protected) // If there aren't twelve properties in crypto.subtle, someone has tampered // with the API! if(l_crypt_keys.length != 12) l_protected = false; else // If we find an unknown key, someone has tampered with the API! l_protected = l_crypt_keys.every(p_value => [ 'decrypt', 'deriveBits', 'deriveKey', 'digest', 'encrypt', 'exportKey', 'generateKey', 'importKey', 'sign', 'unwrapKey', 'verify', 'wrapKey' ].includes(p_value)); // If any function isn't native code, someone has tampered with the API! if(l_protected) l_protected = l_crypt_keys.every(p_value => (typeof l_cryptoapi[p_value] == 'function') && l_cryptoapi[p_value].toString().match(/\(\)\s\{\n\s{4}\[native\ code\]\n\}$/)); if(l_protected) { Object.freeze(l_cryptoapi); Object.freeze(crypto); console.info('protect.js: The cryptographic API is now PROTECTED against polyfill attacks!'); } else { console.error('protect.js: The cryptographic API has been found to have been tampered with!'); console.error('protect.js: Anything that is relying on this API has to be considered insecure!'); console.error('protect.js: !!! WATCH OUT !!! SOMEONE MAY BE DOING SOMETHING REALLY NASTY !!!'); } return l_protected; };
现在需要解决的问题:有没有办法绕过非可枚举属性的限制,让原本的动态校验逻辑生效?
解决方案
核心原因
Object.keys()只会返回对象的可枚举自有属性,而Web Crypto API的属性都是非可枚举的,所以无法通过它获取。我们需要用能获取所有自有属性(包括非可枚举)的API。
可行方案
可以使用Object.getOwnPropertyNames()或Reflect.ownKeys()来获取crypto.subtle的所有自有属性,包括非可枚举的。但需要注意:
- 不同浏览器可能会有少量非标准的额外属性,所以校验逻辑需要调整:以预期的属性列表为基准,而非严格匹配属性数量
- 同时可以额外检查是否存在未预期的属性,作为篡改的判断依据
修改后的代码
function check_crypto() { const l_cryptoapi = crypto.subtle; let l_protected = true; // 定义SubtleCrypto的标准预期属性列表 const expectedKeys = [ 'decrypt', 'deriveBits', 'deriveKey', 'digest', 'encrypt', 'exportKey', 'generateKey', 'importKey', 'sign', 'unwrapKey', 'verify', 'wrapKey' ]; // 第一步:校验对象类型 if (crypto.toString() !== '[object Crypto]' || l_cryptoapi.toString() !== '[object SubtleCrypto]') { l_protected = false; } else { // 获取crypto.subtle的所有自有属性(包括非可枚举) const allSubtleKeys = Object.getOwnPropertyNames(l_cryptoapi); // 第二步:检查所有预期属性是否都存在 const allExpectedExist = expectedKeys.every(key => allSubtleKeys.includes(key)); if (!allExpectedExist) { l_protected = false; } else { // 可选:检查是否存在未预期的额外属性(部分浏览器可能有合法扩展,可根据需求调整) const hasUnexpectedKeys = allSubtleKeys.some(key => !expectedKeys.includes(key)); if (hasUnexpectedKeys) { l_protected = false; } else { // 第三步:校验所有预期属性都是原生函数 l_protected = expectedKeys.every(key => { const fn = l_cryptoapi[key]; return typeof fn === 'function' && /\(\)\s\{\n\s{4}\[native\ code\]\n\}$/.test(fn.toString()); }); } } } if (l_protected) { Object.freeze(l_cryptoapi); Object.freeze(crypto); console.info('protect.js: The cryptographic API is now PROTECTED against polyfill attacks!'); } else { console.error('protect.js: The cryptographic API has been found to have been tampered with!'); console.error('protect.js: Anything that is relying on this API has to be considered insecure!'); console.error('protect.js: !!! WATCH OUT !!! SOMEONE MAY BE DOING SOMETHING REALLY NASTY !!!'); } return l_protected; };
关键调整说明
- 用
Object.getOwnPropertyNames(l_cryptoapi)替代Object.keys(),获取所有自有属性(包括非可枚举) - 校验逻辑改为以预期属性列表为核心:先确保所有预期属性都存在,再检查是否有额外属性(可选步骤,可根据浏览器兼容性调整)
- 直接遍历预期列表校验每个属性是否为原生函数,避免依赖动态获取的键数组
- 代码结构优化,提升可读性
内容的提问来源于stack exchange,提问作者Robidu
相关产品推荐
相关产品推荐

