diff --git a/cmd/dashboard/dashboard/pages/7_Chromium.py b/cmd/dashboard/dashboard/pages/7_Chromium.py index 5b7244d..8a51fb2 100644 --- a/cmd/dashboard/dashboard/pages/7_Chromium.py +++ b/cmd/dashboard/dashboard/pages/7_Chromium.py @@ -7,14 +7,8 @@ from typing import List import extra_streamlit_components as stx import streamlit as st import utils -from st_aggrid import ( - AgGrid, - ColumnsAutoSizeMode, - DataReturnMode, - GridOptionsBuilder, - GridUpdateMode, - JsCode, -) +from st_aggrid import (AgGrid, ColumnsAutoSizeMode, DataReturnMode, + GridOptionsBuilder, GridUpdateMode, JsCode) from streamlit_searchbox import st_searchbox from streamlit_toggle import st_toggle_switch @@ -191,6 +185,8 @@ if "authentication_status" in st.session_state and st.session_state["authenticat df_cookies_download = df_cookies_download.drop("source", axis=1) df_cookies_download = df_cookies_download.drop("username", axis=1) df_cookies_download = df_cookies_download.drop("browser", axis=1) + df_cookies_download = df_cookies_download.drop("unique_db_id", axis=1) + df_cookies_download = df_cookies_download.drop("notes", axis=1) df_cookies_download["hostOnly"] = False df_cookies_download["httpOnly"] = False df_cookies_download["secure"] = True diff --git a/cmd/enrichment/enrichment/lib/helpers.py b/cmd/enrichment/enrichment/lib/helpers.py index 56790e0..625a78e 100644 --- a/cmd/enrichment/enrichment/lib/helpers.py +++ b/cmd/enrichment/enrichment/lib/helpers.py @@ -147,6 +147,11 @@ def convert_chromium_timestamp_to_datetime(timestamp: int) -> datetime.datetime: return datetime.datetime(1601, 1, 1) + datetime.timedelta(microseconds=timestamp) +def convert_epoch_seconds_to_datetime(timestamp: float) -> datetime.datetime: + """Converts epoch seconds to a datetime.""" + return datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=timestamp) + + async def process_chromium_history( object_id: str, file_path: str, @@ -289,6 +294,24 @@ async def process_chromium_logins( await chromium_logins_q.Send(chromium_login_message.SerializeToString()) +async def is_chromium_cookie_json(file_path: str) -> bool: + """ + Helper that downloads the specified file and tries to load it as a json, + checking various fields to see if it's likely a Chromium cookie json dump. + """ + try: + with open(file_path, "r") as f: + file_json = json.loads(f.read()) + if type(file_json) == list and len(file_json) > 0: + if type(file_json[0]) == dict and len(file_json[0]) > 10 and len(file_json[0]) < 20: + fields = list(file_json[0].keys()) + if "name" in fields and "domain" in fields and "sameSite" in fields and "httpOnly" in fields: + return True + return False + except: + return False + + async def process_chromium_cookies( object_id: str, file_path: str, metadata: pb.Metadata, parsed_data: pb.ParsedData, chromium_cookies_q: MessageQueueProducerInterface ) -> None: @@ -370,6 +393,75 @@ async def process_chromium_cookies( await chromium_cookies_q.Send(chromium_cookie_message.SerializeToString()) +async def process_cookies_json( + object_id: str, file_path: str, metadata: pb.Metadata, parsed_data: pb.ParsedData, chromium_cookies_q: MessageQueueProducerInterface +) -> None: + """ + Helper that parses out the appropriate data from a (likely Chromium) cookies JSON, builds + the appropriate protobuf messages, and publishes them to the passed queue. + """ + + try: + with open(file_path, "r") as f: + cookie_json_all = json.loads(f.read()) + + cookie_message = pb.ChromiumCookieMessage() + cookie_message.metadata.CopyFrom(metadata) + + for cookie_json in cookie_json_all: + try: + cookie = pb.ChromiumCookie() + + # user_data_directory is not known here + + cookie.originating_object_id = object_id + cookie.host_key = cookie_json["domain"].lower() + cookie.path = cookie_json["path"] + cookie.name = cookie_json["name"] + cookie.is_decrypted = True + cookie.value_dec = cookie_json["value"] + + expires_raw = 0 + if "expires" in cookie_json: + expires_raw = cookie_json["expires"] + elif "expirationDate" in cookie_json: + expires_raw = cookie_json["expirationDate"] + expires_dt = convert_epoch_seconds_to_datetime(expires_raw) + cookie.expires.FromDatetime(expires_dt) + + if "httpOnly" in cookie_json: + cookie.is_httponly = cookie_json["httpOnly"] + + if "sameSite" in cookie_json: + if cookie_json["sameSite"].upper() == "NONE": + cookie.samesite = "NONE" + elif cookie_json["sameSite"].upper() == "LAX": + cookie.samesite = "LAX" + elif cookie_json["sameSite"].upper() == "STRICT": + cookie.samesite = "STRICT" + else: + cookie.samesite = "UNKNOWN" + + if "session" in cookie_json: + cookie.is_session = cookie_json["session"] + + if "secure" in cookie_json: + cookie.is_secure = cookie_json["secure"] + + if "sourcePort" in cookie_json: + cookie.source_port = cookie_json["sourcePort"] + + cookie_message.data.append(cookie) + + except Exception as e: + await logger.awarning(f"Error parsing a specific cookie in process_chromium_cookies_json: {e}") + + await chromium_cookies_q.Send(cookie_message.SerializeToString()) + + except Exception as e: + await logger.aerror(f"Error in process_chromium_cookies_json: {e}") + + ################################################## # # DPAPI helpers diff --git a/cmd/enrichment/enrichment/tasks/file_processor.py b/cmd/enrichment/enrichment/tasks/file_processor.py index 71d30e8..b5b3e52 100644 --- a/cmd/enrichment/enrichment/tasks/file_processor.py +++ b/cmd/enrichment/enrichment/tasks/file_processor.py @@ -455,6 +455,13 @@ class FileProcessor(TaskInterface): await logger.ainfo("Detected Chromium state file, emitting ChromiumStateFileMessage") await self.out_q_chromiumstatefile.Send(chromium_state_file_message.SerializeToString()) + # we have a likely JSON Chromium cookie dump + elif file_data.magic_type == "JSON data" and (await helpers.is_chromium_cookie_json(file_path_on_disk)): + await logger.ainfo("Detected Chromium cookies JSON file, processing") + await helpers.process_cookies_json( + file_data.object_id, file_path_on_disk, metadata, file_data.parsed_data, self.out_q_chromiumcookies + ) + # if this file is Seatbelt data, emit a raw_data message so the data is properly processed elif file_data.magic_type == "JSON data" and helpers.scan_with_yara(file_path_on_disk, "seatbelt_json"): skip_dpapi_carve = True