Skip to content

Commit 9b442c5

Browse files
committed
Merge branch '3234-generate-community-strings' into develop
Issue #3234 PR #3311
2 parents 7d14f8b + 5259f45 commit 9b442c5

File tree

4 files changed

+165
-2
lines changed

4 files changed

+165
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from itertools import chain
2+
from typing import Callable, Iterable, List
3+
4+
from common.credentials import (
5+
Credentials,
6+
CredentialsComponent,
7+
Identity,
8+
Password,
9+
Secret,
10+
Username,
11+
)
12+
from infection_monkey.exploit.tools import identity_type_filter, secret_type_filter
13+
14+
15+
def generate_community_strings(credentials: Iterable[Credentials]) -> Iterable[str]:
16+
"""
17+
Yields community strings from credentials
18+
19+
:param credentials: The credentials from which to generate community strings
20+
:return: An iterable of potential community strings
21+
"""
22+
for credential in _select_credentials_components(
23+
credentials,
24+
identity_type_filter=identity_type_filter([Username]),
25+
secret_type_filter=secret_type_filter([Password]),
26+
):
27+
yield _credentials_component_to_community_string(credential)
28+
29+
30+
def _select_credentials_components(
31+
input_credentials: Iterable[Credentials],
32+
identity_type_filter: Callable[[Identity], bool] = lambda identity: True,
33+
secret_type_filter: Callable[[Identity], bool] = lambda secret: True,
34+
) -> Iterable[CredentialsComponent]:
35+
identities: List[Identity] = []
36+
secrets: List[Secret] = []
37+
38+
for credentials in input_credentials:
39+
if credentials.identity is not None:
40+
identities.append(credentials.identity)
41+
if credentials.secret is not None:
42+
secrets.append(credentials.secret)
43+
44+
for credential in chain(
45+
filter(identity_type_filter, _deduplicate(identities)),
46+
filter(secret_type_filter, _deduplicate(secrets)),
47+
):
48+
yield credential
49+
50+
51+
def _deduplicate(iterable: Iterable[CredentialsComponent]) -> Iterable[CredentialsComponent]:
52+
# Using dict.fromkeys() instead of set() because dicts preserve order
53+
return dict.fromkeys(iterable).keys()
54+
55+
56+
def _credentials_component_to_community_string(credential: CredentialsComponent) -> str:
57+
if isinstance(credential, Username):
58+
return credential.username
59+
elif isinstance(credential, Password):
60+
return credential.password.get_secret_value()
61+
else:
62+
raise TypeError(f"Unexpected credential type: {type(credential)}")

monkey/common/credentials/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
from .username import Username
66
from .encoding import get_plaintext, SecretEncodingConfig
77

8-
from .credentials import Credentials, Identity, Secret
8+
from .credentials import Credentials, CredentialsComponent, Identity, Secret

monkey/common/credentials/credentials.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
from . import LMHash, NTHash, Password, SSHKeypair, Username
77
from .encoding import SecretEncodingConfig
88

9-
Secret = Union[Password, LMHash, NTHash, SSHKeypair]
109
Identity = Username
10+
Secret = Union[Password, LMHash, NTHash, SSHKeypair]
11+
CredentialsComponent = Union[Identity, Secret]
1112

1213

1314
class Credentials(InfectionMonkeyBaseModel):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from agent_plugins.exploiters.snmp.src.community_string_generator import generate_community_strings
2+
3+
from common.credentials import Credentials, LMHash, Password, SSHKeypair, Username
4+
5+
USERNAME_A_STR = "a"
6+
USERNAME_B_STR = "b"
7+
USERNAME_C_STR = "c"
8+
USERNAME_A = Username(username=USERNAME_A_STR)
9+
USERNAME_B = Username(username=USERNAME_B_STR)
10+
USERNAME_C = Username(username=USERNAME_C_STR)
11+
LMHASH_A_STR = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
12+
LMHASH_A = LMHash(lm_hash=LMHASH_A_STR)
13+
NTHASH_A_STR = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
14+
NTHASH_A = LMHash(lm_hash=NTHASH_A_STR)
15+
PASSWORD_A_STR = "a"
16+
PASSWORD_B_STR = "b"
17+
PASSWORD_C_STR = "c"
18+
PASSWORD_A = Password(password=PASSWORD_A_STR)
19+
PASSWORD_B = Password(password=PASSWORD_B_STR)
20+
PASSWORD_C = Password(password=PASSWORD_C_STR)
21+
SSHKEYPAIR_A = SSHKeypair(private_key="a", public_key="b")
22+
23+
24+
def test_ordering():
25+
# All identities should come before all secrets
26+
# All identities should be in the same order as the input credentials
27+
# All secrets should be in the same order as the input credentials
28+
credentials = [
29+
Credentials(identity=USERNAME_A, secret=PASSWORD_A),
30+
Credentials(identity=USERNAME_B, secret=None),
31+
Credentials(identity=None, secret=PASSWORD_B),
32+
Credentials(identity=USERNAME_C, secret=PASSWORD_C),
33+
]
34+
expected_result = [
35+
USERNAME_A_STR,
36+
USERNAME_B_STR,
37+
USERNAME_C_STR,
38+
PASSWORD_A_STR,
39+
PASSWORD_B_STR,
40+
PASSWORD_C_STR,
41+
]
42+
43+
assert list(generate_community_strings(credentials)) == expected_result
44+
45+
46+
def test_identities_are_filtered():
47+
# The only identity type allowed is Username
48+
credentials = [
49+
Credentials(identity=USERNAME_A, secret=PASSWORD_A),
50+
Credentials(identity=None, secret=PASSWORD_B),
51+
]
52+
expected_result = [USERNAME_A_STR, PASSWORD_A_STR, PASSWORD_B_STR]
53+
54+
result = list(generate_community_strings(credentials))
55+
56+
assert result == expected_result
57+
58+
59+
def test_secrets_are_filtered():
60+
# The only secret type allowed is Password
61+
credentials = [
62+
Credentials(identity=None, secret=PASSWORD_A),
63+
Credentials(identity=USERNAME_A, secret=LMHASH_A),
64+
Credentials(identity=USERNAME_B, secret=PASSWORD_B),
65+
Credentials(identity=None, secret=NTHASH_A),
66+
Credentials(identity=None, secret=SSHKEYPAIR_A),
67+
]
68+
expected_result = [USERNAME_A_STR, USERNAME_B_STR, PASSWORD_A_STR, PASSWORD_B_STR]
69+
70+
result = list(generate_community_strings(credentials))
71+
72+
assert result == expected_result
73+
74+
75+
def test_identities_are_unique():
76+
credentials = [
77+
Credentials(identity=USERNAME_A, secret=None),
78+
Credentials(identity=USERNAME_A, secret=PASSWORD_A),
79+
Credentials(identity=USERNAME_B, secret=None),
80+
Credentials(identity=USERNAME_B, secret=None),
81+
]
82+
expected_result = [USERNAME_A_STR, USERNAME_B_STR, PASSWORD_A_STR]
83+
84+
result = list(generate_community_strings(credentials))
85+
86+
assert result == expected_result
87+
88+
89+
def test_secrets_are_unique():
90+
credentials = [
91+
Credentials(identity=None, secret=PASSWORD_A),
92+
Credentials(identity=USERNAME_A, secret=PASSWORD_A),
93+
Credentials(identity=None, secret=PASSWORD_B),
94+
Credentials(identity=None, secret=PASSWORD_B),
95+
]
96+
expected_result = [USERNAME_A_STR, PASSWORD_A_STR, PASSWORD_B_STR]
97+
98+
result = list(generate_community_strings(credentials))
99+
100+
assert result == expected_result

0 commit comments

Comments
 (0)