随着互联网的迅速发展,用户在不同的网站和服务上创建了大量的账户,这就需要记住大量的用户名和密码。为了提高用户体验和安全性,现代浏览器提供了自动保存用户凭据的功能。本文将深入探讨Firefox浏览器如何安全地存储和加密用户凭据,以及如何通过编程方式获取这些凭据。
Firefox浏览器在最近的版本中,相较于旧版本和其他浏览器,其安全性有了显著提升。过去,只需要获取位于Firefox配置文件目录中的signons.txt文件,就可以找到所有存储的凭据。但从3.5版本开始,文本格式被SQLite数据库和JSON文件(即logins.json)所替代。此外,如果设置了“主密码”,则在不知道这个密码的情况下,无法解密存储的凭据。如果未设置主密码,可以在logins.json中找到数据并解密凭据。每个凭据的用户名和密码都使用PK#11进行加密。
首先,需要找到要获取的数据的位置,即Firefox配置文件路径。通常位于c:\users\<用户账户>\AppData\Local\Mozilla\Firefox\profiles.ini。以下是一个C++函数,用于获取Firefox配置文件路径:
std::wstring GetFirefoxProfilePath() {
wchar_t strAppData[MAX_PATH];
SHGetSpecialFolderPath(NULL, strAppData, CSIDL_APPDATA, TRUE);
wchar_t strIniPath[MAX_PATH];
swprintf_s(strIniPath, MAX_PATH, L"%s\\Mozilla\\Firefox\\profiles.ini", strAppData);
wchar_t strRelativePath[MAX_PATH];
GetPrivateProfileString(L"Profile0", L"Path", L"", strRelativePath, MAX_PATH, strIniPath);
wchar_t strFullPath[MAX_PATH];
swprintf_s(strFullPath, MAX_PATH, L"%s\\Mozilla\\Firefox\\%s", strAppData, strRelativePath);
wchar_t strShortPath[MAX_PATH];
GetShortPathName(strFullPath, strShortPath, MAX_PATH);
if (!PathFileExists(strShortPath)) {
return L"";
}
return ((std::wstring)strShortPath);
}
nss3.dll是一组称为网络安全服务(NSS)的库所使用的。这些库旨在支持跨平台开发支持SSL、S/MIME和其他互联网安全标准的通信应用程序。由于本文是关于Windows的实现,只提及Windows使用的库:nss3.dll(Windows共享库)、nss3.lib(Windows导入库绑定到nss3.dll)和nss.lib(Windows静态库)。首先,需要映射打算使用的nss3.dll中的函数。
fpNSS_Init = (NSS_Init_p)GetProcAddress(moduleNSS, "NSS_Init");
fpNSS_Shutdown = (NSS_Shutdown_p)GetProcAddress(moduleNSS, "NSS_Shutdown");
fpPL_ArenaFinish = (PL_ArenaFinish_p)GetProcAddress(moduleNSS, "PL_ArenaFinish");
fpPR_Cleanup = (PR_Cleanup_p)GetProcAddress(moduleNSS, "PR_Cleanup");
fpPK11_GetInternalKeySlot = (PK11_GetInternalKeySlot_p)GetProcAddress(moduleNSS, "PK11_GetInternalKeySlot");
fpPK11_FreeSlot = (PK11_FreeSlot_p)GetProcAddress(moduleNSS, "PK11_FreeSlot");
fpPK11SDR_Decrypt = (PK11SDR_Decrypt_p)GetProcAddress(moduleNSS, "PK11SDR_Decrypt");
PK11_CheckUserPassword = (PK11CheckUserPassword)GetProcAddress(moduleNSS, "PK11_CheckUserPassword");
Firefox引入了一种新的安全措施,称为主密码。主密码是用于加密所有存储凭据的集中加密密钥。这个密钥被哈希处理,但没有存储在任何地方,因此无法找到。
进行了一些测试,试图了解在设置了主密码的情况下,需要做什么才能解密Firefox存储的凭据。首先,应该解释一下在已知主密码的情况下,如何程序化地解密凭据。
另外,黑客可能会使用暴力破解技术尝试猜测主密码。这可以通过运行所有可能的字符串(或字母数字字符串)组合来完成,长度为1-7个字符(使用这种方法几乎不可能破解更长的密码)。另一种方法是运行常用密码列表。这种方法称为“字典攻击”。
以下代码检查"MyGuess"是否确实是主密码。如果是,可以继续执行工具的其余代码以解密所有存储的密码。
bool GuessMasterPassword(char *MyGuess) {
bool result = FALSE;
PK11SlotInfo *pK11Slot = fpPK11_GetInternalKeySlot();
if (PK11_CheckUserPassword(pK11Slot, MyGuess) == SECSuccess) {
result = true;
}
(*PK11FreeSlot)(pK11Slot);
return result;
}
代码的下一部分将解密凭据条目的任何加密部分,无论是否设置了主密码,都应该使用这段代码。如果没有设置主密码,仍然需要执行此代码,如果设置了主密码,假设GuessMasterPassword对于任何原因都返回了'true'(知道它,猜到了它,或者暴力破解了它)。
LPSTR DecryptString(LPSTR strCryptData) {
if (strCryptData[0] == 0x0) return FALSE;
DWORD dwOut;
LPSTR strClearData = "";
LPBYTE lpBuffer = base64_decode((char *)strCryptData, strlen(strCryptData), (int *)&dwOut);
PK11SlotInfo *pK11Slot = fpPK11_GetInternalKeySlot();
if (pK11Slot) {
SECItem pInSecItem, pOutSecItem;
pInSecItem.data = lpBuffer;
pInSecItem.len = dwOut;
pOutSecItem.data = 0;
pOutSecItem.len = 0;
if (fpPK11SDR_Decrypt(&pInSecItem, &pOutSecItem, NULL) == 0) {
strClearData = (LPSTR)malloc(pOutSecItem.len + 1);
memcpy(strClearData, pOutSecItem.data, pOutSecItem.len);
*(strClearData+pOutSecItem.len) = '\0';
}
fpPK11_FreeSlot(pK11Slot);
}
return strClearData;
}
为了能够处理各种浏览器的记录,每个浏览器都使用自己的方法来存储日期和时间。需要将每个日期/时间记录转换为单一格式,以便稍后可以用于操作,例如查找给定日期的所有凭据,或自从上次检查以来。当涉及到Firefox时,它以大整数的形式存储日期。这个数字称为Epoch Time。可以将Epoch Time输入到这个网站,看看它代表的日期,反之亦然。
首先,从JSON元素中读取日期/时间,但只使用前10个字符(共13个字符)。
double tempnum = json_object_get_number(commit, strDateTime);
string datetime;
datetime = std::to_string(tempnum);
datetime = datetime.substr(0, 10);
Parson库提供了一个函数,用于读取数字为"double",这就是为什么首先将日期/时间读取到一个double变量中。接下来,将double转换为std::string。然后,去掉最后3位数字,留下字符串长度为10个字符。调用FirefoxTimeToSysTime(),将字符串发送给它。将SYSTEMTIME转换为CTime非常简单:
SYSTEMTIME newtime = utils::FirefoxTimeToSysTime(datetime);
CTime ct(newtime);
稍后可以在报告或屏幕上显示数据,将CTime变量转换为用户友好的日期格式。在Secured Globe, Inc.,使用以下格式:
#define SG_FRIEDLY_DATEFORMAT L"%d-%m-%Y, %H:%M:%S"
因此,当需要使用存储的凭据并显示其一个元素时,将使用:
credentials[i].DateCreated.FormatGmt(SG_FRIEDLY_DATEFORMAT).GetBuffer();
FormatGmt很重要,如果想处理来自不同国家的用户的数据。对于本地使用,代码将是:
credentials[i].DateCreated.Format(SG_FRIEDLY_DATEFORMAT).GetBuffer();