Query banco de dados de senha do Firefox

5

Braiam disse que o Firefox armazena os dados de senha para sites de login em ~/.mozilla/firefox/key3.db e ~/.mozilla/firefox/signons.sqlite arquivos. Esses arquivos podem ser lidos com algum editor de sqlite.

Eu tento consultar meu nome de usuário e senha de um site (por exemplo, link ) no banco de dados do Firefox. Eu não posso fazê-lo através do Firefox, porque o meu Firefox GUI não está funcionando, e eu sou relativamente novo e também interessado em aprender a usar bancos de dados para fazer o trabalho.

  1. quais são os diferentes papéis de key3.db e signons.sqlite ?
  2. Eu pesquisei na internet, e é correto que eu use sqlite3 para abrir um banco de dados?

    $ sqlite3 key3.db 
    SQLite version 3.7.9 2011-11-01 00:52:41
    Enter ".help" for instructions
    Enter SQL statements terminated with a ";"
    sqlite> .tables
    Error: file is encrypted or is not a database
    

    Eu acho que o motivo do fracasso é que, no Firefox, eu configurei um mestre palavra-chave para acessar as senhas que armazena. Como devo proceder para consultar a senha de um determinado site?

    Meu sistema operacional é Ubuntu, aqui está o tipo de arquivo de key3.db :

    $ file key3.db 
    key3.db: Berkeley DB 1.85 (Hash, version 2, native byte-order)
    
  3. O que devo ler e aprender para consultar a senha de um determinado Nome do site?

    A leitura do link ajuda?

Para garethTheRed:

Eu tentei o seu comando. Não retorna nada no entanto. A saída é abismal:

$ sqlite3 signons.sqlite
SQLite version 3.7.9 2011-11-01 00:52:41
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
moz_deleted_logins  moz_disabledHosts   moz_logins        
sqlite> select * from moz_logins;
...
55|https://sourceforge.net||https://sourceforge.net|form_loginname|form_pw|MDIEEPgAAAAAAAAAAAAAAAAAAAEwF\AYIKoZIhvcNAwcECCPrVdOzWamBBAjPs0DI8FrUnQ==|MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECCnZved1LRQMBBBV\DtXpOvAp0TQHibFeX3NL|{16e782de-4c65-426f-81dc-ee0361816262}|1|1327675445094|1403706275829|1327675445094|
...

O Firefox criptografa senhas independentemente de haver uma chave mestra? Se sim, podemos descriptografá-los na linha de comando (minha CLI do firefox ainda pode funcionar)?

Como alternativa, é possível que o navegador Chrome possa ler e importar as senhas armazenadas pelo Firefox?

    
por Tim 10.08.2014 / 15:52

5 respostas

4

Alguns caras parecem ter colado todo o código necessário juntos aqui :

#!/usr/bin/env python
"Recovers your Firefox or Thunderbird passwords"

import base64
from collections import namedtuple
from ConfigParser import RawConfigParser, NoOptionError
from ctypes import (Structure, CDLL, byref, cast, string_at, c_void_p, 
    c_uint, c_ubyte, c_char_p)
from getpass import getpass
import logging
from optparse import OptionParser
import os
try:
    from sqlite3 import dbapi2 as sqlite
except ImportError:
    from pysqlite2 import dbapi2 as sqlite
from subprocess import Popen, CalledProcessError, PIPE
import sys


LOGLEVEL_DEFAULT = 'warn'

log = logging.getLogger()
PWDECRYPT = 'pwdecrypt'

SITEFIELDS = ['id', 'hostname', 'httpRealm', 'formSubmitURL', 'usernameField', 'passwordField', 'encryptedUsername', 'encryptedPassword', 'guid', 'encType', 'plain_username', 'plain_password' ]
Site = namedtuple('FirefoxSite', SITEFIELDS)
'''The format of the SQLite database is:
(id                 INTEGER PRIMARY KEY,hostname           TEXT NOT NULL,httpRealm          TEXT,formSubmitURL      TEXT,usernameField      TEXT NOT NULL,passwordField      TEXT NOT NULL,encryptedUsername  TEXT NOT NULL,encryptedPassword  TEXT NOT NULL,guid               TEXT,encType            INTEGER);
'''



#### These are libnss definitions ####
class SECItem(Structure):
    _fields_ = [('type',c_uint),('data',c_void_p),('len',c_uint)]

class secuPWData(Structure):
    _fields_ = [('source',c_ubyte),('data',c_char_p)]

(PW_NONE, PW_FROMFILE, PW_PLAINTEXT, PW_EXTERNAL) = (0, 1, 2, 3)
# SECStatus
(SECWouldBlock, SECFailure, SECSuccess) = (-2, -1, 0)
#### End of libnss definitions ####


def get_default_firefox_profile_directory(dir='~/.mozilla/firefox'):
    '''Returns the directory name of the default profile

    If you changed the default dir to something like ~/.thunderbird,
    you would get the Thunderbird default profile directory.'''

    profiles_dir = os.path.expanduser(dir)
    profile_path = None

    cp = RawConfigParser()
    cp.read(os.path.join(profiles_dir, "profiles.ini"))
    for section in cp.sections():
        if not cp.has_option(section, "Path"):
            continue

        if (not profile_path or
            (cp.has_option(section, "Default") and cp.get(section, "Default").strip() == "1")):
            profile_path = os.path.join(profiles_dir, cp.get(section, "Path").strip())

    if not profile_path:
        raise RuntimeError("Cannot find default Firefox profile")

    return profile_path


def get_encrypted_sites(firefox_profile_dir=None):
    'Opens signons.sqlite and yields encryped password data'

    if firefox_profile_dir is None:
        firefox_profile_dir = get_default_firefox_profile_directory()
    password_sqlite = os.path.join(firefox_profile_dir, "signons.sqlite")
    query = '''SELECT id, hostname, httpRealm, formSubmitURL,
                      usernameField, passwordField, encryptedUsername,
                      encryptedPassword, guid, encType, 'noplainuser', 'noplainpasswd' FROM moz_logins;'''

    # We don't want to type out all the column from the DB as we have 
    ## stored them in the SITEFIELDS already. However, we have two 
    ## components extra, the plain usename and password. So we remove 
    ## that from the list, because the table doesn't have that column. 
    ## And we add two literal SQL strings to make our "Site" data 
    ## structure happy
    #queryfields = SITEFIELDS[:-2] + ["'noplainuser'", "'noplainpassword'"]
    #query = '''SELECT %s 
    #           FROM moz_logins;''' % ', '.join(queryfields)

    connection = sqlite.connect(password_sqlite)
    try:
        cursor = connection.cursor()
        cursor.execute(query)

        for site in map(Site._make, cursor.fetchall()):
          yield site
    finally:
        connection.close()

def decrypt(encrypted_string, firefox_profile_directory, password = None):
    '''Opens an external tool to decrypt strings

    This is mostly for historical reasons or if the API changes. It is 
    very slow because it needs to call out a lot. It uses the 
    "pwdecrypt" tool which you might have packaged. Otherwise, you 
    need to build it yourself.'''

    log = logging.getLogger('firefoxpasswd.decrypt')
    execute = [PWDECRYPT, '-d', firefox_profile_directory]
    if password:
        execute.extend(['-p', password])
    process = Popen(execute,
                    stdin=PIPE, stdout=PIPE, stderr=PIPE)
    output, error = process.communicate(encrypted_string)

    log.debug('Sent: %s', encrypted_string)
    log.debug('Got: %s', output)

    NEEDLE = 'Decrypted: "' # This string is prepended to the decrypted password if found
    output = output.strip()
    if output == encrypted_string:
        log.error('Password was not correct. Please try again without a '
                   'password or with the correct one')

    index = output.index(NEEDLE) + len(NEEDLE)
    password = output[index:-1] # And we strip the final quotation mark

    return password


class NativeDecryptor(object):
    'Calls the NSS API to decrypt strings'

    def __init__(self, directory, password = ''):
        '''You need to give the profile directory and optionally a 
        password. If you don't give a password but one is needed, you 
        will be prompted by getpass to provide one.'''
        self.directory = directory
        self.log = logging.getLogger('NativeDecryptor')
        self.log.debug('Trying to work on %s', directory)

        self.libnss = CDLL('libnss3.so')
        if self.libnss.NSS_Init(directory) != 0:
            self.log.error('Could not initialize NSS')

        # Initialize to the empty string, not None, because the password
        # function expects rather an empty string
        self.password = password = password or ''


        slot = self.libnss.PK11_GetInternalKeySlot()

        pw_good = self.libnss.PK11_CheckUserPassword(slot, c_char_p(password))
        while pw_good != SECSuccess:
            msg = 'Password is not good (%d)!' % pw_good
            print >>sys.stderr, msg
            password = getpass('Please enter password: ')
            pw_good = self.libnss.PK11_CheckUserPassword(slot, c_char_p(password))
            #raise RuntimeError(msg)

        # That's it, we're done with passwords, but we leave the old 
        # code below in, for nostalgic reasons.

        if password is None:
            pwdata = secuPWData()
            pwdata.source = PW_NONE
            pwdata.data = 0
        else:
            # It's not clear whether this actually works
            pwdata = secuPWData()
            pwdata.source = PW_PLAINTEXT
            pwdata.data = c_char_p (password) 
            # It doesn't actually work :-(


            # Now follow some attempts that were not succesful!
            def setpwfunc():
                # One attempt was to use PK11PassworFunc. Didn't work.
                def password_cb(slot, retry, arg):
                    #s = self.libnss.PL_strdup(password)
                    s = self.libnss.PL_strdup("foo")
                    return s

                PK11PasswordFunc = CFUNCTYPE(c_void_p, PRBool, c_void_p)
                c_password_cb = PK11PasswordFunc(password_cb)
                #self.libnss.PK11_SetPasswordFunc(c_password_cb)


            # To be ignored
            def changepw():                
                # Another attempt was to use ChangePW. Again, no effect.
                #ret = self.libnss.PK11_ChangePW(slot, pwdata.data, 0);
                ret = self.libnss.PK11_ChangePW(slot, password, 0)
                if ret == SECFailure:
                    raise RuntimeError('Setting password failed! %s' % ret)

        #self.pwdata = pwdata


    def __del__(self):
        self.libnss.NSS_Shutdown()


    def decrypt(self, string, *args):
        'Decrypts a given string'

        libnss =  self.libnss

        uname = SECItem()
        dectext = SECItem()        
        #pwdata = self.pwdata

        cstring = SECItem()
        cstring.data  = cast( c_char_p( base64.b64decode(string)), c_void_p)
        cstring.len = len(base64.b64decode(string))
        #if libnss.PK11SDR_Decrypt (byref (cstring), byref (dectext), byref (pwdata)) == -1:
        self.log.debug('Trying to decrypt %s (error: %s)', string, libnss.PORT_GetError())
        if libnss.PK11SDR_Decrypt (byref (cstring), byref (dectext)) == -1:
            error = libnss.PORT_GetError()
            libnss.PR_ErrorToString.restype = c_char_p
            error_str = libnss.PR_ErrorToString(error)
            raise Exception ("%d: %s" % (error, error_str))

        decrypted_data = string_at(dectext.data, dectext.len)

        return decrypted_data


    def encrypted_sites(self):
        'Yields the encryped passwords from the profile'
        sites = get_encrypted_sites(self.directory)

        return sites


    def decrypted_sites(self):
        'Decrypts the encrypted_sites and yields the results'

        sites = self.encrypted_sites()

        for site in sites:
            plain_user = self.decrypt(site.encryptedUsername)
            plain_password = self.decrypt(site.encryptedPassword)
            site = site._replace(plain_username=plain_user,
                plain_password=plain_password)

            yield site


def get_firefox_sites_with_decrypted_passwords(firefox_profile_directory = None, password = None):
    'Old school decryption of passwords using the external tool'
    if not firefox_profile_directory:
        firefox_profile_directory = get_default_firefox_profile_directory()
    #decrypt = NativeDecryptor(firefox_profile_directory).decrypt
    for site in get_encrypted_sites(firefox_profile_directory):
        plain_user = decrypt(site.encryptedUsername, firefox_profile_directory, password)
        plain_password = decrypt(site.encryptedPassword, firefox_profile_directory, password)
        site = site._replace(plain_username=plain_user, plain_password=plain_password)
        log.debug("Dealing with Site: %r", site)
        log.info("user: %s, passwd: %s", plain_user, plain_password)
        yield site

def main_decryptor(firefox_profile_directory, password, thunderbird=False):
    'Main function to get Firefox and Thunderbird passwords'
    if not firefox_profile_directory:
        if thunderbird:
            dir = '~/.thunderbird/'
        else:
            dir = '~/.mozilla/firefox'
        firefox_profile_directory = get_default_firefox_profile_directory(dir)

    decryptor = NativeDecryptor(firefox_profile_directory, password)

    for site in decryptor.decrypted_sites():
        print site

if __name__ == "__main__":
    parser = OptionParser()
    parser.add_option("-d", "--directory", default=None,
                  help="the Firefox profile directory to use")
    parser.add_option("-p", "--password", default=None,
                  help="the master password for the Firefox profile")
    parser.add_option("-l", "--loglevel", default=LOGLEVEL_DEFAULT,
                  help="the level of logging detail [debug, info, warn, critical, error]")
    parser.add_option("-t", "--thunderbird", default=False, action='store_true',
                  help="by default we try to find the Firefox default profile."
                  " But you can as well ask for Thunderbird's default profile."
                  " For a more reliable way, give the directory with -d.")
    parser.add_option("-n", "--native", default=True, action='store_true',
                  help="use the native decryptor, i.e. make Python use "
                  "libnss directly instead of invoking the helper program"
                  "DEFUNCT! this option will not be checked.")
    parser.add_option("-e", "--external", default=False, action='store_true',
                  help="use an external program 'pwdecrypt' to actually "
                    "decrypt the passwords. This calls out a lot and is dead "
                    "slow. "
                    "You need to use this method if you have a password "
                    "protected database though.")
    options, args = parser.parse_args()

    loglevel = {'debug': logging.DEBUG, 'info': logging.INFO,
                'warn': logging.WARN, 'critical':logging.CRITICAL,
                'error': logging.ERROR}.get(options.loglevel, LOGLEVEL_DEFAULT)
    logging.basicConfig(level=loglevel)
    log = logging.getLogger()

    password = options.password

    if not options.external:
        sys.exit (main_decryptor(options.directory, password, thunderbird=options.thunderbird))
    else:
        for site in get_firefox_sites_with_decrypted_passwords(options.directory, password):
            print site

Veja a discussão relacionada nos fóruns do mozilla.

    
por 10.08.2014 / 22:37
2

Embora file mostre key3.db no formato Berleley DB 1.85, não é o caso. Está em um formato proprietário da Mozilla. É usado para criptografar os nomes de usuários e senhas em signons.sqlite .

Você pode ver a data em signons.sqlite (mas não decifrar os nomes de usuários e senhas) usando sqlite3 :

sqlite3 signons.sqlite
SQLite version 3.8.5 2014-06-04 14:06:34
Enter ".help" for usage hints.
sqlite> .tables
moz_deleted_logins  moz_disabledHosts   moz_logins        
sqlite> select * from moz_logins;
1|https://bugs.archlinux.org||https://bugs.archlinux.org|user_name|password|MDoEEP...
[more here]

Para pesquisar um site específico, use uma consulta SQL básica:

sqlite> select * FROM moz_logins WHERE hostname LIKE "%arch%";
32|https://bbs.archlinux.org||https://bbs.archlinux.org|req_username|req_password|MD...

Observe que a frase de pesquisa está entre aspas duplas. O % é um curinga, portanto, no exemplo acima, ele procura por qualquer texto, seguido por arch , seguido por qualquer texto. Isso cobre http://bbs.archlinux.org .

    
por 10.08.2014 / 17:33
2

O arquivo key3.db contém a chave usada para criptografar as senhas armazenadas em signons.sqlite .

Como está em um formato personalizado, é necessário um programa especial para trabalhar com ele, em vez de usar comandos de banco de dados padrão.

Parece haver uma ferramenta para o Windows usar o arquivo key3.db , Veja a resposta desta pergunta no SO: Qual é a chave de criptografia do banco de dados key3.db no perfil do firefox?

A resposta de @ StéphaneChazelas fornece um script python que deve funcionar no Linux; Última versão aqui: link

    
por 10.08.2014 / 17:07
2

Infelizmente, os dois scripts Python vinculados nas outras respostas falham de maneira semelhante com uma falha de segmentação no meu sistema. O que eu encontrei é nss-senhas , que parece estar escrito em OCAML e C, então Eu não tenho certeza se funciona em todo lugar, mas é pelo menos facilmente instalável em distros baseadas no Debian como o Ubuntu:

$ sudo apt install nss-passwords
[...]
$ nss-passwords stackoverflow
[it asks for the main password]
| https://stackoverflow.com | [email protected] | this_is_not_my_real_password |

Usei-o apenas para encontrar a senha do stackverflow para poder escrever sobre ele e ele funciona muito bem! Como meta é isso!

    
por 26.12.2016 / 12:59
1

No Ubuntu / Debian você pode instalar o nss-passwords package, que pode ser usado para obter as senhas uma por uma (por site). Você pode especificar um diretório que contenha cert8.db , key3.db , logins.json signons.sqlite arquivos (o padrão tentará encontrar o perfil do Firefox) com o comando nss-passwords -d <path-with-files> <site-url-to-query> .

O nome dos sites pode ser consultado procurando no arquivo de texto não criptografado logins.json em versões mais recentes do firefox (> 32) ou usando sqlite3 signons.sqlite e, em seguida, select * from moz_logins;

Por exemplo, se você quiser consultar todos os sites, com um novo perfil do Firefox, precisará copiar os arquivos cert8.db , key3.db , logins.json para um diretório e executar:

cat logins.json | jq ".logins[].hostname" | uniq | xargs nss-passwords -d .

Nota: jq é uma ferramenta para lidar com JSONs.

    
por 20.04.2017 / 11:36