Skip to content

Commit bc3283c

Browse files
Merge pull request #911 from shreyamalviya/zerologon-exploiter
Zerologon Exploiter
2 parents fdeb54d + 43cac35 commit bc3283c

File tree

20 files changed

+1515
-154
lines changed

20 files changed

+1515
-154
lines changed

LICENSE

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
Everyone is permitted to copy and distribute verbatim copies
66
of this license document, but changing it is not allowed.
77

8+
This product includes software developed by SecureAuth Corporation
9+
(https://www.secureauth.com/).
10+
811
Preamble
912

1013
The GNU General Public License is a free, copyleft license for
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
title: "Zerologon"
3+
date: 2021-01-31T19:46:12+05:30
4+
draft: false
5+
tags: ["exploit", "windows"]
6+
---
7+
8+
The Zerologon exploiter exploits [CVE-2020-1472](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1472).
9+
10+
This exploiter is unsafe.
11+
* It will temporarily change the target domain controller's password.
12+
* It may break the target domain controller's communication with other systems in the network, affecting functionality.
13+
14+
It is, therefore, **not** enabled by default.
15+
16+
17+
### Description
18+
19+
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).
20+
21+
To download the relevant security update and read more, click [here](https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-1472).
22+
23+
24+
### Notes
25+
26+
* The Infection Monkey exploiter implementation is based on implementations by [@dirkjanm](https://github.com/dirkjanm/CVE-2020-1472/) and [@risksense](https://github.com/risksense/zerologon).

monkey/infection_monkey/exploit/HostExploiter.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ def base_package_name():
3636
# Usual values are 'vulnerability' or 'brute_force'
3737
EXPLOIT_TYPE = ExploitType.VULNERABILITY
3838

39+
# Determines if successful exploitation should stop further exploit attempts on that machine.
40+
# Generally, should be True for RCE type exploiters and False if we don't expect the exploiter to run the monkey agent.
41+
# Example: Zerologon steals credentials
42+
RUNS_AGENT_ON_SUCCESS = True
43+
3944
@property
4045
@abstractmethod
4146
def _EXPLOITED_SERVICE(self):
@@ -104,4 +109,5 @@ def add_executed_cmd(self, cmd):
104109
:param cmd: String of executed command. e.g. 'echo Example'
105110
"""
106111
powershell = True if "powershell" in cmd.lower() else False
107-
self.exploit_info['executed_cmds'].append({'cmd': cmd, 'powershell': powershell})
112+
self.exploit_info['executed_cmds'].append(
113+
{'cmd': cmd, 'powershell': powershell})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import pytest
2+
3+
from infection_monkey.exploit.zerologon import ZerologonExploiter
4+
from infection_monkey.model.host import VictimHost
5+
6+
7+
DOMAIN_NAME = "domain-name"
8+
IP = "0.0.0.0"
9+
NETBIOS_NAME = "NetBIOS Name"
10+
11+
USERS = ["Administrator", "Bob"]
12+
RIDS = ["500", "1024"]
13+
LM_HASHES = ["abc123", "098zyx"]
14+
NT_HASHES = ["def456", "765vut"]
15+
16+
17+
@pytest.fixture
18+
def zerologon_exploiter_object(monkeypatch):
19+
def mock_report_login_attempt(**kwargs):
20+
return None
21+
22+
host = VictimHost(IP, DOMAIN_NAME)
23+
obj = ZerologonExploiter(host)
24+
monkeypatch.setattr(obj, "dc_name", NETBIOS_NAME, raising=False)
25+
monkeypatch.setattr(obj, "report_login_attempt", mock_report_login_attempt)
26+
return obj
27+
28+
29+
def test_assess_exploit_attempt_result_no_error(zerologon_exploiter_object):
30+
dummy_exploit_attempt_result = {"ErrorCode": 0}
31+
assert zerologon_exploiter_object.assess_exploit_attempt_result(
32+
dummy_exploit_attempt_result
33+
)
34+
35+
36+
def test_assess_exploit_attempt_result_with_error(zerologon_exploiter_object):
37+
dummy_exploit_attempt_result = {"ErrorCode": 1}
38+
assert not zerologon_exploiter_object.assess_exploit_attempt_result(
39+
dummy_exploit_attempt_result
40+
)
41+
42+
43+
def test_assess_restoration_attempt_result_restored(zerologon_exploiter_object):
44+
dummy_restoration_attempt_result = object()
45+
assert zerologon_exploiter_object.assess_restoration_attempt_result(
46+
dummy_restoration_attempt_result
47+
)
48+
49+
50+
def test_assess_restoration_attempt_result_not_restored(zerologon_exploiter_object):
51+
dummy_restoration_attempt_result = False
52+
assert not zerologon_exploiter_object.assess_restoration_attempt_result(
53+
dummy_restoration_attempt_result
54+
)
55+
56+
57+
def test__extract_user_creds_from_secrets_good_data(zerologon_exploiter_object):
58+
mock_dumped_secrets = [
59+
f"{USERS[i]}:{RIDS[i]}:{LM_HASHES[i]}:{NT_HASHES[i]}:::"
60+
for i in range(len(USERS))
61+
]
62+
expected_extracted_creds = {
63+
USERS[0]: {
64+
"RID": int(RIDS[0]),
65+
"lm_hash": LM_HASHES[0],
66+
"nt_hash": NT_HASHES[0],
67+
},
68+
USERS[1]: {
69+
"RID": int(RIDS[1]),
70+
"lm_hash": LM_HASHES[1],
71+
"nt_hash": NT_HASHES[1],
72+
},
73+
}
74+
assert (
75+
zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets)
76+
is None
77+
)
78+
assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds
79+
80+
81+
def test__extract_user_creds_from_secrets_bad_data(zerologon_exploiter_object):
82+
mock_dumped_secrets = [
83+
f"{USERS[i]}:{RIDS[i]}:::{LM_HASHES[i]}:{NT_HASHES[i]}:::"
84+
for i in range(len(USERS))
85+
]
86+
expected_extracted_creds = {
87+
USERS[0]: {"RID": int(RIDS[0]), "lm_hash": "", "nt_hash": ""},
88+
USERS[1]: {"RID": int(RIDS[1]), "lm_hash": "", "nt_hash": ""},
89+
}
90+
assert (
91+
zerologon_exploiter_object._extract_user_creds_from_secrets(mock_dumped_secrets)
92+
is None
93+
)
94+
assert zerologon_exploiter_object._extracted_creds == expected_extracted_creds
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import pytest
2+
from nmb.NetBIOS import NetBIOS
3+
4+
from infection_monkey.exploit.zerologon_utils.vuln_assessment import \
5+
get_dc_details
6+
from infection_monkey.model.host import VictimHost
7+
8+
9+
DOMAIN_NAME = "domain-name"
10+
IP = "0.0.0.0"
11+
12+
13+
@pytest.fixture
14+
def host():
15+
return VictimHost(IP, DOMAIN_NAME)
16+
17+
18+
def _get_stub_queryIPForName(netbios_names):
19+
def stub_queryIPForName(*args, **kwargs):
20+
return netbios_names
21+
return stub_queryIPForName
22+
23+
24+
def test_get_dc_details_multiple_netbios_names(host, monkeypatch):
25+
NETBIOS_NAMES = ["Name1", "Name2", "Name3"]
26+
27+
stub_queryIPForName = _get_stub_queryIPForName(NETBIOS_NAMES)
28+
monkeypatch.setattr(NetBIOS, "queryIPForName", stub_queryIPForName)
29+
30+
dc_ip, dc_name, dc_handle = get_dc_details(host)
31+
assert dc_ip == IP
32+
assert dc_name == NETBIOS_NAMES[0]
33+
assert dc_handle == f"\\\\{NETBIOS_NAMES[0]}"
34+
35+
36+
def test_get_dc_details_no_netbios_names(host, monkeypatch):
37+
NETBIOS_NAMES = []
38+
39+
stub_queryIPForName = _get_stub_queryIPForName(NETBIOS_NAMES)
40+
monkeypatch.setattr(NetBIOS, "queryIPForName", stub_queryIPForName)
41+
42+
dc_ip, dc_name, dc_handle = get_dc_details(host)
43+
assert dc_ip == IP
44+
assert dc_name == ""
45+
assert dc_handle == "\\\\"

0 commit comments

Comments
 (0)