web-dev-qa-db-ja.com

Firefoxパスワードデータベースのクエリ

Braiam FirefoxはログインWebサイトのパスワードデータを~/.mozilla/firefox/key3.dbおよび~/.mozilla/firefox/signons.sqliteファイルに保存すると述べています。これらのファイルは、sqliteエディターで読み取ることができます。

FirefoxのデータベースからWebサイトのユーザー名とパスワード(例 https://sourceforge.net/account/login.php )を照会しようとしています。 FirefoxのGUIが機能していないため、Firefoxを使用してそれを行うことはできません。また、データベースを使用して仕事をすることを学ぶのはかなり初めてで、また興味があります。

  1. key3.dbsignons.sqliteの異なる役割は何ですか?
  2. インターネットで検索しましたが、sqlite3を使用してデータベースを開くことは正しいですか?

    $ 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
    

    失敗の理由は、Firefoxでは、保存されているパスワードにアクセスするようにmasterキーワードを設定したためだと思います。特定のWebサイトのパスワードを照会するにはどうすればよいですか?

    私のOSはUbuntuです。ファイルタイプはkey3.dbです。

    $ file key3.db 
    key3.db: Berkeley DB 1.85 (Hash, version 2, native byte-order)
    
  3. 特定のWebサイト名からパスワードを照会するには、何を読んで学習する必要がありますか?

    読書 http://www.sqlite.org/cli.html は役に立ちますか?


ガレス・ザ・レッド:

私はあなたの命令を試みました。 ただし、何も返さない。 出力はひどいです:

$ 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|\4
...

Firefoxはマスターキーがあるかどうかに関係なくパスワードを暗号化しますか?はいの場合、コマンドラインでそれらを復号化できますか(Firefox CLIは引き続き機能します)?

または、ChromeブラウザがFirefoxに保存されているパスワードを読み取ってインポートできる可能性はありますか?

5
Tim

一部の男は必要なすべてのコードを一緒に接着しているようです ここ

#!/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

Mozillaフォーラムの 関連する議論 を参照してください。

4

残念ながら、他の回答にリンクされているPythonスクリプトは両方とも、システムのセグメンテーション違反で同様に失敗します。私が見つけたのは nss-passwords で、これはOCAMLとCなので、どこでも機能するかどうかはわかりませんが、UbuntuなどのDebianベースのディストリビューションに少なくとも簡単にインストールできます。

$ 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 |

私はそれをstackverflowのパスワードを見つけるために使用したので、それについて書くことができ、それは素晴らしい働きをします!なんてメタだ!

2
Marcos Dione

ファイルkey3.dbには、signons.sqliteに格納されているパスワードの暗号化に使用されるキーが含まれています。

カスタム形式であるため、標準のデータベースコマンドを使用する代わりに、特別なプログラムを使用して作業する必要があります。

Windowsがkey3.dbファイルを利用するためのツールがあるようです。SOでこの質問の回答を参照してください: firefoxプロファイルのkey3.dbデータベースの暗号化キーは何ですか?

@StéphaneChazelasの答えは、Linuxで機能するpythonスクリプトを提供します。
最新バージョンはこちら: https://hg.cryptobitch.de/firefox-passwords/file/

2
Volker Siegel

filekey3.dbがBerleley DB 1.85形式であることを示していますが、そうではありません。これは、Mozilla独自の形式です。 signons.sqliteのユーザー名とパスワードを暗号化するために使用されます。

signons.sqliteを使用して、日付を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]

特定のWebサイトを検索するには、基本的なSQLクエリを使用します。

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

検索フレーズは二重引用符で囲まれていることに注意してください。 %はワイルドカードであるため、上記の例では、Arch、任意のテキストが続くテキストを探します。これはhttp://bbs.archlinux.orgをカバーしています。

2
garethTheRed

Ubuntu/Debianでは、nss-passwordsパッケージをインストールできます。これを使用すると、パスワードを1つずつ(サイトごとに)取得できます。コマンドcert8.dbを使用して、key3.dblogins.jsonsignons.sqlitenss-passwords -d <path-with-files> <site-url-to-query>ファイルを含むディレクトリを指定できます(デフォルトでは、Firefoxプロファイルが検索されます)。

サイトの名前を照会するには、Firefoxの新しいバージョン(> 32)でクリアテキストファイルlogins.jsonを検索するか、sqlite3 signons.sqliteを使用してからselect * from moz_logins;を使用します。

たとえば、新しいFirefoxプロファイルを使用してすべてのサイトをクエリする場合は、ファイルcert8.dbkey3.dblogins.jsonをディレクトリにコピーして実行する必要があります。

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

注:jqはJSONを処理するためのツールです。

1
greuze