diff --git a/README.md b/README.md index 7da6673..5c0dd08 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,19 @@ > Disclaimer: This tool is only intended for security research. Users are responsible for all legal and related liabilities resulting from the use of this tool. The original author does not assume any legal responsibility. +## Recent Updates + +### Firefox 144+ Support + +HackBrowserData now supports decryption of saved passwords in Firefox 144 and later versions. + +Starting from Firefox 144, Mozilla migrated password encryption from 3DES to AES-256-CBC to enhance security. HackBrowserData has been updated accordingly and remains fully compatible with the latest Firefox encryption scheme. + +For more details: +- [Firefox 144.0 Release Notes](https://www.firefox.com/en-US/firefox/144.0/releasenotes/) +- [How Firefox securely saves passwords](https://support.mozilla.org/en-US/kb/how-firefox-securely-saves-passwords) + + ## Supported Browser ### Windows diff --git a/browser/firefox/firefox.go b/browser/firefox/firefox.go index cc6fa52..13791e9 100644 --- a/browser/firefox/firefox.go +++ b/browser/firefox/firefox.go @@ -117,7 +117,8 @@ func queryMetaData(db *sql.DB) ([]byte, []byte, error) { } func queryNssPrivate(db *sql.DB) ([]byte, []byte, error) { - const query = `SELECT a11, a102 from nssPrivate` + // To ensure compatibility with newer profiles, always select the newest key. + const query = `SELECT a11, a102 from nssPrivate ORDER BY id DESC LIMIT 1` var nssA11, nssA102 []byte if err := db.QueryRow(query).Scan(&nssA11, &nssA102); err != nil { return nil, nil, err @@ -160,7 +161,11 @@ func processMasterKey(metaItem1, metaItem2, nssA11, nssA102 []byte) ([]byte, err if len(finallyKey) < 24 { return nil, errors.New("length of final key is less than 24 bytes") } - return finallyKey[:24], nil + // Historically, the derived PBE key was truncated to 24 bytes for 3DES usage. + // Starting from Firefox 144+, NSS switches to AES-256-CBC without changing + // the underlying key derivation logic. The full derived key must be preserved + // to support modern cipher suites. + return finallyKey, nil } func (f *Firefox) Name() string { diff --git a/browserdata/password/password.go b/browserdata/password/password.go index 031734a..7fc8117 100644 --- a/browserdata/password/password.go +++ b/browserdata/password/password.go @@ -228,7 +228,11 @@ func getFirefoxLoginData() ([]loginData, error) { user []byte pass []byte ) + // Use formSubmitURL if available, otherwise fallback to hostname m.LoginURL = v.Get("formSubmitURL").String() + if m.LoginURL == "" { + m.LoginURL = v.Get("hostname").String() + } user, err = base64.StdEncoding.DecodeString(v.Get("encryptedUsername").String()) if err != nil { return nil, err diff --git a/crypto/asn1pbe.go b/crypto/asn1pbe.go index 4c2b97c..b4b8f3f 100644 --- a/crypto/asn1pbe.go +++ b/crypto/asn1pbe.go @@ -179,12 +179,37 @@ type loginPBE struct { func (l loginPBE) Decrypt(globalSalt []byte) ([]byte, error) { key, iv := l.deriveKeyAndIV(globalSalt) - return DES3Decrypt(key, iv, l.Encrypted) + // The encryption algorithm can be reliably inferred from IV length: + // - 8 bytes : 3DES-CBC (legacy Firefox versions) + // - 16 bytes : AES-CBC (Firefox 144+) + if len(iv) == 8 { + // Use 3DES for old Firefox versions + return DES3Decrypt(key[:24], iv, l.Encrypted) + } else if len(iv) == 16 { + // Firefox 144+ uses 32-byte keys (AES-256-CBC) + // Note: AES128CBCDecrypt is a misnomer - it actually supports all AES key lengths + return AES128CBCDecrypt(key, iv, l.Encrypted) + } + + return nil, errors.New("unsupported IV length for loginPBE decryption") } func (l loginPBE) Encrypt(globalSalt, plaintext []byte) ([]byte, error) { key, iv := l.deriveKeyAndIV(globalSalt) - return DES3Encrypt(key, iv, plaintext) + // The encryption algorithm can be reliably inferred from IV length: + // - 8 bytes : 3DES-CBC (legacy Firefox versions) + // - 16 bytes : AES-CBC (Firefox 144+) + // This avoids relying on NSS-specific OIDs, which have changed historically. + if len(iv) == 8 { + // Use 3DES for old Firefox versions + return DES3Encrypt(key[:24], iv, plaintext) + } else if len(iv) == 16 { + // Firefox 144+ uses 32-byte keys (AES-256-CBC) + // Note: AES128CBCDecrypt is a misnomer - it actually supports all AES key lengths + return AES128CBCEncrypt(key, iv, plaintext) + } + + return nil, errors.New("unsupported IV length for loginPBE encryption") } func (l loginPBE) deriveKeyAndIV(globalSalt []byte) ([]byte, []byte) { diff --git a/crypto/crypto.go b/crypto/crypto.go index 71de252..6a5d19a 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -11,6 +11,11 @@ import ( var ErrCiphertextLengthIsInvalid = errors.New("ciphertext length is invalid") +// AES128CBCDecrypt decrypts data using AES-CBC mode. +// Note: Despite the function name, this supports all AES key sizes. +// The Go standard library's aes.NewCipher automatically selects the AES variant +// based on the key length: 16 bytes (AES-128), 24 bytes (AES-192), or 32 bytes (AES-256). +// TODO: Rename to AESCBCDecrypt to avoid confusion about supported key lengths. func AES128CBCDecrypt(key, iv, ciphertext []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil {