Firefox
和Chrome瀏覽器不同,Mozilla擁有自己的加密庫,被稱爲網絡安全服務(NSS),特別之處是NSS使用了ASN.1進行數據序列化。
ASN1
- ASN.1 – Abstract Syntax Notation dot one,數字1被ISO加在ASN的後邊,是爲了保持ASN的開放性,可以讓以後功能更加強大的ASN被命名爲ASN.2等,但至今也沒有出現。
- ASN.1是一種對數據進行表示、編碼、傳輸和解碼的數據格式。它提供了一整套正規的格式用於描述對象的結構,而不管語言上如何執行及這些數據的具體指代,也不用去管到底是什麼樣的應用程序。
Chrome和Firefox之間的有一個很大的區別,那就是Firefox允許用戶提供一個主密碼來加密所有存儲的登錄名和密碼。如果用戶設置了主密碼,需要解密者提供主密碼才能解密登錄信息。
項目大量參考了https://github.com/lclevy/firepwd,以及下面這張Firefox的密碼加解密流程圖(原圖地址爲https://github.com/lclevy/firepwd/blob/master/mozilla_pbe.pdf),雖然是Firefox <32 版本的加密邏輯,但是可以幫助我們理清Firefox的加解密原理:
登錄信息存放位置
用戶的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)。
nssPrivate中 a11 列存放的是用於加解密的主密鑰。
登錄信息解密
Firefox 版本 >= 58.0.2 < 75
- 根據上述的描述,解密Firefox存儲在本地的登錄信息需要以下步驟:
- 找到當前計算機Firefox的profile目錄,檢查
key4.db
和logins.json
文件是否存在。 - 如果存在,從
key4.db
中提取已編碼+加密的password-check數據,先ASN1解碼然後使用3DES解密被加密的password-check字符串(這樣做是爲了確認提取的密碼是否正確)。 - 從
key4.db
中提取編碼的+加密的主密鑰 ,ASN.1解碼,然後3DES解密主密鑰。 - 從
logins.json
中讀取加密的登錄名和密碼,ASN.1解碼,然後3DES使用主密鑰解密登錄數據
Firefox 版本 >= 75
和Firefox 版本 >= 58.0.2 < 75不同的是,在加密password-check數據和主密鑰使用了hmacWithSHA256
的哈希算法和AES256 cbc
的加密算法,所以解密步驟如下所示:
- 根據上述的描述,解密Firefox存儲在本地的登錄信息需要以下步驟:
- 找到當前計算機Firefox的profile目錄,檢查
key4.db
和logins.json
文件是否存在。 - 如果存在,從
key4.db
中提取已編碼+加密的password-check數據,先ASN1解碼然後使用AES解密被加密的password-check字符串(這樣做是爲了確認提取的密碼是否正確)。 - 從
key4.db
中提取編碼的+加密的主密鑰 ,ASN.1解碼,然後3DES解密主密鑰。 - 從
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
文件
- 遍歷中的每個
Login
對象JSONLogins
- ASN.1解碼每個用戶名和密碼
- 3DES使用主密鑰解密每個用戶名和密碼
- 輸出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);
}
}
最終結果如下圖所示:
工具加入知識星球獲取,現在只是個基礎,後期會逐步更新所有瀏覽器的解密工具,還不加入星球一起快樂擊劍?
號外
寬字節安全團隊第一期線下網絡安全就業班開班了,由寬字節安全團隊獨立運營,一線紅隊大佬帶隊,有豐富的漏洞研究、滲透測試、應急響應的經驗與沉澱,乾貨多多,歡迎添加客服諮詢
客服微信:unicodesec