验证神锁离线版插件的安全机制 – 作者:idguardoffline

v2-6defcfd2a406679919ac90f3d21ef2e4_720w.jpg

“在互联网上,没人知道你是一条狗”。知名国际大厂也未必像他们说的一样保护我们的数据安全:

备受舆论压力的大厂都这样,那管理所有密码的密码管理器能放心吗?神锁离线版插件声称采用了端到端加密技术,连内部员工都无法偷取用户密码是不是真的呢?

来,我们告诉你怎么亲手验证我们的安全技术!

神锁离线版可能是唯一一个能告诉用户怎样验证安全技术的密码管理器。

不用太担心不懂技术细节,只需要有一点耐心,就可以和我们一起动手做实验!

v2-2332126de3fb61df8f2911831ac037dc_720w.jpg

比萨斜塔 来自 Saffron Blaze

首先,本次实验的目的是验证:

  • 密码会不会偷偷发送给服务器?
  • 如果没有直接发送密码,会不会把密钥偷偷发送给服务器?

基本原理

回顾一下神锁离线版插件架构和原理:

v2-6855f8f72d1eee9e779855524f0e93e5_720w.jpg

我们在app中选择要填充的账号后,用户名和密码是怎么从手机发送到插件的呢?

  1. app加密用户名和密码等信息;
  2. app启动手机浏览器,通过浏览器打开网页程序,将要传输的加密数据发送到云端;
  3. 插件从云端接收到加密数据后,解密出用户名和密码,填充到网页中。

插件实现端到端加密(End-to-end encryption, E2EE)的关键在于借助ECDH算法,可以在插件和app(下图中Alice和Bob)之间建立共享的加密密钥,确保通信安全。

v2-021e68ea520e9be6940d48bc008920a8_720w.jpg

来自 David Göthberg

主要步骤:

  • app和插件分别生成一次性的椭圆曲线(ECC)密钥对
  • 交换可以公开的公钥(Public Key)
  • 使用自己的私钥(Private Key)和对方的公钥(Public Key),生成相同的加密密钥(shared secret)

实验准备

实验条件:安装并使用神锁离线版app和插件。

以 Edge 浏览器为例,先设置断点,以便截获传输数据进行分析。步骤如下:

  1. 从浏览器菜单中,找到 扩展,打开插件管理界面

v2-c22a36f0d2f2ed472137afd6510dc247_720w.jpg

2. 在插件管理中找到神锁离线版,点击 背景页链接,打开浏览器调试窗口

v2-920e5dda2a29b644914402559678ff36_720w.jpg

3. 设置截获数据的断点:

v2-c0d3f858a162f6d66fa304f53500de7a_720w.jpg

这里稍微麻烦一点,点击4次,分别选择:

  • 源代码(Sources),
  • 页面(Page),
  • 程序脚本(background.js),
  • 找到onServerMessage函数的定义,在左边行号上点击,设置红色的断点。

实验操作

  1. 浏览器中打开登录网页 http://www.stealmylogin.com/demo.html,会看到扫码填充框

v2-2dca9e6d99159fec23a788cc81683b2b_720w.jpg

2. 使用神锁离线版app扫码,app从二维码中可以得到正在填充的网站域名和插件公钥

实验中不要使用真实账号密码,以防泄密给第三方网站。

3. 选择账号后,app会打开手机浏览器开始填充。点击浏览器的地址栏,复制地址并记录下来。地址有点长,大概是这个样子:

https://app-link.bluespace.tech:8081/remote-fill/index.html#a=&t=1609300964&host=www.stealmylogin.com&to=15579832-e41c-4b17-8615-b6507a02dd4b&key=MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAELzVA%2B9qJXZJShDcwWxo5%2FWWQTOGi%2FuaRAi1%2BHrcLWw6vISQmzusRptXE%2BCTSVi2HWxQ3BQjCDDd%2FPqTyATgkeUyv3GbfBBXVw2q8LjVpt1wnlrFG729QsnExYVqgI5WP&iv=v6DyQ%2Bmx16mt71Ab&cipher=1Ug43zUPuRkQn74m1309FL0J5BNGcqT5FHcwubzYnpBLAscF%2Bn78RfmurjiWIgOP3cbZ1%2F5Kz%2Fa9sjWyZozdUv1HXmo3u0k3%2F16KkPGuWaaTsHt8uCV7tQP0MNIs84fxr4REnBY%3D

简单讲解一下,#符号前面是网页地址,后面是传递给网页程序的数据项目,以 &分隔,每个项目格式是 名字=数据。整理其中的数据项目:

    • t=1609300964是填充的时间,用于检测是否过期
    • host=www.stealmylogin.com是当前正在填充的网站
    • to=15579832-e41c-4b17-8615-b6507a02dd4b是插件的接收地址,每次填充都随机生成
    • 接下来是端到端加密三元素,使用Base64编码
      • key是app密钥对的公钥,可以和插件的私钥一起生成加密密钥
      • ivAES-GCM加密参数,随机初始向量(Initial Vector)
      • cipher是加密后的密文

4. 很快插件背景页就会在断点捕获到传输过来的数据,大概是这个样子:

v2-cf8e815eb87793325de3794752d8d0ad_720w.jpg

5. 鼠标移动到 message上,查看捕获的消息,大概是这个样子:

{
    to: "15579832-e41c-4b17-8615-b6507a02dd4b",
    from: "a3ae6e09-8b2c-43bf-a61b-08f4022adfc5",
    ts: "1609300964",
    id: "83072ef7-11fe-49fe-bac3-787f92881bbf",
    data: "{
      "key":"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAELzVA+9qJXZJShDcwWxo5/WWQTOGi/uaRAi1+HrcLWw6vISQmzusRptXE+CTSVi2HWxQ3BQjCDDd/PqTyATgkeUyv3GbfBBXVw2q8LjVpt1wnlrFG729QsnExYVqgI5WP",
      "iv":"v6DyQ+mx16mt71Ab",
      "cipher":"1Ug43zUPuRkQn74m1309FL0J5BNGcqT5FHcwubzYnpBLAscF+n78RfmurjiWIgOP3cbZ1/5Kz/a9sjWyZozdUv1HXmo3u0k3/16KkPGuWaaTsHt8uCV7tQP0MNIs84fxr4REnBY="
    }"
  }

这个消息内容也要记录下来分析(记录的时候,可以像上面一样调整顺序换行)。

实验分析

收集到所有数据后,我们就可以进行数据分析了。

先对比手机端发送的数据和插件接收到的数据:

v2-f4bbfec175e82d862c36cf06f8af701c_720w.jpg

  1. 填充的网站host=www.stealmylogin.com没有发送给插件。这个数据项只用于在手机浏览器中向用户展示,云端并不知道用户当前填充的网站,隐私无忧。

2. 发送地址和消息ID不是app通过浏览器发送给插件的,是网页发送程序在发送消息的时候生成的随机UUID,用于标记消息。

要验证这两点,需要查看 https://app-link.bluespace.tech:8081/remote-fill/index.html中 script.js的源代码,后面有程序员准备的讲解

数据就这些了,对于第一个目标,我们只需把它们和密码对比一下,就很容易确认有没有偷偷发送给云端。

但是还不能完全确认:会不会在传输的数据中夹带密钥,让云端有能力解密出传输的数据呢?

深度分析加密数据

接下来我们要验证有没有带私货给云端解密。

v2-2bfccb60dec2f01b32347c0c91f7b5e9_720w.jpg

图片来自 Metinegrioglu

发送的其他数据都很简单,只有加密三元组:公钥key,加密IV和密文数据,看起来很奇怪,也比较长,会不会夹带密钥呢?

1.key公钥是否夹带了额外数据?

验证方法:截获的公钥长度是不是160个字符?

知识点

  • 插件使用ECC P-384曲线,公钥使用 spki格式编码,长度是120字节。
  • Base64编码会将3字节二进制数据变换成4个字符。

示例截获的数据 MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAELzVA+9qJXZJShDcwWxo5/WWQTOGi/uaRAi1+HrcLWw6vISQmzusRptXE+CTSVi2HWxQ3BQjCDDd/PqTyATgkeUyv3GbfBBXVw2q8LjVpt1wnlrFG729QsnExYVqgI5WP与公钥长度一致,没有夹带额外数据。

2.iv是否夹带了额外数据?

验证方法:截获的iv长度是不是16个字符?

知识点

  • AES-256-GCM加密的初始向量IV是完全随机的12字节二进制数据。
  • Base64编码会将3字节二进制数据变换成4个字符。

示例截获的数据 v6DyQ+mx16mt71Ab正好是16个字符,没有夹带额外数据。

如果我们多做几次实验,会发现即使是同一个账号,每次IV也是不一样的。

程序员在调试的时候,还可以把密钥打印出来和IV对比,看看是不是一样的。

3.cipher密文是否夹带了额外数据?

验证方法:是否能够成功填充密码?

知识点

  • 数据加密使用 AES-256-GCM算法,可以帮助检测密文数据的完整性。如果密文有改动,解密就会失败。

程序员在调试的时候,看到 decryptMessage函数执行没有出错就可以证明没有夹带额外数据了。

进行这3个数据项的检验,就可以判定是否夹带了额外数据。

程序员详解

前面讲解了验证方法,但是没有详细程序实现,显然不是程序员的菜。接下来讲解程序实现中的关键点。

插件和网页发送程序的源代码都没有最小化和混淆,清晰可读,非常方便程序员们帮忙审计。

数据发送

查看 https://app-link.bluespace.tech:8081/remote-fill/index.html网页源码很容易找到主要代码 script.js

  1. 解析传递给网页程序的参数
const params = new URLSearchParams(window.location.hash);
  const timestamp = params.get('t');
  const host = params.get('host');
  const to = params.get('to');
  const key = params.get('key');
  const iv = params.get('iv');
  const cipher = params.get('cipher');

向网页传递参数使用#(window.location.hash)分隔,而不是?,可以防止我们的app开发者写了bug导致泄漏用户信息,因为#后面的数据,不会传递到云端。

2. 将解析出来的数据t,to,key,iv,cipher组装成一个Json对象的消息。

const data = JSON.stringify({key: key, iv: iv, cipher: cipher});
  const uuid = uuidv4();
  const request = {to: to, from: uuid, ts: timestamp, id: uuidv4(), data: data};

这里可以确认,host不会发送到云端,不会泄漏用户隐私。

3. 再接下来就是使用AJAX将组装的消息发送到云端。

数据接收

数据通过websocket接收,入口是 onmessage事件,数据处理就是断点的函数 onServerMessage,核心解密部分是crypto.js中的 decryptMessage函数:

importPublicKeyText = (Base64Text) => window.crypto.subtle.importKey(this.publicKeyFormat, Base64.decode(Base64Text), this.curve, true, []);

  deriveKey = (publicKey, privateKey) => window.crypto.subtle.deriveKey({name: "ECDH", public: publicKey}, privateKey, {name: "AES-GCM", length: 256}, true, ["encrypt", "decrypt"]);

  decrypt = (ivBytes, cipherBytes, secretKey) => window.crypto.subtle.decrypt({name: "AES-GCM", iv: ivBytes}, secretKey, cipherBytes);

  decryptMessage = async function(keyPair, data) {
    const encrypted = JSON.parse(data);
    const publicKey = await this.importPublicKeyText(encrypted.key);
    const secretKey = await this.deriveKey(publicKey, keyPair.privateKey);

    const ivBytes = Uint8Array.from(window.atob(encrypted.iv), c => c.charCodeAt(0));
    const cipherBytes = Uint8Array.from(window.atob(encrypted.cipher), c => c.charCodeAt(0));
    const bytes = await this.decrypt(ivBytes, cipherBytes, secretKey);
    return new TextDecoder().decode(bytes);
  };

主要三个操作:

  1. importPublicKeyText导入 spki格式的公钥;
  2. deriveKey调用 ECDH算法生成256位的AES密钥;
  3. decrypt使用 AES-GCM模式解密出原文。GCM是一种特别的AES模式,可以校验数据的完整性,防止篡改。

验证总结

尽管密码学和网络安全都是很专业的技术领域,我们仍然希望尽可能让更多人了解和验证我们使用的安全技术。在设计神锁离线插件时,我们不仅会努力创造最安全的技术,同时还会尽力尝试为用户提供验证技术的方法。

如果没有相关专业知识,可能还是会留下一些疑问。不过我们相信,通过这样详细的验证方法讲解,足以让具备一定相关知识的技术人员能够弄清楚每个细节。

来源:freebuf.com 2021-03-25 18:58:09 by: idguardoffline

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论