mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-08-15 23:50:19 +02:00
feat: Add support for sameSite field (#628)
Recent browsers (and HTTP) added "sameSite" setting to limit cross site attacks. Add support for exporting this property from Firefox and Chrome and to export to csv and cookie-editor format. Also extract new "session" and "hostOnly" properties for cookie-editor format. Tested on Firefox, Chrome and Chromium. Signed-off-by: Frediano Ziglio <freddy77@gmail.com>
This commit is contained in:
@@ -14,7 +14,7 @@ import (
|
||||
const (
|
||||
defaultCookieQuery = `SELECT name, encrypted_value, host_key, path,
|
||||
creation_utc, expires_utc, is_secure, is_httponly,
|
||||
has_expires, is_persistent FROM cookies`
|
||||
has_expires, is_persistent, samesite FROM cookies`
|
||||
countCookieQuery = `SELECT COUNT(*) FROM cookies`
|
||||
)
|
||||
|
||||
@@ -27,15 +27,27 @@ func extractCookies(masterKeys masterkey.MasterKeys, path string) ([]types.Cooki
|
||||
hasExpire, isPersistent int
|
||||
createdAt, expireAt int64
|
||||
encryptedValue []byte
|
||||
sameSite int
|
||||
)
|
||||
if err := rows.Scan(&name, &encryptedValue, &host, &cookiePath,
|
||||
&createdAt, &expireAt, &isSecure, &isHTTPOnly,
|
||||
&hasExpire, &isPersistent); err != nil {
|
||||
&hasExpire, &isPersistent, &sameSite); err != nil {
|
||||
return types.CookieEntry{}, err
|
||||
}
|
||||
|
||||
value, _ := decryptValue(masterKeys, encryptedValue)
|
||||
value = stripCookieHash(value, host)
|
||||
sameSiteStr := "unspecified"
|
||||
switch sameSite {
|
||||
case 0:
|
||||
sameSiteStr = "none"
|
||||
case 1:
|
||||
sameSiteStr = "lax"
|
||||
case 2:
|
||||
sameSiteStr = "strict"
|
||||
case -1:
|
||||
// not specified by Set-Cookie
|
||||
}
|
||||
return types.CookieEntry{
|
||||
Name: name,
|
||||
Host: host,
|
||||
@@ -47,6 +59,7 @@ func extractCookies(masterKeys masterkey.MasterKeys, path string) ([]types.Cooki
|
||||
IsPersistent: isPersistent != 0,
|
||||
ExpireAt: timeEpoch(expireAt),
|
||||
CreatedAt: timeEpoch(createdAt),
|
||||
SameSite: sameSiteStr,
|
||||
}, nil
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
const (
|
||||
firefoxCookieQuery = `SELECT name, value, host, path,
|
||||
creationTime, expiry, isSecure, isHttpOnly FROM moz_cookies`
|
||||
creationTime, expiry, isSecure, isHttpOnly, sameSite FROM moz_cookies`
|
||||
firefoxCountCookieQuery = `SELECT COUNT(*) FROM moz_cookies`
|
||||
)
|
||||
|
||||
@@ -21,12 +21,24 @@ func extractCookies(path string) ([]types.CookieEntry, error) {
|
||||
name, value, host, cookiePath string
|
||||
isSecure, isHTTPOnly int
|
||||
createdAt, expiry int64
|
||||
sameSite int
|
||||
)
|
||||
if err := rows.Scan(&name, &value, &host, &cookiePath,
|
||||
&createdAt, &expiry, &isSecure, &isHTTPOnly); err != nil {
|
||||
&createdAt, &expiry, &isSecure, &isHTTPOnly, &sameSite); err != nil {
|
||||
return types.CookieEntry{}, err
|
||||
}
|
||||
hasExpire := expiry > 0
|
||||
sameSiteStr := "unspecified"
|
||||
switch sameSite {
|
||||
case 0:
|
||||
sameSiteStr = "none"
|
||||
case 1:
|
||||
sameSiteStr = "lax"
|
||||
case 2:
|
||||
sameSiteStr = "strict"
|
||||
case 256:
|
||||
// not specified by Set-Cookie
|
||||
}
|
||||
return types.CookieEntry{
|
||||
Name: name,
|
||||
Host: host,
|
||||
@@ -38,6 +50,7 @@ func extractCookies(path string) ([]types.CookieEntry, error) {
|
||||
IsPersistent: hasExpire,
|
||||
ExpireAt: firefoxSeconds(expiry),
|
||||
CreatedAt: firefoxMicros(createdAt),
|
||||
SameSite: sameSiteStr,
|
||||
}, nil
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
+17
-1
@@ -3,6 +3,7 @@ package output
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/moond4rk/hackbrowserdata/types"
|
||||
)
|
||||
@@ -15,6 +16,8 @@ type cookieEditorFormatter struct {
|
||||
|
||||
func (f *cookieEditorFormatter) ext() string { return "json" }
|
||||
|
||||
var sameSiteNone = "no_restriction"
|
||||
|
||||
func (f *cookieEditorFormatter) format(w io.Writer, rows []row) error {
|
||||
if len(rows) == 0 {
|
||||
return nil
|
||||
@@ -32,6 +35,13 @@ func (f *cookieEditorFormatter) format(w io.Writer, rows []row) error {
|
||||
if !c.ExpireAt.IsZero() {
|
||||
expDate = float64(c.ExpireAt.Unix())
|
||||
}
|
||||
sameSite := &c.SameSite
|
||||
switch c.SameSite {
|
||||
case "none":
|
||||
sameSite = &sameSiteNone
|
||||
case "", "unspecified":
|
||||
sameSite = nil
|
||||
}
|
||||
entries = append(entries, cookieEditorEntry{
|
||||
Domain: c.Host,
|
||||
ExpirationDate: expDate,
|
||||
@@ -40,6 +50,9 @@ func (f *cookieEditorFormatter) format(w io.Writer, rows []row) error {
|
||||
Path: c.Path,
|
||||
Secure: c.IsSecure,
|
||||
Value: c.Value,
|
||||
SameSite: sameSite,
|
||||
Session: expDate == 0.,
|
||||
HostOnly: !strings.HasPrefix(c.Host, "."),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -52,10 +65,13 @@ func (f *cookieEditorFormatter) format(w io.Writer, rows []row) error {
|
||||
// cookieEditorEntry matches the CookieEditor browser extension's import format.
|
||||
type cookieEditorEntry struct {
|
||||
Domain string `json:"domain"`
|
||||
ExpirationDate float64 `json:"expirationDate"`
|
||||
ExpirationDate float64 `json:"expirationDate,omitempty"`
|
||||
HTTPOnly bool `json:"httpOnly"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Secure bool `json:"secure"`
|
||||
Value string `json:"value"`
|
||||
SameSite *string `json:"sameSite"`
|
||||
Session bool `json:"session"`
|
||||
HostOnly bool `json:"hostOnly"`
|
||||
}
|
||||
|
||||
@@ -106,14 +106,14 @@ func TestWrite_CSV_Cookie(t *testing.T) {
|
||||
assert.Equal(t,
|
||||
[]string{
|
||||
"browser", "profile", "host", "path", "name", "value",
|
||||
"is_secure", "is_http_only", "has_expire", "is_persistent", "expire_at", "created_at",
|
||||
"is_secure", "is_http_only", "has_expire", "is_persistent", "expire_at", "created_at", "same_site",
|
||||
},
|
||||
records[0],
|
||||
)
|
||||
assert.Equal(t,
|
||||
[]string{
|
||||
"Chrome", "Default", ".example.com", "/", "session", "abc123",
|
||||
"true", "true", "true", "true", "2026-01-15T10:30:00Z", "2026-01-15T10:30:00Z",
|
||||
"true", "true", "true", "true", "2026-01-15T10:30:00Z", "2026-01-15T10:30:00Z", "",
|
||||
},
|
||||
records[1],
|
||||
)
|
||||
|
||||
@@ -54,7 +54,7 @@ func TestStructCSVHeader(t *testing.T) {
|
||||
expect []string
|
||||
}{
|
||||
{"LoginEntry", types.LoginEntry{}, []string{"url", "username", "password", "created_at"}},
|
||||
{"CookieEntry", types.CookieEntry{}, []string{"host", "path", "name", "value", "is_secure", "is_http_only", "has_expire", "is_persistent", "expire_at", "created_at"}},
|
||||
{"CookieEntry", types.CookieEntry{}, []string{"host", "path", "name", "value", "is_secure", "is_http_only", "has_expire", "is_persistent", "expire_at", "created_at", "same_site"}},
|
||||
{"BookmarkEntry", types.BookmarkEntry{}, []string{"id", "name", "type", "url", "folder", "created_at"}},
|
||||
{"HistoryEntry", types.HistoryEntry{}, []string{"url", "title", "visit_count", "last_visit"}},
|
||||
{"DownloadEntry", types.DownloadEntry{}, []string{"url", "target_path", "mime_type", "total_bytes", "start_time", "end_time"}},
|
||||
@@ -87,7 +87,7 @@ func TestStructCSVRow(t *testing.T) {
|
||||
IsSecure: true, IsHTTPOnly: true, HasExpire: true, IsPersistent: false,
|
||||
ExpireAt: refTime, CreatedAt: refTime,
|
||||
},
|
||||
[]string{".example.com", "/", "session", "abc", "true", "true", "true", "false", "2026-01-15T10:30:00Z", "2026-01-15T10:30:00Z"},
|
||||
[]string{".example.com", "/", "session", "abc", "true", "true", "true", "false", "2026-01-15T10:30:00Z", "2026-01-15T10:30:00Z", ""},
|
||||
},
|
||||
{
|
||||
"HistoryEntry_int",
|
||||
|
||||
@@ -22,6 +22,7 @@ type CookieEntry struct {
|
||||
IsPersistent bool `json:"is_persistent" csv:"is_persistent"`
|
||||
ExpireAt time.Time `json:"expire_at" csv:"expire_at"`
|
||||
CreatedAt time.Time `json:"created_at" csv:"created_at"`
|
||||
SameSite string `json:"same_site" csv:"same_site"`
|
||||
}
|
||||
|
||||
// BookmarkEntry represents a single browser bookmark.
|
||||
|
||||
Reference in New Issue
Block a user