Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

2004 scan target configuration #2133

Merged
merged 12 commits into from
Aug 1, 2022
Merged
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import re

from marshmallow import Schema, ValidationError, fields, post_load, validate, validates
from marshmallow import Schema, fields, post_load, validate

from .agent_sub_configurations import (
CustomPBAConfiguration,
Expand All @@ -14,49 +12,19 @@
TCPScanConfiguration,
)
from .utils import freeze_lists

valid_windows_custom_pba_filename_regex = re.compile(r"^[^<>:\"\\\/|?*]*[^<>:\"\\\/|?* \.]+$|^$")
valid_linux_custom_pba_filename_regex = re.compile(r"^[^\0/]*$")
from .validators import (
validate_ip,
validate_linux_filename,
validate_subnet_range,
validate_windows_filename,
)


class CustomPBAConfigurationSchema(Schema):
linux_command = fields.Str()
linux_filename = fields.Str(
validate=validate.Regexp(regex=valid_linux_custom_pba_filename_regex)
)
linux_filename = fields.Str(validate=validate_linux_filename)
windows_command = fields.Str()
windows_filename = fields.Str(
validate=validate.Regexp(regex=valid_windows_custom_pba_filename_regex)
)

@validates("windows_filename")
def validate_windows_filename_not_reserved(self, windows_filename):
# filename shouldn't start with any of these and be followed by a period
if windows_filename.split(".")[0].upper() in [
"CON",
"PRN",
"AUX",
"NUL",
"COM1",
"COM2",
"COM3",
"COM4",
"COM5",
"COM6",
"COM7",
"COM8",
"COM9",
"LPT1",
"LPT2",
"LPT3",
"LPT4",
"LPT5",
"LPT6",
"LPT7",
"LPT8",
"LPT9",
]:
raise ValidationError("Invalid Windows filename: reserved name used")
windows_filename = fields.Str(validate=validate_windows_filename)

@post_load
def _make_custom_pba_configuration(self, data, **kwargs):
Expand All @@ -73,10 +41,10 @@ def _make_plugin_configuration(self, data, **kwargs):


class ScanTargetConfigurationSchema(Schema):
blocked_ips = fields.List(fields.Str())
inaccessible_subnets = fields.List(fields.Str())
blocked_ips = fields.List(fields.Str(validate=validate_ip))
inaccessible_subnets = fields.List(fields.Str(validate=validate_subnet_range))
local_network_scan = fields.Bool()
subnets = fields.List(fields.Str())
subnets = fields.List(fields.Str(validate=validate_subnet_range))

@post_load
@freeze_lists
Expand Down
14 changes: 14 additions & 0 deletions monkey/common/agent_configuration/agent_sub_configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ class PluginConfiguration:

@dataclass(frozen=True)
class ScanTargetConfiguration:
"""
Configuration of network targets to scan and exploit

Attributes:
:param blocked_ips: IP's that won't be scanned
Example: ("1.1.1.1", "2.2.2.2")
:param inaccessible_subnets: Subnet ranges that shouldn't be accessible for the agent
Example: ("1.1.1.1", "2.2.2.2/24", "myserver")
:param local_network_scan: Whether or not the agent should scan the local network
:param subnets: Subnet ranges to scan
Example: ("192.168.1.1-192.168.2.255", "3.3.3.3", "2.2.2.2/24",
"myHostname")
"""

blocked_ips: Tuple[str, ...]
inaccessible_subnets: Tuple[str, ...]
local_network_scan: bool
Expand Down
8 changes: 8 additions & 0 deletions monkey/common/agent_configuration/validators/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .filenames import validate_linux_filename, validate_windows_filename
from .ip_ranges import (
validate_ip,
validate_hostname,
validate_ip_range,
validate_subnet_range,
validate_ip_network,
)
24 changes: 24 additions & 0 deletions monkey/common/agent_configuration/validators/filenames.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import re
from pathlib import PureWindowsPath

from marshmallow import ValidationError

_valid_windows_filename_regex = re.compile(r"^[^<>:\"\\\/|?*]*[^<>:\"\\\/|?* \.]+$|^$")
_valid_linux_filename_regex = re.compile(r"^[^\0/]*$")


def validate_linux_filename(linux_filename: str):
if not re.match(_valid_linux_filename_regex, linux_filename):
raise ValidationError(f"Invalid Unix filename {linux_filename}: illegal characters")


def validate_windows_filename(windows_filename: str):
_validate_windows_filename_not_reserved(windows_filename)
if not re.match(_valid_windows_filename_regex, windows_filename):
raise ValidationError(f"Invalid Windows filename {windows_filename}: illegal characters")


def _validate_windows_filename_not_reserved(windows_filename: str):
# filename shouldn't start with any of these and be followed by a period
if PureWindowsPath(windows_filename).is_reserved():
Copy link
Collaborator

Choose a reason for hiding this comment

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

Fantastic!

raise ValidationError(f"Invalid Windows filename {windows_filename}: reserved name used")
67 changes: 67 additions & 0 deletions monkey/common/agent_configuration/validators/ip_ranges.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import re
from ipaddress import AddressValueError, IPv4Address, IPv4Network, NetmaskValueError

from marshmallow import ValidationError


def validate_subnet_range(subnet_range: str):
try:
return validate_ip(subnet_range)
except ValidationError:
pass

try:
return validate_ip_range(subnet_range)
except ValidationError:
pass

try:
return validate_ip_network(subnet_range)
except ValidationError:
pass

try:
return validate_hostname(subnet_range)
except ValidationError:
raise ValidationError(f"Invalid subnet range {subnet_range}")


def validate_hostname(hostname: str):
# Based on hostname syntax: https://www.rfc-editor.org/rfc/rfc1123#page-13
hostname_segments = hostname.split(".")
if any((part.endswith("-") or part.startswith("-") for part in hostname_segments)):
raise ValidationError(f"Hostname segment can't start or end with a hyphen: {hostname}")
if not any((char.isalpha() for char in hostname_segments[-1])):
raise ValidationError(f"Last segment of a hostname must contain a letter: {hostname}")

valid_characters_pattern = r"^[A-Za-z0-9\-]+$"
valid_characters_regex = re.compile(valid_characters_pattern)
matches = (
re.match(valid_characters_regex, hostname_segment) for hostname_segment in hostname_segments
)

if not all(matches):
raise ValidationError(f"Hostname contains invalid characters: {hostname}")


def validate_ip_network(ip_network: str):
try:
IPv4Network(ip_network, strict=False)
except (NetmaskValueError, AddressValueError):
raise ValidationError(f"Invalid IPv4 network {ip_network}")


def validate_ip_range(ip_range: str):
ip_range = ip_range.replace(" ", "")
ips = ip_range.split("-")
if len(ips) != 2:
raise ValidationError(f"Invalid IP range {ip_range}")
validate_ip(ips[0])
validate_ip(ips[1])


def validate_ip(ip: str):
try:
IPv4Address(ip)
except AddressValueError:
raise ValidationError(f"Invalid IP address {ip}")
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import pytest
from marshmallow import ValidationError

from common.agent_configuration.validators.ip_ranges import validate_ip, validate_subnet_range


@pytest.mark.parametrize("ip", ["192.168.56.1", "0.0.0.0"])
def test_validate_ip_valid(ip):
validate_ip(ip)


@pytest.mark.parametrize("ip", ["1.1.1", "257.256.255.255", "1.1.1.1.1"])
def test_validate_ip_invalid(ip):
with pytest.raises(ValidationError):
validate_ip(ip)


@pytest.mark.parametrize("ip", ["192.168.56.1", "0.0.0.0"])
def test_validate_subnet_range__ip_valid(ip):
validate_subnet_range(ip)


@pytest.mark.parametrize("ip", ["1.1.1", "257.256.255.255", "1.1.1.1.1"])
def test_validate_subnet_range__ip_invalid(ip):
with pytest.raises(ValidationError):
validate_subnet_range(ip)


@pytest.mark.parametrize("ip_range", ["1.1.1.1 - 2.2.2.2", "1.1.1.255-1.1.1.1"])
def test_validate_subnet_range__ip_range_valid(ip_range):
validate_subnet_range(ip_range)


@pytest.mark.parametrize(
"ip_range",
[
"1.1.1-2.2.2.2",
"0-.1.1.1-2.2.2.2",
"a..1.1.1-2.2.2.2",
"257.1.1.1-2.2.2.2",
"1.1.1.1-2.2.2.2-3.3.3.3",
],
)
def test_validate_subnet_range__ip_range_invalid(ip_range):
with pytest.raises(ValidationError):
validate_subnet_range(ip_range)


@pytest.mark.parametrize("hostname", ["infection.monkey", "1nfection-Monkey", "1.1.1.1a"])
def test_validate_subnet_range__hostname_valid(hostname):
validate_subnet_range(hostname)


@pytest.mark.parametrize(
"hostname", ["hy&!he.host", "čili-peppers.are-hot", "one.two-", "one-.two", "one@two", ""]
)
def test_validate_subnet_range__hostname_invalid(hostname):
with pytest.raises(ValidationError):
validate_subnet_range(hostname)


@pytest.mark.parametrize("cidr_range", ["1.1.1.1/24", "1.1.1.1/0"])
def test_validate_subnet_range__cidr_valid(cidr_range):
validate_subnet_range(cidr_range)


@pytest.mark.parametrize("cidr_range", ["1.1.1/24", "1.1.1.1/-1", "1.1.1.1/33", "1.1.1.1/222"])
def test_validate_subnet_range__cidr_invalid(cidr_range):
with pytest.raises(ValidationError):
validate_subnet_range(cidr_range)