- Implement `Browser` interface and related functions - Add `Passwords` method to `chromium` for exporting and returning passwords - Ignore `/docs` directoryfeat/library
parent
3eaa2dd2bf
commit
aa2e1af5b5
@ -0,0 +1,86 @@ |
|||||||
|
package hackbrowserdata |
||||||
|
|
||||||
|
type Browser interface { |
||||||
|
BrowserData |
||||||
|
|
||||||
|
Init() error |
||||||
|
} |
||||||
|
|
||||||
|
func NewBrowser(b browser, options ...BrowserOption) (Browser, error) { |
||||||
|
browser := browsers[b] |
||||||
|
if setter, ok := browser.(browserOptionsSetter); ok { |
||||||
|
for _, option := range options { |
||||||
|
option(setter) |
||||||
|
} |
||||||
|
} |
||||||
|
if err := browser.Init(); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return browser, nil |
||||||
|
} |
||||||
|
|
||||||
|
type browser string |
||||||
|
|
||||||
|
type BrowserData interface { |
||||||
|
Passwords() ([]Password, error) |
||||||
|
|
||||||
|
Cookies() ([]Cookie, error) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) BrowsingData(items []browserDataType) ([]BrowserData, error) { |
||||||
|
for _, item := range items { |
||||||
|
_ = item |
||||||
|
} |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) AllBrowsingData() ([]BrowserData, error) { |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (f *firefox) BrowsingData(items []browserDataType) (BrowserData, error) { |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
|
||||||
|
const ( |
||||||
|
Chrome browser = "chrome" |
||||||
|
Firefox browser = "firefox" |
||||||
|
Yandex browser = "yandex" |
||||||
|
) |
||||||
|
|
||||||
|
type browserType int |
||||||
|
|
||||||
|
const ( |
||||||
|
browserTypeChromium browserType = iota + 1 |
||||||
|
browserTypeFirefox |
||||||
|
browserTypeYandex |
||||||
|
) |
||||||
|
|
||||||
|
func (b browser) Type() browserType { |
||||||
|
switch b { |
||||||
|
case Firefox: |
||||||
|
return browserTypeYandex |
||||||
|
case Yandex: |
||||||
|
return browserTypeFirefox |
||||||
|
default: |
||||||
|
return browserTypeChromium |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
browsers = map[browser]Browser{ |
||||||
|
Chrome: &chromium{ |
||||||
|
name: Chrome, |
||||||
|
storage: chromeStorageName, |
||||||
|
profilePath: chromeProfilePath, |
||||||
|
supportedData: []browserDataType{TypePassword}, |
||||||
|
}, |
||||||
|
Firefox: &firefox{ |
||||||
|
name: "", |
||||||
|
storage: "", |
||||||
|
profilePath: "", |
||||||
|
}, |
||||||
|
Yandex: &chromium{}, |
||||||
|
} |
||||||
|
) |
@ -0,0 +1,60 @@ |
|||||||
|
package hackbrowserdata |
||||||
|
|
||||||
|
type browserDataType int |
||||||
|
|
||||||
|
const ( |
||||||
|
TypePassword browserDataType = iota + 1 |
||||||
|
TypeCookie |
||||||
|
TypeHistory |
||||||
|
TypeBookmark |
||||||
|
TypeCreditCard |
||||||
|
TypeDownload |
||||||
|
TypeExtensions |
||||||
|
TypeSessionStorage |
||||||
|
TypeLocalStorage |
||||||
|
) |
||||||
|
|
||||||
|
func (i browserDataType) Filename(b browser) string { |
||||||
|
switch b.Type() { |
||||||
|
case browserTypeChromium: |
||||||
|
return i.chromiumFilename() |
||||||
|
case browserTypeFirefox: |
||||||
|
return i.firefoxFilename() |
||||||
|
case browserTypeYandex: |
||||||
|
return i.yandexFilename() |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
func (i browserDataType) chromiumFilename() string { |
||||||
|
switch i { |
||||||
|
case TypePassword: |
||||||
|
return "Login Data" |
||||||
|
case TypeCookie: |
||||||
|
return "Cookies" |
||||||
|
case TypeHistory: |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
func (i browserDataType) yandexFilename() string { |
||||||
|
switch i { |
||||||
|
case TypePassword: |
||||||
|
return "Login State" |
||||||
|
case TypeCookie: |
||||||
|
return "Cookies" |
||||||
|
case TypeHistory: |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
func (i browserDataType) firefoxFilename() string { |
||||||
|
switch i { |
||||||
|
case TypePassword: |
||||||
|
return "logins.json" |
||||||
|
case TypeCookie: |
||||||
|
return "cookies.sqlite" |
||||||
|
case TypeHistory: |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
@ -0,0 +1,139 @@ |
|||||||
|
package hackbrowserdata |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"crypto/sha1" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io/fs" |
||||||
|
"os/exec" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"golang.org/x/crypto/pbkdf2" |
||||||
|
|
||||||
|
"github.com/moond4rk/hackbrowserdata/utils/fileutil" |
||||||
|
) |
||||||
|
|
||||||
|
type chromium struct { |
||||||
|
name browser |
||||||
|
storage string |
||||||
|
profilePath string |
||||||
|
profilePaths []string |
||||||
|
disableFindAllUser bool |
||||||
|
masterKey []byte |
||||||
|
supportedData []browserDataType |
||||||
|
supportedDataMap map[browserDataType]struct{} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) Init() error { |
||||||
|
if err := c.initBrowserData(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if err := c.initProfile(); err != nil { |
||||||
|
return fmt.Errorf("profile path '%s' does not exist %w", c.profilePath, ErrBrowserNotExists) |
||||||
|
} |
||||||
|
if err := c.initMasterKey(); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) initBrowserData() error { |
||||||
|
if c.supportedDataMap == nil { |
||||||
|
c.supportedDataMap = make(map[browserDataType]struct{}) |
||||||
|
} |
||||||
|
for _, v := range c.supportedData { |
||||||
|
c.supportedDataMap[v] = struct{}{} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) initProfile() error { |
||||||
|
if !fileutil.IsDirExists(c.profilePath) { |
||||||
|
return ErrBrowserNotExists |
||||||
|
} |
||||||
|
if !c.disableFindAllUser { |
||||||
|
profilesPaths, err := c.findAllProfiles() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
c.profilePaths = profilesPaths |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) findAllProfiles() ([]string, error) { |
||||||
|
var profiles []string |
||||||
|
root := fileutil.ParentDir(c.profilePath) |
||||||
|
err := filepath.Walk(root, func(path string, info fs.FileInfo, err error) error { |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
// if the path ends with "History", add it to the list
|
||||||
|
if strings.HasSuffix(path, "History") { |
||||||
|
if !strings.Contains(path, "System Profile") { |
||||||
|
profiles = append(profiles, filepath.Dir(path)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// calculate the depth of the current path
|
||||||
|
depth := len(strings.Split(path, string(filepath.Separator))) - len(strings.Split(root, string(filepath.Separator))) |
||||||
|
|
||||||
|
// if the depth is more than 2 and it's a directory, skip it
|
||||||
|
if info.IsDir() && path != root && depth >= 2 { |
||||||
|
return filepath.SkipDir |
||||||
|
} |
||||||
|
|
||||||
|
return err |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return profiles, err |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) initMasterKey() error { |
||||||
|
var ( |
||||||
|
stdout, stderr bytes.Buffer |
||||||
|
) |
||||||
|
args := []string{"find-generic-password", "-wa", strings.TrimSpace(c.storage)} |
||||||
|
cmd := exec.Command("security", args...) //nolint:gosec
|
||||||
|
cmd.Stdout = &stdout |
||||||
|
cmd.Stderr = &stderr |
||||||
|
if err := cmd.Run(); err != nil { |
||||||
|
return fmt.Errorf("run security command failed: %w, message %s", err, stderr.String()) |
||||||
|
} |
||||||
|
|
||||||
|
if stderr.Len() > 0 { |
||||||
|
if strings.Contains(stderr.String(), "could not be found") { |
||||||
|
return ErrCouldNotFindInKeychain |
||||||
|
} |
||||||
|
return errors.New(stderr.String()) |
||||||
|
} |
||||||
|
|
||||||
|
secret := bytes.TrimSpace(stdout.Bytes()) |
||||||
|
if len(secret) == 0 { |
||||||
|
return ErrNoPasswordInOutput |
||||||
|
} |
||||||
|
salt := []byte("saltysalt") |
||||||
|
// @https://source.chromium.org/chromium/chromium/src/+/master:components/os_crypt/os_crypt_mac.mm;l=157
|
||||||
|
key := pbkdf2.Key(secret, salt, 1003, 16, sha1.New) |
||||||
|
if key == nil { |
||||||
|
return ErrWrongSecurityCommand |
||||||
|
} |
||||||
|
c.masterKey = key |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) setProfilePath(p string) { |
||||||
|
c.profilePath = p |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) setEnableAllUsers(e bool) { |
||||||
|
c.disableFindAllUser = e |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) setStorageName(s string) { |
||||||
|
c.storage = s |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
package hackbrowserdata |
||||||
|
|
||||||
|
import ( |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func TestChromium_Init(t *testing.T) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
func BenchmarkChromium_Init(b *testing.B) { |
||||||
|
chromium := browsers[Chrome] |
||||||
|
for i := 0; i < b.N; i++ { |
||||||
|
if err := chromium.Init(); err != nil { |
||||||
|
b.Fatal(err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
package hackbrowserdata |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
chromeStorageName = "Chrome" |
||||||
|
chromeBetaStorageName = "Chrome" |
||||||
|
chromiumStorageName = "Chromium" |
||||||
|
edgeStorageName = "Microsoft Edge" |
||||||
|
braveStorageName = "Brave" |
||||||
|
operaStorageName = "Opera" |
||||||
|
vivaldiStorageName = "Vivaldi" |
||||||
|
coccocStorageName = "CocCoc" |
||||||
|
yandexStorageName = "Yandex" |
||||||
|
arcStorageName = "Arc" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
homeDir, _ = os.UserHomeDir() |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
chromeProfilePath = homeDir + "/Library/Application Support/Google/Chrome/Default/" |
||||||
|
chromeBetaProfilePath = homeDir + "/Library/Application Support/Google/Chrome Beta/Default/" |
||||||
|
chromiumProfilePath = homeDir + "/Library/Application Support/Chromium/Default/" |
||||||
|
edgeProfilePath = homeDir + "/Library/Application Support/Microsoft Edge/Default/" |
||||||
|
braveProfilePath = homeDir + "/Library/Application Support/BraveSoftware/Brave-Browser/Default/" |
||||||
|
operaProfilePath = homeDir + "/Library/Application Support/com.operasoftware.Opera/Default/" |
||||||
|
operaGXProfilePath = homeDir + "/Library/Application Support/com.operasoftware.OperaGX/Default/" |
||||||
|
vivaldiProfilePath = homeDir + "/Library/Application Support/Vivaldi/Default/" |
||||||
|
coccocProfilePath = homeDir + "/Library/Application Support/Coccoc/Default/" |
||||||
|
yandexProfilePath = homeDir + "/Library/Application Support/Yandex/YandexBrowser/Default/" |
||||||
|
arcProfilePath = homeDir + "/Library/Application Support/Arc/User Data/Default" |
||||||
|
|
||||||
|
firefoxProfilePath = homeDir + "/Library/Application Support/Firefox/Profiles/" |
||||||
|
) |
@ -0,0 +1,15 @@ |
|||||||
|
package hackbrowserdata |
||||||
|
|
||||||
|
type Cookie struct { |
||||||
|
Domain string |
||||||
|
Expiration float64 |
||||||
|
Value string |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) Cookies() ([]Cookie, error) { |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (f *firefox) Cookies() ([]Cookie, error) { |
||||||
|
return nil, nil |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
package hackbrowserdata |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
ErrBrowserNotExists = errors.New("browser not exists") |
||||||
|
ErrWrongSecurityCommand = errors.New("wrong security command") |
||||||
|
ErrNoPasswordInOutput = errors.New("no password in output") |
||||||
|
ErrCouldNotFindInKeychain = errors.New("could not be find in keychain") |
||||||
|
) |
@ -0,0 +1,50 @@ |
|||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
hkb "github.com/moond4rk/hackbrowserdata" |
||||||
|
) |
||||||
|
|
||||||
|
func main() { |
||||||
|
chrome, err := hkb.NewBrowser(hkb.Chrome) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
passwords, err := chrome.Passwords() |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
for _, pass := range passwords { |
||||||
|
fmt.Printf("%+v\n", pass) |
||||||
|
} |
||||||
|
// cookies, err := chrome.Cookies()
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// creditCards, err := browser.CreditCards()
|
||||||
|
// bookmarks, err := browser.Bookmarks()
|
||||||
|
// downloads, err := browser.Downloads()
|
||||||
|
// extensions, err := browser.Extensions()
|
||||||
|
// history, err := browser.History()
|
||||||
|
// localStorage, err := browser.LocalStorage()
|
||||||
|
// sessionStorage, err := browser.SessionStorage()
|
||||||
|
|
||||||
|
// items := []hkb.browsingDataType{hkb.Cookie, hkb.Password, hkb.History, hkb.Bookmark}
|
||||||
|
// browsingData, err := browser.BrowsingDatas(items)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// _ = cookies
|
||||||
|
// items := []hkb.browsingDataType{hkb.Cookie}
|
||||||
|
// datas, err := browser.BrowsingDatas(items)
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// _ = datas
|
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
package hackbrowserdata |
||||||
|
|
||||||
|
type firefox struct { |
||||||
|
name string |
||||||
|
storage string |
||||||
|
profilePath string |
||||||
|
enableAllUser bool |
||||||
|
masterKey []byte |
||||||
|
} |
||||||
|
|
||||||
|
func (f *firefox) Init() error { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (f *firefox) setEnableAllUsers(e bool) { |
||||||
|
f.enableAllUser = e |
||||||
|
} |
||||||
|
|
||||||
|
func (f *firefox) setProfilePath(p string) { |
||||||
|
f.profilePath = p |
||||||
|
} |
||||||
|
|
||||||
|
func (f *firefox) setStorageName(s string) { |
||||||
|
f.storage = s |
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
package hackbrowserdata |
||||||
|
|
||||||
|
import ( |
||||||
|
"path/filepath" |
||||||
|
) |
||||||
|
|
||||||
|
type BrowserOption func(browserOptionsSetter) |
||||||
|
|
||||||
|
type browserOptionsSetter interface { |
||||||
|
setProfilePath(string) |
||||||
|
|
||||||
|
setEnableAllUsers(bool) |
||||||
|
|
||||||
|
setStorageName(string) |
||||||
|
} |
||||||
|
|
||||||
|
func WithProfilePath(p string) BrowserOption { |
||||||
|
return func(b browserOptionsSetter) { |
||||||
|
b.setProfilePath(filepath.Clean(p)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func WithEnableAllUsers(e bool) BrowserOption { |
||||||
|
return func(b browserOptionsSetter) { |
||||||
|
b.setEnableAllUsers(e) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func WithStorageName(s string) BrowserOption { |
||||||
|
return func(b browserOptionsSetter) { |
||||||
|
b.setStorageName(s) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,111 @@ |
|||||||
|
package hackbrowserdata |
||||||
|
|
||||||
|
import ( |
||||||
|
"database/sql" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"path/filepath" |
||||||
|
"time" |
||||||
|
|
||||||
|
// import sqlite3 driver
|
||||||
|
_ "github.com/mattn/go-sqlite3" |
||||||
|
|
||||||
|
"github.com/moond4rk/hackbrowserdata/crypto" |
||||||
|
"github.com/moond4rk/hackbrowserdata/item" |
||||||
|
"github.com/moond4rk/hackbrowserdata/log" |
||||||
|
"github.com/moond4rk/hackbrowserdata/utils/fileutil" |
||||||
|
"github.com/moond4rk/hackbrowserdata/utils/typeutil" |
||||||
|
) |
||||||
|
|
||||||
|
type Password struct { |
||||||
|
Profile string |
||||||
|
Username string |
||||||
|
Password string |
||||||
|
encryptPass []byte |
||||||
|
encryptUser []byte |
||||||
|
LoginURL string |
||||||
|
CreateDate time.Time |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) Passwords() ([]Password, error) { |
||||||
|
if _, ok := c.supportedDataMap[TypePassword]; !ok { |
||||||
|
return nil, errors.New("password for c.name is not supported") |
||||||
|
} |
||||||
|
var fullPass []Password |
||||||
|
for _, profile := range c.profilePaths { |
||||||
|
passFile := filepath.Join(profile, TypePassword.Filename(c.name)) |
||||||
|
if !fileutil.IsFileExists(passFile) { |
||||||
|
fmt.Println(passFile) |
||||||
|
return nil, errors.New("password file does not exist") |
||||||
|
} |
||||||
|
if err := fileutil.CopyFile(passFile, item.TempChromiumPassword); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
passwords, err := c.exportPasswords(profile, item.TempChromiumPassword) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if len(passwords) > 0 { |
||||||
|
fullPass = append(fullPass, passwords...) |
||||||
|
} |
||||||
|
} |
||||||
|
return fullPass, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *chromium) exportPasswords(profile, dbfile string) ([]Password, error) { |
||||||
|
db, err := sql.Open("sqlite3", dbfile) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
defer db.Close() |
||||||
|
rows, err := db.Query(queryChromiumLogin) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
var passwords []Password |
||||||
|
for rows.Next() { |
||||||
|
var ( |
||||||
|
url, username string |
||||||
|
encryptPass, password []byte |
||||||
|
create int64 |
||||||
|
) |
||||||
|
if err := rows.Scan(&url, &username, &encryptPass, &create); err != nil { |
||||||
|
log.Warn(err) |
||||||
|
} |
||||||
|
pass := Password{ |
||||||
|
Profile: filepath.Base(profile), |
||||||
|
Username: username, |
||||||
|
encryptPass: encryptPass, |
||||||
|
LoginURL: url, |
||||||
|
} |
||||||
|
if len(encryptPass) > 0 { |
||||||
|
if len(c.masterKey) == 0 { |
||||||
|
password, err = crypto.DPAPI(encryptPass) |
||||||
|
} else { |
||||||
|
password, err = crypto.DecryptPass(c.masterKey, encryptPass) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
log.Error(err) |
||||||
|
} |
||||||
|
} |
||||||
|
if create > time.Now().Unix() { |
||||||
|
pass.CreateDate = typeutil.TimeEpoch(create) |
||||||
|
} else { |
||||||
|
pass.CreateDate = typeutil.TimeStamp(create) |
||||||
|
} |
||||||
|
pass.Password = string(password) |
||||||
|
passwords = append(passwords, pass) |
||||||
|
} |
||||||
|
return passwords, nil |
||||||
|
} |
||||||
|
|
||||||
|
const ( |
||||||
|
queryChromiumLogin = `SELECT origin_url, username_value, password_value, date_created FROM logins` |
||||||
|
) |
||||||
|
|
||||||
|
func (f *firefox) Passwords() ([]Password, error) { |
||||||
|
if f.masterKey != nil { |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
return nil, nil |
||||||
|
} |
Loading…
Reference in new issue