Merge pull request #1626 from guardicore/1592-i-puppet

Create IPuppet interface
This commit is contained in:
Mike Salvatore 2021-11-23 07:57:08 -05:00 committed by GitHub
commit 4da7c1ab5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 322 additions and 0 deletions

View File

@ -0,0 +1,89 @@
import abc
import threading
from collections import namedtuple
from enum import Enum
from typing import Dict, Optional, Tuple
class PortStatus(Enum):
OPEN = 1
CLOSED = 2
PortScanData = namedtuple("PortScanData", ["port", "status", "banner", "service"])
class IPuppet(metaclass=abc.ABCMeta):
@abc.abstractmethod
def run_sys_info_collector(self, name: str) -> Dict:
"""
Runs a system info collector
:param str name: The name of the system info collector to run
:return: A dictionary containing the information collected from the system
:rtype: Dict
"""
@abc.abstractmethod
def run_pba(self, name: str, options: Dict) -> None:
"""
Runs a post-breach action (PBA)
:param str name: The name of the post-breach action to run
:param Dict options: A dictionary containing options that modify the behavior of the PBA
"""
@abc.abstractmethod
def ping(self, host: str) -> Tuple[bool, Optional[str]]:
"""
Sends a ping (ICMP packet) to a remote host
:param str host: The domain name or IP address of a host
:return: A tuple that contains whether or not the host responded and the host's inferred
operating system
:rtype: Tuple[bool, Optional[str]]
"""
@abc.abstractmethod
def scan_tcp_port(self, host: str, port: int, timeout: int) -> PortScanData:
"""
Scans a TCP port on a remote host
:param str host: The domain name or IP address of a host
:param int port: A TCP port number to scan
:param int timeout: The maximum amount of time (in seconds) to wait for a response
:return: The data collected by scanning the provided host:port combination
:rtype: PortScanData
"""
@abc.abstractmethod
def fingerprint(self, name: str, host: str) -> Dict:
"""
Runs a fingerprinter against a remote host
:param str name: The name of the fingerprinter to run
:param str host: The domain name or IP address of a host
:return: A dictionary containing the information collected by the fingerprinter
:rtype: Dict
"""
@abc.abstractmethod
def exploit_host(self, name: str, host: str, options: Dict, interrupt: threading.Event) -> bool:
"""
Runs an exploiter against a remote host
:param str name: The name of the exploiter to run
:param str host: The domain name or IP address of a host
:param Dict options: A dictionary containing options that modify the behavior of the
exploiter
:return: True if exploitation was successful, False otherwise
:rtype: bool
"""
@abc.abstractmethod
def run_payload(self, name: str, options: Dict, interrupt: threading.Event) -> None:
"""
Runs a payload
:param str name: The name of the payload to run
:param Dict options: A dictionary containing options that modify the behavior of the payload
"""
@abc.abstractmethod
def cleanup(self) -> None:
"""
Revert any changes made to the system by the puppet.
"""

View File

@ -0,0 +1,225 @@
import logging
import threading
from typing import Dict, Optional, Tuple
from infection_monkey.i_puppet import IPuppet, PortScanData, PortStatus
DOT_1 = "10.0.0.1"
DOT_2 = "10.0.0.2"
DOT_3 = "10.0.0.3"
DOT_4 = "10.0.0.4"
logger = logging.getLogger()
class MockPuppet(IPuppet):
def run_sys_info_collector(self, name: str) -> Dict:
logger.debug(f"run_sys_info_collector({name})")
# TODO: More collectors
if name == "LinuxInfoCollector":
return {
"credentials": {},
"network_info": {
"networks": [
{"addr": "10.0.0.7", "netmask": "255.255.255.0"},
{"addr": "10.45.31.103", "netmask": "255.255.255.0"},
{"addr": "192.168.33.241", "netmask": "255.255.0.0"},
]
},
"ssh_info": [
{
"name": "m0nk3y",
"home_dir": "/home/m0nk3y",
"public_key": "ssh-rsa "
"AAAAB3NzaC1yc2EAAAADAQABAAABAQCqhqTJfcrAbTUPzQ+Ou9bhQjmP29jRBz00BAdvNu77Y1SwM/+wETxapv7QPG55oc04Y5qR1KaItcwz3Prh7Qe/ohP/I2mIhP5tDRNfYHxXaGtj58wQhFrkrUhERVvEvwyvb97RWPAtAJjWT8+S6ASjjvyUNHulFIjJ0Yptlj2fboeh1eETDQ4FKfofpgwmab110ct2500FOtY1MWqFgpRvV0EX8WgJoscQ5FnsJAn6Ueb3DnsrIDq1LtK1rmxGSiZwpgOCwvyC1FFfHeP+cfpPsS+G9pBSYm2VqR42QL1BJL1pm4wFPVrBDmzORVQRf35k6agL7loRlfmAt28epDi1 ubuntu@test\n", # noqa: E501
"private_key": "-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEpAIBAAKCAQEAqoakyX3KwG01D80PjrvW4UI5j9vY0Qc9NAQHbzbu+2NUsDP/\n"
"sBE8Wqb+0DxueaHNOGOakdSmiLXMM9z64e0Hv6IT/yNpiIT+bQ0TX2B8V2hrY+fM\n"
"Ew0OBSn6H6YMJmm9ddHLdudNBTrWNTFqhYKUb1dBF/FoCaLHEORZ7CQJ+lHm9w57\n"
"KyA6tS7Sta5sRkomcKYDgsL8gtRRXx3j/nH6T7EvhvaQUmJtlakeNkC9QSS9aZuM\n"
"snegLvVSlHVmKe8SjD0YAF7g9HH/vm0R2jYTYSArslw4mUZMjTcAQ/XBeDHDkNZq\n"
"x9ECzXdeZhXCXlKcadC+kNp+yT4MwkHAjid6AyalSDJ+9k3QRaI6ItxofWJhnZdB\n"
"RxQtnkJNOZCMKqwxmxUweX7AyShT1KdBdkw0VzkY0O3VUgdR9IzQu73eME5Qr4LM\n"
"5x+rFy0EggHkzCXecviDDQ/SJZEDR4yE0SCxwY0GxVfDdvM6aoLK7wLfu0hG+hjO\n"
"ewXmOAECgYEA4yA14atxKYWf8tAJnmH+IJi1nuiyBoaKJh9nGulGTFVpugytkfdy\n"
"omGYsvlSJd6x4KPM2nXuSD9uvS0ZDeHDXbPJcFAPscghwwIekunQigECgYEAwDRl\n"
"QOhBx8PpicbRmoEe06zb+gRNTYTnvcHgkJN275pqTn1hIAdQSGnyuyWdCN6CU8cg\n"
"p7ecLbCujAstim4H8LG6xMv8jBgVeBKclKEEy9IpvMZ/DGOdUS4/RMWkdVbcFFHZ\n"
"57gycmFwgN7ZFXdMkuCCZi2KCa4jX54G1VNX0+k64cLV8lgQXvVyl9QdvBkt8NqB\n"
"Zoce2vfDrFkUHoxQmAl2jvn8925KkAdga4Zj+zvLgmcryxCFZnA6IvxaoHzrUSxO\n"
"HpuEdCFek/4gyhXPbYQO99ZtOjx0mXwZVqRaEA1kvhX3+PjoPRO2wgBLXVNyb+P5\n"
"5Bxfk6XI40UAUSYv6XQlfIQj0xz/YfSkWbOwTJOShgMbJtiZVFuZ2YcEjSYXzNtv\n"
"WBM0+05OGqjxdyI+qpjHqrZVWN9WvvkH0gJz+zvcorygINMnuSjpNCw4nipXHaud\n"
"LbiqWK42eTmVSiFH+pH+YwVaTatc0RfQ7OP218GD8dtkTgw2JFOzbA==\n"
"-----END RSA PRIVATE KEY-----\n",
"known_hosts": "|1|pERVcy3opIGJnp7HVTpeA0FmuEY=|L64j7430lwkSFrmcn49Nf8YEsLc= " # noqa: E501
"ssh-rsa "
"AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n" # noqa: E501
"|1|DXEyHSAtnxSSWb4z6XLaxHJL/aM=|zjIBopXOz1GB9hbdpVcYsHY+eSU= "
"ssh-rsa "
"AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==\n" # noqa: E501
"10.197.94.221 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL3o1lUn7mZ6HNKDlkFJH9lvFIOXpTH62XkxM7wKXeZbKUy1BKnx2Jkkpv6736XnbFNkUHSnPlCAYDBqsH4nr28=\n" # noqa: E501
"|1|kVjsp1IWhGMsWfrbQuhLUABrNMk=|xKCh+yr8mPEyCLZ2/E5bC8bjvw0= "
"ecdsa-sha2-nistp256 "
"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL3o1lUn7mZ6HNKDlkFJH9lvFIOXpTH62XkxM7wKXeZbKUy1BKnx2Jkkpv6736XnbFNkUHSnPlCAYDBqsH4nr28=\n" # noqa: E501
"other_host,fd42:5289:fddc:ffdf:216:3eff:fe5b:9114 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL3o1lUn7mZ6HNKDlkFJH9lvFIOXpTH62XkxM7wKXeZbKUy1BKnx2Jkkpv6736XnbFNkUHSnPlCAYDBqsH4nr28=\n" # noqa: E501
"|1|S6K6SneX+l7xTM1gNLvDAAzj4gs=|cSOIX6qf5YuIe2aw/KmUrM2ye/c= "
"ecdsa-sha2-nistp256 "
"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL3o1lUn7mZ6HNKDlkFJH9lvFIOXpTH62XkxM7wKXeZbKUy1BKnx2Jkkpv6736XnbFNkUHSnPlCAYDBqsH4nr28=\n", # noqa: E501
}
],
}
if name == "ProcessListCollector":
return {
"process_list": {
1: {
"cmdline": "/sbin/init",
"full_image_path": "/sbin/init",
"name": "systemd",
"pid": 1,
"ppid": 0,
},
65: {
"cmdline": "/lib/systemd/systemd-journald",
"full_image_path": "/lib/systemd/systemd-journald",
"name": "systemd-journald",
"pid": 65,
"ppid": 1,
},
84: {
"cmdline": "/lib/systemd/systemd-udevd",
"full_image_path": "/lib/systemd/systemd-udevd",
"name": "systemd-udevd",
"pid": 84,
"ppid": 1,
},
192: {
"cmdline": "/lib/systemd/systemd-networkd",
"full_image_path": "/lib/systemd/systemd-networkd",
"name": "systemd-networkd",
"pid": 192,
"ppid": 1,
},
17749: {
"cmdline": "-zsh",
"full_image_path": "/bin/zsh",
"name": "zsh",
"pid": 17749,
"ppid": 17748,
},
18392: {
"cmdline": "/home/ubuntu/venvs/monkey/bin/python " "monkey_island.py",
"full_image_path": "/usr/bin/python3.7",
"name": "python",
"pid": 18392,
"ppid": 17502,
},
18400: {
"cmdline": "/home/ubuntu/git/monkey/monkey/monkey_island/bin/mongodb/bin/mongod " # noqa: E501
"--dbpath /home/ubuntu/.monkey_island/db",
"full_image_path": "/home/ubuntu/git/monkey/monkey/monkey_island/bin/mongodb/bin/mongod", # noqa: E501
"name": "mongod",
"pid": 18400,
"ppid": 18392,
},
26535: {
"cmdline": "ACCESS DENIED",
"full_image_path": "null",
"name": "null",
"pid": 26535,
"ppid": 26469,
},
29291: {
"cmdline": "python infection_monkey.py m0nk3y -s " "localhost:5000",
"full_image_path": "/usr/bin/python3.7",
"name": "python",
"pid": 29291,
"ppid": 17749,
},
}
}
return {}
def run_pba(self, name: str, options: Dict) -> None:
logger.debug(f"run_pba({name}, {options})")
return None
def ping(self, host: str) -> Tuple[bool, Optional[str]]:
logger.debug(f"run_ping({host})")
if host == DOT_1:
return (True, "windows")
if host == DOT_2:
return (False, None)
if host == DOT_3:
return (True, "Linux")
if host == DOT_4:
return (False, None)
return (False, None)
def scan_tcp_port(self, host: str, port: int, timeout: int = 3) -> PortScanData:
logger.debug(f"run_scan_tcp_port({host}, {port}, {timeout})")
dot_1_results = {
22: PortScanData(22, PortStatus.CLOSED, None, None),
445: PortScanData(445, PortStatus.OPEN, "SMB BANNER", "tcp-445"),
3389: PortScanData(3389, PortStatus.OPEN, "", "tcp-3389"),
}
dot_3_results = {
22: PortScanData(22, PortStatus.OPEN, "SSH BANNER", "tcp-22"),
443: PortScanData(443, PortStatus.OPEN, "HTTPS BANNER", "tcp-443"),
3389: PortScanData(3389, PortStatus.CLOSED, "", None),
}
if host == DOT_1:
return dot_1_results.get(port, _get_empty_results(port))
if host == DOT_3:
return dot_3_results.get(port, _get_empty_results(port))
return _get_empty_results(port)
def fingerprint(self, name: str, host: str) -> Dict:
logger.debug(f"fingerprint({name}, {host})")
dot_1_results = {
"SMBFinger": {
"os": {"type": "windows", "version": "vista"},
"services": {"tcp-445": {"name": "SSH", "os": "linux"}},
}
}
dot_3_results = {
"SSHFinger": {"os": "linux", "services": {"tcp-22": {"name": "SSH"}}},
"HTTPFinger": {
"services": {"tcp-https": {"name": "http", "data": ("SERVER_HEADERS", DOT_3)}}
},
}
if host == DOT_1:
return dot_1_results.get(name, {})
if host == DOT_3:
return dot_3_results.get(name, {})
return {}
def exploit_host(self, name: str, host: str, options: Dict, interrupt: threading.Event) -> bool:
logger.debug(f"exploit_hosts({name}, {host}, {options})")
successful_exploiters = {DOT_1: {"PowerShellExploiter"}, DOT_3: {"SSHExploiter"}}
return name in successful_exploiters.get(host, {})
def run_payload(self, name: str, options: Dict, interrupt: threading.Event) -> None:
logger.debug(f"run_payload({name}, {options})")
return None
def cleanup(self) -> None:
print("Cleanup called!")
pass
def _get_empty_results(port: int):
return PortScanData(port, False, None, None)

View File

@ -201,3 +201,11 @@ environment # unused variable (monkey/monkey_island/cc/models/monkey.py:59)
_.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:35)
_.instance_name # unused attribute (monkey/common/cloud/azure/azure_instance.py:64)
GCPHandler # unused function (envs/monkey_zoo/blackbox/test_blackbox.py:57)
# TODO: Reevaluate these as the agent refactor progresses
run_sys_info_collector
ping
scan_tcp_port
fingerprint
interrupt
MockPuppet