Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Empire for system-wide deployment #757

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

D3vil0p3r
Copy link

@D3vil0p3r D3vil0p3r commented Oct 21, 2024

Describe your changes

Refactored code to make Empire to be deployed at system-level (multi-user) with no impact on the current approach based on working only in a case Empire is cloned in a user-granted directory.

This PR makes Empire working also in case it is installed in a system directory without changing the current grants. It is possible by refactoring code by following XDG Base Dir specifications.

Issue ticket number and link (if there is one)

#756

Checklist before requesting a review

  • I have performed a self-review of my code
  • If it is a core feature, I have added thorough tests.
  • I have added an entry to CHANGELOG.md
  • I have updated the documentation in docs/ (if applicable)

@D3vil0p3r D3vil0p3r marked this pull request as ready for review October 21, 2024 14:14
@D3vil0p3r D3vil0p3r marked this pull request as draft October 21, 2024 14:19
@D3vil0p3r D3vil0p3r marked this pull request as ready for review October 21, 2024 14:38
@vinnybod
Copy link

vinnybod commented Oct 26, 2024

The reason the run_as_user function exists is because users are likely to run empire with sudo since it is required to open lower port numbers, but we don't want log files, downloads, cloned submodules, etc to end up with root user permissions.

For example, this function prevents the installation and the update of submodules and Starkiller because it "removes" privileges to the running sudo user.

Can you explain this in a bit more detail? You shouldn't need sudo to update the starkiller submodule.

@D3vil0p3r
Copy link
Author

D3vil0p3r commented Oct 27, 2024

Can you explain this in a bit more detail? You shouldn't need sudo to update the starkiller submodule.

Hey. sudo is not needed if I clone the Empire repository in my HOME folder, so in the case Empire is installed for only the current user. If an admin would like to install Empire at system-level (all users use case), it is needed to use sudo to get/update starkiller submodule, because it will be written in a system-level directory (i.e., /usr/share/Empire) where the standard user won't have grants.

@D3vil0p3r
Copy link
Author

@vinnybod any clue about my last comment?

@vinnybod
Copy link

vinnybod commented Nov 3, 2024

The current assumption Empire makes is that it is:

  • not installed as admin
  • run as admin for opening ports
  • doesn't write files as admin

I'd approve a PR that adds your admin install use case as long as it still supports the above assumptions too.

@D3vil0p3r
Copy link
Author

D3vil0p3r commented Nov 3, 2024

I need to do with you a deeper analysis to properly work on the code.

Problem statement

Empire cannot work at system-level, for example when on my host I have 10 user accounts (i.e., in a school), I cannot install one single Empire instance for everyone but I need to install N instances for each user by consuming a lot of resources.

Your requirements

  • not installed as admin
  • run as admin for opening ports
  • doesn't write files as admin

not installed as admin

  • Why don't you need Empire installed as root? I didn't get well your first comment above.

run as admin for opening ports

So, opening ports on the local network, and admin is needed.

  • Currently is this done right?
  • Is it done by asking user to run Empire as sudo?
  • In which code section are they opened?

doesn't write files as admin

  • Why?
  • Can you provide me a detailed motivation?
  • What are the files that must not be written as admin? And on which code section are used?

run_as_user() scope

Currently run_as_user() is used in:

  • cloning starkiller
  • fetch, checkout and pull Empire git repository
  • fetch submodules of Empire git repository

So, currently, run_as_user() function keeps these ONLY 3 operations above as low privilege even if the user uses sudo. But if Empire has other operations that write on the system, they should be still written as root

My proposal

I don't see relevant risks to install Empire at system-level (so as root) and to pull updates and submodules. If you want to be compliant with the minimum privilege principles, what we can do is to allow Empire to be installed as root at system-level (as occurs for any tools/packages in Linux), but we can identify all those files, resources, elements that must be dealt as current user instead of root, and manage them in the user HOME folder.

In this context run_as_user() can be used to exclusively deal with those files created by Empire. And we can create an additional function run_as_sudo() used only to update repository and submodules if Empire was installed at system-level, and for opening the needed ports.

@vinnybod
Copy link

vinnybod commented Nov 11, 2024

Why don't you need Empire installed as root? I didn't get well your first comment above.
I don't see relevant risks to install Empire at system-level (so as root) and to pull updates and submodules. If

It's more of a convenience factor for us at maintainers. There have been a lot of issues opened in the past due to file permissions issues. So enforcing installs and the file writes to be user-level decreases that.

For example:
git repo was cloned without sudo, but submodules were pulled with sudo causing future git commands to fail due to mixed file ownership
Running empire within an IDE for development fails due to the log files requiring elevated write access due to previously being written to as sudo

So, opening ports on the local network, and admin is needed.
Currently is this done right?
Is it done by asking user to run Empire as sudo?
In which code section are they opened?

We handle this automatically with the ps-empire command
http listeners bind to ports and many users use priveleged ports (<1024)

doesn't write files as admin
Why?
Can you provide me a detailed motivation?
What are the files that must not be written as admin? And on which code section are used?

Sort of got into this above, but ideally all the files in the application directory would have uniform access permissions.

It is less about risks and more about alleviating inbound requests related to issues stemming from people installing in different ways. My intention is that for 90%+ of users, the standard install that we enforce "just works".
We'd still want to unblock the minority, but not at the expense of making the "easy" case less functional.

@D3vil0p3r
Copy link
Author

D3vil0p3r commented Nov 11, 2024

I try to split the problem in small problems because involving all users could be complex but still possible. If we want to try to "unblock" the minority of users, and to not impact the current 90+% users, I would start to manage from the raising issues when Empire is installed system-wide BUT with non-root run. I will edit the PR code.

1. Issue: Permission denied on directories specified in config files

empire-server

Traceback (most recent call last):
  File "/usr/share/empire/empire.py", line 13, in <module>
    server.run(args)
  File "/usr/share/empire/empire/server/server.py", line 161, in run
    setup_logging(args)
  File "/usr/share/empire/empire/server/server.py", line 38, in setup_logging
    log_dir.mkdir(parents=True, exist_ok=True)
  File "/usr/lib/python3.12/pathlib.py", line 1311, in mkdir
    os.mkdir(self, mode)
PermissionError: [Errno 13] Permission denied: '/usr/share/empire/empire/server/downloads/logs'

Solution: code will check config.yaml file for the path of server and client. If the specified logs path is at user-level, it creates as-is; else... fallback to the standard user directory empire logs path $HOME/.empire/.

This approach is compliant to Linux specs (XDG Base Directory Specification that standardizes where application files, including logs, should be stored within the user’s home directory.)

Advantages:

  • Standardized approach
  • Empire usage for multi-user systems
  • Multiplatform (Linux, Darwin, Windows)
  • Not impact on the current Empire permission approach discussed above

2. Add check on fetch_submodules()

Added:

def fetch_submodules():
    if not os.path.exists(Path(".git")):
        log.info("No .git directory found. Skipping submodule fetch.")
        return
    command = ["git", "submodule", "update", "--init", "--recursive"]
    run_as_user(command)

to run this function only if .git has been found. It is the same logic you implemented for check_submodules().

3. config.yaml must be copied in HOME user folder

In Linux/Windows, configuration files must be copied from the default one to HOME folder, in order to allow it to manage according XDG standard specification. It allows to cover both user-level and system-level approach.

Furthermore, the definition of config file path must be managed centrally.

4. Manage dirs specified in config.yaml to fallback to $HOME directory - WIP

server/config.yaml has:

directories:
  downloads: empire/server/downloads/
  module_source: empire/server/data/module_source/
  obfuscated_module_source: empire/server/data/obfuscated_module_source/

I would suggest to manage the last two paths in a fixed manner on the code by removing them from config file, because Empire already provides all those modules. If a user changes those two lines, Empire starts to trigger errors because cannot find its directory where stored modules. If a user wants to add a custom module, Empire docs can report that user can add it in <root-empire-dir>/empire/server/data/{module_source, obfuscated_module_source} directory.

@D3vil0p3r D3vil0p3r closed this Nov 11, 2024
@D3vil0p3r D3vil0p3r reopened this Nov 11, 2024
@D3vil0p3r D3vil0p3r force-pushed the patch-2 branch 2 times, most recently from c8bf478 to 6b001e5 Compare November 11, 2024 23:55
@D3vil0p3r D3vil0p3r changed the title Replace run_as_user function by run_command Support Empire for system-wide deployment Nov 11, 2024
@vinnybod
Copy link

@D3vil0p3r thanks for iterating on this with me 😅 . I'm still learning the best practices around Linux.
Not sure if you are aware, but we do control the directory for most of what empire writes via these config properties.

@D3vil0p3r
Copy link
Author

Yes I am aware but I cannot write directly it because yaml file cannot expand env variables in case of Linux or Windows systems. So my approach is "first, copy config.yaml to home dir ~/.empire/{server,client} and use it, then, if cannot write on directories specified on config.yaml due to Permission Error, use a fallback directory in user home folder and write this info in config.yaml.

@D3vil0p3r
Copy link
Author

D3vil0p3r commented Nov 12, 2024

@vinnybod

1st Question

what is the difference between empire/server/data/module_source/ and empire/server/modules/ directories?

Why in empire/server/modules/ I have, for example, SpawnnProcess.yaml but in empire/server/data/module_source/ I don't have it?

These two folders are changed/written at runtime during the usage of Empire? If not, what is the reason to have

directories:
  module_source: empire/server/data/module_source/
  obfuscated_module_source: empire/server/data/obfuscated_module_source/

in server/config.yaml? If a user changes those two paths, Empire does not refer to those modules anymore (unless user copies and pastes them to its target directory) and it produces a lot of errors. If these two paths must be static, I would suggest to remove them from config.yaml. If you agree, I can do that and deal these two paths directly in the code.

2nd Question

One additional note: since you don't want users use sudo for running Empire, server.py has the following:

INVOKE_OBFS_DST_DIR_BASE = "/usr/local/share/powershell/Modules/Invoke-Obfuscation"
...
...
    # invoke obfuscation
    if os.path.exists(f"{INVOKE_OBFS_DST_DIR_BASE}"):
        shutil.rmtree(INVOKE_OBFS_DST_DIR_BASE)
    pathlib.Path(pathlib.Path(INVOKE_OBFS_SRC_DIR_BASE).parent).mkdir(
        parents=True, exist_ok=True
    )
    shutil.copytree(
        INVOKE_OBFS_SRC_DIR_BASE, INVOKE_OBFS_DST_DIR_BASE, dirs_exist_ok=True
    )

this copy will always fail due to missing permission. Still, here I would suggest to write in ~/.local/share/powershell/Modules/ so you don't need sudo.

3rd Question

When Empire is run the first time (for example on a Linux system), why is it running building/compilation by MSBuild?
Is it done for some plugins? Which one?
Where is the code triggering this building?
It is the last point I need to manage for system-wide Empire deployment before PR is ready to be reviewed.

Usually compilation must not run at runtime. Best practices suggest to do it at "building time". Is it possible you build them offline and upload the output files directly on the Empire repository so this build process code can be removed?

@vinnybod
Copy link

1st Question
what is the difference between empire/server/data/module_source/ and empire/server/modules/ directories?

The module_source directory contains source code for the modules, like the powershell code for the powershell modules. The modules contains the yaml definition for a module and optionally a python file if it needs a custom generation. There' some more info about modules here: https://bc-security.gitbook.io/empire-wiki/module-development

Why in empire/server/modules/ I have, for example, SpawnnProcess.yaml but in empire/server/data/module_source/ I don't have it?

Not every module has a 1 to 1 mapping with source code in module_source. There are modules that have the code directly in the yaml, generated dynamically, or multiple modules that map to the same source file.

If a user changes those two paths, Empire does not refer to those modules anymore (unless user copies and pastes them to its target directory) and it produces a lot of errors. If these two paths must be static, I would suggest to remove them from config.yaml

I don't recall the exact reason that these are in the config.yaml. But if I were to guess, I was moving towards a model where all the data directories that empire needs to read/write can be configurable. I'd prefer to keep them in the config for now.

2nd Question
this copy will always fail due to missing permission. Still, here I would suggest to write in ~/.local/share/powershell/Modules/ so you don't need sudo.

I am fine moving /usr/local/share/powershell/Modules/Invoke-Obfuscation to whatever directory makes the most sense in linux.

3rd Question
When Empire is run the first time (for example on a Linux system), why is it running building/compilation by MSBuild?
Is it done for some plugins? Which one?
Where is the code triggering this building?
It is the last point I need to manage for system-wide Empire deployment before PR is ready to be reviewed.
Usually compilation must not run at runtime. Best practices suggest to do it at "building time". Is it possible you build them offline and upload the output files directly on the Empire repository so this build process code can be removed?

This is coming from compiling the csharp compiler https://github.com/BC-SECURITY/Empire/blob/main/empire/server/plugins/csharpserver/csharpserver.py#L98-L100

We have actually already addressed this point in the upcoming 6.0 release which is due in a few months, so you shouldn't bother changing that. The 6.0 release will download a binary in the install script instead of compiling it at runtime.

@D3vil0p3r
Copy link
Author

@vinnybod did you get the chance to review my changes?

empire/server/server.py Outdated Show resolved Hide resolved
empire/server/server.py Show resolved Hide resolved
empire/config_manager.py Show resolved Hide resolved
Comment on lines 18 to 31
CONFIG_CLIENT_PATH.parent.mkdir(parents=True, exist_ok=True)
CONFIG_SERVER_PATH.parent.mkdir(parents=True, exist_ok=True)

if not CONFIG_CLIENT_PATH.exists():
shutil.copy(SOURCE_CONFIG_CLIENT, CONFIG_CLIENT_PATH)
log.info(f"Copied {SOURCE_CONFIG_CLIENT} to {CONFIG_CLIENT_PATH}")
else:
log.info(f"{CONFIG_CLIENT_PATH} already exists.")

if not CONFIG_SERVER_PATH.exists():
shutil.copy(SOURCE_CONFIG_SERVER, CONFIG_SERVER_PATH)
log.info(f"Copied {SOURCE_CONFIG_SERVER} to {CONFIG_SERVER_PATH}")
else:
log.info(f"{CONFIG_SERVER_PATH} already exists.")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential issue I see here is a user can pass a custom config location via --config but this assumes the config is always in the repo directory. Is there a way to support --config?

Copy link
Author

@D3vil0p3r D3vil0p3r Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No @vinnybod . It is not a potential issue because:
1- When you run Empire the first time, it runs config_init() and copies the two config files to user home config dir if they don't exist... BUT...
2- client config and server config say:

  • if user provides --config, load config from the provided location. Then...
  • if the provided config is empty (that means either you passed NO config file or you didn't use --config parameter), use the default client config file (the one we copied on HOME)

@vinnybod
Copy link

You will need to make sure linting and formatting are passing

poetry run ruff check . --fix
poetry run black . 

@D3vil0p3r
Copy link
Author

D3vil0p3r commented Nov 21, 2024

$ sudo poetry run ruff check . --fix

All checks passed!
$ sudo poetry run black .
reformatted /home/user/Empire/empire/client/src/EmpireCliConfig.py
reformatted /home/user/Empire/empire/config_manager.py

All done! ✨ 🍰 ✨
2 files reformatted, 389 files left unchanged.

@vinnybod
Copy link

@D3vil0p3r thanks, i'll actually test it out this weekend. Can you add an entry to the changelog.md ?

@D3vil0p3r
Copy link
Author

@D3vil0p3r thanks, i'll actually test it out this weekend. Can you add an entry to the changelog.md ?

Added. Thanks

@D3vil0p3r
Copy link
Author

@vinnybod any news about your review?

("database", "sqlite", "location"): config_dict["database"]["sqlite"][
"location"
],
("starkiller", "directory"): config_dict["starkiller"]["directory"],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some tests failing here. The reason is that some of these fields are optional and have defaults.

My recommendation, do the call to config_manager.check_config_permission with the validated config object, just dump it back to a dict.

config.py

empire_config = EmpireConfig(config_dict)
config_manager.check_config_permission(empire_config.model_dump(), "server")

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhm how to do that?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EmpireConfig(config_dict).model_dump() will give a config dict that has been fully validated, meaning that the unspecified parameters will be set to their defaults.
Then you can do your permission checks, rewrites, etc.

@vinnybod
Copy link

vinnybod commented Nov 24, 2024

A couple notable changes that need to be documented in some way. In general, I am in favor of these changes but we just need to make sure that these things are noted in the changelog (and wiki if necessary).

  • The new default config location is ~/.empire/. Making changes to the config in empire/server/config.yaml will no longer work unless the config in ~/.empire is removed.
  • It looks like Starkiller is now getting cloned into ~/.empire. I am fine with this change (and moving writable files to ~/.empire in general. But its not clear to me as a user why this is happening. If my config.yaml says empire/server/api/v2/starkiller I would have expected it to be in the repo dir.
    • Again, I am in favor of moving the writable files to the home directory, but it needs to be a bit more clear that is what is happening.
  • Same comment as above but with the logs/downloads

I think we are pretty close to landing this!

@D3vil0p3r
Copy link
Author

@vinnybod the reason why all those things are moved to $HOME/.empire is because, if you install empire at system-level, in general all runtime writable files, if stored on installation folder /usr/share/empire, you cannot run empire because you will get a Permission Denied error.

By these changes, even if Empire is installed at system level, by moving runtime writable files on $HOME/.empire directory, the user can still run Empire with no sudo.

In particular, StarKiller is cloned to that home folder because it periodically check for updates, so its directory needs to be writable at runtime.

Note that, if I remember well, when you run Empire first time, in config.yaml file you won't have empire/server/api/v2/starkiller but $HOME/.empire/server/api/v2/starkiller (same for other writable file paths), so config.yaml file will be consistent.

So just to summarize and according to what I reported above, what is missing before merging?

@vinnybod
Copy link

At a minimum, needed for merge:

  • Tests are failing. In particular, startup shouldn't fail because starkiller.directory is not set in the yaml since EmpireConfig defaults most of the empty directory fields.
  • A note in the CHANGELOG and wiki (perhaps at https://github.com/BC-SECURITY/Empire/tree/main/docs/quickstart/configuration) to describe how Empire now looks for a config.yaml and how it rewrites the paths to ~/.empire

--

Just some other musings, don't feel obligated to change the implementation here.
Would it be simpler to have a app_data configuration that is defaulted to ~/.empire? Most users won't change it, but it will be painfully obvious that all file writes are going to ~/.empire. Would that allow us to avoid managing all the checks/rewrites on the paths that we added here?

@D3vil0p3r
Copy link
Author

D3vil0p3r commented Nov 24, 2024

Just some other musings, don't feel obligated to change the implementation here.
Would it be simpler to have a app_data configuration that is defaulted to ~/.empire? Most users won't change it, but it will be painfully obvious that all file writes are going to ~/.empire. Would that allow us to avoid managing all the checks/rewrites on the paths that we added here?

@vinnybod The problem is that yaml files cannot expand environment variables, so if your objective is to set upstream, in config.yaml the path $HOME/.empire/server/xxx, $HOME or ~ is not expanded. The only way currently is to allow the code to do that as in my PR... OR, we can still hardcode $HOME/.empire/server/xxx in config.yaml and the code just replaces $HOME by the actual $HOME value of the current user. What do you think of this approach?

Tests are failing. In particular, startup shouldn't fail because starkiller.directory is not set in the yaml since EmpireConfig defaults most of the empty directory fields.

It is not clear to me how to solve this. Can you guide me how can I run those tests that fail?

@vinnybod
Copy link

Can you guide me how can I run those tests that fail?

poetry run pytest . will run all tests. You can run specific test files like poetry run pytest empire/test/test_config.py

It is not clear to me how to solve this.

https://github.com/D3vil0p3r/Empire/blob/patch-2/empire/server/core/config.py#L140-L141

I think you could just change this loader to something like

tmp_config_dict = EmpireConfig(config_dict).model_dump()
# Change this function to return back the updated dict after it writes out the file
config_dict = config_manager.check_config_permission(tmp_config_dict, "server")
empire_config = EmpireConfig(config_dict)

@D3vil0p3r D3vil0p3r force-pushed the patch-2 branch 2 times, most recently from 7007733 to 564fd2e Compare November 25, 2024 08:36
@D3vil0p3r
Copy link
Author

D3vil0p3r commented Nov 25, 2024

@vinnybod I updated the wiki files and changelog in this PR. Can you give a look?

I also fixed the test error you got. The reason was that the config dict was structured in an inconsistent manner. I fixed it and now that test is correct.

By running now poetry run pytest empire/test/test_config.py there is only 1 error but not related to the files I edited:

/usr/lib/python3.12/site-packages/starlette/testclient.py:593: in post
    return super().post(
/usr/lib/python3.12/site-packages/httpx/_client.py:1157: in post
    return self.request(
/usr/lib/python3.12/site-packages/starlette/testclient.py:484: in request
    return super().request(
/usr/lib/python3.12/site-packages/httpx/_client.py:837: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
/usr/lib/python3.12/site-packages/httpx/_client.py:926: in send
    response = self._send_handling_auth(
/usr/lib/python3.12/site-packages/httpx/_client.py:954: in _send_handling_auth
    response = self._send_handling_redirects(
/usr/lib/python3.12/site-packages/httpx/_client.py:991: in _send_handling_redirects
    response = self._send_single_request(request)
/usr/lib/python3.12/site-packages/httpx/_client.py:1027: in _send_single_request
    response = transport.handle_request(request)
/usr/lib/python3.12/site-packages/starlette/testclient.py:377: in handle_request
    raise exc
/usr/lib/python3.12/site-packages/starlette/testclient.py:374: in handle_request
    portal.call(self.app, scope, receive, send)
/usr/lib/python3.12/site-packages/anyio/from_thread.py:287: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
/usr/lib/python3.12/concurrent/futures/_base.py:456: in result
    return self.__get_result()
/usr/lib/python3.12/concurrent/futures/_base.py:401: in __get_result
    raise self._exception
/usr/lib/python3.12/site-packages/anyio/from_thread.py:218: in _call_func
    retval = await retval_or_awaitable
/usr/lib/python3.12/site-packages/fastapi/applications.py:1054: in __call__
    await super().__call__(scope, receive, send)
/usr/lib/python3.12/site-packages/starlette/applications.py:113: in __call__
    await self.middleware_stack(scope, receive, send)
/usr/lib/python3.12/site-packages/starlette/middleware/errors.py:187: in __call__
    raise exc
/usr/lib/python3.12/site-packages/starlette/middleware/errors.py:165: in __call__
    await self.app(scope, receive, _send)
/usr/lib/python3.12/site-packages/starlette/middleware/gzip.py:20: in __call__
    await responder(scope, receive, send)
/usr/lib/python3.12/site-packages/starlette/middleware/gzip.py:39: in __call__
    await self.app(scope, receive, self.send_with_gzip)
empire/server/api/middleware.py:36: in __call__
    await super().__call__(scope, receive, send)
/usr/lib/python3.12/site-packages/starlette/middleware/cors.py:85: in __call__
    await self.app(scope, receive, send)
/usr/lib/python3.12/site-packages/starlette/middleware/exceptions.py:62: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
/usr/lib/python3.12/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
/usr/lib/python3.12/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
/usr/lib/python3.12/site-packages/starlette/routing.py:715: in __call__
    await self.middleware_stack(scope, receive, send)
/usr/lib/python3.12/site-packages/starlette/routing.py:735: in app
    await route.handle(scope, receive, send)
/usr/lib/python3.12/site-packages/starlette/routing.py:288: in handle
    await self.app(scope, receive, send)
/usr/lib/python3.12/site-packages/starlette/routing.py:76: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
/usr/lib/python3.12/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
/usr/lib/python3.12/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
/usr/lib/python3.12/site-packages/starlette/routing.py:73: in app
    response = await f(request)
/usr/lib/python3.12/site-packages/fastapi/routing.py:301: in app
    raw_response = await run_endpoint_function(
/usr/lib/python3.12/site-packages/fastapi/routing.py:212: in run_endpoint_function
    return await dependant.call(**values)
empire/server/api/v2/listener/listener_api.py:65: in create_listener
    resp, err = listener_service.create_listener(db, listener_req)
empire/server/core/listener_service.py:96: in create_listener
    template_instance, err = self._validate_listener_options(
empire/server/core/listener_service.py:225: in _validate_listener_options
    validated, err = template_instance.validate_options()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <http_malleable.Listener object at 0x75a0984e2180>

    def validate_options(self) -> tuple[bool, str | None]:
        """
        Validate all options for this listener.
        """
    
        profile_name = self.options["Profile"]["Value"]
        profile_data = (
            SessionLocal()
            .query(models.Profile)
            .filter(models.Profile.name == profile_name)
            .first()
        )
        try:
            profile = malleable.Profile()
>           profile.ingest(content=profile_data.data)
E           AttributeError: 'NoneType' object has no attribute 'data'

empire/server/listeners/http_malleable.py:180: AttributeError

Comment on lines +113 to +115
downloads=Path("empire/server/downloads"),
module_source=Path("empire/server/modules"),
obfuscated_module_source=Path("empire/server/modules_obfuscated")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just add these as the defaults on the DirectoriesConfig

class DirectoriesConfig(EmpireBaseModel):
    downloads: Path = Path("empire/server/downloads")
    module_source: Path("empire/server/modules")
    obfuscated_module_source: Path("empire/server/data/obfuscated_module_source")

Copy link
Author

@D3vil0p3r D3vil0p3r Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you mean

    module_source: Path = Path("empire/server/modules")
    obfuscated_module_source: Path = Path("empire/server/data/obfuscated_module_source")

instead of

    module_source: Path("empire/server/modules")
    obfuscated_module_source: Path("empire/server/data/obfuscated_module_source")

Btw, fixed.

empire/server/core/config.py Outdated Show resolved Hide resolved
empire/server/core/config.py Outdated Show resolved Hide resolved

# Write the updated configuration back to the correct YAML file
with open(config_path, "w") as config_file:
yaml.safe_dump(paths2str(config_dict), config_file)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you would be better off doing something like

config_path.write_text(EmpireConfig(config_dict).model_dump(mode='json'))

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EmpireConfig is not defined in config_manager.py and, if I import it from config.py, we get circular dependency error:

Traceback (most recent call last):
  File "/home/athena/Empire/empire.py", line 5, in <module>
    from empire import arguments, config_manager
  File "/home/athena/Empire/empire/config_manager.py", line 7, in <module>
    from empire.server.core.config import EmpireConfig
  File "/home/athena/Empire/empire/server/core/config.py", line 151, in <module>
    elif config_manager.CONFIG_SERVER_PATH.exists():
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: partially initialized module 'empire.config_manager' has no attribute 'CONFIG_SERVER_PATH' (most likely due to a circular import)

I would leave as is @vinnybod

@vinnybod
Copy link

vinnybod commented Nov 26, 2024

there is only 1 error but not related to the files I edited:

It's possible you don't have the malleable profile submodule cloned down.
try: git submodule update --init --force

@D3vil0p3r
Copy link
Author

there is only 1 error but not related to the files I edited:

It's possible you don't have the malleable profile submodule cloned down. try: git submodule update --init --force

Indeed. Now all test_config tests work well.

dmore pushed a commit to dmore/Empire-red-c2-pure-powershell-agent-compat-python3-linux-agents that referenced this pull request Nov 26, 2024
* Added initial implementation of c# bof yamls

* updated bof yamls to take in formatting types

* added clipboard and secinject

* added nanodump

* added tgtdelegation

* fixed formatting

* update folders and changelog

* added pytest

* fixed pytest

* fixed seatbelt

* Update empire/server/modules/bof/secinject.py

Co-authored-by: Vincent Rose <[email protected]>

* trying to figure this out

* Update empire/server/modules/bof/nanodump.py

Co-authored-by: Vincent Rose <[email protected]>

* Update empire/server/core/module_service.py

Co-authored-by: Vincent Rose <[email protected]>

* Update empire/server/core/module_service.py

Co-authored-by: Vincent Rose <[email protected]>

* fixed dashes

* fixed vinnybod comments and nanodump.yaml

* fixed formatting

* fixed formatting

* simplified redundant functions

* formatting

* Update empire/server/core/module_service.py

Co-authored-by: Vincent Rose <[email protected]>

* Update empire/server/core/module_models.py

Co-authored-by: Vincent Rose <[email protected]>

* Update empire/server/modules/bof/tgtdelegation.py

Co-authored-by: Vincent Rose <[email protected]>

* sim108 fixes

* fixed nanodump and removed pytest

* second round of SA modules

* added 2nd round of bofs

* fixed errors

* formatting

* updated changelog

* added docs for bof-modules

* Update docs/module-development/bof-modules.md

Co-authored-by: Vincent Rose <[email protected]>

* made fixes

---------

Co-authored-by: Vincent Rose <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants