diff --git a/README.md b/README.md index 342c6474..bfe416d5 100644 --- a/README.md +++ b/README.md @@ -24,32 +24,46 @@ $ pip3 install . ``` Using pip: - ``` $ pip3 install attackmate ``` +Using uv: +``` +$ git clone https://github.com/ait-aecid/attackmate.git +$ cd attackmate +$ uv sync +``` + ## Execute +With pip: ``` $ attackmate playbook.yml ``` +With uv: +``` +$ uv run attackmate playbook.yml +``` + ![AttackMate Demo](docs/source/images/Demo.gif "AttackMate Demo") ## Documentation -Please take a look at our documentation for how to install and use attackmate: +Please take a look at our documentation on how to install and use attackmate: * [Installation](https://ait-testbed.github.io/attackmate/main/installation/index.html) * [Documentation](https://ait-testbed.github.io/attackmate/main/index.html) * [Command Reference](https://ait-testbed.github.io/attackmate/main/playbook/commands/index.html) * [Example Playbooks](https://ait-testbed.github.io/attackmate/main/playbook/examples.html) -* [Arxiv Paper](https://arxiv.org/pdf/2601.14108) + +## Publications +* [AttackMate: Realistic Emulation and Automation of Cyber Attack Scenarios Across the Kill Chain](https://arxiv.org/pdf/2601.14108) on Arxiv ## Contribution -We're happily taking patches and other contributions. Please see the following links for how to get started: +We're happily taking patches and other contributions. Please see the following links on how to get started: - [Contribution Guide](https://ait-testbed.github.io/attackmate/main/developing/contribution.html) @@ -64,13 +78,12 @@ information may result in criminal charges. ## Security -AttackMate should only be executed against own test or training systems. -For this reason, every software bug is treated equally, regardless of -whether it is security relevant or not. +AttackMate should only be executed against systems you own or have explicit permission to test. +For this reason, all software bugs are treated with equal priority, regardless of whether they have security implications. *Please note that AttackMate could easily be executed in a dangerous way. For example, by parsing the RESULT_STDOUT of a malicious server. The server response could lead to -a command injection. Keep that in mind! +a command injection. Keep that in mind and always treat external input with caution! ## License diff --git a/docs/source/about.rst b/docs/source/about.rst index 9371dbe1..57651d7b 100644 --- a/docs/source/about.rst +++ b/docs/source/about.rst @@ -2,6 +2,6 @@ About ===== -AttackMate is a command line tool that can perform entire attack chains based on playbooks. It can be used for cybersecurity training as well as for testing detection engines. AttackMate is so designed that any part of an attack can be easily replaced or varied. Furthermore, the playbooks enable the portability and reproducibility of attack chains. A key focus was on user-friendliness and flexibility for attack chain designers. +AttackMate is a command-line tool that executes full attack chains defined in playbooks. It is designed for cybersecurity training, red team exercises, and testing detection engines. Individual attack steps can be freely replaced or varied, and playbooks ensure that attack chains are portable and reproducible. A key focus was on user-friendliness and flexibility for attack chain designers. .. image:: images/Demo.gif diff --git a/docs/source/attackmate/attackmate.rst b/docs/source/attackmate/attackmate.rst index bbc2dec7..6c2199a7 100644 --- a/docs/source/attackmate/attackmate.rst +++ b/docs/source/attackmate/attackmate.rst @@ -1,77 +1,382 @@ attackmate package -============== +================== -Submodules ----------- +Core +---- -attackmate.baseexecutor module --------------------------- +.. automodule:: attackmate.attackmate + :members: + :undoc-members: + :show-inheritance: -.. automodule:: attackmate.baseexecutor +.. automodule:: attackmate.metadata :members: :undoc-members: :show-inheritance: -attackmate.metadata module ----------------------- +.. automodule:: attackmate.variablestore + :members: + :undoc-members: + :show-inheritance: -.. automodule:: attackmate.metadata +.. automodule:: attackmate.result :members: :undoc-members: :show-inheritance: -attackmate.msfexecutor module -------------------------- +.. automodule:: attackmate.processmanager + :members: + :undoc-members: + :show-inheritance: -.. automodule:: attackmate.msfexecutor +.. automodule:: attackmate.command :members: :undoc-members: :show-inheritance: -attackmate.attackmate module --------------------- +Schemas +------- -.. automodule:: attackmate.attackmate +.. automodule:: attackmate.schemas.base :members: :undoc-members: :show-inheritance: -attackmate.schemas module ---------------------- +.. automodule:: attackmate.schemas.config + :members: + :undoc-members: + :show-inheritance: -.. automodule:: attackmate.schemas +.. automodule:: attackmate.schemas.command_types :members: :undoc-members: :show-inheritance: -attackmate.shellexecutor module ---------------------------- +.. automodule:: attackmate.schemas.command_subtypes + :members: + :undoc-members: + :show-inheritance: -.. automodule:: attackmate.shellexecutor +.. automodule:: attackmate.schemas.bettercap :members: :undoc-members: :show-inheritance: -attackmate.sleepexecutor module ---------------------------- +.. automodule:: attackmate.schemas.browser + :members: + :undoc-members: + :show-inheritance: -.. automodule:: attackmate.sleepexecutor +.. automodule:: attackmate.schemas.debug :members: :undoc-members: :show-inheritance: -attackmate.varparse module ----------------------- +.. automodule:: attackmate.schemas.father + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.http + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.include + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.json + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.loop + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.metasploit + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.playbook + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.regex + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.remote + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.setvar + :members: + :undoc-members: + :show-inheritance: -.. automodule:: attackmate.varparse +.. automodule:: attackmate.schemas.shell :members: :undoc-members: :show-inheritance: -Module contents ---------------- +.. automodule:: attackmate.schemas.sleep + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.sliver + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.ssh + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.tempfile + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.schemas.vnc + :members: + :undoc-members: + :show-inheritance: + +Executors +--------- + +.. automodule:: attackmate.executors.baseexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.executor_factory + :members: + :undoc-members: + :show-inheritance: + +Executor Features +^^^^^^^^^^^^^^^^^ + +.. automodule:: attackmate.executors.features.background + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.features.cmdvars + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.features.conditional + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.features.exitonerror + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.features.looper + :members: + :undoc-members: + :show-inheritance: + +Bettercap +^^^^^^^^^ + +.. automodule:: attackmate.executors.bettercap.bettercapexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.bettercap.client + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.bettercap.api + :members: + :undoc-members: + :show-inheritance: + +Browser +^^^^^^^ + +.. automodule:: attackmate.executors.browser.browserexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.browser.sessionstore + :members: + :undoc-members: + :show-inheritance: + +Common +^^^^^^ + +.. automodule:: attackmate.executors.common.debugexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.common.includeexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.common.jsonexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.common.loopexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.common.regexexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.common.setvarexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.common.sleepexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.common.tempfileexecutor + :members: + :undoc-members: + :show-inheritance: + +Father +^^^^^^ + +.. automodule:: attackmate.executors.father.fatherexecutor + :members: + :undoc-members: + :show-inheritance: + +HTTP +^^^^ + +.. automodule:: attackmate.executors.http.httpclientexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.http.webservexecutor + :members: + :undoc-members: + :show-inheritance: + +Metasploit +^^^^^^^^^^ + +.. automodule:: attackmate.executors.metasploit.msfexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.metasploit.msfpayloadexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.metasploit.msfsessionexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.metasploit.msfsessionstore + :members: + :undoc-members: + :show-inheritance: + +Remote +^^^^^^ + +.. automodule:: attackmate.executors.remote.remoteexecutor + :members: + :undoc-members: + :show-inheritance: + +Shell +^^^^^ + +.. automodule:: attackmate.executors.shell.shellexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.shell.sessionstore + :members: + :undoc-members: + :show-inheritance: + +Sliver +^^^^^^ + +.. automodule:: attackmate.executors.sliver.sliverexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.sliver.sliversessionexecutor + :members: + :undoc-members: + :show-inheritance: + +SSH +^^^ + +.. automodule:: attackmate.executors.ssh.sshexecutor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.ssh.sftpfeature + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.ssh.interactfeature + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: attackmate.executors.ssh.sessionstore + :members: + :undoc-members: + :show-inheritance: + +VNC +^^^ + +.. automodule:: attackmate.executors.vnc.vncexecutor + :members: + :undoc-members: + :show-inheritance: -.. automodule:: attackmate +.. automodule:: attackmate.executors.vnc.sessionstore :members: :undoc-members: :show-inheritance: diff --git a/docs/source/basic.rst b/docs/source/basic.rst index 2d6e19a0..a3b96ebb 100644 --- a/docs/source/basic.rst +++ b/docs/source/basic.rst @@ -2,7 +2,7 @@ Basic Usage =========== -AttackMate ships with a executable stub called "attackmate" that can be called like follows: +AttackMate is invoked via the ``attackmate`` command: :: @@ -24,7 +24,8 @@ AttackMate ships with a executable stub called "attackmate" that can be called l Sample Playbook =============== -In our first example we use the following playbook.yml: +The following playbook demonstrates a simple reconnaissance chain using nmap, regex parsing, +and a conditional nikto scan: .. code-block:: yaml @@ -47,19 +48,31 @@ In our first example we use the following playbook.yml: cmd: nikto -host $TARGET -port $PORT only_if: $PORT == 8000 -.. warning:: +.. note:: - For this playbook it is required to have nmap and nikto installed! - This playbook also needs a webserver at localhost on port 8000. - You can run ``python3 -mhttp.server`` in a seperate shell to start - the webserver. + This playbook requires ``nmap`` and ``nikto`` to be installed, and a web server + running on ``localhost:8000``. You can start one with: + + :: + + $ python3 -mhttp.server First Run ========= -Now we can run the playbook using the following command: -(We can supply the full path to the playbook, otherwise the parser tries to find it in the current working directory or in the folder /etc/attackmate/playbooks) +Run the playbook with ``--debug`` for verbose output: + +:: + + $ attackmate --debug playbook.yml + +.. note:: + + The playbook path can be absolute, relative to the current working directory, + or relative to ``/etc/attackmate/playbooks``. + +Expected output: :: @@ -76,33 +89,33 @@ Now we can run the playbook using the following command: Explanation =========== -In the **vars**-section we have three variables That can be used later in the **commands**-section. -The nmap-binary is expected at the location */usr/bin/nmap*. The target to attack is *localhost* and +**vars** defines reusable variables that can be referenced throughout the ``commands`` section via ``$VARNAME`` substitution. +In this example, we define the path to the nmap binary, the target host, and the web port to attack. +The nmap-binary is expected at the location */usr/bin/nmap*, the target to attack is *localhost* and the web-port to attack is *8000*. -:: +.. code-block:: yaml vars: NMAP: /usr/bin/nmap TARGET: localhost WEBPORT: "8000" -The first command executes an nmap-script-scan on port *8000* at *localhost*. This command illustrates -how to use variables: +The first command executes an nmap script scan on port *8000* against the target *localhost*. This command illustrates +how to use variables: Variables are substituted at runtime using ``$VARNAME`` syntax: -:: +.. code-block:: yaml commands: - type: shell cmd: $NMAP -sC -p $WEBPORT $TARGET -As soon as nmap finishes, it automatically stores the output the the built-in variable ``RESULT_STDOUT``. -The regex command executes a regex search using the content of the nmap output. The regular expression is -`(\d+)/tcp open\s+http`. If the expression matches, it will "group" the port number in the variable -``$MATCH_0`` which is a volatile variable and is deleted after the regex-command finishes. In the setting -*output* is a variable defined with the name ``PORT`` and it will be set with the value of ``$MATCH_0``. +As soon as nmap finishes, its output is automatically stored in the built-in variable ``RESULT_STDOUT``. +The regex command searches this output using the expression ``(\d+)/tcp open\s+http``. +If it matches, the captured port number is stored in the volatile variable ``$MATCH_0`` (deleted after the regex-command finishes), +which is then assigned to the persistent variable ``PORT`` via the ``output`` mapping: -:: +.. code-block:: yaml - type: regex cmd: (\d+)/tcp open\s+http @@ -110,10 +123,10 @@ The regex command executes a regex search using the content of the nmap output. output: PORT: $MATCH_0 -The final command is again a shell command that is supposed to execute a nikto scan using the previously -parsed variable ``$PORT``. This command will only be executed if the condition ``$PORT == 8000`` is **True**. +The final command is a shell command that executes a nikto scan using the previously +parsed ``$PORT`` variable. The ``only_if`` condition ensures this command will only be executed if ``$PORT == 8000`` is **True**. -:: +.. code-block:: yaml - type: shell cmd: nikto -host $TARGET -port $PORT diff --git a/docs/source/conf.py b/docs/source/conf.py index 64b7dbff..54deb5ed 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -8,7 +8,7 @@ import os import sys -sys.path.insert(0, os.path.abspath('../..')) +sys.path.insert(0, os.path.abspath('../../src')) project = 'AttackMate' copyright = '2023, Wolfgang Hotwagner' @@ -37,6 +37,7 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = 'sphinx_rtd_theme' +html_logo = 'images/AttackMate_logo_no_logo.png' html_static_path = ['_static'] html_theme_options = { diff --git a/docs/source/configuration/bettercap_config.rst b/docs/source/configuration/bettercap_config.rst index d6a08503..71d1b60b 100644 --- a/docs/source/configuration/bettercap_config.rst +++ b/docs/source/configuration/bettercap_config.rst @@ -4,14 +4,13 @@ bettercap_config ================ -bettercap_config holds settings for the Bettercap rest-api. The configuration -always starts with an identifier for the connection. This identifier can be -selected when executing a command in a playbook. The first connection in this -file is the default if no explicit connection was selected in the command. +``bettercap_config`` holds connection profiles for the Bettercap REST API. Each profile +is identified by a user-defined name that can be referenced in playbook commands via the +``connection`` field. If no connection is specified, the first profile in the configuration +is used as the default. .. code-block:: yaml - ### bettercap_config: default: url: "http://localhost:8081" @@ -22,9 +21,13 @@ file is the default if no explicit connection was selected in the command. username: btrcp password: somepass + + +The following playbook example shows how to target a specific connection, and how the +default connection is used when none is specified: + .. code-block:: yaml - # bettercap-playbook.yml: commands: # this is executed on the remote host: - type: bettercap @@ -39,24 +42,24 @@ file is the default if no explicit connection was selected in the command. .. confval:: url - This option stores the url to the rest-api + URL of the Bettercap REST API. :type: str .. confval:: username - The http basic username for the rest-api + HTTP Basic Auth username for the Bettercap REST API. :type: str .. confval:: password - The http basic password for the rest-api + HTTP Basic Auth password for the Bettercap REST API. :type: str .. confval:: cafile - The path to the ca-file for the encryption if https is in use. + Path to the CA certificate file used for TLS verification when connecting via HTTPS. :type: str diff --git a/docs/source/configuration/command_config.rst b/docs/source/configuration/command_config.rst index 2d01a2e6..a0fb8d0f 100644 --- a/docs/source/configuration/command_config.rst +++ b/docs/source/configuration/command_config.rst @@ -7,25 +7,23 @@ These are settings for **all** commands. .. code-block:: yaml - ### cmd_config: loop_sleep: 5 command_delay: 0 .. confval:: loop_sleep - All commands can be configured to be executed in a loop. For example: if a command - does not deliver the expected output, the command can be executed again until the - output has the expected value. Between the executions can be a sleep time of certain - seconds. + All commands can be configured to be executed in a loop. For example: + retrying until the output of a command matches an expected value, + with an optional sleep interval between attempts. :type: int :default: 5 .. confval:: command_delay - This delay in seconds is applied to all commands in the playbook. - It is not applied to debug, setvar and sleep commands. + A delay in seconds applied **before** each command in the playbook. + Does not apply to ``debug``, ``setvar``, and ``sleep`` commands. :type: float :default: 0 diff --git a/docs/source/configuration/index.rst b/docs/source/configuration/index.rst index b6befdbd..df7ac9ed 100644 --- a/docs/source/configuration/index.rst +++ b/docs/source/configuration/index.rst @@ -11,7 +11,7 @@ is not used, attackmate will search at the following locations for the config-fi #. **$HOME/.config/attackmate.yml** #. **/etc/attackmate.yml** -The optional configuration-file is in yaml-format and is divided into five sections: +The optional configuration file is in yaml-format and is divided into five sections: * **cmd_config**: defines settings for all commands * **msf_config**: connection settings for the msfrpcd @@ -20,11 +20,10 @@ The optional configuration-file is in yaml-format and is divided into five secti * **remote_config**: connection settings for the remote attackmate server The following configuration file is an example for a basic configuration with -sliver and metasploit: +sliver, metasploit and remote attackmate server: .. code-block:: yaml - ### cmd_config: loop_sleep: 5 command_delay: 0 @@ -42,7 +41,14 @@ sliver and metasploit: sliver_config: config_file: /home/attacker/.sliver-client/configs/attacker_localhost.cfg -For detailed information about the config sections see: + remote_config: + remote_server_name: + url: "https://10.0.0.5:5000" + username: admin + password: securepassword + cafile: "/path/to/cert.pem" + +For detailed information about each config section see: .. toctree:: :maxdepth: 4 diff --git a/docs/source/configuration/msf_config.rst b/docs/source/configuration/msf_config.rst index 596f3a24..afddee4a 100644 --- a/docs/source/configuration/msf_config.rst +++ b/docs/source/configuration/msf_config.rst @@ -4,48 +4,47 @@ msf_config ========== -msf_config holds settings for the Metasploit modules and sessions. -Most of these settings control the Metsaploit RPC connection. +``msf_config`` holds connection settings for the Metasploit RPC daemon (``msfrpcd``). .. code-block:: yaml - ### msf_config: password: securepassword server: 10.18.3.86 + .. confval:: server - This option stores the servername or ip-address of the msfrpcd + The servername or IP address of the ``msfrpcd``. :type: str :default: 127.0.0.1 -.. confval:: password +.. confval:: port - This option stores the password of the rpc-connection. + Port on which ``msfrpcd`` is listening. - :type: str - :default: None + :type: int + :default: 55553 -.. confval:: ssl +.. confval:: uri - This option enables encryption for the rpc-connection + URI of the RPC API. - :type: bool - :default: True +.. confval:: ssl -.. confval:: port + Enables encryption for the RPC connection. - This option sets the port for the rpc-connection. + :type: bool + ::default: ``True`` - :type: int - :default: 55553 +.. confval:: password -.. confval:: uri + The password for the RPC connection. - This option sets uri of the rpc-api. + :type: str + :default: None diff --git a/docs/source/configuration/remote_config.rst b/docs/source/configuration/remote_config.rst index c032c7d2..f184c5e5 100644 --- a/docs/source/configuration/remote_config.rst +++ b/docs/source/configuration/remote_config.rst @@ -4,9 +4,11 @@ remote_config ============= -The remote_config section defines connections to remote AttackMate instances. This allows one AttackMate instance to act as a controller, dispatching playbooks or commands to remote nodes. +``remote_config`` defines connections to remote AttackMate instances. This allows one AttackMate instance to act as a controller, dispatching playbooks or commands to remote nodes. -Like other executors, the configuration uses unique identifiers (names) for each connection. If a command in a playbook does not explicitly specify a connection, the first entry defined in this configuration is used as the default. +Each connection is identified by a user-defined name that can be referenced in playbook +commands via the ``connection`` field. If no connection is specified, the first entry in +the configuration is used as the default. .. code-block:: yaml @@ -23,42 +25,49 @@ Like other executors, the configuration uses unique identifiers (names) for each cafile: "/path/to/another_cert.pem" +The following example shows how to target a specific remote instance, and how the +default connection is used when none is specified: + .. code-block:: yaml commands: - # Executed on 'another_server' - - type: remote - connection: another_server - cmd: execute_command - remote_command: - type: shell - cmd: "whoami" - - # Executed on 'remote_server' (defaults to first remote_config entry)) - - type: remote - cmd: execute_playbook - playbook_path: path/to/playbook.yml + # Executed on 'another_server' + - type: remote + connection: another_server + cmd: execute_command + remote_command: + type: shell + cmd: "whoami" + + # Executed on 'remote_server' (defaults to first remote_config entry)) + - type: remote + cmd: execute_playbook + playbook_path: path/to/playbook.yml .. confval:: url The base URL of the remote AttackMate REST API. -:type: str :required: True +:type: str +:required: True .. confval:: username The username for authentication with the remote AttackMate instance. -:type: str :required: False +:type: str +:required: False .. confval:: password The password for authentication with the remote AttackMate instance. -:type: str :required: False +:type: str +:required: False .. confval:: cafile -The path to a CA certificate file used to verify the remote server's SSL certificate. This is highly recommended when using HTTPS. +The path to a CA certificate file used to verify the remote server's TLS certificate. Strongly recommended when connecting over HTTPS. -:type: str :required: False +:type: str +:required: False diff --git a/docs/source/configuration/sliver_config.rst b/docs/source/configuration/sliver_config.rst index ad629cfb..e0c356f1 100644 --- a/docs/source/configuration/sliver_config.rst +++ b/docs/source/configuration/sliver_config.rst @@ -4,16 +4,17 @@ sliver_config ============= -sliver_config stores all settings to controll the connection to the sliver-API. +``sliver_config`` stores all settings to control the connection to the Sliver API. .. code-block:: yaml - ### sliver_config: config_file: /home/attacker/.sliver-client/configs/attacker_localhost.cfg .. confval:: config_file - Path to the sliver-client configfile. + Path to the Sliver client configuration file. This file is generated by the Sliver + server during operator setup and is typically found under + ``~/.sliver-client/configs/``. :type: str diff --git a/docs/source/developing/architecture.rst b/docs/source/developing/architecture.rst index 5da12c27..ca860d2f 100644 --- a/docs/source/developing/architecture.rst +++ b/docs/source/developing/architecture.rst @@ -12,7 +12,7 @@ C1 – System Context Diagram :width: 80% :alt: System Context Diagram -The System Context diagram shows how **AttackMate** fits into its environment. It illustrates the main user +The System Context diagram shows how AttackMate fits into its environment. It illustrates the main user (e.g., a pentester or researcher), the software systems it interacts with (e.g., vulnerable target systems, external frameworks like Metasploit or Sliver), and the nature of those interactions. diff --git a/docs/source/developing/baseexecutor.rst b/docs/source/developing/baseexecutor.rst index cdabe903..0d5f7e16 100644 --- a/docs/source/developing/baseexecutor.rst +++ b/docs/source/developing/baseexecutor.rst @@ -39,6 +39,7 @@ Example: from attackmate.executors.base_executor import BaseExecutor from attackmate.result import Result + @executor_factory.register_executor('custom') class CustomExecutor(BaseExecutor): async def _exec_cmd(self, command) -> Result: self.logger.info(f"Executing custom command: {command.cmd}") diff --git a/docs/source/developing/command.rst b/docs/source/developing/command.rst index a0f2da35..18d75e59 100644 --- a/docs/source/developing/command.rst +++ b/docs/source/developing/command.rst @@ -14,7 +14,24 @@ This section details the steps required to integrate a new command. All Commands in AttackMate inherit from ``BaseCommand``. To create a new command, define a class in `/src/attackmate/schemas` and register it using the ``@CommandRegistry.register('')`` decorator. -For example, to add a ``debug`` command: +Registering the command in the ``CommandRegistry`` allows the command to be also instantiated dynamically using the ``Command.create()`` method and is essential to +make it usable in external python scripts. + +.. note:: + + **Registration rules:** + + - Every command class must have a **unique** ``type`` literal — this is the sole discriminator + used to identify a command in the union. + - A command may additionally define a ``cmd`` field to express sub-behaviors (e.g. + ``Literal['file', 'dir']``). In this case, branching on ``cmd`` belongs in the + **executor**, not in the schema's union discrimination. + - The ``type`` + ``cmd`` nested union pattern seen in ``SliverSessionCommands`` is + **legacy** and must not be replicated. New command families must always use a unique + ``type`` per class. + + +**Example: a simple command with no sub-behaviors** :: @@ -30,14 +47,26 @@ For example, to add a ``debug`` command: wait_for_key: bool = False cmd: str = '' -Registering the command in the ``CommandRegistry`` allows the command to be also instantiated dynamically using the ``Command.create()`` method and is essential to -make them usable in external python scripts. + +**Example: a command with sub-behaviors expressed via** ``cmd`` + +:: + + from typing import Literal + from .base import BaseCommand + from attackmate.command import CommandRegistry + + @CommandRegistry.register('mktemp') + class TempfileCommand(BaseCommand): + type: Literal['mktemp'] + cmd: Literal['file', 'dir'] = 'file' + variable: str 2. Implement the Command Execution =================================== -The new command should be handled by an executor in `src/attackmate/executors`` that extends ``BaseExecutor`` and implements the ``_exec_cmd()`` method. For example: +The new command should be handled by an executor in `src/attackmate/executors` that extends ``BaseExecutor`` and implements the ``_exec_cmd()`` method. For example: :: @@ -51,16 +80,25 @@ The new command should be handled by an executor in `src/attackmate/executors`` self.logger.info(f"Executing debug command: {command.cmd}") return Result(stdout="Debug executed", returncode=0) + 3. Ensure the Executor Handles the New Command ============================================== The ``ExecutorFactory`` class manages and creates executor instances based on command types. It maintains a registry (``_executors``) that maps command type strings to executor classes, allowing for dynamic execution of different command types. Executors are registered using the ``register_executor`` method, which provides a decorator to associate a command type with a class. + When a command is executed, the ``create_executor`` method retrieves the corresponding executor class, filters the constructor arguments based on the class's signature, and then creates an instance. Accordingly, executors must be registered using the ``@executor_factory.register_executor('')`` decorator. +:: + + @executor_factory.register_executor('debug') + class DebugExecutor(BaseExecutor): + # implementation of the executor + + If the new executor class requires additional initialization arguments, these must be added to the ``_get_executor_config`` method in ``attackmate.py``. All configurations are always passed to the ``ExecutorFactory``. The factory filters the provided configurations based on the class constructor signature, ensuring that only the required parameters are used. @@ -84,7 +122,7 @@ The factory filters the provided configurations based on the class constructor s 4. Modify the Loop Command to Include the New Command ===================================================== -Update the ``LoopCommand`` schema to include the new command. +in `/src/attackmate/schemas/loop.py` update the ``LoopCommand`` schema to include the new command. :: @@ -95,21 +133,47 @@ Update the ``LoopCommand`` schema to include the new command. ] -5. Modify playbook.py to Include the New Command -===================================================== - -Update the ``Playbook`` schema to include the new command. +4. Modify the RemotelyExecutableCommand Union to Include the New Command +======================================================================== +in `src/attackmate/schemas/command_subtypes.py`, update the ``RemotelyExecutableCommand`` type alias to include the new command :: - Commands = List[ + RemotelyExecutableCommand: TypeAlias = Annotated[ Union[ + SliverSessionCommands, + SliverCommands, + BrowserCommand, ShellCommand, DebugCommand, # Newly added command # ... other command classes ... - ] + ], + Field(discriminator='type'), # Outer discriminator (type) ] +.. note:: + + ``RemotelyExecutableCommand`` defines the complete set of commands that can be executed + on a remote AttackMate instance. It is a Pydantic discriminated union using ``type`` as + its sole discriminator — every command class must define a unique ``type`` literal, which + is used to resolve the correct class from the union. + + **Adding a new command to** ``RemotelyExecutableCommand``\ **:** + + Simply add the new command class directly to the ``Union`` in ``RemotelyExecutableCommand``. + No further schema-level discrimination is needed — any sub-behaviors should be expressed + via a ``cmd`` field on the class and handled in the executor. + + **Legacy pattern (do not replicate):** + + The nested ``SliverSessionCommands`` and ``SliverCommands`` aliases use a two-level + discrimination strategy — an outer ``type`` discriminator to identify the command family, + and an inner ``cmd`` discriminator to resolve the specific sub-command. This follows + Pydantic's `nested discriminated unions + `_ pattern + but couples sub-behavior decisions into the schema layer. New command families must + **not** replicate this pattern. + Once these steps are completed, the new command will be fully integrated into AttackMate and available for execution. 6. Add Documentation diff --git a/docs/source/developing/contribution.rst b/docs/source/developing/contribution.rst index 26128f00..cd79fe4a 100644 --- a/docs/source/developing/contribution.rst +++ b/docs/source/developing/contribution.rst @@ -9,8 +9,8 @@ We're happily taking patches and other contributions. Below is a summary of the Bug reports and enhancement requests ==================================== -Bug reports and enhancement requests are an important part of making `attackmate` more stable and are curated through Github issues. -Before reporting an issue, check our backlog of open issues to see if anybody else has already reported it. +Bug reports and enhancement requests are an important part of making AttackMate more stable and are curated through Github issues. +Before reporting an issue, check our backlog of open issues to see if anybody else has already reported it. If that is the case, you might be able to give additional information on that issue. Bug reports are very helpful to us in improving the software, and therefore, they are very welcome. It is very important to give us at least the following information in a bug report: @@ -18,9 +18,9 @@ at least the following information in a bug report: 1. Description of the bug. Describe the problem clearly. 2. Steps to reproduce. With the following configuration, go to.., click.., see error 3. Expected behavoir. What should happen? -4. Environment. What was the environment for the test(version, browser, etc..) +4. Environment. What was the environment for the test (version, browser, etc..) -For reporting security-related issues, see `SECURITY.md`_ +For reporting security-related issues, see `SECURITY.md`_ .. _SECURITY.md: https://github.com/ait-testbed/attackmate/blob/main/SECURITY.md @@ -35,7 +35,7 @@ To contribute to this project, you must fork the project and create a pull reque 1. Fork ------- -Go to `https://github.com/ait-testbed/attackmate.git `_ and click on fork. Please note that you must login first to GitHub. +Go to `https://github.com/ait-testbed/attackmate.git `_ and click on fork. Please note that you must login to GitHub first. 2. Clone -------- @@ -59,11 +59,11 @@ Every single workpackage should be developed in it's own feature-branch. Use a n 4. Develop your feature and improvements in the feature-branch -------------------------------------------------------------- -Please make sure that you commit only improvements that are related to the workpage you created the feature-branch for. See the section :ref:`Development ` for detailed information about how to develope code for `attackmate`. +Please make sure that you commit only improvements that are related to the workpage you created the feature-branch for. See the section :ref:`Development ` for detailed information about how to develope code for AttackMate. .. note:: - `attackmate` uses `prek`_ to ensure code quality. Make sure that you use it properly. + AttackMate uses `prek`_ to ensure code quality. Make sure to use it properly. .. _prek: https://github.com/j178/prek @@ -100,7 +100,7 @@ After that we can squash the last n commits together: git rebase -i HEAD~n -Finally you can push the changes to YOUR github-repository: +Finally you can push the changes to YOUR GitHub repository: :: @@ -113,7 +113,7 @@ Additional documentation: 7. Submit your pull-request --------------------------- -Use the GitHub-Webinterface to create a pull-request. Make sure that the target-repository is `ait-testbed/attackmate`. +Use the GitHub webinterface to create a pull-request. Make sure that the target-repository is `ait-testbed/attackmate`. If your pull-request was accepted and merged into the development branch continue with :ref:`Update your local main branch `. If it wasn't accepted, read the comments and fix the problems. Before pushing the changes make sure that you squashed them with your last commit: @@ -140,12 +140,11 @@ Additional infos: * `https://www.atlassian.com/git/tutorials/merging-vs-rebasing `_ -9. Update your main branch in your github-repository +9. Update your main branch in your GitHub repository ---------------------------------------------------- -Please make sure that you updated your local development branch as described in section 8. above. After that push the changes to your github-repository to keep it up2date: +Please make sure that you updated your local development branch as described in section 8 above. Then push the changes to your GitHub repository to keep it up2date: :: git push - diff --git a/docs/source/developing/development.rst b/docs/source/developing/development.rst index 77b2d860..3462111a 100644 --- a/docs/source/developing/development.rst +++ b/docs/source/developing/development.rst @@ -4,12 +4,13 @@ Development =========== -This section describes how to setup a development environment and how to contribute to `attackmate`. +This section describes how to setup a development environment and how to contribute to AttackMate. .. note:: - Read the :ref:`Contribution Guide ` to follow and understand the development workflow. - + Read the :ref:`Contribution Guide ` to follow and understand the development workflow, and familiarise yourself with :ref:`Adding a New Command ` + and :ref:`Adding a New Executor ` before getting started. + Setup a development environment diff --git a/docs/source/developing/integration.rst b/docs/source/developing/integration.rst index 76b13c23..4bb22d52 100644 --- a/docs/source/developing/integration.rst +++ b/docs/source/developing/integration.rst @@ -67,11 +67,11 @@ Understanding the Result Object =============================== When executing a command with AttackMate, the result is returned as an instance of the ``Result`` class. This object contains the standard output (`stdout`) and the return code (`returncode`) of the executed command. -Commands that run in the Background return Result(None,None) +Commands that run in the Background return Result('Command started in background', 0) .. note:: Regular Commands return a ``Result`` object. - Commands that run in background mode return ``Result(None,None)``. + Commands that run in background mode return ``Result('Command started in background', 0)``. Attributes ---------- diff --git a/docs/source/how.rst b/docs/source/how.rst index 64beb20e..80e8da4e 100644 --- a/docs/source/how.rst +++ b/docs/source/how.rst @@ -2,6 +2,16 @@ How it works ============ -AttackMate is designed so that there can be different commands in a playbook which can be executed by corresponding executors. For example, there are executors that can orchestrate Metasploit or executors for the Sliver framework. The attacks that are carried out are real attacks and require vulnerable systems to be successful. +AttackMate executes playbooks consisting of commands, each handled by a corresponding +executor. Executors integrate with external tools and frameworks, for example the +Metasploit executor can run modules and manage sessions, while the Sliver executor can +generate implants and issue C2 commands. A full list of available executors and their +commands can be found in the :ref:`commands ` reference. + +.. note:: + + AttackMate executes real attacks and requires intentionally vulnerable or + dedicated test systems. Never run AttackMate against systems + you do not own or do not have explicit permission to test. .. image:: images/attackmate-schema.png diff --git a/docs/source/installation/ansible.rst b/docs/source/installation/ansible.rst index d3510796..c5300056 100644 --- a/docs/source/installation/ansible.rst +++ b/docs/source/installation/ansible.rst @@ -5,11 +5,11 @@ Installation with Ansible ========================= It is possible to automatically install AttackMate using -Ansible. The `ansible-role `_ also deploys the sliver-fix. +Ansible. The `ansible-role `_ also deploys the :ref:`sliver-fix`. .. note:: - Currently the ansible role only works with Debian and Ubuntu distributions. + Currently the ansible role supports Kali, Debian and Ubuntu distributions. Installation Steps @@ -22,7 +22,7 @@ Installation Steps $ sudo apt update $ sudo apt install ansible -y -2. Set Up the Playbook +2. Set Up the Playbook: Create a new directory for your AttackMate setup, navigate into it, and create your playbook file: @@ -32,10 +32,10 @@ Installation Steps $ cd my-attackmate $ touch install_attackmate.yml -Open the `install_attackmate.yml` file and fill it with this sample playbook (it also can be found on the README-page -of the `github-repository `_), which installs AttackMate on localhost: +Open the `install_attackmate.yml` file and fill it with this sample playbook to install AttackMate on localhost. The playbook can also can be found on the README page +of the `github-repository `_, have a look at the role variables there for further configuration options. -:: +.. code-block:: yaml - name: Install attackmate become: true @@ -64,16 +64,16 @@ of the `github-repository `_), localhost ansible_connection=local -3. Clone the Ansible Role +4. Clone the Ansible Role: - Ansible expects all roles to be in the **roles** directory. Create this directory and clone the repository: + Ansible expects all roles to be in the **roles** directory. Create this directory and clone the ansible role repository: :: $ mkdir -p roles/attackmate $ git clone https://github.com/ait-testbed/attackmate-ansible roles/attackmate -4. Run the playbook +5. Run the playbook: :: diff --git a/docs/source/installation/docker.rst b/docs/source/installation/docker.rst index 9443a4b5..5fabe93b 100644 --- a/docs/source/installation/docker.rst +++ b/docs/source/installation/docker.rst @@ -22,4 +22,4 @@ Build the image using the following command: .. note:: Docker will also compile grpcio from sources in order to make - the sliver-api work. This might take a while. + the Sliver API work (see :ref:`sliver-fix`). This might take a while. diff --git a/docs/source/installation/index.rst b/docs/source/installation/index.rst index 62758f90..0976eaf1 100644 --- a/docs/source/installation/index.rst +++ b/docs/source/installation/index.rst @@ -17,4 +17,3 @@ and :ref:`Sliver `. sliverfix ansible docker - uv diff --git a/docs/source/installation/manual.rst b/docs/source/installation/manual.rst index 355439a0..8c30b924 100644 --- a/docs/source/installation/manual.rst +++ b/docs/source/installation/manual.rst @@ -1,5 +1,3 @@ -.. _manual: - ========================= Installation from sources ========================= @@ -15,7 +13,7 @@ following tools: .. note:: - python3-venv must only be installed if AttackMate should be installed in a virtual environment + ``python3-venv`` only needs to be installed if AttackMate should be installed in a virtual environment. Download the sources: @@ -24,20 +22,38 @@ Download the sources: $ git clone https://github.com/ait-aecid/attackmate.git $ cd attackmate -Optional: Create virtual environment and activate it: +**Option A: Install with pip** + +Optional: Create a virtual environment and activate it: :: $ python3 -mvenv venv $ source venv/bin/activate -Finally install attackmate and it's dependencies: +Install AttackMate and its dependencies: :: $ pip3 install . +**Option B: Install with uv** + +``uv`` manages the virtual environment and dependencies automatically — no manual venv setup needed: + +:: + + $ pip3 install uv + $ uv sync + +Run AttackMate via: + +:: + + $ uv run attackmate playbook.yml + .. warning:: - Please note that you need to :ref:`sliver-fix` if you want + Please note that you if you install from source you also need to install :ref:`sliver-fix` if you want to use the sliver commands! + The Dockerfile and ansible role :ref:`ansible` already include the sliver-fix. diff --git a/docs/source/installation/sliverfix.rst b/docs/source/installation/sliverfix.rst index 72c006c7..d342a5a0 100644 --- a/docs/source/installation/sliverfix.rst +++ b/docs/source/installation/sliverfix.rst @@ -12,7 +12,7 @@ the environment variable ``GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=True`` .. note:: - Please note that this fix is already included in the Dockerfile. + Please note that this fix is already included in the Dockerfile and the Ansible role. First install all required build-tools. This example will install the build-tools in Debian-based distributions: @@ -36,8 +36,7 @@ dependencies: (venv)$ git submodule update --init (venv)$ pip install -r requirements.txt -Now remove the packages we want to compile by our own: - +Now remove the packages we want to compile ourselves: :: (venv)$ pip uninstall --yes protobuf diff --git a/docs/source/installation/uv.rst b/docs/source/installation/uv.rst deleted file mode 100644 index 99d39b70..00000000 --- a/docs/source/installation/uv.rst +++ /dev/null @@ -1,62 +0,0 @@ -.. _uv: - -========================= -Installation with uv -========================= - -It is possible install AttackMate using `uv `_ package manager. - - -Installation Steps -================== - -1. Install uv: - -:: - - $ curl -LsSf https://astral.sh/uv/install.sh | sh - -2. Git clone AttackMate - -:: - - $ git clone https://github.com/ait-testbed/attackmate.git - -3. Navigate into the repository - -:: - - $ cd attackmate - - -4. Create a virtual environment with uv - -:: - - $ uv venv - -4. Create a package using uv - -:: - - $ uv build - -5. Install AttackMate and it`s dependencies using - -:: - - $ uv pip install . - - -6. You can run AttackMate in the project environment with - -:: - - $ uv run attackmate - - - -.. warning:: - - Please note that you need to :ref:`sliver-fix` if you want - to use the sliver commands! diff --git a/docs/source/playbook/commands/bettercap.rst b/docs/source/playbook/commands/bettercap.rst index b1d2e807..3063e72c 100644 --- a/docs/source/playbook/commands/bettercap.rst +++ b/docs/source/playbook/commands/bettercap.rst @@ -4,13 +4,14 @@ bettercap ========= -This command communicates with the bettercap rest-api. It supports all -endpoints of the official api. Please see `Bettercap Rest-Api Docs `_ +This command communicates with the Bettercap REST API. It supports all +endpoints of the official API. Please see `Bettercap Rest-Api Docs `_ for additional information. All commands return a json-formatted string. -All commands support the setting: `connection`. This settings allows to query a api-command on a specific host. The name -of the connection must be set in attackmate.yml. If connection is not set, the command will be executed on the first -connection in attackmate.yml: +All ``bettercap`` commands support the ``connection`` field, which specifies which configured +host to target. Connection profiles are defined in ``attackmate.yml`` under +:ref:`bettercap_config`. If ``connection`` is omitted, the first profile in the configuration +is used as the default. .. code-block:: yaml @@ -33,55 +34,52 @@ connection in attackmate.yml: data: cmd: "net.sniff on" connection: remote - # this is executed on localhost: + # this is executed on localhost (default): - type: bettercap cmd: get_events .. note:: - To configure the connection to the bettercap rest-api see :ref:`bettercap_config` + To configure the connection to the bettercap REST API see :ref:`bettercap_config` post_api_session ---------------- -Post a command to the interactive session. +Post a command to the interactive Bettercap session. + +.. confval:: data + + Key-value pairs of POST data to send to the session endpoint. + + :type: Dict[str, str] .. code-block:: yaml - ### commands: - type: bettercap cmd: post_api_session data: cmd: "net.sniff on" -.. confval:: data +get_file +-------- - Dict(key/values) of post-data: +Get a file from the Bettercap API server. - :type: Dict[str,str] +.. confval:: filename -get_file --------- + Full path of the file to retrieve from the API server. -Get a file from the api-server. + :type: str .. code-block:: yaml - ### commands: - type: bettercap cmd: get_file filename: "/etc/passwd" -.. confval:: filename - - Full path of the filename on the api-server. - - :type: str - - delete_api_events ----------------- @@ -89,33 +87,28 @@ Clear the events buffer. .. code-block:: yaml - ### commands: - type: bettercap cmd: delete_api_events - get_events ---------- -Get all events +Get all events from the current session. .. code-block:: yaml - ### commands: - type: bettercap cmd: get_events - get_session_modules ------------------- -Get session modules +Get all modules active in the current session. .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_modules @@ -123,11 +116,10 @@ Get session modules get_session_env --------------- -Get session environment +Get the environment variables of the current session. .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_env @@ -135,11 +127,10 @@ Get session environment get_session_gateway ------------------- -Get session gateway +Get gateway information for the current session. .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_gateway @@ -147,11 +138,10 @@ Get session gateway get_session_interface --------------------- -Get session interface +Get network interface information for the current session. .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_interface @@ -159,11 +149,10 @@ Get session interface get_session_options ------------------- -Get session options +Get the configured options for the current session. .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_options @@ -171,11 +160,10 @@ Get session options get_session_packets ------------------- -Get session packets +Get packet statistics for the current session. .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_packets @@ -183,11 +171,10 @@ Get session packets get_session_started_at ---------------------- -Get session started at +Get the timestamp of when the current session was started. .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_started_at @@ -195,25 +182,22 @@ Get session started at get_session_hid --------------- -Get a JSON of the HID devices in the current session - -.. code-block:: yaml - - ### - commands: - - type: bettercap - cmd: get_session_hid +Get a JSON list of HID devices discovered in the current session. .. confval:: mac - Optional parameter to return the info of a specific endpoint + Optional. Filter results to a specific device by MAC address. :type: str + :required: False .. code-block:: yaml - ### commands: + - type: bettercap + cmd: get_session_hid + + # Filter by MAC address: - type: bettercap cmd: get_session_hid mac: "32:26:9f:a4:08" @@ -221,24 +205,24 @@ Get a JSON of the HID devices in the current session get_session_ble --------------- -Get a JSON of the BLE devices in the current session. +Get a JSON list of BLE devices discovered in the current session. + .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_ble .. confval:: mac - Optional parameter to return the info of a specific endpoint + Optional. Return the info of a specific device by MAC address. :type: str + :required: False .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_ble @@ -247,24 +231,23 @@ Get a JSON of the BLE devices in the current session. get_session_lan --------------- -Get a JSON of the lan devices in the current session +Get a JSON of the lan devices in the current session. .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_lan .. confval:: mac - Optional parameter to return the info of a specific endpoint + Optional. Return the info of a specific device by MAC address. :type: str + :required: False .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_lan @@ -273,24 +256,23 @@ Get a JSON of the lan devices in the current session get_session_wifi ---------------- -Get a JSON of the wifi devices (clients and access points) in the current session +Get a JSON of the wifi devices (clients and access points) in the current session. .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_wifi .. confval:: mac - Optional parameter to return the info of a specific endpoint + Optional. Return the info of a specific device by MAC address. :type: str + :required: False .. code-block:: yaml - ### commands: - type: bettercap cmd: get_session_wifi diff --git a/docs/source/playbook/commands/browser.rst b/docs/source/playbook/commands/browser.rst index c86d6bb1..3733e195 100644 --- a/docs/source/playbook/commands/browser.rst +++ b/docs/source/playbook/commands/browser.rst @@ -2,13 +2,14 @@ browser ======= -Execute commands using a Playwright-managed browser (Chromium). This executor can launch a browser, navigate to pages, click elements, type into fields, and take screenshots. +Execute commands using a Playwright-managed Chromium browser. This executor can launch a browser, navigate to pages, click elements, type into fields, and take screenshots. .. note:: - By default, if no session is provided or created, the command will run in an ephemeral browser session - (launched and destroyed for each command). If you want to persist browser state (cookies, localStorage, - etc.) across multiple commands, use the :confval:`creates_session` or :confval:`session` options. + By default, each command runs in an ephemeral browser session that is launched and + destroyed automatically. To persist browser state (cookies, localStorage, + etc.) across multiple commands, use the :confval:`creates_session` to open a named + session and :confval:`session` to reuse it. .. code-block:: yaml @@ -22,26 +23,26 @@ Execute commands using a Playwright-managed browser (Chromium). This executor ca url: $URL creates_session: my_session - # Reuse the existing "my_session" to type text into an input field + # Type into a field, reusing the named session: - type: browser cmd: type selector: "input[id='searchInput']" text: "Test" session: "my_session" - # Click on the "submit" button, still reusing "my_session" + # Click on the "submit" button, reusing the named session: - type: browser cmd: click selector: "button[type='submit']" session: "my_session" - # Take a screenshot of the current page in the "my_session" + # Take a screenshot of the current page, reusing the named session: - type: browser cmd: screenshot screenshot_path: "example.png" session: "my_session" - # Open a page in an ephemeral session (automatically closed) + # Open a page in an ephemeral session (automatically closed after command) - type: browser cmd: visit url: "https://www.google.com" @@ -51,39 +52,45 @@ Execute commands using a Playwright-managed browser (Chromium). This executor ca Specifies the browser action to execute. One of ``visit``, ``click``, ``type``, ``screenshot``. :type: str + :default: ``visit`` .. confval:: url - URL to visit for the ``visit`` command. + URL to navigate to for the ``visit`` command. :type: str + :required: when ``cmd: visit`` .. confval:: selector - CSS selector identifying the element to interact with for the ``click`` or ``type`` commands. + CSS selector identifying the target element to interact with for the ``click`` or ``type`` commands. :type: str + :required: when ``cmd: click`` OR ``cmd: type`` .. confval:: text Text to type into the specified element for the ``type`` command. :type: str + :required: when ``cmd: type`` .. confval:: screenshot_path - Specifies the file path where a screenshot should be saved for the ``screenshot`` command. + Filepath where a screenshot should be saved for the ``screenshot`` command. :type: str + :required: when ``cmd: screenshot`` .. confval:: creates_session - A session name to create when running this command. Once created, the session is retained in the + Name of a new browser session to create. Once created, the session is retained in the session store for reuse by subsequent ``browser`` commands that specify ``session``. - If a session of the same name already exists, it is automatically closed before creating the new one. + If a session with the same name already exists, it is automatically closed and replaced. :type: str + :required: False .. confval:: session @@ -94,20 +101,25 @@ Execute commands using a Playwright-managed browser (Chromium). This executor ca :type: str + :required: False + .. confval:: headless Run the browser in headless mode. Useful for CI/CD pipelines or servers without a GUI. :type: bool - :default: false - Example: + :required: False + + :default: ``False`` + + Example: - .. code-block:: yaml + .. code-block:: yaml - - type: browser - cmd: visit - url: "https://example.com" - creates_session: ci_session - headless: true + - type: browser + cmd: visit + url: "https://example.com" + creates_session: ci_session + headless: true diff --git a/docs/source/playbook/commands/debug.rst b/docs/source/playbook/commands/debug.rst index 33ec553e..b837dc1d 100644 --- a/docs/source/playbook/commands/debug.rst +++ b/docs/source/playbook/commands/debug.rst @@ -2,12 +2,11 @@ debug ===== -This command prints out strings and variables and is for debugging -purposes/printing to the console only. This command does not modify the Builtin Variable ``RESULT_STDOUT``. +The ``debug`` command prints messages and variables to the console. It is intended for +troubleshooting playbooks and does not modify ``RESULT_STDOUT`` (:ref:`builtin variables `). .. code-block:: yaml - ### vars: $SERVER_ADDRESS: 192.42.0.254 $NMAP: /usr/bin/nmap @@ -19,32 +18,36 @@ purposes/printing to the console only. This command does not modify the Builtin .. confval:: cmd - A message to print on the screen. + A message to print to the console. Supports variable substitution. :type: str :default: ``empty_string`` + :required: False .. confval:: varstore - Print out all variables that are stored in the VariableStore. + Print out all variables currently stored in the VariableStore. :type: bool :default: ``False`` + :required: False .. confval:: exit This setting causes the programm to exit when the command was executed. It will exit with an error in order to indicate - that this is an exceptional break. + that this is an intentional early termination. :type: bool :default: ``False`` + :required: False .. confval:: wait_for_key - This setting causes the programm to pause until the user - hits the enter key. + Pause execution until the user presses Enter. Useful for stepping through a + playbook manually. :type: bool :default: ``False`` + :required: False diff --git a/docs/source/playbook/commands/father.rst b/docs/source/playbook/commands/father.rst index c91fe421..5ce2a603 100644 --- a/docs/source/playbook/commands/father.rst +++ b/docs/source/playbook/commands/father.rst @@ -2,7 +2,7 @@ father ====== -The Fahter LD_PRELOAD rootkit requires to compile the config settings into the binary. +The Father LD_PRELOAD rootkit requires to compile the config settings into the binary. This command compiles the binary and stores the path in the variable ``LAST_FATHER_PATH``. If ``local_path`` is not defined, the command will create a temporary directory and copy the sources into the directory before compiling the rootkit. @@ -12,7 +12,6 @@ Father can be found at `this GitHub-Page `_ .. code-block:: yaml - ### commands: - type: father cmd: generate @@ -31,6 +30,7 @@ Father can be found at `this GitHub-Page `_ :type: int :default: ``1337`` + :required: False .. confval:: srcport @@ -38,13 +38,15 @@ Father can be found at `this GitHub-Page `_ :type: int :default: ``54321`` + :required: False .. confval:: epochtime - Time for timebomb() to go off, in seconds since 1970-01-01 + Time for ``timebomb()`` to go off, in seconds since 1970-01-01. :type: int :default: ``0000000000`` + :required: False .. confval:: env_var @@ -53,6 +55,7 @@ Father can be found at `this GitHub-Page `_ :type: str :default: ``lobster`` + :required: False .. confval:: file_prefix @@ -60,13 +63,15 @@ Father can be found at `this GitHub-Page `_ :type: str :default: ``lobster`` + :required: False .. confval:: preload_file - Hide this preload file(hide the rootkit) + Hide this preload file (hide the rootkit). :type: str :default: ``ld.so.preload`` + :required: False .. confval:: hiddenport @@ -74,20 +79,23 @@ Father can be found at `this GitHub-Page `_ :type: str(hex) :default: ``D431`` + :required: False .. confval:: shell_pass - Password for accept() backdoor shell + Password for ``accept()`` backdoor shell. :type: str :default: ``lobster`` + :required: False .. confval:: install_path - Location of rootkit on disk + Location of rootkit on disk. :type: str :default: ``/lib/selinux.so.3`` + :required: False .. confval:: local_path @@ -95,6 +103,7 @@ Father can be found at `this GitHub-Page `_ If not set, the builder will generate a temporary path. :type: str + :required: False .. confval:: arch @@ -103,6 +112,7 @@ Father can be found at `this GitHub-Page `_ :type: str :default: ``amd64`` + :required: False .. confval:: build_command @@ -111,3 +121,4 @@ Father can be found at `this GitHub-Page `_ :type: str :default: ``make`` + :required: False diff --git a/docs/source/playbook/commands/httpclient.rst b/docs/source/playbook/commands/httpclient.rst index 18903041..0d5be17f 100644 --- a/docs/source/playbook/commands/httpclient.rst +++ b/docs/source/playbook/commands/httpclient.rst @@ -2,16 +2,17 @@ http-client =========== -Execute HTTP-requests like curl does. This command also supports HTTP/2 +Send HTTP requests with support for common methods, custom headers, cookies, and HTTP/2. .. code-block:: yaml - ### commands: + # Simple HTTP/2 GET request: - type: http-client url: https://www.google.com http2: True + # POST request with headers, cookies, and form data: - type: http-client cmd: POST url: https://api.myapp.tld @@ -24,6 +25,7 @@ Execute HTTP-requests like curl does. This command also supports HTTP/2 view: edit id: 10 + # PUT request uploading a local file - type: http-client cmd: PUT url: https://api.myapp.tld/dav @@ -33,25 +35,19 @@ Execute HTTP-requests like curl does. This command also supports HTTP/2 .. confval:: cmd - The HTTP-Method to use. Supported methods are: - - * GET - * POST - * PUT - * DELETE - * PATCH - * HEAD - * OPTIONS + HTTP method to use. Supported values: ``GET``, ``POST``, ``PUT``, ``DELETE``, + ``PATCH``, ``HEAD``, ``OPTIONS``. :type: str :default: ``GET`` + :required: False .. confval:: url - Address of the target website. + URL of the target. :type: str - :required: ``True`` + :required: True .. confval:: output_headers @@ -59,58 +55,67 @@ Execute HTTP-requests like curl does. This command also supports HTTP/2 :type: str :default: ``False`` + :required: False .. confval:: headers - Include these extra headers in the request when sending HTTP to a server. + Additional HTTP headers to include in the request. :type: dict[str,str] + :required: False .. confval:: cookies - Pass the data to the HTTP server in the Cookie header. + Cookies to send in the ``Cookie`` header. :type: dict[str,str] + :required: False .. confval:: data - Sends the specified data in a POST request to the HTTP server, in the same - way that a browser does when a user has filled in an HTML form and presses - the submit button. + Form data to send in the request body. Equivalent to submitting an HTML form. + Typically used with ``POST``. :type: dict[str,str] + :required: False .. confval:: local_path - Load content from the given file and send it via HTTP. This is useful for - dav uploads. + Path to a local file whose contents will be sent as the request body. Useful + for WebDAV uploads. :type: str + :required: False .. confval:: useragent - Change the user-agent string. + Override the ``User-Agent`` header. :type: str :default: ``AttackMate`` + :required: False .. confval:: follow - Automatically follow redirects + Automatically follow HTTP redirects. :type: bool :default: ``False`` + :required: False .. confval:: verify - This option makes attackmate skip the secure connection verification step and proceed without checking. + Verify the server's TLS certificate. Set to ``False`` to skip verification + (e.g. for self-signed certificates). :type: bool :default: ``False`` + :required: False .. confval:: http2 - Try to use HTTP version 2. AttackMate will negotiate the HTTP version with the server and use HTTP2 if possible. + Negotiate HTTP/2 with the server and use it if supported. :type: bool :default: ``False`` + :required: False diff --git a/docs/source/playbook/commands/include.rst b/docs/source/playbook/commands/include.rst index 79b140fb..fd2cb66b 100644 --- a/docs/source/playbook/commands/include.rst +++ b/docs/source/playbook/commands/include.rst @@ -27,7 +27,7 @@ Include and run commands from another yaml-file. .. confval:: local_path - Path to the yaml-file + Path to the yaml file. :type: str - :required: ``True`` + :required: True diff --git a/docs/source/playbook/commands/index.rst b/docs/source/playbook/commands/index.rst index 9ab32c3f..f0909163 100644 --- a/docs/source/playbook/commands/index.rst +++ b/docs/source/playbook/commands/index.rst @@ -4,14 +4,14 @@ Commands ======== -The *'commands-section'* holds a list of AttackMate-commands that are executed sequentially from +The ``commands:`` section of a playbook holds a list of AttackMate commands that are executed sequentially from top to bottom. -Every command, regardless of the type has the following general options: +Every command, regardless of its type supports the following general options: .. confval:: cmd - *cmd* defines the command that should be executed. The purpose of this option varies depending on the type of command. + The command that should be executed. The meaning of this option varies depending on the type of command. :type: str @@ -30,40 +30,37 @@ Every command, regardless of the type has the following general options: .. confval:: exit_on_error - If this option is true, attackmate will exit with an error if the command returns with a return code - that is not zero. + Attackmate will exit with an error if the command returns a non-zero exit code. :type: bool :default: ``True`` .. confval:: error_if - If this option is set, an error will be raised if the string was found in the **output** - of the command. + Raise an error if the given pattern is found in the command output. :type: str(regex) .. code-block:: yaml commands: - # throw an error + # throws an error - type: http-client cmd: get - url: https://www.google.com + url: https://www.bing.com error_if: ".*bing.*" .. confval:: error_if_not - If this option is set, an error will be raised if the string was not found in the **output** - of the command. + Raise an error if the given pattern is **not** found in the command output. :type: str(regex) .. code-block:: yaml commands: - # throw an error + # throws an error - type: http-client cmd: get url: https://www.google.com @@ -72,8 +69,8 @@ Every command, regardless of the type has the following general options: .. confval:: loop_if - If this option is set, the command will be executed again if the string was found in the - **output** of the command. + Re-execute the command if the given pattern is found in the output. Repeats + until the pattern no longer matches or ``loop_count`` is reached. :type: str(regex) @@ -89,8 +86,8 @@ Every command, regardless of the type has the following general options: .. confval:: loop_if_not - If this option is set, the command will be executed again if the string was not found in the - **output** of the command. + Re-execute the command if the given pattern is **not** found in the output. Repeats + until the pattern matches or ``loop_count`` is reached. :type: str(regex) @@ -105,97 +102,129 @@ Every command, regardless of the type has the following general options: .. confval:: loop_count - Number of Repetitions if *loop_if* or *loop_if_not* matches. + Maximum number of repetitions when ``loop_if* or ``loop_if_not`` is set. - :type: ini + :type: int :default: ``3`` .. confval:: only_if - Execute this command only if the condition is true. The following operators are supported: - - * var1 == var2 - * var1 != var2 - * var1 is var2 - * var1 is not var2 - * var1 < var2 - * var1 <= var2 - * var1 > var2 - * var1 >= var2 - * string !~ pattern - * string =~ pattern - * not var - * var - * None + Execute this command only if the condition evaluates to ``True``. Supported operators: + + * ``var1 == var2``, ``var1 != var2`` + * ``var1 is var2``, ``var1 is not var2`` + * ``var1 < var2``, ``var1 <= var2``, ``var1 > var2``, ``var1 >= var2`` + * ``string =~ pattern`` — matches if string satisfies the regex pattern + * ``string !~ pattern`` — matches if string does not satisfy the regex pattern + * ``not var``, ``var``, ``None`` :type: str(condition) - The =~ operator is used to check if a string matches a regular expression pattern. - The !~ operator is used to check if a string does not match a regular expression pattern. + .. note:: + + The ``=~`` operator is used to check if a string matches a regular expression pattern. + The ``!~`` operator is used to check if a string does **not** match a regular expression pattern. .. code-block:: yaml commands: + # Get the PID of the running mysqld process: - type: shell cmd: pgrep mysqld + # Extract the first captured group from the output, splitting on newlines. + # Stores the result in $KILLPID via the MATCH_0 capture variable: - type: regex mode: split cmd: "\n" output: KILLPID: $MATCH_0 - # Execute this command only - # if it is not the init-process + # Only kill if it is not the init process with PID 1: - type: shell cmd: kill $KILLPID only_if: $KILLPID > 1 - # Execute this command only if the regex pattern is found + # Only execute if the regex pattern matches: - type: shell cmd: echo "regex match found" only_if: some_string =~ some[_]?string + .. warning:: + + When comparing strings with integers, standard Python conventions apply: + + **Equality / Inequality** (``==``, ``!=``): + A string and an integer are never equal, so ``"1" == 1`` is ``False`` + and ``"1" != 1`` is ``True``. + + **Identity** (``is``, ``is not``): + Compares object identity, not value. ``"1" is 1`` is always ``False`` + because a string and an integer are distinct objects, regardless of + their apparent values. + + **Ordering** (``<``, ``<=``, ``>``, ``>=``): + Comparing a string with an integer raises a ``TypeError`` in Python 3 + since the two types have no defined ordering. These operators should + only be used when both operands share the same type. + + **Integers and Booleans** (``==``, ``!=``): + In Python, ``bool`` is a subclass of ``int``, so ``1 == True`` and + ``0 == False`` are both ``True``, while any other integer (e.g. + ``2 == True``) is ``False``. This also means that if a + ``$variable`` has been set to the string ``"True"`` or ``"False"``, + comparing it against a boolean literal (``$var == True``) will + always yield ``False`` — because the resolved value is a ``str`` + while the literal is parsed as a ``bool`` by ``ast``. Use string + literals for boolean-like flags stored in the ``VariableStore``: + ``$flag == "True"``. + + Importantly, before a condition is evaluated, all ``$variable`` references are + resolved by the ``VariableStore``. **The store holds every value as a + plain Python** ``str``, even values that were originally integers + are coerced to ``str`` on ingress. .. confval:: background - Execute the command as a subprocess in background. If set to *True*, - the functionality for *error_if* and *error_if_not* as well as printing - the output is disabled. + Execute the command as a background subprocess. When enabled, output is not printed + and ``error_if`` / ``error_if_not`` have no effect. - Background-Mode is currently not implemented for the following commands: + :type: bool + :default: ``False`` + + .. note:: + + The command in background-mode will change the :ref:`builtin variables ` + ``RESULT_STDOUT`` to "Command started in Background" and ``RESULT_CODE`` to 0. + + Background mode is not supported for * MsfModuleCommand * IncludeCommand * VncCommand * BrowserCommand - Background-Mode together with a session is currently not implemented for the following commands: + Background mode together with a session is not supported for the following commands: * SSHCommand * SFTPCommand - :type: bool - :default: ``False`` - .. note:: - The command in background-mode will not change global variables like - RESULT_STDOUT or RESULT_CODE. + .. confval:: kill_on_exit - If this command runs in background-mode, the option *kill_on_exit* controls - if the main process will wait for this subprocess before exitting or if the - main process will simply kill the subprocess. + If this command runs in background mode, the option ``kill_on_exit`` controls + whether the main process kills the subprocess on exit (``True``) or waits for it to finish (``False``). :type: bool :default: ``True`` .. confval:: metadata - The option *metadata* can be used to pass a dictionary of key value pairs. - These are not used in command execution but are logged together with the command. + An optional dictionary of key-value pairs that are logged alongside the command + but have no effect on execution. :type: Dict :default: None @@ -210,7 +239,7 @@ Every command, regardless of the type has the following general options: author: Ellen Ripley -The next pages will describe all possible commands in detail. +The next pages will describe each command type in detail. .. toctree:: :maxdepth: 4 diff --git a/docs/source/playbook/commands/json.rst b/docs/source/playbook/commands/json.rst index f27299fd..1dfbface 100644 --- a/docs/source/playbook/commands/json.rst +++ b/docs/source/playbook/commands/json.rst @@ -2,12 +2,13 @@ json ==== -Parse variables from a JSON file or from a variable (for example, ``RESULT_STDOUT``) that contains a valid JSON string. -If the "local_path" option is used, the JSON is read directly from the specified file. The "cmd" option is optional. If "local_path" is defined, the "cmd" option will be ignored. -If no "local_path" is set, the JSON is read from the "cmd" option. The variables are recursively parsed from the JSON input and saved in the variable store. -The variables are recursively parsed from the json input and are saved as single variables in the variable store. -Variable names are generated by concatenating keys at each level using an underscore (_) as a separator. -If the lowest-level value is a list of primitives (e.g., strings, integers), the list is preserved as-is without further flattening. +Parse JSON into variables and store them in the variable store. Input can be either a +JSON file (via ``local_path``) or a variable containing a valid JSON string (via ``cmd``). +If ``local_path`` is set, ``cmd`` is ignored. + +Keys are flattened recursively into variable names by joining each level with an underscore +(``_``). Lists of primitives (strings, integers, etc.) are preserved as-is without further +flattening. List elements that are objects are indexed numerically (e.g. ``friends_0_name``). Example ------- @@ -16,103 +17,115 @@ Given the following JSON input: .. code-block:: json - { - "first_list": [1, 2, 3], - "user": { - "name": "John Doe", - "age": 30, - "address": { - "street": "123 Main St", - "city": "New York", - "postal_codes": [10001, 10002] - }, - "friends": [ - { - "name": "Jane Smith", - "age": 28, - "address": { - "street": "456 Oak Rd", - "city": "Los Angeles", - "postal_codes": [90001, 90002] - } - }, - { - "name": "Emily Davis", - "age": 35, - "address": { - "street": "789 Pine Ln", - "city": "Chicago", - "postal_codes": [60007, 60008] - } - } - ] - } - } - -The variables would be saved in the variable store as follows: + { + "first_list": [1, 2, 3], + "user": { + "name": "John Doe", + "age": 30, + "address": { + "street": "123 Main St", + "city": "New York", + "postal_codes": [10001, 10002] + }, + "friends": [ + { + "name": "Jane Smith", + "age": 28, + "address": { + "street": "456 Oak Rd", + "city": "Los Angeles", + "postal_codes": [90001, 90002] + } + }, + { + "name": "Emily Davis", + "age": 35, + "address": { + "street": "789 Pine Ln", + "city": "Chicago", + "postal_codes": [60007, 60008] + } + } + ] + } + } + +The variables will be saved in the variable store: .. code-block:: yaml - first_list: [1, 2, 3] - user_name: "John Doe" - user_age: 30 - user_address_street: "123 Main St" - user_address_city: "New York" - user_address_postal_codes: [10001, 10002, 10003] - user_friends_0_name: "Jane Smith" - user_friends_0_age: 28 - user_friends_0_address_street: "456 Oak Rd" - user_friends_0_address_city: "Los Angeles" - user_friends_0_address_postal_codes: [90001, 90002] - user_friends_1_name: "Emily Davis" - user_friends_1_age: 35 - user_friends_1_address_street: "789 Pine Ln" - user_friends_1_address_city: "Chicago" - user_friends_1_address_postal_codes: [60007, 60008] + first_list: [1, 2, 3] + user_name: "John Doe" + user_age: 30 + user_address_street: "123 Main St" + user_address_city: "New York" + user_address_postal_codes: [10001, 10002] + user_friends_0_name: "Jane Smith" + user_friends_0_age: 28 + user_friends_0_address_street: "456 Oak Rd" + user_friends_0_address_city: "Los Angeles" + user_friends_0_address_postal_codes: [90001, 90002] + user_friends_1_name: "Emily Davis" + user_friends_1_age: 35 + user_friends_1_address_street: "789 Pine Ln" + user_friends_1_address_city: "Chicago" + user_friends_1_address_postal_codes: [60007, 60008] Configuration ------------- +.. note:: + + Either ``local_path`` or ``cmd`` must be provided. + .. confval:: local_path - The JSON input to parse from. Valid input is a path to a JSON file. If "local_path" is set, the "cmd" option will be ignored. + Path to a JSON file to parse. Takes precedence over ``cmd`` if both are set. :type: str - :required: False + :required: Either ``local_path`` or ``cmd`` must be provided. .. confval:: cmd - The JSON input to parse from. Valid input is a variable name from the variable store (without the leading ``$``) that contains a valid JSON string. + Name of a variable in the variable store (without the leading ``$``) whose value + is a valid JSON string. :type: str - :required: False - - Either ``local_path`` OR ``cmd`` is required. + :required: Either ``local_path`` or ``cmd`` must be provided. .. confval:: varstore - If set to ``True``, logs the variable store before and after adding variables using the JSON command. + Log the variable store before and after the command executes, useful for debugging. :type: bool + :default: ``False`` :required: False Examples -------- +Parse a JSON file directly: + +.. code-block:: yaml + + commands: + - type: json + local_path: "/path/to/samplefile.json" + varstore: True + +Parse JSON from a shell command's output: + .. code-block:: yaml - commands: - - type: json - local_path: "/path/to/samplefile.json" - varstore: True - - type: shell - cmd: | - cat <`_ -This command does not modify the Builtin Variable ``RESULT_STDOUT``. +Parse and transform variables using regular expressions. For more information +about regular expressions and regex syntax see `Python Regex `_. -The following example parses the portnumber from the output of the last command and stores it in variable "UNREALPORT": +.. note:: + + This command does not modify ``RESULT_STDOUT``. + + +The following example extracts a port number from the output of a shell command and stores it in the variable ``UNREALPORT``: .. code-block:: yaml @@ -23,7 +27,7 @@ The following example parses the portnumber from the output of the last command cmd: "Port: $UNREALPORT" -By using the mode "split", strings that are seperated by whitespaces can be tokenized: +Using ``mode: split``, a string can be tokenized by a delimiter, in this case, whitespace ``"\ +"``: .. code-block:: yaml @@ -41,19 +45,33 @@ By using the mode "split", strings that are seperated by whitespaces can be toke - type: debug cmd: "Port: $UNREALPORT" -.. confval:: mode +.. confval:: cmd - Specifies the python regex-function. One of: ``search``, ``split``, ``sub`` or ``findall``. + The regular expression pattern to apply. :type: str - :default: ``findall`` + :required: True + +.. confval:: mode + + The Python regex function to use. One of: + + * ``findall`` - find all non-overlapping matches + * ``search`` - find the first match anywhere in the string + * ``split`` - split the string by occurrences of the pattern + * ``sub`` - replace occurrences of the pattern with :confval:`replace` + + :type: str + :default: ``findall`` + :required: False .. confval:: replace - This variable must be set for sub mode. It holds the replacement-string for the substitution. + This variable must be set for ``mode: sub``. It holds the replacement-string for the substitution. - :type: str - :default: ``None`` + :type: str + :default: ``None`` + :required: when ``mode: sub`` .. code-block:: yaml @@ -76,27 +94,30 @@ By using the mode "split", strings that are seperated by whitespaces can be toke .. confval:: input - Parse the value of this variable. + Name of the variable whose value will be used as the regex input + (without the leading ``$``). :type: str :default: ``RESULT_STDOUT`` + :required: False .. confval:: output - Defines where to store the results of the regular expression. This - must be a list of key-value pairs("variable-name": "$MATCH"). The matches - of the regular expressions are stored in temporary variables $MATCH. If the - match is stored in a list or in a list of tuples the variablename will be - numbered by the index. For example: "$MATCH_0_0" for the first element in the - first occurance. The first match (even if there is only one) is indexed MATCH_0. - If the regex-command does not match, no output variable will be set! - Note that if sub() or split() do not have a match the input string is returned. - Additionally, ``REGEX_MATCHES_LIST`` is set every time a regex command yields matches and it contains a list of all matches. + Mapping of variable names to match references (e.g. ``MYVAR: $MATCH_0``). + :type: dict[str,str] + :required: True - .. note:: + Matches are indexed as ``$MATCH_0``, ``$MATCH_1``, etc. For nested results + (lists of tuples), matches are indexed as ``$MATCH_0_0``, ``$MATCH_0_1``, etc. - A dump containing all matches will be printed if attackmate runs in debug-mode. + If the pattern does not match, no output variables are set. If ``sub`` or + ``split`` find no match, the original input string is returned. - :type: dict[str,str] - :required: True + The :ref:`builtin variables ` ``REGEX_MATCHES_LIST`` is also populated with a list of + all matches whenever the command produces results. + + + .. note:: + + Running AttackMate in debug mode (--debug) prints a full dump of all matches. diff --git a/docs/source/playbook/commands/remote.rst b/docs/source/playbook/commands/remote.rst index 5a8dc4da..a7011b55 100644 --- a/docs/source/playbook/commands/remote.rst +++ b/docs/source/playbook/commands/remote.rst @@ -2,9 +2,15 @@ remote ====== -This command executes playbooks or commands on a remote AttackMate instance. -The connection to the remote instance is defined in the ``remote_config`` section of the configuration file. -If no connection is specified, the first entry in the ``remote_config`` section will be used as default. +Execute playbooks or commands on a remote AttackMate instance. +Remote connections are defined in the ``remote_config`` section of the configuration file. +If no ``connection`` is specified, the first entry in the ``remote_config`` section is used as default. + +.. warning:: + + Options such as ``background`` and ``only_if`` defined on the ``remote`` command apply + to the **local** execution context, not to the command or playbook running on the remote + instance. Configuration ============= @@ -58,44 +64,42 @@ Options .. confval:: cmd - The remote operation to perform. Must be one of the following: + The operation to perform on the remote instance. One of: - ``execute_command`` — Execute a single AttackMate command on the remote instance. - Requires ``remote_command`` to be set. + Requires ``remote_command`` - ``execute_playbook`` — Execute a full playbook YAML file on the remote instance. - Requires ``playbook_path`` to be set. + Requires ``playbook_path`` :type: str (``execute_command`` | ``execute_playbook``) + :required: True .. confval:: connection - The name of the remote connection to use, as defined in the ``remote_config`` section - of the configuration file. If omitted, the first entry in ``remote_config`` is used - as the default connection. + Name of the remote connection to use, as defined in the ``remote_config`` section + of the AttackMate configuration file. If omitted, the first entry in ``remote_config`` is used. :type: str :default: first entry in ``remote_config`` + :required: False .. confval:: playbook_path - Path to a local YAML playbook file that will be read and sent to the remote AttackMate - instance for execution. Required when ``cmd`` is ``execute_playbook``. + Path to a local YAML playbook file to sent to and execute on the remote AttackMate. + Required when ``cmd`` is ``execute_playbook``. :type: str + :required: when ``cmd: execute_playbook`` .. confval:: remote_command - An inline AttackMate command definition that will be executed on the remote instance. - This supports any command type that the remote AttackMate instance is configured to handle - (e.g., ``shell``, ``sliver``, etc., EXCEPT another remote_command). Required when ``cmd`` is ``execute_command``. + An inline AttackMate command to execute on the remote instance. + Supports any command type that the remote AttackMate instance is configured to handle + (e.g., ``type: shell``, ``type: sliver``, etc., EXCEPT ``type: remote`` itself). Required when ``cmd`` is ``execute_command``. :type: RemotelyExecutableCommand + :required: when ``cmd: execute_command`` -.. warning:: - - Parameters such as ``background`` and ``only_if`` defined at the top-level ``remote`` command - apply to the **local** execution of the remote command, not to the command executed on the - remote instance. Examples ======== @@ -116,8 +120,8 @@ Execute a shell command on a remote instance type: shell cmd: id -Execute a playbook on a remote instance using the default connection --------------------------------------------------------------------- +Execute a playbook on the default remote connection +--------------------------------------------------- .. code-block:: yaml diff --git a/docs/source/playbook/commands/setvar.rst b/docs/source/playbook/commands/setvar.rst index 47f31590..316a2b7a 100644 --- a/docs/source/playbook/commands/setvar.rst +++ b/docs/source/playbook/commands/setvar.rst @@ -2,13 +2,15 @@ setvar ====== -Set a variable. This could be used for string interpolation or for -copying variables. -This command does not modify the Builtin Variable ``RESULT_STDOUT``. +Set a variable to a string value. Useful for string interpolation and copying or +transforming existing variables. + +.. note:: + + This command does not modify ``RESULT_STDOUT``. .. code-block:: yaml - ### vars: FOO: "WORLD" @@ -19,24 +21,38 @@ This command does not modify the Builtin Variable ``RESULT_STDOUT``. .. confval:: variable - The variable-name that stores the value of *cmd* + Name of the variable to set (without the leading ``$``). :type: str - :required: ``True`` + :required: True .. confval:: cmd - The value of the variable + The value to assign to the variable. Supports variable substitution. :type: str - :required: ``True`` + :required: True .. confval:: encoder - If encoder is set, the command in cmd will be encoded before stored in ``variable``. - Please note that if encoding fails, this command will fallback to plain cmd and will - print out a warning. + Encode or decode the value of ``cmd`` before storing it in ``variable``. + + Supported values: + + * ``base64-encoder`` — encode to Base64 + * ``base64-decoder`` — decode from Base64 + * ``rot13`` — apply ROT13 + * ``urlencoder`` — percent-encode for use in URLs + * ``urldecoder`` — decode percent-encoded strings + + :type: str['base64-encoder', 'base64-decoder', 'rot13', 'urlencoder', 'urldecoder'] + + .. note:: + + Note that if encoding fails, the plain value is stored and a warning is printed. + + Example: .. code-block:: yaml @@ -96,5 +112,3 @@ This command does not modify the Builtin Variable ``RESULT_STDOUT``. - type: debug cmd: $TEST - - :type: str['base64-encoder', 'base64-decoder', 'rot13', 'urlencoder', 'urldecoder'] diff --git a/docs/source/playbook/commands/sftp.rst b/docs/source/playbook/commands/sftp.rst index 1f114ebd..0d5839e1 100644 --- a/docs/source/playbook/commands/sftp.rst +++ b/docs/source/playbook/commands/sftp.rst @@ -2,16 +2,14 @@ sftp ==== -Upload or download files using SSH. This command is -also executed by the SSHExecutor and therefor all -ssh-related settings can be used. SSH-sessions can also -be used with the sftp-command! +Upload or download files over SSH. This command shares the same connection settings and +session cache as the :ref:`ssh ` command — all SSH options apply, and sessions +created by either command can be reused by the other. .. note:: - This command caches all the settings so + This command caches all settings so that they only need to be defined once. - .. code-block:: yaml vars: @@ -19,6 +17,7 @@ be used with the sftp-command! $SSH_SERVER: 10.10.10.19 commands: + # Upload a file and create a named session: - type: sftp cmd: put local_path: /tmp/linpeas.sh @@ -26,51 +25,59 @@ be used with the sftp-command! hostname: $SSH_SERVER username: aecid key_filename: "/home/alice/.ssh/id_rsa" - creates_session: "attacker" + creates_session: attacker - # cached ssh-settings. creates new ssh-connection + # Download a file using cached connection settings, creates new connection: - type: sftp cmd: get remote_path: /etc/passwd local_path: /tmp/remote_passwd - # reuses existing session "attacker" + # Reuse the "attacker" session from the first command in an ssh command: - type: ssh - session: "attacker" - cmd: "id" + session: attacker + cmd: id + +File Transfer +------------- .. confval:: cmd - SFTP-command to use. Valid commands are *put* or *get*. + The SFTP operation to perform. + + * ``put`` — upload a file from the local machine to the remote host + * ``get`` — download a file from the remote host to the local machine :type: str - :required: ``True`` + :required: True -.. confval:: remote_path +.. confval:: local_path - The filepath on the remote machine. + Path to the file on the local machine. :type: str - :required: ``True`` + :required: True -.. confval:: local_path +.. confval:: remote_path - The filepath on the local machine. + Path to the file on the remote machine. :type: str - :required: ``True`` + :required: True .. confval:: mode - The file permissions on the remote file(e.g. *755*). + File permissions to set on the remote file after upload (e.g. ``755``). :type: str + :required: False +Connection +---------- .. confval:: hostname - This option sets the hostname or ip-address of the - remote ssh-server. + Hostname or IP address of the remote SSH server. :type: str @@ -83,82 +90,82 @@ be used with the sftp-command! .. confval:: username - Specifies the user to log in as on the remote machine. + Username to authenticate as on the remote host. :type: str .. confval:: password - Specifies the password to use. An alternative would be to use a key_file. + Password for authentication. An alternative is to use :confval:`key_filename`. :type: str -.. confval:: passphrase +.. confval:: key_filename - Use this passphrase to decrypt the key_file. This is only necessary if the - keyfile is protected by a passphrase. + Path to a private key file for authentication. :type: str -.. confval:: key_filename +.. confval:: passphrase - Path to the keyfile. + Passphrase to decrypt :confval:`key_filename`, if the key is passphrase-protected. :type: str .. confval:: timeout - The timeout to drop a connection attempt in seconds. + Timeout in seconds for connection attempts. :type: float .. confval:: clear_cache - Normally all settings for ssh-connections are cached. This allows to defined - all settings in one command and all following commands can reuse these settings - without set them in every single command. If a new connection with different - settings should be configured, this setting allows to reset the cache to default - values. + Clear all cached connection settings before this command runs, allowing a fresh + connection to be configured. (Normally all settings for ssh-connections are cached. This allows to define + all settings in one command and reuse them in the following commands without having to redefine them) + :type: bool :default: ``False`` + :required: False - .. note:: - - This setting will not clear the session store. +Sessions +-------- .. confval:: creates_session - A session name that identifies the session that is created when - executing this command. This session-name can be used by using the - option "session" + Name to assign to the session opened by this command. Can be reused in subsequent + ``sftp`` or ``ssh`` commands via :confval:`session`. :type: str .. confval:: session - Reuse an existing ssh-session. This setting works only if another - ssh-command was executed with the command-option "creates_session" + Name of an existing session to reuse. The session must have been created previously + via :confval:`creates_session` in an ``sftp`` or ``ssh`` command. :type: str + :required: False + +Jump Host +--------- .. confval:: jmp_hostname - This option sets the hostname or ip-address of the - remote jump server. + Hostname or IP address of an SSH jump host to tunnel through. :type: str .. confval:: jmp_port - Port to connect to on the jump-host. + Port to connect to on the jump host. :type: int :default: ``22`` .. confval:: jmp_username - Specifies the user to log in as on the jmp-host. + Username to authenticate as on the jump host. :type: str - :default: ``same as username`` + :default: same as :confval:`username` diff --git a/docs/source/playbook/commands/shell.rst b/docs/source/playbook/commands/shell.rst index e176411c..32d5dfea 100644 --- a/docs/source/playbook/commands/shell.rst +++ b/docs/source/playbook/commands/shell.rst @@ -1,13 +1,11 @@ - ===== shell ===== -Execute local shell-commands. +Execute local shell commands. .. code-block:: yaml - ### vars: $SERVER_ADDRESS: 192.42.0.254 $NMAP: /usr/bin/nmap @@ -18,92 +16,109 @@ Execute local shell-commands. .. confval:: cmd - The command-line that should be executed locally. + The command line to execute locally. Supports variable substitution. :type: str + :required: True -.. confval:: creates_session +.. confval:: command_shell - A session name that identifies the session that is created when - executing this command. This session-name can be used by using the - option "session". + The shell used to execute commands. :type: str + :default: ``/bin/sh`` + :required: False -.. confval:: session +Interactive Mode +---------------- - Reuse an existing interactive session. This setting works only if another - shell-command was executed with the command-option "creates_session" and "interactive" true +.. confval:: interactive - :type: str + Run the command in interactive mode. -.. confval:: interactive + :type: bool + :default: ``False`` + :required: False - When the shell-command is executed, the command will block until the execution finishes. - However, for some exploits it is necessary to run a command and send keystrokes to an - interactive session. For example run with the first command "vim" and with the second command - send keystrokes to the open vim-session. In interactive-mode the command will try reading the - output until no output is written for a certain amount of seconds. + Instead of waiting for the command to finish, + AttackMate reads output until no new output appears for :confval:`command_timeout` + seconds. Useful for commands that require follow-up keystrokes (e.g. opening ``vim`` + and sending input in a subsequent command). - This mode works only on unix and unix-like operating systems! + This mode works only on Unix and Unix-like systems. .. warning:: - Please note that you **MUST** send a newline when you execute a ssh-command interactively. - - :type: bool - :default: ``False`` + Commands executed in interactive mode **MUST** end with a newline character (``\n``). .. code-block:: yaml commands: - # creates new ssh-connection and session + # Open nmap in interactive mode and create a named session: - type: shell cmd: "nmap --interactive\n" interactive: True - creates_session: "attacker" + creates_session: attacker - # break out of the nmap-interactive-mode + # Send a command to the open interactive session: - type: shell cmd: "!sh\n" interactive: True - session: "attacker" + session: attacker + +.. confval:: creates_session + + Name to assign to the interactive session opened by this command. Can be reused + in subsequent commands via :confval:`session`. + + Only meaningful when :confval:`interactive` is ``True``. + + :type: str + :required: False + +.. confval:: session + + Name of an existing interactive session to reuse. The session must have been + created previously via :confval:`creates_session` with :confval:`interactive` + set to ``True``. + + :type: str + :required: False .. confval:: command_timeout - The interactive-mode works with timeouts while reading the output. If there is no output for some seconds, - the command will stop reading. + Seconds to wait for new output before stopping in interactive mode. :type: int :default: ``15`` + :required: False .. confval:: read - Wait for output. This option is useful for interactive commands that do not return any output. - Normally attackmate will wait until the command_timeout was reached. With read is False, attackmate - will not wait for any output and simply return an empty string. + Wait for output after executing the command. Set to ``False`` to return + immediately with an empty result, useful for fire-and-forget interactive + commands that produce no output. :type: bool :default: ``True`` + :required: False -.. confval:: command_shell - - Use this shell when executing commands. - - :type: str - :default: ``/bin/sh`` +Binary Mode +----------- .. confval:: bin - Enable binary mode. In this mode only hex-characters are allowed. + Enable binary mode. In this mode, ``cmd`` must be a hex-encoded string representing + the raw bytes to execute. :type: bool :default: ``False`` + :required: False .. code-block:: yaml commands: + # "6964" is the hex encoding of "id": - type: shell - # hex-code for "id" cmd: "6964" - bin: True + bin: true diff --git a/docs/source/playbook/commands/sleep.rst b/docs/source/playbook/commands/sleep.rst index e81bc673..a2344776 100644 --- a/docs/source/playbook/commands/sleep.rst +++ b/docs/source/playbook/commands/sleep.rst @@ -2,30 +2,20 @@ sleep ===== -Sleep a certain amount of seconds. +Pause execution for a fixed or randomised number of seconds. .. code-block:: yaml - ### commands: + # Sleep for 60 seconds - type: sleep seconds: 60 -.. confval:: min_sec - - This option defines the minimum seconds to sleep. This - is only relevant if option **random** is set to True - - :type: int - :default: ``0`` - - .. confval:: seconds - This options sets the seconds to sleep. If the option - **random** is set to True, this option is the maximum time - to sleep. + Number of seconds to sleep. When :confval:`random` is ``True``, this serves as + the upper bound of the random range. :type: int :default: ``1`` @@ -34,22 +24,27 @@ Sleep a certain amount of seconds. .. confval:: random - This option allows to randomize the seconds to wait. The minimum - and maximum seconds for the range can be set by **min_sec** and - **seconds**. - + Sleep for a random duration between :confval:`min_sec` and :confval:`seconds` + instead of a fixed duration. :type: bool :default: ``False`` + :required: False + + +.. confval:: min_sec + Lower bound in seconds for the random sleep range. Only used when + :confval:`random` is ``True``. - The following example will take a random amount of seconds between 30 seconds - and 60 seconds: + :type: int + :default: ``0`` + :required: False .. code-block:: yaml - ### commands: + # Sleep for a random duration between 30 and 60 seconds - type: sleep seconds: 60 min_sec: 30 @@ -58,7 +53,7 @@ Sleep a certain amount of seconds. .. confval:: cmd - This option is ignored + This option is ignored. It is only present to allow the use of the generic command syntax and does not have any effect on the behavior of the command. :type: str :default: ``sleep`` diff --git a/docs/source/playbook/commands/sliver-session.rst b/docs/source/playbook/commands/sliver-session.rst index fc29806f..2ff9d0d0 100644 --- a/docs/source/playbook/commands/sliver-session.rst +++ b/docs/source/playbook/commands/sliver-session.rst @@ -4,165 +4,79 @@ sliver-session ============== -There are multiple commands from type 'sliver-session' to execute commands in an -active sliver session. +Execute commands within an active Sliver implant session. All commands require a +:confval:`session` field identifying the target implant. -ls --- - -List files and directories on the remote host - -.. code-block:: yaml - - ### - commands: - - type: sliver-session - cmd: ls - remote_path: /etc - session: implant-name - - -.. confval:: remote_path - - Path to list all files +.. note:: - :type: str - :required: ``True`` + **For developers:** The ``sliver`` and ``sliver-session`` command families use a legacy + ``type`` + ``cmd`` discrimination pattern and should not be replicated. New commands + must define a unique ``type`` literal and handle sub-behavior branching via ``cmd`` + in the executor. See :ref:`command` for details. .. confval:: session - The name of the sliver implant to connect to. Defined previously by the by sliver generate_implant command. + Name of the Sliver implant session to operate in. The implant must have been + generated and deployed previously via the sliver :ref:`generate_implant ` command. :type: str - :required: ``True`` + :required: True +File System +----------- -cd --- +ls +^^^ -Change the working directory +List files and directories on the remote host. .. code-block:: yaml - ### commands: - type: sliver-session - cmd: cd - remote_path: /home + cmd: ls + remote_path: /etc session: implant-name .. confval:: remote_path - Path to change to + Path to list all files. :type: str - :required: ``True`` - - -netstat -------- - -Print network connection information - -.. code-block:: yaml - - ### - commands: - - type: sliver-session - cmd: netstat - tcp: True - udp: True - ipv4: True - ipv6: False - listening: True - session: implant-name - - -.. confval:: tcp - - Display information about TCP sockets - - :type: bool - :default: ``True`` - -.. confval:: udp + :required: True - Display information about UDP sockets - :type: bool - :default: ``True`` - -.. confval:: ipv4 - - Display information about IPv4 sockets - - :type: bool - :default: ``True`` - -.. confval:: ipv6 - - Display information about IPv6 sockets - - :type: bool - :default: ``True`` - -.. confval:: listening - - Display information about listening sockets - - :type: bool - :default: ``True`` - - -execute -------- +cd +^^^ -Execute a program on the remote system +Change the working directory of the active session. .. code-block:: yaml - ### commands: - type: sliver-session - cmd: execute - exe: /usr/bin/grep - args: - - root - - /etc/passwd - output: True + cmd: cd + remote_path: /home session: implant-name -.. confval:: exe +.. confval:: remote_path - Command to execute + Path to change to :type: str - :required: ``True`` - -.. confval:: args - - List of command arguments + :required: True - :type: List[str] - -.. confval:: output - - Capture command output - - :type: bool - :default: ``True`` mkdir ------ +^^^^^ Create a remote directory. .. code-block:: yaml - ### commands: - type: sliver-session cmd: mkdir @@ -172,60 +86,66 @@ Create a remote directory. .. confval:: remote_path - Path to the directory to create + Path to the directory to create. :type: str - :required: ``True`` + :required: True -ifconfig --------- +pwd +^^^ -View network interface configurations +Print working directory of the active session. .. code-block:: yaml - ### commands: - type: sliver-session - cmd: ifconfig + cmd: pwd session: implant-name -ps --- -List processes of the remote system +rm +^^^ + +Delete a remote file or directory. .. code-block:: yaml - ### commands: - type: sliver-session - cmd: ps + cmd: rm + remote_path: /tmp/somefile session: implant-name +.. confval:: remote_path -pwd ---- + Path to the file to remove. -Print working directory of the active session. + :type: str + :required: True -.. code-block:: yaml +.. confval:: recursive - ### - commands: - - type: sliver-session - cmd: pwd - session: implant-name + Recursively remove files + + :type: bool + :default: ``False`` + +.. confval:: force + + Ignore safety and forcefully remove files. + + :type: bool + :default: ``False`` download --------- +^^^^^^^^ Download a file or directory from the remote system. Directories will be downloaded as a gzipped tar-file. .. code-block:: yaml - ### commands: - type: sliver-session cmd: download @@ -236,17 +156,17 @@ Download a file or directory from the remote system. Directories will be downloa .. confval:: remote_path - Path to the file or directory to download + Path to the file or directory to download. :type: str - :required: ``True`` + :required: True .. confval:: local_path Local path where the downloaded file will be saved. :type: str - :required: ``False`` + :required: False :default: ``.`` .. confval:: recurse @@ -257,13 +177,12 @@ Download a file or directory from the remote system. Directories will be downloa :default: ``False`` upload ------- +^^^^^^ Upload a file to the remote system. .. code-block:: yaml - ### commands: - type: sliver-session cmd: upload @@ -273,98 +192,201 @@ Upload a file to the remote system. .. confval:: remote_path - Path to the file or directory to upload to + Destination path on the remote host. :type: str - :required: ``True`` + :required: True .. confval:: local_path - Local path to the file to upload + Path to the local file to upload. :type: str .. confval:: is_ioc - Track uploaded file as an ioc + Mark the uploaded file as an indicator of compromise (IOC) for tracking purposes. :type: bool :default: ``False`` +Network +------- -process_dump ------------- +netstat +^^^^^^^ -Dumps the process memory of a given pid to a local file. +Display network connection information for the remote host. .. code-block:: yaml - ### commands: - type: sliver-session - cmd: process_dump - pid: 102 - local_path: /home/user/some_service.dump + cmd: netstat + tcp: True + udp: True + ipv4: True + ipv6: False + listening: True session: implant-name -.. confval:: pid - Target Pid +.. confval:: tcp - :type: int - :required: ``True`` + Display information about TCP sockets. + :type: bool + :default: ``True`` -.. confval:: local_path +.. confval:: udp - Save to file. + Display information about UDP sockets. - :type: str - :required: ``True`` + :type: bool + :default: ``True`` +.. confval:: ipv4 -rm --- + Display information about IPv4 sockets. -Delete a remote file or directory. + :type: bool + :default: ``True`` -.. confval:: remote_path +.. confval:: ipv6 - Path to the file to remove + Display information about IPv6 sockets. - :type: str - :required: ``True`` + :type: bool + :default: ``True`` -.. confval:: recursive +.. confval:: listening - Recursively remove files + Display information about listening sockets :type: bool - :default: ``False`` + :default: ``True`` -.. confval:: force +ifconfig +^^^^^^^^ - Ignore safety and forcefully remove files +Display network interface configuration of the remote host. - :type: bool - :default: ``False`` +.. code-block:: yaml + commands: + - type: sliver-session + cmd: ifconfig + session: implant-name -terminate + +Processes --------- -Kills a remote process designated by PID +ps +^^^ + +List processes of the remote system. + +.. code-block:: yaml + + commands: + - type: sliver-session + cmd: ps + session: implant-name + +execute +^^^^^^^ + +Execute a program on the remote host. + +.. code-block:: yaml + + commands: + - type: sliver-session + cmd: execute + exe: /usr/bin/grep + args: + - root + - /etc/passwd + output: True + session: implant-name + + +.. confval:: exe + + Command to execute. + + :type: str + :required: True + +.. confval:: args + + List of command arguments. + + :type: List[str] + +.. confval:: output + + Capture command output. + + :type: bool + :default: ``True`` + +terminate +^^^^^^^^^ + +Kill a process on the remote host by PID. + +.. code-block:: yaml + + commands: + - type: sliver-session + cmd: terminate + pid: 1234 + session: implant-name .. confval:: pid PID of the process to kill. :type: int - :required: ``True`` + :required: True .. confval:: force - Disregard safety and kill the PID. + Disregard safety and kill the process. :type: bool :default: ``False`` + +Memory +------ + +process_dump +^^^^^^^^^^^^ + +Dump the memory of a running process to a local file. + +.. code-block:: yaml + + commands: + - type: sliver-session + cmd: process_dump + pid: 102 + local_path: /home/user/some_service.dump + session: implant-name + +.. confval:: pid + + Target PID. + + :type: int + :required: True + + +.. confval:: local_path + + Save to file. + + :type: str + :required: True diff --git a/docs/source/playbook/commands/sliver.rst b/docs/source/playbook/commands/sliver.rst index 909e32ae..8a816581 100644 --- a/docs/source/playbook/commands/sliver.rst +++ b/docs/source/playbook/commands/sliver.rst @@ -4,16 +4,22 @@ sliver ====== -There are multiple commands from type 'sliver' to controll the sliver-server via API. +Control the Sliver C2 server via its API. All commands use ``type: sliver``. + +.. note:: + + **For developers:** The ``sliver`` and ``sliver-session`` command families use a legacy + ``type`` + ``cmd`` discrimination pattern and should not be replicated. New commands + must define a unique ``type`` literal and handle sub-behavior branching via ``cmd`` + in the executor. See :ref:`command` for details. start_https_listener -------------------- -Start an HTTPS-Listener +Start an HTTPS listener on the Sliver server. .. code-block:: yaml - ### commands: - type: sliver cmd: start_https_listener @@ -23,70 +29,70 @@ Start an HTTPS-Listener .. confval:: host - Interface to bind server to. + Network interface to bind the listener to. :type: str :default: ``0.0.0.0`` .. confval:: port - TCP-Listen port + TCP port to listen on. :type: int :default: ``443`` .. confval:: domain - Limit responses to specific domain + Limit responses to specific domain. :type: str :default: `` `` .. confval:: website - Website name + Website name to associate with this listener. :type: str :default: `` `` .. confval:: acme - Attempt to provision a let's encrypt certificate + Attempt to provision a let's encrypt certificate. :type: bool :default: ``False`` .. confval:: persistent - Make persistent across restarts. + Keep the listener running across Sliver server restarts. :type: bool :default: ``False`` .. confval:: enforce_otp - Enable or disable OTP authentication + Require OTP authentication for connecting implants. :type: bool :default: ``True`` .. confval:: randomize_jarm - Enable randomized Jarm fingerprints + Enable randomized JARM fingerprints. :type: bool :default: ``True`` .. confval:: long_poll_timeout - Server-Side long poll timeout(in seconds) + Server-side long poll timeout(in seconds). :type: int :default: ``1`` .. confval:: long_poll_jitter - Server-Side long poll jitter(in seconds) + Server-side long poll jitter(in seconds) :type: int :default: ``2`` @@ -103,11 +109,10 @@ generate_implant ---------------- Generates a new sliver binary and saves the implant to a given path or to /tmp/. -The path to the implant is saved and can be retrieved from the variable store as $LAST_SLIVER_IMPLANT. +The path to the implant is saved and can be retrieved from the :ref:`builtin variable ` ``$LAST_SLIVER_IMPLANT``. .. code-block:: yaml - ### commands: - type: sliver cmd: generate_implant @@ -119,8 +124,7 @@ The path to the implant is saved and can be retrieved from the variable store as .. confval:: target - Compile the binary for the given operatingsystem to the given architecture. The - following targets are supported: + Target operating system and architecture. Supported values: * darwin/amd64 * darwin/arm64 @@ -134,14 +138,14 @@ The path to the implant is saved and can be retrieved from the variable store as .. confval:: c2url - Url which is used by the implant to find the C2 server. + URL which is used by the implant to reach the C2 server. :type: str :required: True .. confval:: format - Specifies the output format for the implant. Valid formats are: + Output format for the implant binary. One of: * EXECUTABLE * SERVICE @@ -154,15 +158,15 @@ The path to the implant is saved and can be retrieved from the variable store as .. confval:: name Name of the implant. - This name is the session used by the attackmate command 'sliver-session'. + This name is the session identifier used by :ref:`sliver-session ` commands. :type: str :required: True .. confval:: filepath - The local filepath to save the implant to. If none is given the implant is saved in /tmp. - The will be random and have the format ^tmp[a-z0-9]{8}$. + The local filepath to save the implant to. If omitted, the implant is saved to ``/tmp``. + The filename will be randomly genrated and have the format ^tmp[a-z0-9]{8}$. :type: str @@ -170,24 +174,21 @@ The path to the implant is saved and can be retrieved from the variable store as .. confval:: IsBeacon - Generate a beacon binary + Generate a beacon-mode implant instead of a session-mode implant. :type: bool - :default: False + :default: ``False`` .. confval:: RunAtLoad - Run the implant entrypoint from DllMain/Constructor(shared library only) + Run the implant entrypoint from DllMain/Constructor (shared library only). :type: bool :default: ``False`` .. confval:: Evasion - Enable evasion features (e.g. overwrite user space hooks) + Enable evasion features such as overwriting user space hooks. :type: bool :default: ``False`` - - :type: bool - :default: False diff --git a/docs/source/playbook/commands/ssh.rst b/docs/source/playbook/commands/ssh.rst index ccba53ec..a36e7a4c 100644 --- a/docs/source/playbook/commands/ssh.rst +++ b/docs/source/playbook/commands/ssh.rst @@ -1,12 +1,14 @@ +.. _ssh: + === ssh === -Execute commands on a remote server via SSH. +Execute commands on a remote host via SSH. .. note:: - This command caches all the settings so + This command caches all settings so that they only need to be defined once. .. code-block:: yaml @@ -16,27 +18,29 @@ Execute commands on a remote server via SSH. $SSH_SERVER: 10.10.10.19 commands: - # creates new ssh-connection and session + # Establish a new connection and create a named session: - type: ssh cmd: nmap $SERVER_ADDRESS hostname: $SSH_SERVER username: aecid key_filename: "/home/alice/.ssh/id_rsa" - creates_session: "attacker" + creates_session: attacker - # cached ssh-settings. creates new ssh-connection + # Reuses cached settings, opens a new connection: - type: ssh - cmd: "echo $SERVER_ADDRESS" + cmd: echo $SERVER_ADDRESS - # reuses existing session "attacker" + # Reuses the existing "attacker" session: - type: ssh - session: "attacker" - cmd: "id" + session: attacker + cmd: id + +Connection +---------- .. confval:: hostname - This option sets the hostname or ip-address of the - remote ssh-server. + Hostname or IP address of the remote SSH server. :type: str @@ -49,102 +53,109 @@ Execute commands on a remote server via SSH. .. confval:: username - Specifies the user to log in as on the remote machine. + Username to authenticate as on the remote host. :type: str .. confval:: password - Specifies the password to use. An alternative would be to use a key_file. + Password for authentication. An alternative is to use :confval:`key_filename`. :type: str -.. confval:: passphrase +.. confval:: key_filename - Use this passphrase to decrypt the key_file. This is only necessary if the - keyfile is protected by a passphrase. + Path to a private key file for authentication. :type: str -.. confval:: key_filename +.. confval:: passphrase - Path to the keyfile. + Passphrase to decrypt :confval:`key_filename`, if the key is passphrase-protected. :type: str - .. confval:: timeout - The timeout to drop a connection attempt in seconds. + Timeout in seconds for connection attempts. :type: float + :default: ``60`` + :required: False .. confval:: clear_cache - Normally all settings for ssh-connections are cached. This allows to defined - all settings in one command and all following commands can reuse these settings - without set them in every single command. If a new connection with different - settings should be configured, this setting allows to reset the cache to default - values. + Clear all cached connection settings before this command runs, allowing a fresh + connection to be configured. (Normally all settings for ssh-connections are cached. This allows to define + all settings in one command and reuse them in the following commands without having to redefine them) :type: bool :default: ``False`` + :required: False - .. note:: - - This setting will not clear the session store. +Sessions +-------- .. confval:: creates_session - A session name that identifies the session that is created when - executing this command. This session-name can be used by using the - option "session" + Name to assign to the session opened by this command. Can be reused in subsequent + commands via :confval:`session`. :type: str .. confval:: session - Reuse an existing ssh-session. This setting works only if another - ssh-command was executed with the command-option "creates_session" + Name of an existing session to reuse. The session must have been created previously + via :confval:`creates_session`. :type: str +Jump Host +--------- + .. confval:: jmp_hostname - This option sets the hostname or ip-address of the - remote jump server. + Hostname or IP address of an SSH jump host to tunnel through. :type: str .. confval:: jmp_port - Port to connect to on the jump-host. + Port to connect to on the jump host. :type: int :default: ``22`` .. confval:: jmp_username - Specifies the user to log in as on the jmp-host. + Username to authenticate as on the jump host. :type: str - :default: ``same as username`` + :default: same as :confval:`username` + +Interactive Mode +---------------- .. confval:: interactive - When the ssh-command is executed, the command will block until the ssh-execution finishes. - However, for some exploits it is necessary to run a command and send keystrokes to an - interactive session. For example run with the first command "vim" and with the second command - send keystrokes to the open vim-session. In interactive-mode the command will try reading the - output until no output is written for a certain amount of seconds. If the output ends with any - string found in ``prompts``, it will stop immediately. + Run the command in interactive mode. + + :type: bool + :default: ``False`` + :required: False + + Instead of waiting for the command to finish, + AttackMate reads output until no new output appears for :confval:`command_timeout` + seconds, or until the output ends with one of the strings in :confval:`prompts`. + + Useful for commands that require keystroke input (e.g. opening ``vim`` and then + sending keystrokes in a follow-up command). .. warning:: - Please note that you **MUST** send a newline when you execute a ssh-command interactively. + Commands executed in interactive mode **MUST** end with a newline character (``\n``). + - :type: bool - :default: ``False`` .. code-block:: yaml @@ -153,45 +164,43 @@ Execute commands on a remote server via SSH. $SSH_SERVER: 10.10.10.19 commands: - # creates new ssh-connection and session + # Open nmap in interactive mode and create a session: - type: ssh cmd: "nmap --interactive\n" interactive: True hostname: $SSH_SERVER username: aecid key_filename: "/home/alice/.ssh/id_rsa" - creates_session: "attacker" + creates_session: attacker - # break out of the nmap-interactive-mode + # Send a command to the open interactive session: - type: ssh cmd: "!sh\n" interactive: True - session: "attacker" + session: attacker .. confval:: command_timeout - The interactive-mode works with timeouts while reading the output. If there is no output for some seconds, - the command will stop reading. + Seconds to wait for new output before stopping in interactive mode. :type: int :default: ``15`` + :required: False .. confval:: prompts - In interactive-mode the command will try reading the output for a certain amount of seconds. If the output - ends with any string found in ``prompts``, the command will stop immediately. - If ``prompts`` is an empty list, no prompt checking will be performed. + List of strings that signal the end of output in interactive mode. When the output + ends with any of these strings, AttackMate stops reading immediately without waiting + for the timeout. Set to an empty list to disable prompt detection. :type: list[str] :default: ``["$ ", "# ", "> "]`` + :required: False .. code-block:: yaml - vars: - $SSH_SERVER: 10.10.10.19 - commands: - # creates new ssh-connection and session + # Custom prompt list: - type: ssh cmd: "nmap --interactive\n" interactive: True @@ -203,16 +212,13 @@ Execute commands on a remote server via SSH. hostname: $SSH_SERVER username: aecid key_filename: "/home/alice/.ssh/id_rsa" - creates_session: "attacker" - + creates_session: attacker .. code-block:: yaml vars: $SSH_SERVER: 10.10.10.19 - - commands: - # prompts is an empty list + # Disable prompt detection entirely: - type: ssh cmd: "id\n" interactive: True @@ -220,25 +226,25 @@ Execute commands on a remote server via SSH. hostname: $SSH_SERVER username: aecid password: password - creates_session: "attacker" + creates_session: attacker +Binary Mode +----------- .. confval:: bin - Enable binary mode. In this mode only hex-characters are allowed. + Enable binary mode. In this mode, ``cmd`` must be a hex-encoded string representing + the raw bytes to send. :type: bool :default: ``False`` + :required: False .. code-block:: yaml - vars: - $SERVER_ADDRESS: 192.42.0.254 - $SSH_SERVER: 10.10.10.19 - commands: + # "6964" is the hex encoding of "id": - type: ssh - # hex-code for "id" cmd: "6964" bin: True hostname: $SSH_SERVER diff --git a/docs/source/playbook/commands/vnc.rst b/docs/source/playbook/commands/vnc.rst index 1f41adfc..7dd3b80a 100644 --- a/docs/source/playbook/commands/vnc.rst +++ b/docs/source/playbook/commands/vnc.rst @@ -2,12 +2,14 @@ vnc === -Execute commands on a remote server via VNC. Uses the `vncdotool `_ library. +Execute commands on a remote host via VNC, using the +`vncdotool `_ library. Connection settings are +cached after the first command, so subsequent commands only need to specify what changes. -.. note:: +.. warning:: - This command caches all the settings so - that they only need to be defined once. + VNC sessions must be explicitly closed with ``cmd: close``, otherwise AttackMate + will hang on exit. .. code-block:: yaml @@ -18,7 +20,7 @@ Execute commands on a remote server via VNC. Uses the `vncdotool `_ virtual machine and -a `Kali Linux `_ with some packages installed: +a `Kali Linux `_ with the following packages installed: * AttackMate * NMap diff --git a/docs/source/playbook/session/index.rst b/docs/source/playbook/session/index.rst index 6312ce17..e782beb3 100644 --- a/docs/source/playbook/session/index.rst +++ b/docs/source/playbook/session/index.rst @@ -4,64 +4,82 @@ Sessions, Interactive ===================== -Many commands of AttackMate support the setting "session" or "interactive. -This chapter is about these important concepts of AttackMate. +Many AttackMate commands support the ``creates_session``, ``session``, and ``interactive`` +options. This page explains these concepts and when to use them. Session -------- +-------- -AttackMate executes all commands stateless. Therefore, each command is executed in a new "environment". -What "environment" means depends on the type of the command. For example, every stateless shell -command spawns a new shell process. As illustrated in the following image, every shell command -is executed in a new `/bin/sh` process. +By default, AttackMate executes all commands statelessly — each command runs in a fresh +environment. What "environment" means depends on the command type: a ``shell`` command +spawns a new ``/bin/sh`` process, an ``ssh`` command opens a new SSH connection, and so on. .. image:: /images/Stateless-Command.png -The ssh-command on the other hand, establishes with every new stateless execution a new SSH connection. -AttackMate will log in to the target with every single command execution. However, sometimes you want the AttackMate not to log on to the target system every time you execute a command. To achieve this, you can use sessions. +This means that directory changes, environment variables, or open connections from one +command are not visible to the next. To persist state across commands, AttackMate supports +sessions. -Many commands support the "creates_session" option. This allows you to specify a session name and AttackMate saves the environment of the command. By using the session name for further commands, it is possible to continue where the previous command left off at any time. The following figure shows how the first command uses create_session to execute a stateful command. The third command can then continue in the same environment as the first command by using the session. +Any command that supports the ``creates_session`` option will save its environment under +the given session name. Subsequent commands can then reference that name via ``session`` +to continue working in the same environment, as illustrated below. .. image:: /images/Stateful-Command.png Interactive ----------- -Many commands work in such a way that they first execute something and then collect and return the output. Sometimes, however, commands are executed that do not produce any output. In such cases, AttackMate would wait forever. One such example would be executing the text editor vim on the command line. Vim is started and waits for input. AttackMate gets no output, or the process does not terminate and so it waits forever. Interactive mode is available for such cases. This mode causes commands to be executed for a limited time only. After this time has elapsed, AttackMate continues. The following example shows how AttackMate executes vim with the help of a session and the interactive mode in a shell command and then types keyboard strokes into the open vim sessions. +Most commands work by executing something and waiting for the process to finish before +collecting its output. This breaks down for interactive programs that wait for user input +and never terminate on their own — for example, opening ``vim`` from a shell command would +cause AttackMate to wait forever for output that never comes. + +Interactive mode solves this by running a command for a limited time only. Instead of +waiting for the process to finish, AttackMate reads output until no new output has arrived +for a configurable timeout period, then moves on to the next command. + +.. warning:: + + Commands executed in interactive mode **MUST** end with a newline character (``\n``). + +The following example opens ``vim``, remaps a key, types text, and saves the file — all +using a combination of sessions and interactive mode: .. code-block:: yaml commands: + # Open vim and create a session: - type: shell cmd: "vim /tmp/test\n" interactive: True creates_session: vim + # Remap 'jj' to Escape in insert mode: - type: shell cmd: ":inoremap jj \n" interactive: True session: vim + # Enter insert mode: - type: shell cmd: "o" interactive: True session: vim + # Type some text: - type: shell cmd: "Hello World" interactive: True session: vim + # Exit insert mode using the remapped key: - type: shell cmd: "jj" interactive: True session: vim + # Save and quit: - type: shell cmd: ":wq!\n" interactive: True session: vim - -.. warning:: - - Please note that you **MUST** send a newline when you execute interactive commands! diff --git a/docs/source/playbook/structure.rst b/docs/source/playbook/structure.rst index 218fc819..7f943562 100644 --- a/docs/source/playbook/structure.rst +++ b/docs/source/playbook/structure.rst @@ -6,24 +6,23 @@ AttackMate playbooks must be written in valid `YAML-format `_ .. code-block:: yaml - ### commands: - type: shell cmd: nmap www.vulnerable-system.tld - type: shell cmd: nikto -host www.vulnerable-system.tld -Usually playbooks also contain a :ref:`variable section ` which contains all the placeholders -that can be used to build commands: +Usually playbooks also contain a :ref:`vars ` section to define reusable +placeholders that can be referenced throughout the ``commands`` section: .. code-block:: yaml - ### vars: TARGET: www.vulnerable-system.tld NMAP: /usr/bin/nmap NIKTO: /usr/bin/nikto + commands: - type: shell cmd: $NMAP -T4 $TARGET diff --git a/docs/source/playbook/vars.rst b/docs/source/playbook/vars.rst index 40d42348..335c133c 100644 --- a/docs/source/playbook/vars.rst +++ b/docs/source/playbook/vars.rst @@ -4,51 +4,52 @@ Variables ========= -Variables may be assigned to by a statement of the form of key-values. -Once assigned, they can be used as placeholders in command-settings. It -is unnecessary to begin variable names with a $-sign when defined in the -vars-section. However, when variables are placed in the commands section, -they always must start with a $-sign. -If the same variable name with the prefix "ATTACKMATE_" exists as an -environment variable it will overwrite the playbook variable value. -i.e. the playbookvariabel $FOO will be overwritten be environment variabel -$ATTACKMATE_FOO. +Variables are defined as key-value pairs in the ``vars`` section and can be used as +placeholders in command settings. Variable names do not require a ``$`` prefix when +defined in the ``vars`` section, but **MUST** be prefixed with ``$`` when referenced +in the ``commands`` section. +If an environment variable with the prefix ``ATTACKMATE_`` exists with the same name, +it will override the playbook variable. For example, the playbook variabel ``$FOO`` will +be overwritten by the environment variabel ``$ATTACKMATE_FOO``. .. code-block:: yaml - ### vars: + # the $-sign is optional here: $SERVER_ADDRESS: 192.42.0.254 - # the $-sign is not necessary here: $NMAP: /usr/bin/nmap commands: - type: shell - # the $-sign is required when using the variable: + # the $-sign is required when referencing a variable: cmd: $NMAP $SERVER_ADDRESS .. note:: - For more information about using the variables see `string.Template `_ + Variable substitution uses Python's `string.Template + `_ syntax. .. note:: - variables in cmd settings of a loop command will be substituted on every iteration of the loop, see :ref:`loop` + Variables in ``cmd`` settings of a ``loop`` command will be substituted on every iteration of the loop, see the :ref:`loop` command for details. + + +.. _builtin-variables: Builtin Variables ================= -The following variables are set by the system: +The following variables are set automatically by AttackMate during execution: -``RESULT_STDOUT`` is set after every command execution (except for debug, regex and setvar commands) and stores the result output. +``RESULT_STDOUT`` Stores the standard output of the most recently executed command. Not set by ``debug``, ``regex``, or ``setvar`` commands. -``RESULT_CODE`` is set after every command execution and stores the returncode. +``RESULT_CODE`` Stores the return code of the most recently executed command. -``LAST_MSF_SESSION`` is set every time after a new metasploit session was created and contains the session number. +``LAST_MSF_SESSION`` Set whenever a new Metasploit session is created. Contains the session number. -``LAST_SLIVER_IMPLANT`` is set every time after a new sliver implant was created and contains the path to the implant file. +``LAST_SLIVER_IMPLANT`` Set whenever a new Sliver implant is generated. Contains the path to the implant file. -``LAST_FATHER_PATH`` is set every time when a father-rootkit was generated. +``LAST_FATHER_PATH`` Set whenever a Father rootkit is generated. Contains the path to the rootkit. -``REGEX_MATCHES_LIST`` is set every time a regex command yields matches and it contains a list of all matches. Note that if sub or split does not have a match the input string is returned. +``REGEX_MATCHES_LIST`` Set every time a regex command yields matches. Contains a list of all matches. If ``sub`` or ``split`` finds no match, the original input string is returned. diff --git a/docs/source/preparation/index.rst b/docs/source/preparation/index.rst index 68d2ab42..7d47e43a 100644 --- a/docs/source/preparation/index.rst +++ b/docs/source/preparation/index.rst @@ -4,8 +4,8 @@ Preparation Even though AttackMate can be used out of the box, for some commands it is necessary to install and set up the corresponding tools. The following -pages are going to explain how to setup and install :ref:`Sliver ` -and :ref:`Metasploit `, as well as :ref:`Playwright ` for browser-based commands. +pages explain how to setup and install :ref:`Sliver ` ( an open-source C2 framework for post-exploitation and implant management), +:ref:`Metasploit ` (a penetration testing framework for exploiting vulnerabilities and managing sessions), as well as :ref:`Playwright ` for browser-based commands. .. toctree:: :maxdepth: 1 diff --git a/docs/source/preparation/metasploit.rst b/docs/source/preparation/metasploit.rst index 066aad34..81c415da 100644 --- a/docs/source/preparation/metasploit.rst +++ b/docs/source/preparation/metasploit.rst @@ -4,21 +4,21 @@ Prepare Metasploit ================== -It is recommended to install Metasploit as it is described in the +It is recommended to install Metasploit as described in the official `Metasploit documentation `_. -If you run Kali Linux, you could also install it from the Kali Linux repositories using apt: +On Kali Linux, you can also install it from the repositories using apt: :: $ sudo apt update && sudo apt install metasploit-framework -AttackMate needs the RPC-daemon(msfrpcd) for communication with Metasploit. -It is possible to protect the daemon with a password. The following example -starts the msfrpcd with a password: +AttackMate communicates with Metasploit via the RPC daemon (``msfrpcd``). Start it +(optionally with a password) before running AttackMate: :: - $ msfrpcd -P securepassword + $ msfrpcd -P securepassword -After starting the msfrpcd it will listen on all interface at port ``55553``. +Once started, ``msfrpcd`` listens on all interfaces on port ``55553``. Configure the +connection in AttackMate via :ref:`msf_config`. diff --git a/docs/source/preparation/sliver.rst b/docs/source/preparation/sliver.rst index e7f7f038..c6a3dc23 100644 --- a/docs/source/preparation/sliver.rst +++ b/docs/source/preparation/sliver.rst @@ -4,17 +4,29 @@ Prepare Sliver ============== -`Sliver `_ is a Post-Exploitation framework with implants for Linux, Windows and MacOs. -In order to use the sliver-commands in AttackMate, a sliver installation is required. -Sliver offers an API on port ``31337`` which is used by AttackMate to interact with it. -Follow the `Instructions offered by BishopFox `_ -to install the Sliver Framework. The simplest method is a curl oneliner: +`Sliver `_ is a post-exploitation framework with +implants for Linux, Windows, and macOS. AttackMate communicates with Sliver via its API +on port ``31337``. + +Installation +------------ + +Follow the `installation instructions by BishopFox `_ +to set up the Sliver framework. The quickest method is: :: - $ curl https://sliver.sh/install|sudo bash + $ curl https://sliver.sh/install | sudo bash + +After installation, Sliver creates an operator named ``root`` and stores its configuration +under ``/root/.sliver-client/configs``, which AttackMate uses to connect. -Sliver will create an operator named "root" and save the configs under ``/root/.sliver-client/configs`` -which can be used by AttackMate. +Configuration +------------- + +AttackMate requires Sliver to run in daemon mode. Ensure the following is set in +``.sliver/configs/server.json``: + +:: -attackm8 only works if daemon-mode is enabled: ``"daemon-mode": "true"`` in the sliver-server under ``.sliver/configs/server.json`` + "daemon-mode": "true" diff --git a/docs/source/readme_link.md b/docs/source/readme_link.md index 7dcb7103..40448cb8 100644 --- a/docs/source/readme_link.md +++ b/docs/source/readme_link.md @@ -1,3 +1,4 @@ ```{include} ../../README.md :relative-images: +:start-line: 2 ``` diff --git a/src/attackmate/attackmate.py b/src/attackmate/attackmate.py index f629a7ed..082653ac 100644 --- a/src/attackmate/attackmate.py +++ b/src/attackmate/attackmate.py @@ -23,6 +23,15 @@ class AttackMate: + """ + Reads a playbook and executes the attack chain. + + :param playbook: The playbook to execute. + :param config: AttackMate configuration. + :param varstore: Initial variable store. + :param is_api_instance: Whether this instance is used as an API. + """ + def __init__( self, playbook: Optional[Playbook] = None, diff --git a/src/attackmate/executors/baseexecutor.py b/src/attackmate/executors/baseexecutor.py index 8f0f49cd..1e331bd7 100644 --- a/src/attackmate/executors/baseexecutor.py +++ b/src/attackmate/executors/baseexecutor.py @@ -20,14 +20,22 @@ class BaseExecutor(ExitOnError, CmdVars, Looper, Background): """ + Base class for all AttackMate executors. - The BaseExecutor is the base class of all Executors. - It enables base functionality for all Executors and - provides a structure for all Executors. + Provides the core execution pipeline for commands, including variable + substitution, conditional execution (``only_if``), background mode, + loop control (``loop_if`` / ``loop_if_not``), error handling, output + logging, and JSON audit logging. - In order to create a custom Executor, one must simply - derive from the BaseExecutor and implement the method - _exec_cmd() + To implement a custom executor, subclass ``BaseExecutor`` and override + :meth:`_exec_cmd`. All other pipeline behaviour is inherited. + + Example:: + + class MyExecutor(BaseExecutor): + async def _exec_cmd(self, command: MyCommand) -> Result: + output = run_my_tool(command.cmd) + return Result(stdout=output, returncode=0) """ @@ -38,17 +46,24 @@ def __init__( cmdconfig=CommandConfig(), substitute_cmd_vars=True, is_api_instance: bool = False): - """Constructor for BaseExecutor + """ + Initialise the executor with shared infrastructure. + Parameters ---------- pm : ProcessManager - Process manager instance. + Process manager used to track and clean up background processes. varstore : VariableStore - Variable store instance. - cmdconfig : CommandConfig, default `None` - Command configuration settings. - substitute_cmd_vars : bool, default `True` - Flag to enable or disable variable substitution in command.cmd + Variable store used for template substitution in commands. + cmdconfig : CommandConfig, optional + Global command configuration (e.g. delays, loop defaults). + Defaults to an empty ``CommandConfig``. + substitute_cmd_vars : bool, optional + If ``True`` (default), variable references in ``command.cmd`` + are substituted from the variable store before execution. + is_api_instance : bool, optional + If ``True``, suppresses ``exit_on_error`` behaviour so that + API callers receive errors as results rather than process exits. """ Background.__init__(self, pm) @@ -63,19 +78,29 @@ def __init__( self.is_api_instance = is_api_instance async def run(self, command: BaseCommand, is_api_instance: bool = False) -> Result: - """Execute the command + """ + Entry point for executing a command. - This method is executed by AttackMate and - executes the given command. This method sets the - run_count to 1 and runs the method exec(). Please note - that this function will exchange all variables of the BaseCommand - with the values of the VariableStore if substitute_cmd_vars is True! + Called by AttackMate for each command in the playbook. Evaluates the + ``only_if`` condition first and skips the command if it is not met. + In background mode, the command is dispatched asynchronously and + returns immediately with a placeholder result. Otherwise, the full + synchronous execution pipeline is run via :meth:`exec`. Parameters ---------- command : BaseCommand - The settings for the command to execute + The command to execute, including all configured options. + is_api_instance : bool, optional + Overrides the instance-level ``is_api_instance`` flag for this + execution. Defaults to ``False``. + Returns + ------- + Result + The result of the command execution, containing stdout and + return code. Returns ``Result(None, None)`` if the ``only_if`` + condition is not met. """ self.is_api_instance = is_api_instance if command.only_if: @@ -85,13 +110,13 @@ async def run(self, command: BaseCommand, is_api_instance: bool = False) -> Resu self.reset_run_count() self.logger.debug(f"Template-Command: '{command.cmd}'") if command.background: - # Background commands always return Result(None,None) + # Background commands always return Result('Command started in background', 0) time_of_execution = datetime.now().isoformat() self.log_json(self.json_logger, command, time_of_execution) await self.exec_background( self.substitute_template_vars(command, self.substitute_cmd_vars) ) - # the background command will return immidiately with Result(None, None) + # the background command will return immidiately with Result('Command started in background', 0) # Return 0 instead of None so the API/Remote Client sees success result = Result('Command started in background', 0) else: @@ -101,15 +126,30 @@ async def run(self, command: BaseCommand, is_api_instance: bool = False) -> Resu return result def log_command(self, command): - """Log starting-status of the command""" + """Log the start of a command execution at INFO level.""" self.logger.info(f"Executing '{command}'") def log_metadata(self, logger: logging.Logger, command): - """Log metadata of the command""" + """Log command metadata as a JSON string, if present.""" if command.metadata: logger.info(f'Metadata: {json.dumps(command.metadata)}') def log_json(self, logger: logging.Logger, command, time): + """ + Serialize a command to JSON and write it to the JSON audit log. + + If serialization fails due to non-serializable types, a warning is + logged instead and execution continues. + + Parameters + ---------- + logger : logging.Logger + The logger to write the JSON entry to. + command : BaseCommand + The command to serialize. + time : str + ISO 8601 timestamp of when the command started. + """ command_dict = self.make_command_serializable(command, time) try: @@ -143,9 +183,17 @@ def make_command_serializable(self, command, time): return command_dict def save_output(self, command: BaseCommand, result: Result): - """Save output of command to a file. This method will - ignore all exceptions and won't stop the programm - on error. + """ + Write command output to a file if ``command.save`` is set. + + Failures are logged as warnings and do not interrupt execution. + + Parameters + ---------- + command : BaseCommand + The command whose output should be saved. + result : Result + The result containing the stdout to write. """ if command.save: try: @@ -155,6 +203,23 @@ def save_output(self, command: BaseCommand, result: Result): self.logger.warning(f'Unable to write output to file {command.save}: {e}') async def exec(self, command: BaseCommand) -> Result: + """ + Run the full synchronous execution pipeline for a command. + + Calls :meth:`_exec_cmd`, then handles JSON logging, output saving, + error checking, variable store updates, and loop condition evaluation. + + Parameters + ---------- + command : BaseCommand + The command to execute. + + Returns + ------- + Result + The result of the command, or a ``Result(str(error), 1)`` if an + :class:`~attackmate.execexception.ExecException` is raised. + """ try: self.log_command(command) self.log_metadata(self.logger, command) @@ -179,4 +244,21 @@ async def _loop_exec(self, command: BaseCommand) -> Result: return result async def _exec_cmd(self, command: Any) -> Result: + """ + Execute the command. Override this method in subclasses. + + This is the only method that must be implemented in a custom executor. + The base implementation is a no-op that returns ``Result(None, None)``. + + Parameters + ---------- + command : Any + The command to execute. Subclasses should type this as their + specific command schema class. + + Returns + ------- + Result + The result of the command execution. + """ return Result(None, None) diff --git a/src/attackmate/schemas/sliver.py b/src/attackmate/schemas/sliver.py index 6b8f029a..39a403bd 100644 --- a/src/attackmate/schemas/sliver.py +++ b/src/attackmate/schemas/sliver.py @@ -56,6 +56,7 @@ class SliverSessionMKDIRCommand(SliverSessionCommand): remote_path: str +@CommandRegistry.register('sliver-session', 'download') class SliverSessionDOWNLOADCommand(SliverSessionCommand): cmd: Literal['download'] remote_path: str @@ -63,6 +64,7 @@ class SliverSessionDOWNLOADCommand(SliverSessionCommand): recurse: bool = False +@CommandRegistry.register('sliver-session', 'upload') class SliverSessionUPLOADCommand(SliverSessionCommand): cmd: Literal['upload'] remote_path: str @@ -71,6 +73,7 @@ class SliverSessionUPLOADCommand(SliverSessionCommand): is_ioc: bool = False +@CommandRegistry.register('sliver-session', 'netstat') class SliverSessionNETSTATCommand(SliverSessionCommand): cmd: Literal['netstat'] tcp: bool = True @@ -80,6 +83,7 @@ class SliverSessionNETSTATCommand(SliverSessionCommand): listening: bool = True +@CommandRegistry.register('sliver-session', 'execute') class SliverSessionEXECCommand(SliverSessionCommand): cmd: Literal['execute'] exe: str @@ -87,21 +91,27 @@ class SliverSessionEXECCommand(SliverSessionCommand): output: bool = True +@CommandRegistry.register('sliver-session', 'ifconfig') +@CommandRegistry.register('sliver-session', 'ps') +@CommandRegistry.register('sliver-session', 'pwd') class SliverSessionSimpleCommand(SliverSessionCommand): cmd: Literal['ifconfig', 'ps', 'pwd'] +@CommandRegistry.register('sliver-session', 'ls') class SliverSessionLSCommand(SliverSessionCommand): cmd: Literal['ls'] remote_path: str +@CommandRegistry.register('sliver-session', 'process_dump') class SliverSessionPROCDUMPCommand(SliverSessionCommand): cmd: Literal['process_dump'] local_path: str pid: StringNumber +@CommandRegistry.register('sliver-session', 'rm') class SliverSessionRMCommand(SliverSessionCommand): cmd: Literal['rm'] remote_path: str @@ -109,6 +119,7 @@ class SliverSessionRMCommand(SliverSessionCommand): force: bool = False +@CommandRegistry.register('sliver-session', 'terminate') class SliverSessionTERMINATECommand(SliverSessionCommand): cmd: Literal['terminate'] pid: StringNumber