aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBogdan Dobrelya <bogdando@yahoo.com>2023-06-29 22:12:28 +0200
committerGitHub <noreply@github.com>2023-06-29 22:12:28 +0200
commitc7868ccc2ba89796789f943d3767400f7ae000b4 (patch)
treee03722ddb36bf3ca658e05a99ba90ed1e739028b
parent6dede46eadde47e7141e11f7134844f93012595a (diff)
downloadwee-slack-c7868ccc2ba89796789f943d3767400f7ae000b4.tar.gz
Firefox-snap and chrome/chrome-beta support (#884)
* Add support for cookies decryption for Chrome/beta * Add support for firefox-snap, and multiple default/custom profiles paths * If no token can be found, like no key localConfig_v2 in webappsstore2, try the best to fetch it from leveldb (or online copy of it, if locked by running browser). When no luck even with that, suggest the user to fetch it manually from dev console, and pass it as an extra argument. This way it still saves some extra cycles for the script users by fetching d/d-s values, and formatting the ultimate registration command string. Not tested on Darwin, leaving this as an exercise for you Adding Chromium support should be straightforward and can be done in follow-ups. Signed-off-by: Bohdan Dobrelia <bdobreli@redhat.com> Co-authored-by: Bogdan Dobrelya <bogdando@mail.ru>
-rwxr-xr-xextract_token_from_browser.py210
1 files changed, 180 insertions, 30 deletions
diff --git a/extract_token_from_browser.py b/extract_token_from_browser.py
index 5798ba0..5db183f 100755
--- a/extract_token_from_browser.py
+++ b/extract_token_from_browser.py
@@ -1,64 +1,214 @@
#!/usr/bin/env python3
+# Examples:
+# python ./extract_token_from_browser.py chrome --profile="Profile 2" \
+# --token=xoxc-foo-bar
+# python ./extract_token_from_browser.py firefox-snap --token=xoxc-baz
import argparse
import json
-from pathlib import Path
+import os
+import secretstorage
+import shutil
import sqlite3
import sys
+from pathlib import Path
+from secretstorage.exceptions import SecretStorageException
+from sqlite3 import OperationalError
+try:
+ import_err = None
+ from Crypto.Cipher import AES
+ from Crypto.Protocol.KDF import PBKDF2
+ from plyvel import DB
+ from plyvel._plyvel import IOError as pIOErr
+except ModuleNotFoundError as e:
+ import_err = e
+ pass
+
+
+class AESCipher:
+
+ def __init__(self, key):
+ self.key = key
+
+ def decrypt(self, text):
+ cipher = AES.new(self.key, AES.MODE_CBC, IV=(b' ' * 16))
+ return self._unpad(cipher.decrypt(text))
+
+ def _unpad(self, s):
+ return s[:-ord(s[len(s) - 1:])]
+
+
parser = argparse.ArgumentParser(
description="Extract Slack tokens from the browser files"
)
parser.add_argument(
"browser", help="Which browser to extract from", metavar="<browser>"
)
+parser.add_argument(
+ "--token", help="Extracted token", metavar="<token>", nargs='?'
+)
+parser.add_argument(
+ "--profile", help="Profile to look up cookies for", metavar="<profile>", nargs='?'
+)
args = parser.parse_args()
-if args.browser != "firefox":
- print("Currently only firefox is supported by this script", file=sys.stderr)
+if args.browser not in ["firefox", "firefox-snap", "chrome", "chrome-beta"]:
+ print("Currently only firefox. firefox-snap, chrome, "
+ "chrome-beta are supported by this script", file=sys.stderr)
sys.exit(1)
if sys.platform.startswith("linux"):
- firefox_path = Path.home().joinpath(".mozilla/firefox")
+ iterations = 1
+ if args.browser == "firefox-snap":
+ browser_data = Path.home().joinpath(
+ "snap/firefox/common/.mozilla/firefox")
+ elif args.browser == "firefox":
+ browser_data = Path.home().joinpath(".mozilla/firefox")
+ else:
+ browser_data = Path.home().joinpath(".config/google-%s" % args.browser)
elif sys.platform.startswith("darwin"):
- firefox_path = Path.home().joinpath("Library/Application Support/Firefox/Profiles")
+ iterations = 1003
+ if args.browser in ["firefox", "firefox-snap"]:
+ browser_data = Path.home().joinpath(
+ "Library/Application Support/Firefox/Profiles")
+ else:
+ browser_data = Path.home().joinpath(
+ "Library/Application Support/Chrome")
else:
- print("Currently only Linux and macOS is supported by this script", file=sys.stderr)
+ print("Currently only Linux and macOS is supported by this script",
+ file=sys.stderr)
sys.exit(1)
+leveldb_path = None
+profile = args.profile
+if args.browser in ["firefox", "firefox-snap"]:
+ cookie_d_query = (
+ "SELECT value FROM moz_cookies WHERE host = '.slack.com' "
+ "AND name = 'd'"
+ )
+ cookie_ds_query = (
+ "SELECT value FROM moz_cookies WHERE host = '.slack.com' "
+ "AND name = 'd-s'"
+ )
+ if not profile:
+ profile = "*.default*"
+
+ default_profile_path = max(
+ [ x for x in browser_data.glob(profile)], key=os.path.getctime)
+ if not default_profile_path:
+ print("Couldn't find the default profile for Firefox", file=sys.stderr)
+ sys.exit(1)
+ cookies_path = default_profile_path.joinpath("cookies.sqlite")
+else:
+ if import_err:
+ print("Missing required modules for Chrome browser support",
+ file=sys.stderr)
+ raise import_err
+
+ # b'v10' is for Chromium, but not Chrome, it seems?
+ prefix = b'v11'
+ cookie_d_query = (
+ "SELECT encrypted_value FROM cookies WHERE "
+ "host_key = '.slack.com' AND name = 'd'"
+ )
+ cookie_ds_query = (
+ "SELECT encrypted_value FROM cookies WHERE "
+ "host_key = '.slack.com' AND name = 'd-s'"
+ )
+ if not profile:
+ profile = "Default"
+
+ default_profile_path = browser_data.joinpath(profile)
+ cookies_path = default_profile_path.joinpath("Cookies")
+ leveldb_path = default_profile_path.joinpath("Local Storage/leveldb")
+
+cookie_d_value = None
+cookie_ds_value = None
try:
- default_profile_path = next(firefox_path.glob("*.default-release"))
-except StopIteration:
- print("Couldn't find the default profile for Firefox", file=sys.stderr)
- sys.exit(1)
+ con = sqlite3.connect(f"file:{cookies_path}?immutable=1", uri=True)
+ cookie_d_value = con.execute(cookie_d_query).fetchone()[0]
+ cookie_ds_value = con.execute(cookie_ds_query).fetchone()[0]
+except TypeError:
+ if not cookie_d_value:
+ print("Couldn't find the 'd' cookie value", file=sys.stderr)
+ sys.exit(1)
+ pass
+finally:
+ con.close()
-cookies_path = default_profile_path.joinpath("cookies.sqlite")
-con = sqlite3.connect(f"file:{cookies_path}?immutable=1", uri=True)
-cookie_d_query = (
- "SELECT value FROM moz_cookies WHERE host = '.slack.com' AND name = 'd'"
-)
-cookie_d_value = con.execute(cookie_d_query).fetchone()[0]
-cookie_ds_query = (
- "SELECT value FROM moz_cookies WHERE host = '.slack.com' AND name = 'd-s'"
-)
-cookie_ds_values = con.execute(cookie_ds_query).fetchone()
-con.close()
+if args.browser in ["chrome", "chrome-beta"]:
+ bus = secretstorage.dbus_init()
+ try:
+ collection = secretstorage.get_default_collection(bus)
+ for item in collection.get_all_items():
+ if item.get_label() == 'Chrome Safe Storage':
+ passwd = item.get_secret()
+ break
+ else:
+ raise Exception('Chrome password not found!')
+ except SecretStorageException:
+ print("Error communicating org.freedesktop.secrets, trying 'peanuts' "
+ "as a password", file=sys.stderr)
+ passwd = 'peanuts'
+
+ salt = b'saltysalt'
+ length = 16
+ key = PBKDF2(passwd, salt, length, iterations)
-if cookie_ds_values:
- cookie_value = f"d={cookie_d_value};d-s={cookie_ds_values[0]}"
+ cipher = AESCipher(key)
+
+ if cookie_d_value[:3] == prefix:
+ cookie_d_value = cipher.decrypt(cookie_d_value[3:]).decode('utf8')
+
+ if cookie_ds_value and cookie_ds_value[:3] == prefix:
+ cookie_ds_value = cipher.decrypt(cookie_ds_value[3:]).decode('utf8')
+
+if cookie_ds_value:
+ cookie_value = f"d={cookie_d_value};d-s={cookie_ds_value}"
else:
cookie_value = cookie_d_value
local_storage_path = default_profile_path.joinpath("webappsstore.sqlite")
-con = sqlite3.connect(f"file:{local_storage_path}?immutable=1", uri=True)
local_storage_query = "SELECT value FROM webappsstore2 WHERE key = 'localConfig_v2'"
-local_config_str = con.execute(local_storage_query).fetchone()[0]
-con.close()
+teams = []
+local_config = None
+try:
+ con = sqlite3.connect(f"file:{local_storage_path}?immutable=1", uri=True)
+ local_config_str = con.execute(local_storage_query).fetchone()[0]
+ local_config = json.loads(local_config_str)
+except (OperationalError, TypeError):
+ if not args.token and args.browser not in ["chrome", "chrome-beta"]:
+ print("Couldn't find the token for team, extract it manually "
+ "by running 'window.prompt(\"Session token:\", "
+ "TS.boot_data.api_token)' in your browser dev console "
+ "and specify it in --token", file=sys.stderr)
+ sys.exit(1)
+finally:
+ con.close()
+
+if not local_config and leveldb_path:
+ try:
+ db = DB(str(leveldb_path))
+ except pIOErr:
+ leveldb_copy = str(leveldb_path) + ".bak"
+ os.makedirs(leveldb_copy, exist_ok=True)
+ shutil.copytree(leveldb_path, leveldb_copy,
+ dirs_exist_ok=True)
+ print(f"Leveldb was locked by a running browser - made an online copy "
+ "of it in {leveldb_copy}", file=sys.stderr)
+ db = DB(str(leveldb_copy))
+
+ local_storage_value = db.get(
+ b'_https://app.slack.com\x00\x01localConfig_v2')
+ local_config = json.loads(local_storage_value[1:])
+ teams = [
+ team for team in local_config["teams"].values() if not team["id"].startswith("E")
+ ]
+
+if not teams:
+ teams = [{"token": args.token, "name": "Slack"}]
-local_config = json.loads(local_config_str)
-teams = [
- team for team in local_config["teams"].values() if not team["id"].startswith("E")
-]
register_commands = [
f"{team['name']}:\n/slack register {team['token']}:{cookie_value}" for team in teams
]