From 59439c368fc7b7c082602af1fbc5a76008de9a44 Mon Sep 17 00:00:00 2001 From: Antonio Date: Fri, 22 Nov 2024 12:39:10 +0100 Subject: [PATCH] Update CHANGELOG and code --- CHANGELOG.md | 4 ++ docs/quickstart/configuration/client.md | 2 + docs/quickstart/configuration/server.md | 2 + empire/client/src/EmpireCliConfig.py | 2 +- empire/config_manager.py | 64 +++++++++++++++++-------- empire/server/core/config.py | 43 +++++++++++------ empire/server/modules/bof/nanodump.py | 3 +- 7 files changed, 81 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d592319d..9ec54468e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Support Empire for system-wide deployment (@D3vil0p3r) +- Paths specified in config.yaml where user does not have write permission will be fallback to ~/.empire directory and config.yaml updated as well (@D3vil0p3r) +- Invoke-Obfuscation is no longer copied to /usr/local/share + ## [5.11.7] - 2024-11-11 - Fix arm installs by installing dotnet and powershell manually diff --git a/docs/quickstart/configuration/client.md b/docs/quickstart/configuration/client.md index 22d0f294f..393f10ee9 100644 --- a/docs/quickstart/configuration/client.md +++ b/docs/quickstart/configuration/client.md @@ -5,6 +5,8 @@ The Client configuration is managed via [empire/client/config.yaml](https://github.com/BC-SECURITY/Empire/blob/master/empire/client/config.yaml). +Once launched, Empire checks for user write permissions on paths specified in `config.yaml`. If the current user does not have write permissions on these paths, `~/.empire` will be set as fallback parent directory and the configuration file will be updated as well. + * **servers** - The servers block is meant to give the user the ability to set up frequently used Empire servers. If a server is listed in this block then when connecting to the server they need only type: `connect -c localhost`. diff --git a/docs/quickstart/configuration/server.md b/docs/quickstart/configuration/server.md index e6c0a9c44..4e00381ee 100644 --- a/docs/quickstart/configuration/server.md +++ b/docs/quickstart/configuration/server.md @@ -2,6 +2,8 @@ The Server configuration is managed via [empire/server/config.yaml](https://github.com/BC-SECURITY/Empire/blob/master/empire/client/config.yaml). +Once launched, Empire checks for user write permissions on paths specified in `config.yaml`. If the current user does not have write permissions on these paths, `~/.empire` will be set as fallback parent directory and the configuration file will be updated as well. + * **suppress-self-cert-warning** - Suppress the http warnings when launching an Empire instance that uses a self-signed cert. * **api** - Configure the RESTful API. This includes the port to run the API on, as well as the path for the SSL certificates. If `empire-priv.key` and `empire-chain.pem` are not found in this directory, self-signed certs will be generated. diff --git a/empire/client/src/EmpireCliConfig.py b/empire/client/src/EmpireCliConfig.py index ec8476c6f..9029ebb71 100644 --- a/empire/client/src/EmpireCliConfig.py +++ b/empire/client/src/EmpireCliConfig.py @@ -18,7 +18,7 @@ def __init__(self): if len(self.yaml.items()) == 0: log.info("Loading default config") self.set_yaml(config_manager.CONFIG_CLIENT_PATH) - config_manager.check_config_permission(self.yaml, "client") + config_manager.check_config_permission(self.yaml, "client") def set_yaml(self, location: str): try: diff --git a/empire/config_manager.py b/empire/config_manager.py index a1c8d34ee..5f172133c 100644 --- a/empire/config_manager.py +++ b/empire/config_manager.py @@ -44,24 +44,32 @@ def check_config_permission(config_dict: dict, config_type: str): # Define paths to check based on config type if config_type == "server": paths_to_check = { - ("api", "cert_path"): config_dict["api"]["cert_path"], - ("database", "sqlite", "location"): config_dict["database"]["sqlite"][ - "location" - ], - ("starkiller", "directory"): config_dict["starkiller"]["directory"], - ("logging", "directory"): config_dict["logging"]["directory"], - ("debug", "last_task", "file"): config_dict["debug"]["last_task"]["file"], - ("directories", "downloads"): config_dict["directories"].get("downloads"), + ("api", "cert_path"): config_dict.get("api", {}).get("cert_path"), + ("database", "sqlite", "location"): config_dict.get("database", {}) + .get("sqlite", {}) + .get("location"), + ("starkiller", "directory"): config_dict.get("starkiller", {}).get( + "directory" + ), + ("logging", "directory"): config_dict.get("logging", {}).get("directory"), + ("debug", "last_task", "file"): config_dict.get("debug", {}) + .get("last_task", {}) + .get("file"), + ("directories", "downloads"): config_dict.get("directories", {}).get( + "downloads" + ), } config_path = CONFIG_SERVER_PATH # Use the server config path elif config_type == "client": paths_to_check = { - ("logging", "directory"): config_dict["logging"]["directory"], - ("directories", "downloads"): config_dict["directories"].get("downloads"), - ("directories", "generated-stagers"): config_dict["directories"].get( - "generated-stagers" + ("logging", "directory"): config_dict.get("logging", {}).get("directory"), + ("directories", "downloads"): config_dict.get("directories", {}).get( + "downloads" ), + ("directories", "generated-stagers"): config_dict.get( + "directories", {} + ).get("generated-stagers"), } config_path = CONFIG_CLIENT_PATH # Use the client config path @@ -70,13 +78,21 @@ def check_config_permission(config_dict: dict, config_type: str): # Check permissions and update paths as needed for keys, dir_path in paths_to_check.items(): - if not os.access(dir_path, os.W_OK): + if dir_path is None: + continue + + current_dir = dir_path + while current_dir and not os.path.exists(current_dir): + current_dir = os.path.dirname(current_dir) + + if not os.access(current_dir, os.W_OK): log.info( - "No write permission for %s. Switching to fallback directory.", dir_path + "No write permission for %s. Switching to fallback directory.", + current_dir, ) user_home = Path.home() fallback_dir = os.path.join( - user_home, ".empire", dir_path.removeprefix("empire/") + user_home, ".empire", str(current_dir).removeprefix("empire/") ) # Update the directory in config_dict @@ -91,10 +107,16 @@ def check_config_permission(config_dict: dict, config_type: str): # Write the updated configuration back to the correct YAML file with open(config_path, "w") as config_file: - yaml.safe_dump(config_dict, config_file) + yaml.safe_dump(paths2str(config_dict), config_file) + + return config_dict + - log.info( - "Updated %s config.yaml to use fallback directory: %s", - config_type, - fallback_dir, - ) +def paths2str(data): + if isinstance(data, dict): + return {key: paths2str(value) for key, value in data.items()} + if isinstance(data, list): + return [paths2str(item) for item in data] + if isinstance(data, Path): + return str(data) + return data diff --git a/empire/server/core/config.py b/empire/server/core/config.py index 1a358dca9..46eea3f51 100644 --- a/empire/server/core/config.py +++ b/empire/server/core/config.py @@ -76,9 +76,9 @@ def __getitem__(self, key): class DirectoriesConfig(EmpireBaseModel): - downloads: Path - module_source: Path - obfuscated_module_source: Path + downloads: Path = Path("empire/server/downloads") + module_source: Path = Path("empire/server/modules") + obfuscated_module_source: Path = Path("empire/server/data/obfuscated_module_source") class LoggingConfig(EmpireBaseModel): @@ -101,17 +101,26 @@ class EmpireConfig(EmpireBaseModel): alias="supress-self-cert-warning", default=True ) api: ApiConfig | None = ApiConfig() - starkiller: StarkillerConfig - submodules: SubmodulesConfig - database: DatabaseConfig + starkiller: StarkillerConfig = StarkillerConfig() + submodules: SubmodulesConfig = SubmodulesConfig() + database: DatabaseConfig = DatabaseConfig( + sqlite=SQLiteDatabaseConfig(), + mysql=MySQLDatabaseConfig(), + defaults=DatabaseDefaultsConfig(), + ) plugins: dict[str, dict[str, str]] = {} - directories: DirectoriesConfig - logging: LoggingConfig - debug: DebugConfig + directories: DirectoriesConfig = DirectoriesConfig() + logging: LoggingConfig = LoggingConfig() + debug: DebugConfig = DebugConfig(last_task=LastTaskConfig()) model_config = ConfigDict(extra="allow") - def __init__(self, config_dict: dict): + def __init__(self, config_dict: dict | None = None): + if config_dict is None: + config_dict = {} + if not isinstance(config_dict, dict): + raise ValueError("config_dict must be a dictionary") + super().__init__(**config_dict) # For backwards compatibility self.yaml = config_dict @@ -128,14 +137,18 @@ def set_yaml(location: str): log.warning(exc) -config_dict = {} +config_dict = EmpireConfig().model_dump() if "--config" in sys.argv: location = sys.argv[sys.argv.index("--config") + 1] log.info(f"Loading config from {location}") - config_dict = set_yaml(location) -if len(config_dict.items()) == 0: + loaded_config = set_yaml(location) + if loaded_config: + config_dict = loaded_config +elif config_manager.CONFIG_SERVER_PATH.exists(): log.info("Loading default config") - config_dict = set_yaml(config_manager.CONFIG_SERVER_PATH) + loaded_config = set_yaml(config_manager.CONFIG_SERVER_PATH) + if loaded_config: + config_dict = loaded_config + config_dict = config_manager.check_config_permission(config_dict, "server") -config_manager.check_config_permission(config_dict, "server") empire_config = EmpireConfig(config_dict) diff --git a/empire/server/modules/bof/nanodump.py b/empire/server/modules/bof/nanodump.py index 30d635115..c214234b8 100644 --- a/empire/server/modules/bof/nanodump.py +++ b/empire/server/modules/bof/nanodump.py @@ -16,8 +16,7 @@ def generate( module=module, params=params, obfuscate=obfuscate ) - for name in params: - value = params[name] + for name, value in params.items(): if name == "write": if value != "": dump_path = value