#2689 Added C2 Sequence diagram to docs and added additional ftp_client request tests.
This commit is contained in:
BIN
docs/_static/c2_sequence.png
vendored
Normal file
BIN
docs/_static/c2_sequence.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
@@ -99,6 +99,12 @@ However, each host implements it's own receive methods.
|
|||||||
- Sends C2 Commands to the C2 Beacon via ``C2Payload.INPUT``.
|
- Sends C2 Commands to the C2 Beacon via ``C2Payload.INPUT``.
|
||||||
- Receives the RequestResponse of the C2 Commands executed by C2 Beacon via ``C2Payload.OUTPUT``.
|
- Receives the RequestResponse of the C2 Commands executed by C2 Beacon via ``C2Payload.OUTPUT``.
|
||||||
|
|
||||||
|
The sequence diagram below clarifies the functionality of both applications:
|
||||||
|
|
||||||
|
.. image:: ../_static/c2_sequence.png
|
||||||
|
:width: 500
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
|
||||||
For further details and more in-depth examples please refer to the ``Command-&-Control notebook``
|
For further details and more in-depth examples please refer to the ``Command-&-Control notebook``
|
||||||
|
|
||||||
|
|||||||
@@ -1454,7 +1454,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"In the case of the C2 Beacon, the C2 Server's IP address must be supplied before the C2 beacon will be able to perform any other actions (including ``APPLICATION EXECUTE``).\n",
|
"In the case of the C2 Beacon, the C2 Server's IP address must be supplied before the C2 beacon will be able to perform any other actions (including ``APPLICATION EXECUTE``).\n",
|
||||||
"\n",
|
"\n",
|
||||||
"If the network contains multiple C2 Servers then it's also possible to switch to a different C2 servers mid-episode which is demonstrated in the below code cells."
|
"If the network contains multiple C2 Servers then it's also possible to switch to a different C2 server mid-episode which is demonstrated in the below code cells."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class AbstractC2(Application, identifier="AbstractC2"):
|
|||||||
keep_alive_inactivity: int = 0
|
keep_alive_inactivity: int = 0
|
||||||
"""Indicates how many timesteps since the last time the c2 application received a keep alive."""
|
"""Indicates how many timesteps since the last time the c2 application received a keep alive."""
|
||||||
|
|
||||||
class C2_Opts(BaseModel):
|
class _C2Opts(BaseModel):
|
||||||
"""A Pydantic Schema for the different C2 configuration options."""
|
"""A Pydantic Schema for the different C2 configuration options."""
|
||||||
|
|
||||||
keep_alive_frequency: int = Field(default=5, ge=1)
|
keep_alive_frequency: int = Field(default=5, ge=1)
|
||||||
@@ -87,7 +87,7 @@ class AbstractC2(Application, identifier="AbstractC2"):
|
|||||||
masquerade_port: Port = Field(default=Port.HTTP)
|
masquerade_port: Port = Field(default=Port.HTTP)
|
||||||
"""The currently chosen port that the C2 traffic is masquerading as. Defaults at HTTP."""
|
"""The currently chosen port that the C2 traffic is masquerading as. Defaults at HTTP."""
|
||||||
|
|
||||||
c2_config: C2_Opts = C2_Opts()
|
c2_config: _C2Opts = _C2Opts()
|
||||||
"""
|
"""
|
||||||
Holds the current configuration settings of the C2 Suite.
|
Holds the current configuration settings of the C2 Suite.
|
||||||
|
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ class C2Server(AbstractC2, identifier="C2Server"):
|
|||||||
grants more opportunity to the blue agent to prevent attacks.
|
grants more opportunity to the blue agent to prevent attacks.
|
||||||
|
|
||||||
Additionally, future editions of primAITE may expand the C2 repertoire to allow for
|
Additionally, future editions of primAITE may expand the C2 repertoire to allow for
|
||||||
more complex red agent behaviour such as file extraction, establishing further fall back channels
|
more complex red agent behaviour such as establishing further fall back channels
|
||||||
or introduce red applications that are only installable via C2 Servers. (T1105)
|
or introduce red applications that are only installable via C2 Servers. (T1105)
|
||||||
|
|
||||||
For more information on the impact of these commands please refer to the terminal
|
For more information on the impact of these commands please refer to the terminal
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class FTPClient(FTPServiceABC):
|
|||||||
dest_ip = request[-1].get("dest_ip_address")
|
dest_ip = request[-1].get("dest_ip_address")
|
||||||
dest_ip = None if dest_ip is None else IPv4Address(dest_ip)
|
dest_ip = None if dest_ip is None else IPv4Address(dest_ip)
|
||||||
|
|
||||||
# TODO: Confirm that the default values lead to a safe failure.
|
# Missing FTP Options results is an automatic failure.
|
||||||
src_folder = request[-1].get("src_folder_name", None)
|
src_folder = request[-1].get("src_folder_name", None)
|
||||||
src_file_name = request[-1].get("src_file_name", None)
|
src_file_name = request[-1].get("src_file_name", None)
|
||||||
dest_folder = request[-1].get("dest_folder_name", None)
|
dest_folder = request[-1].get("dest_folder_name", None)
|
||||||
@@ -63,7 +63,10 @@ class FTPClient(FTPServiceABC):
|
|||||||
f"{self.name}: Received a FTP Request to transfer file: {src_file_name} to Remote IP: {dest_ip}."
|
f"{self.name}: Received a FTP Request to transfer file: {src_file_name} to Remote IP: {dest_ip}."
|
||||||
)
|
)
|
||||||
return RequestResponse(
|
return RequestResponse(
|
||||||
status="failure", data={"reason": "Unable to locate requested file on local file system."}
|
status="failure",
|
||||||
|
data={
|
||||||
|
"reason": "Unable to locate given file on local file system. Perhaps given options are invalid?"
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return RequestResponse.from_bool(
|
return RequestResponse.from_bool(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from typing import Tuple
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from primaite.interface.request import RequestResponse
|
||||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||||
from primaite.simulator.network.hardware.nodes.host.server import Server
|
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||||
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
from primaite.simulator.system.services.ftp.ftp_client import FTPClient
|
||||||
@@ -105,3 +106,65 @@ def test_ftp_client_tries_to_connect_to_offline_server(ftp_client_and_ftp_server
|
|||||||
|
|
||||||
# client should have retrieved the file
|
# client should have retrieved the file
|
||||||
assert ftp_client.file_system.get_file(folder_name="downloads", file_name="test_file.txt") is None
|
assert ftp_client.file_system.get_file(folder_name="downloads", file_name="test_file.txt") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_ftp_client_store_file_in_server_via_request_success(ftp_client_and_ftp_server):
|
||||||
|
"""
|
||||||
|
Test checks to see if the client can successfully store files in the backup server via the request manager.
|
||||||
|
"""
|
||||||
|
ftp_client, computer, ftp_server, server = ftp_client_and_ftp_server
|
||||||
|
|
||||||
|
assert ftp_client.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
assert ftp_server.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
# create file on ftp client
|
||||||
|
ftp_client.file_system.create_file(file_name="test_file.txt")
|
||||||
|
|
||||||
|
ftp_opts = {
|
||||||
|
"src_folder_name": "root",
|
||||||
|
"src_file_name": "test_file.txt",
|
||||||
|
"dest_folder_name": "client_1_backup",
|
||||||
|
"dest_file_name": "test_file.txt",
|
||||||
|
"dest_ip_address": server.network_interfaces.get(next(iter(server.network_interfaces))).ip_address,
|
||||||
|
}
|
||||||
|
|
||||||
|
ftp_client.apply_request(["send", ftp_opts])
|
||||||
|
|
||||||
|
assert ftp_server.file_system.get_file(folder_name="client_1_backup", file_name="test_file.txt")
|
||||||
|
|
||||||
|
|
||||||
|
def test_ftp_client_store_file_in_server_via_request_failure(ftp_client_and_ftp_server):
|
||||||
|
"""
|
||||||
|
Test checks to see if the client fails to store files in the backup server via the request manager.
|
||||||
|
"""
|
||||||
|
ftp_client, computer, ftp_server, server = ftp_client_and_ftp_server
|
||||||
|
|
||||||
|
assert ftp_client.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
assert ftp_server.operating_state == ServiceOperatingState.RUNNING
|
||||||
|
|
||||||
|
# create file on ftp client
|
||||||
|
ftp_client.file_system.create_file(file_name="test_file.txt")
|
||||||
|
|
||||||
|
# Purposefully misconfigured FTP Options
|
||||||
|
ftp_opts = {
|
||||||
|
"src_folder_name": "root",
|
||||||
|
"dest_ip_address": server.network_interfaces.get(next(iter(server.network_interfaces))).ip_address,
|
||||||
|
}
|
||||||
|
|
||||||
|
request_response: RequestResponse = ftp_client.apply_request(["send", ftp_opts])
|
||||||
|
|
||||||
|
assert request_response.status == "failure"
|
||||||
|
|
||||||
|
# Purposefully misconfigured FTP Options
|
||||||
|
|
||||||
|
ftp_opts = {
|
||||||
|
"src_folder_name": "root",
|
||||||
|
"src_file_name": "not_a_real_file.txt",
|
||||||
|
"dest_folder_name": "client_1_backup",
|
||||||
|
"dest_file_name": "test_file.txt",
|
||||||
|
"dest_ip_address": server.network_interfaces.get(next(iter(server.network_interfaces))).ip_address,
|
||||||
|
}
|
||||||
|
|
||||||
|
request_response: RequestResponse = ftp_client.apply_request(["send", ftp_opts])
|
||||||
|
|
||||||
|
assert request_response.status == "failure"
|
||||||
|
|||||||
Reference in New Issue
Block a user