Merge remote-tracking branch 'upstream/develop' into enhancement/mitre-ui-review-fixes

# Conflicts:
#	monkey/monkey_island/cc/main.py
This commit is contained in:
VakarisZ 2020-04-01 09:20:55 +03:00
commit bc166ef67d
45 changed files with 1359 additions and 343 deletions

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

@ -546,6 +546,38 @@ fullTest.conf is a good config to start, because it covers all machines.
</tbody>
</table>
<table>
<thead>
<tr class="header">
<th><p><span id="_Toc536021463" class="anchor"></span>Nr. <strong>12</strong> Tunneling M4</p>
<p>(10.2.0.12)</p></th>
<th>(Exploitable)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>OS:</td>
<td><strong>Windows server 2019 x64</strong></td>
</tr>
<tr class="odd">
<td>Default 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>
<thead>
<tr class="header">

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

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

View File

@ -7,9 +7,12 @@ import urllib
from logging import getLogger
from urllib.parse import urlsplit
import requests
import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
from infection_monkey.network.tools import get_interface_to_target
import infection_monkey.control
__author__ = 'hoffer'
@ -108,12 +111,35 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
timeout = 30 # timeout with clients, set to None not to make persistent connection
proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header
protocol_version = "HTTP/1.1"
def do_POST(self):
try:
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length).decode()
LOG.info("Received bootloader's request: {}".format(post_data))
try:
dest_path = self.path
r = requests.post(url=dest_path,
data=post_data,
verify=False,
proxies=infection_monkey.control.ControlClient.proxies)
self.send_response(r.status_code)
except requests.exceptions.ConnectionError as e:
LOG.error("Couldn't forward request to the island: {}".format(e))
self.send_response(404)
except Exception as e:
LOG.error("Failed to forward bootloader request: {}".format(e))
finally:
self.end_headers()
self.wfile.write(r.content)
except Exception as e:
LOG.error("Failed receiving bootloader telemetry: {}".format(e))
def version_string(self):
return ""
def do_CONNECT(self):
LOG.info("Received a connect request!")
# just provide a tunnel, transfer the data with no modification
req = self
req.path = "https://%s/" % req.path.replace(':443', '')

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

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 time
import logging
from threading import Thread
MINIMUM_MONGO_DB_VERSION_REQUIRED = "3.6.0"
@ -26,10 +27,21 @@ from monkey_island.cc.environment.environment import env
from monkey_island.cc.database import is_db_server_up, get_db_version
from monkey_island.cc.resources.monkey_download import MonkeyDownload
from common.version import get_version
from monkey_island.cc.bootloader_server import BootloaderHttpServer
from monkey_island.cc.setup import setup
def main():
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()
bootloader_server_thread.join()
def start_island_server():
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop

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

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

@ -2,7 +2,7 @@ from bson import ObjectId
from monkey_island.cc.database import mongo
import monkey_island.cc.services.node
from monkey_island.cc.models import Monkey
from monkey_island.cc.models.monkey import Monkey, MonkeyNotFoundError
__author__ = "itay.mizeretz"
@ -145,7 +145,10 @@ class EdgeService:
from_id = edge["from"]
to_id = edge["to"]
from_label = Monkey.get_label_by_id(from_id)
try:
from_label = Monkey.get_label_by_id(from_id)
except MonkeyNotFoundError:
from_label = node_service.get_node_by_id(from_id)['domain_name']
if to_id == ObjectId("000000000000000000000000"):
to_label = 'MonkeyIsland'

View File

@ -1,4 +1,6 @@
from datetime import datetime, timedelta
from typing import Dict
import socket
from bson import ObjectId
@ -6,9 +8,9 @@ import monkey_island.cc.services.log
from monkey_island.cc.database import mongo
from monkey_island.cc.models import Monkey
from monkey_island.cc.services.edge import EdgeService
from monkey_island.cc.utils import local_ip_addresses
import socket
from monkey_island.cc.utils import local_ip_addresses, is_local_ips
from monkey_island.cc import models
from monkey_island.cc.services.utils.node_states import NodeStates
__author__ = "itay.mizeretz"
@ -122,20 +124,25 @@ class NodeService:
@staticmethod
def get_monkey_group(monkey):
keywords = []
if len(set(monkey["ip_addresses"]).intersection(local_ip_addresses())) != 0:
monkey_type = "island_monkey"
keywords.extend(["island", "monkey"])
else:
monkey_type = "manual" if NodeService.get_monkey_manual_run(monkey) else "monkey"
keywords.append(monkey_type)
monkey_os = NodeService.get_monkey_os(monkey)
monkey_running = "" if Monkey.get_single_monkey_by_id(monkey["_id"]).is_dead() else "_running"
return "%s_%s%s" % (monkey_type, monkey_os, monkey_running)
keywords.append(NodeService.get_monkey_os(monkey))
if not Monkey.get_single_monkey_by_id(monkey["_id"]).is_dead():
keywords.append("running")
return NodeStates.get_by_keywords(keywords).value
@staticmethod
def get_node_group(node):
def get_node_group(node) -> str:
if 'group' in node and node['group']:
return node['group']
node_type = "exploited" if node.get("exploited") else "clean"
node_os = NodeService.get_node_os(node)
return "%s_%s" % (node_type, node_os)
return NodeStates.get_by_keywords([node_type, node_os]).value
@staticmethod
def monkey_to_net_node(monkey, for_report=False):
@ -166,6 +173,12 @@ class NodeService:
"os": NodeService.get_node_os(node)
}
@staticmethod
def set_node_group(node_id: str, node_group: NodeStates):
mongo.db.node.update({"_id": node_id},
{'$set': {'group': node_group.value}},
upsert=False)
@staticmethod
def unset_all_monkey_tunnels(monkey_id):
mongo.db.monkey.update(
@ -207,6 +220,43 @@ class NodeService:
})
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
@staticmethod
def create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool):
new_node_insert_result = mongo.db.node.insert_one(
{
"ip_addresses": bootloader_telem['ips'],
"domain_name": bootloader_telem['hostname'],
"will_monkey_run": will_monkey_run,
"exploited": False,
"creds": [],
"os":
{
"type": bootloader_telem['system'],
"version": bootloader_telem['os_version']
}
})
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
@staticmethod
def get_or_create_node_from_bootloader_telem(bootloader_telem: Dict, will_monkey_run: bool) -> Dict:
if is_local_ips(bootloader_telem['ips']):
raise NodeCreationException("Bootloader ran on island, no need to create new node.")
new_node = mongo.db.node.find_one({"ip_addresses": {"$in": bootloader_telem['ips']}})
if new_node is None:
new_node = NodeService.create_node_from_bootloader_telem(bootloader_telem, will_monkey_run)
if bootloader_telem['tunnel']:
dst_node = NodeService.get_node_or_monkey_by_ip(bootloader_telem['tunnel'])
else:
dst_node = NodeService.get_monkey_island_node()
edge = EdgeService.get_or_create_edge(new_node['_id'], dst_node['id'])
mongo.db.edge.update({"_id": edge["_id"]},
{'$set': {'tunnel': bool(bootloader_telem['tunnel']),
'ip_address': bootloader_telem['ips'][0],
'group': NodeStates.get_by_keywords(['island']).value}},
upsert=False)
return new_node
@staticmethod
def get_or_create_node(ip_address, domain_name=''):
new_node = mongo.db.node.find_one({"ip_addresses": ip_address})
@ -354,3 +404,6 @@ class NodeService:
@staticmethod
def get_hostname_by_id(node_id):
return NodeService.get_node_hostname(mongo.db.monkey.find_one({'_id': node_id}, {'hostname': 1}))
class NodeCreationException(Exception):
pass

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

@ -1,16 +1,11 @@
const groupNames = ['clean_unknown', 'clean_linux', 'clean_windows', 'exploited_linux', 'exploited_windows', 'island',
'island_monkey_linux', 'island_monkey_linux_running', 'island_monkey_windows', 'island_monkey_windows_running',
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
let getGroupsOptions = () => {
let getGroupsOptions = (stateList) => {
let groupOptions = {};
for (let groupName of groupNames) {
groupOptions[groupName] =
for (let stateName of stateList) {
groupOptions[stateName] =
{
shape: 'image',
size: 50,
image: require('../../images/nodes/' + groupName + '.png')
image: require('../../images/nodes/' + stateName + '.png')
};
}
@ -52,11 +47,11 @@ export const basic_options = {
}
};
export const options = (() => {
export function getOptions(stateList) {
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */
opts.groups = getGroupsOptions();
opts.groups = getGroupsOptions(stateList);
return opts;
})();
}
export const optionsPth = (() => {
let opts = JSON.parse(JSON.stringify(basic_options)); /* Deep copy */

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);
break;
case 'node':
info = this.props.item.group.includes('monkey', 'manual') ? this.infectedAssetInfo(this.props.item) :
this.props.item.group !== 'island' ? this.assetInfo(this.props.item) : this.islandAssetInfo();
if (this.props.item.group.includes('monkey') && this.props.item.group.includes('starting')) {
info = this.assetInfo(this.props.item);
} else if (this.props.item.group.includes('monkey', 'manual')) {
info = this.infectedAssetInfo(this.props.item)
} else if (this.props.item.group !== 'island') {
info = this.assetInfo(this.props.item)
} else {
info = this.islandAssetInfo();
}
break;
case 'island_edge':
info = this.islandEdgeInfo();

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

View File

@ -3,8 +3,6 @@ import '../../styles/report/ReportPage.scss';
import React from 'react';
import {Route} from 'react-router-dom';
import {Col, Nav, NavItem} from 'react-bootstrap';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {edgeGroupToColor, options} from 'components/map/MapOptions';
import AuthComponent from '../AuthComponent';
import MustRunMonkeyWarning from '../report-components/common/MustRunMonkeyWarning';
import AttackReport from '../report-components/AttackReport'

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -1,5 +1,7 @@
import socket
import sys
from typing import List
import collections
import array
@ -49,6 +51,11 @@ else:
return result
def is_local_ips(ips: List) -> bool:
filtered_local_ips = [ip for ip in local_ip_addresses() if not ip.startswith('169.254')]
return collections.Counter(ips) == collections.Counter(filtered_local_ips)
# The local IP addresses list should not change often. Therefore, we can cache the result and never call this function
# more than once. This stopgap measure is here since this function is called a lot of times during the report
# generation.