Skip to content

Instantly share code, notes, and snippets.

@vkuznet
Forked from dacort/cookiemonster.go
Created February 5, 2022 23:49
Show Gist options
  • Save vkuznet/7f0fd6778db6ba0abf1542c54da24aec to your computer and use it in GitHub Desktop.
Save vkuznet/7f0fd6778db6ba0abf1542c54da24aec to your computer and use it in GitHub Desktop.
Simple script to extract (encrypted) cookies out of Chrome OS X cookie store. Usage: ./cookiemonster domain.com
package main
import (
"code.google.com/p/go.crypto/pbkdf2"
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"database/sql"
"fmt"
"log"
"os"
"os/exec"
"os/user"
"strings"
_ "github.com/mattn/go-sqlite3"
)
// Inpsiration
// http://n8henrie.com/2013/11/use-chromes-cookies-for-easier-downloading-with-python-requests/
// Chromium Mac os_crypt: http://dacort.me/1ynPMgx
var (
salt = "saltysalt"
iv = " "
length = 16
password = ""
iterations = 1003
)
// Cookie - Items for a cookie
type Cookie struct {
Domain string
Key string
Value string
EncryptedValue []byte
}
// DecryptedValue - Get the unencrypted value of a Chrome cookie
func (c *Cookie) DecryptedValue() string {
if c.Value > "" {
return c.Value
}
if len(c.EncryptedValue) > 0 {
encryptedValue := c.EncryptedValue[3:]
return decryptValue(encryptedValue)
}
return ""
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: %s [domain]\n", os.Args[0])
os.Exit(2)
}
func main() {
if len(os.Args) != 2 {
usage()
}
domain := os.Args[1]
password = getPassword()
// TODO: Output in Netscape format
for _, cookie := range getCookies(domain) {
fmt.Printf("%s/%s: %s\n", cookie.Domain, cookie.Key, cookie.DecryptedValue())
}
}
func decryptValue(encryptedValue []byte) string {
key := pbkdf2.Key([]byte(password), []byte(salt), iterations, length, sha1.New)
block, err := aes.NewCipher(key)
if err != nil {
log.Fatal(err)
}
decrypted := make([]byte, len(encryptedValue))
cbc := cipher.NewCBCDecrypter(block, []byte(iv))
cbc.CryptBlocks(decrypted, encryptedValue)
plainText, err := aesStripPadding(decrypted)
if err != nil {
fmt.Println("Error decrypting:", err)
return ""
}
return string(plainText)
}
// In the padding scheme the last <padding length> bytes
// have a value equal to the padding length, always in (1,16]
func aesStripPadding(data []byte) ([]byte, error) {
if len(data)%length != 0 {
return nil, fmt.Errorf("decrypted data block length is not a multiple of %d", length)
}
paddingLen := int(data[len(data)-1])
if paddingLen > 16 {
return nil, fmt.Errorf("invalid last block padding length: %d", paddingLen)
}
return data[:len(data)-paddingLen], nil
}
func getPassword() string {
parts := strings.Fields("security find-generic-password -wga Chrome")
cmd := parts[0]
parts = parts[1:len(parts)]
out, err := exec.Command(cmd, parts...).Output()
if err != nil {
log.Fatal("error finding password ", err)
}
return strings.Trim(string(out), "\n")
}
func getCookies(domain string) (cookies []Cookie) {
usr, _ := user.Current()
cookiesFile := fmt.Sprintf("%s/Library/Application Support/Google/Chrome/Default/Cookies", usr.HomeDir)
db, err := sql.Open("sqlite3", cookiesFile)
if err != nil {
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query("SELECT name, value, host_key, encrypted_value FROM cookies WHERE host_key like ?", fmt.Sprintf("%%%s%%", domain))
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var name, value, hostKey string
var encryptedValue []byte
rows.Scan(&name, &value, &hostKey, &encryptedValue)
cookies = append(cookies, Cookie{hostKey, name, value, encryptedValue})
}
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment