Skip to content

Commit

Permalink
Merge pull request #66 from ait-aecid/feature_interactive_shell
Browse files Browse the repository at this point in the history
Feature interactive shell
  • Loading branch information
whotwagner committed Feb 2, 2024
2 parents 8a68182 + 4264f83 commit cfbddce
Show file tree
Hide file tree
Showing 52 changed files with 906 additions and 555 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ cython_debug/
# VIM
.vim/
.swp
.swo
.swn
.swm

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repos:
- id: check-ast

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.0
rev: v1.7.1
hooks:
- id: mypy
additional_dependencies: [pydantic, types-PyYAML, types-requests, types-paramiko, types-tabulate]
Expand Down
14 changes: 14 additions & 0 deletions docs/source/installation/ansible.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.. _ansible:

=========================
Installation with Ansible
=========================

It is possible to automatically install AttackMate using
Ansible. The `ansible-role <https://github.com/ait-aecid/attackmate-ansible>`_ also deploys the sliver-fix.

A sample playbook can be found in the README-page of the `github-repository <https://github.com/ait-aecid/attackmate-ansible>`_

.. note::

Currently the ansible role only works with Debian and Ubuntu distributions.
1 change: 1 addition & 0 deletions docs/source/installation/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ and :ref:`Sliver <prep_sliver>`.

manual
sliverfix
ansible
docker
62 changes: 62 additions & 0 deletions docs/source/playbook/commands/shell.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,65 @@ Execute local shell-commands.
The command-line that should be executed locally.

:type: str

.. 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".

:type: str

.. confval:: session

Reuse an existing interactive session. This setting works only if another
shell-command was executed with the command-option "creates_session" and "interactive" true

:type: str

.. confval:: interactive

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.

.. warning::

Please note that you **MUST** send a newline when you execute a ssh-command interactively.

:type: bool
:default: ``False``

.. code-block:: yaml
commands:
# creates new ssh-connection and session
- type: shell
cmd: "nmap --interactive\n"
interactive: True
creates_session: "attacker"
# break out of the nmap-interactive-mode
- type: shell
cmd: "!sh\n"
interactive: True
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.

:type: int
:default: ``15``

.. 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.

:type: bool
:default: ``True``
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ requires-python = ">=3.7"
keywords = ["Pentest", "Attack", "Orchestration", "Hacking", "Simulating", "Attackchain"]
license = {text = "GPL-3.0"}
dependencies = [
"pydantic ~= 1.10",
"pydantic ~= 2.5",
"colorlog",
"pymetasploit3",
"pyaml",
Expand Down
35 changes: 18 additions & 17 deletions src/attackmate/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from typing import Optional
from colorlog import ColoredFormatter
from .attackmate import AttackMate
from .schemas import Config, Playbook
from attackmate.schemas.config import Config
from attackmate.schemas.playbook import Playbook
from .metadata import __version_string__


Expand All @@ -23,10 +24,10 @@ def initialize_output_logger(debug: bool):
output_logger.setLevel(logging.DEBUG)
else:
output_logger.setLevel(logging.INFO)
file_handler = logging.FileHandler("output.log", mode='w')
file_handler = logging.FileHandler('output.log', mode='w')
formatter = logging.Formatter(
'--- %(asctime)s %(levelname)s: ---\n\n%(message)s',
datefmt="%Y-%m-%d %H:%M:%S")
datefmt='%Y-%m-%d %H:%M:%S')
file_handler.setFormatter(formatter)
output_logger.addHandler(file_handler)

Expand All @@ -38,15 +39,15 @@ def initialize_logger(debug: bool):
else:
playbook_logger.setLevel(logging.INFO)
console_handler = logging.StreamHandler()
LOGFORMAT = (" %(asctime)s %(log_color)s%(levelname)-8s%(reset)s"
"| %(log_color)s%(message)s%(reset)s")
formatter = ColoredFormatter(LOGFORMAT, datefmt="%Y-%m-%d %H:%M:%S")
LOGFORMAT = (' %(asctime)s %(log_color)s%(levelname)-8s%(reset)s'
'| %(log_color)s%(message)s%(reset)s')
formatter = ColoredFormatter(LOGFORMAT, datefmt='%Y-%m-%d %H:%M:%S')
console_handler.setFormatter(formatter)
playbook_logger.addHandler(console_handler)
file_handler = logging.FileHandler("attackmate.log", mode='w')
file_handler = logging.FileHandler('attackmate.log', mode='w')
formatter = logging.Formatter(
'%(asctime)s %(levelname)s - %(message)s',
datefmt="%Y-%m-%d %H:%M:%S")
datefmt='%Y-%m-%d %H:%M:%S')
file_handler.setFormatter(formatter)
playbook_logger.addHandler(file_handler)
return playbook_logger
Expand Down Expand Up @@ -78,9 +79,9 @@ def parse_config(config_file: Optional[str], logger: logging.Logger) -> Config:
"""
default_cfg_path = [
".attackmate.yml",
os.environ['HOME'] + "/.config/attackmate.yml",
"/etc/attackmate.yml"]
'.attackmate.yml',
os.environ['HOME'] + '/.config/attackmate.yml',
'/etc/attackmate.yml']
try:
if config_file is None:
for file in default_cfg_path:
Expand All @@ -90,15 +91,15 @@ def parse_config(config_file: Optional[str], logger: logging.Logger) -> Config:
except OSError:
pass
if cfg is not None:
logger.debug(f"Cfgfile {file} loaded")
logger.debug(f'Cfgfile {file} loaded')
return cfg
logger.debug("No config-file found. Using empty default-config")
logger.debug('No config-file found. Using empty default-config')
return Config()
else:
logger.debug(f"Cfgfile {config_file} loaded")
logger.debug(f'Cfgfile {config_file} loaded')
return load_configfile(config_file)
except OSError:
logger.error(f"Error: Could not open file {config_file}")
logger.error(f'Error: Could not open file {config_file}')
exit(1)
except yaml.parser.ParserError as e:
logger.error(e)
Expand Down Expand Up @@ -126,7 +127,7 @@ def parse_playbook(playbook_file: str, logger: logging.Logger) -> Playbook:
playbook_object = Playbook.parse_obj(pb_yaml)
return playbook_object
except OSError:
logger.error(f"Error: Could not open playbook file {playbook_file}")
logger.error(f'Error: Could not open playbook file {playbook_file}')
exit(1)


Expand All @@ -151,7 +152,7 @@ def parse_args():
version=__version_string__)
parser.add_argument(
'playbook',
help="Playbook in yaml-format")
help='Playbook in yaml-format')
return parser.parse_args()


Expand Down
3 changes: 2 additions & 1 deletion src/attackmate/attackmate.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
from attackmate.executors.common.debugexecutor import DebugExecutor
from attackmate.executors.common.includeexecutor import IncludeExecutor
from attackmate.executors.common.regexexecutor import RegExExecutor
from .schemas import Config, Playbook, Commands
from attackmate.schemas.config import Config
from attackmate.schemas.playbook import Playbook, Commands
from .variablestore import VariableStore
from .processmanager import ProcessManager

Expand Down
3 changes: 2 additions & 1 deletion src/attackmate/executors/baseexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from attackmate.executors.features.conditional import Conditional
from attackmate.result import Result
from attackmate.execexception import ExecException
from attackmate.schemas import BaseCommand, CommandConfig
from attackmate.schemas.base import BaseCommand
from attackmate.schemas.config import CommandConfig
from attackmate.variablestore import VariableStore
from attackmate.processmanager import ProcessManager
from typing import Any
Expand Down
2 changes: 1 addition & 1 deletion src/attackmate/executors/common/debugexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from attackmate.executors.baseexecutor import BaseExecutor
from attackmate.result import Result
from attackmate.schemas import DebugCommand
from attackmate.schemas.debug import DebugCommand


class DebugExecutor(BaseExecutor):
Expand Down
3 changes: 2 additions & 1 deletion src/attackmate/executors/common/includeexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from typing import Callable
from attackmate.executors.baseexecutor import BaseExecutor
from attackmate.result import Result
from attackmate.schemas import IncludeCommand, Playbook, Commands
from attackmate.schemas.include import IncludeCommand
from attackmate.schemas.playbook import Playbook, Commands
from attackmate.variablestore import VariableStore
from attackmate.execexception import ExecException
from attackmate.processmanager import ProcessManager
Expand Down
2 changes: 1 addition & 1 deletion src/attackmate/executors/common/regexexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from attackmate.executors.baseexecutor import BaseExecutor
from attackmate.result import Result
from attackmate.schemas import RegExCommand
from attackmate.schemas.regex import RegExCommand
from string import Template
from typing import Match
import re
Expand Down
2 changes: 1 addition & 1 deletion src/attackmate/executors/common/setvarexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from attackmate.executors.baseexecutor import BaseExecutor
from attackmate.result import Result
from attackmate.schemas import SetVarCommand
from attackmate.schemas.setvar import SetVarCommand
import base64
import codecs
import urllib.parse
Expand Down
2 changes: 1 addition & 1 deletion src/attackmate/executors/common/tempfileexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Any
from attackmate.executors.baseexecutor import BaseExecutor
from attackmate.result import Result
from attackmate.schemas import TempfileCommand
from attackmate.schemas.tempfile import TempfileCommand
from attackmate.variablestore import VariableStore
from attackmate.processmanager import ProcessManager

Expand Down
2 changes: 1 addition & 1 deletion src/attackmate/executors/father/fatherexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from typing import Any
from attackmate.executors.baseexecutor import BaseExecutor
from attackmate.result import Result
from attackmate.schemas import FatherCommand
from attackmate.schemas.father import FatherCommand
from attackmate.variablestore import VariableStore
from attackmate.processmanager import ProcessManager

Expand Down
2 changes: 1 addition & 1 deletion src/attackmate/executors/features/background.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from attackmate.schemas import BaseCommand
from attackmate.schemas.base import BaseCommand
from attackmate.processmanager import ProcessManager
from attackmate.result import Result
from multiprocessing import Queue
Expand Down
10 changes: 8 additions & 2 deletions src/attackmate/executors/features/cmdvars.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import copy

from attackmate.result import Result
from attackmate.schemas import BaseCommand
from attackmate.schemas.base import BaseCommand, StringNumber
from attackmate.variablestore import VariableStore
from attackmate.execexception import ExecException

Expand Down Expand Up @@ -56,7 +56,13 @@ def replace_variables(self, command: BaseCommand) -> BaseCommand:
return template_cmd

@staticmethod
def variable_to_int(variablename: str, value: str) -> int:
def variable_to_int(variablename: str, value: StringNumber) -> int:
if not value:
raise ExecException(f'Variable {variablename} has not a numeric value: {value}')

if isinstance(value, int):
return value

if value.isnumeric():
return int(value)
else:
Expand Down
2 changes: 1 addition & 1 deletion src/attackmate/executors/features/exitonerror.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re
import logging
from attackmate.result import Result
from attackmate.schemas import BaseCommand
from attackmate.schemas.base import BaseCommand


class ExitOnError:
Expand Down
3 changes: 2 additions & 1 deletion src/attackmate/executors/features/looper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import logging
from attackmate.executors.features.cmdvars import CmdVars
from attackmate.result import Result
from attackmate.schemas import BaseCommand, CommandConfig
from attackmate.schemas.base import BaseCommand
from attackmate.schemas.config import CommandConfig


class Looper:
Expand Down
2 changes: 1 addition & 1 deletion src/attackmate/executors/http/httpclientexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import Optional
from attackmate.executors.baseexecutor import BaseExecutor
from attackmate.result import Result
from attackmate.schemas import HttpClientCommand
from attackmate.schemas.http import HttpClientCommand
from attackmate.execexception import ExecException


Expand Down
2 changes: 1 addition & 1 deletion src/attackmate/executors/http/webservexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from attackmate.executors.baseexecutor import BaseExecutor
from attackmate.result import Result
from attackmate.execexception import ExecException
from attackmate.schemas import WebServCommand
from attackmate.schemas.http import WebServCommand
from attackmate.executors.features.cmdvars import CmdVars
from http.server import HTTPServer, BaseHTTPRequestHandler
import magic
Expand Down
3 changes: 2 additions & 1 deletion src/attackmate/executors/metasploit/msfexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from attackmate.execexception import ExecException
from attackmate.result import Result
from attackmate.executors.features.cmdvars import CmdVars
from attackmate.schemas import MsfModuleCommand, BaseCommand
from attackmate.schemas.base import BaseCommand
from attackmate.schemas.metasploit import MsfModuleCommand
from attackmate.executors.metasploit.msfsessionstore import MsfSessionStore
from attackmate.processmanager import ProcessManager
from multiprocessing import Manager
Expand Down
3 changes: 2 additions & 1 deletion src/attackmate/executors/metasploit/msfpayloadexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
from attackmate.processmanager import ProcessManager
from attackmate.variablestore import VariableStore
from attackmate.executors.features.cmdvars import CmdVars
from attackmate.schemas import MsfPayloadCommand, CommandConfig
from attackmate.schemas.metasploit import MsfPayloadCommand
from attackmate.schemas.config import CommandConfig


class MsfPayloadExecutor(BaseExecutor):
Expand Down
3 changes: 2 additions & 1 deletion src/attackmate/executors/metasploit/msfsessionexecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from attackmate.executors.baseexecutor import BaseExecutor
from attackmate.execexception import ExecException
from attackmate.result import Result
from attackmate.schemas import MsfSessionCommand, BaseCommand
from attackmate.schemas.base import BaseCommand
from attackmate.schemas.metasploit import MsfSessionCommand
from attackmate.processmanager import ProcessManager


Expand Down
5 changes: 5 additions & 0 deletions src/attackmate/executors/metasploit/msfsessionstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ def get_session_by_name(self, name: str, msfsessions, block: bool = True) -> str
if name in self.sessions:
if v['exploit_uuid'] == self.sessions[name]:
return k
else:
self.logger.debug(f"uuid {self.sessions[name]} does not match with any entry in sessions")
else:
self.logger.debug(f"{name} not found in msfsessions")

if not block:
raise ExecException(f'Session ({name}) not found')
else:
Expand Down
Loading

0 comments on commit cfbddce

Please sign in to comment.