From a2e6b0bfbd5dbc3062daf0e64141a08eb0f94fd3 Mon Sep 17 00:00:00 2001
From: Mike Salvatore <mike.s.salvatore@gmail.com>
Date: Thu, 2 Sep 2021 12:29:49 -0400
Subject: [PATCH] Agent: Add LM and NT hashes to PowerShell Credentials

Adds two list parameters to get_credentials() that contain LM and NT
hashes respectively. Adds a "secret_type" field to Credentials so that
the user of the Credentials object can distinguish between using cached
credentials (on windows), passwords, and NT or LM hashes.
---
 monkey/infection_monkey/exploit/powershell.py | 13 ++-
 .../exploit/powershell_utils/credentials.py   | 56 +++++++++++--
 .../powershell_utils/test_auth_options.py     |  8 +-
 .../powershell_utils/test_credentials.py      | 81 +++++++++++++++----
 4 files changed, 132 insertions(+), 26 deletions(-)

diff --git a/monkey/infection_monkey/exploit/powershell.py b/monkey/infection_monkey/exploit/powershell.py
index 6bfabb1e2..c20580989 100644
--- a/monkey/infection_monkey/exploit/powershell.py
+++ b/monkey/infection_monkey/exploit/powershell.py
@@ -13,7 +13,11 @@ from infection_monkey.exploit.powershell_utils.auth_options import (
     AuthOptions,
     get_auth_options,
 )
-from infection_monkey.exploit.powershell_utils.credentials import Credentials, get_credentials
+from infection_monkey.exploit.powershell_utils.credentials import (
+    Credentials,
+    SecretType,
+    get_credentials,
+)
 from infection_monkey.exploit.powershell_utils.powershell_client import (
     AuthenticationError,
     IPowerShellClient,
@@ -49,7 +53,11 @@ class PowerShellExploiter(HostExploiter):
             return False
 
         credentials = get_credentials(
-            self._config.exploit_user_list, self._config.exploit_password_list, is_windows_os()
+            self._config.exploit_user_list,
+            self._config.exploit_password_list,
+            [],
+            [],
+            is_windows_os(),
         )
         auth_options = get_auth_options(credentials, is_https)
 
@@ -89,6 +97,7 @@ class PowerShellExploiter(HostExploiter):
         credentials = Credentials(
             username="dummy_username",
             secret="dummy_password",
+            secret_type=SecretType.PASSWORD,
         )
 
         auth_options = AuthOptions(
diff --git a/monkey/infection_monkey/exploit/powershell_utils/credentials.py b/monkey/infection_monkey/exploit/powershell_utils/credentials.py
index ab3c7f542..982f9da29 100644
--- a/monkey/infection_monkey/exploit/powershell_utils/credentials.py
+++ b/monkey/infection_monkey/exploit/powershell_utils/credentials.py
@@ -1,21 +1,36 @@
 from dataclasses import dataclass
+from enum import Enum
 from itertools import product
 from typing import List, Union
 
 
+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], is_windows: bool
+    usernames: List[str],
+    passwords: List[str],
+    lm_hashes: List[str],
+    nt_hashes: List[str],
+    is_windows: bool,
 ) -> List[Credentials]:
     credentials = []
     credentials.extend(_get_empty_credentials(is_windows))
     credentials.extend(_get_username_only_credentials(usernames, is_windows))
     credentials.extend(_get_username_password_credentials(usernames, passwords))
+    credentials.extend(_get_username_lm_hash_credentials(usernames, lm_hashes))
+    credentials.extend(_get_username_nt_hash_credentials(usernames, nt_hashes))
 
     return credentials
 
@@ -24,7 +39,7 @@ def get_credentials(
 # will be used to attempt to log into the victim.
 def _get_empty_credentials(is_windows: bool) -> List[Credentials]:
     if is_windows:
-        return [Credentials(username=None, secret=None)]
+        return [Credentials(username=None, secret=None, secret_type=SecretType.CACHED)]
 
     return []
 
@@ -32,10 +47,18 @@ def _get_empty_credentials(is_windows: bool) -> List[Credentials]:
 # On Windows systems, when password == None, the current user's password will bu 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="") for username in usernames]
+    credentials = [
+        Credentials(username=username, secret="", secret_type=SecretType.PASSWORD)
+        for username in usernames
+    ]
 
     if is_windows:
-        credentials.extend([Credentials(username=username, secret=None) for username in usernames])
+        credentials.extend(
+            [
+                Credentials(username=username, secret=None, secret_type=SecretType.CACHED)
+                for username in usernames
+            ]
+        )
 
     return credentials
 
@@ -43,6 +66,27 @@ def _get_username_only_credentials(usernames: List[str], is_windows: bool) -> Li
 def _get_username_password_credentials(
     usernames: List[str], passwords: List[str]
 ) -> List[Credentials]:
-    username_password_pairs = product(usernames, passwords)
+    return _get_username_secret_credentials(usernames, passwords, SecretType.PASSWORD)
 
-    return [Credentials(credentials[0], credentials[1]) for credentials in username_password_pairs]
+
+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
+    ]
diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_auth_options.py b/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_auth_options.py
index 0a917adac..d19fffbd0 100644
--- a/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_auth_options.py
+++ b/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_auth_options.py
@@ -6,12 +6,12 @@ from infection_monkey.exploit.powershell_utils.auth_options import (
     ENCRYPTION_NEVER,
     get_auth_options,
 )
-from infection_monkey.exploit.powershell_utils.credentials import Credentials
+from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType
 
 CREDENTIALS = [
-    Credentials("user1", "password1"),
-    Credentials("user2", ""),
-    Credentials("user3", None),
+    Credentials("user1", "password1", SecretType.PASSWORD),
+    Credentials("user2", "", SecretType.PASSWORD),
+    Credentials("user3", None, SecretType.CACHED),
 ]
 
 
diff --git a/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_credentials.py b/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_credentials.py
index 64a13a5e5..0954d9dc8 100644
--- a/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_credentials.py
+++ b/monkey/tests/unit_tests/infection_monkey/exploit/powershell_utils/test_credentials.py
@@ -1,44 +1,97 @@
-from infection_monkey.exploit.powershell_utils.credentials import Credentials, get_credentials
+from infection_monkey.exploit.powershell_utils.credentials import (
+    Credentials,
+    SecretType,
+    get_credentials,
+)
 
 TEST_USERNAMES = ["user1", "user2"]
 TEST_PASSWORDS = ["p1", "p2"]
+TEST_LM_HASHES = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"]
+TEST_NT_HASHES = ["cccccccccccccccccccccccccccccccc", "dddddddddddddddddddddddddddddddd"]
 
 
 def test_get_credentials__empty_windows_true():
-    credentials = get_credentials([], [], True)
+    credentials = get_credentials([], [], [], [], True)
 
     assert len(credentials) == 1
-    assert credentials[0] == Credentials(username=None, secret=None)
+    assert credentials[0] == Credentials(username=None, secret=None, secret_type=SecretType.CACHED)
 
 
 def test_get_credentials__empty_windows_false():
-    credentials = get_credentials([], [], False)
+    credentials = get_credentials([], [], [], [], False)
 
     assert len(credentials) == 0
 
 
 def test_get_credentials__username_only_windows_true():
-    credentials = get_credentials(TEST_USERNAMES, [], True)
+    credentials = get_credentials(TEST_USERNAMES, [], [], [], True)
 
     assert len(credentials) == 5
-    assert Credentials(username=TEST_USERNAMES[0], secret="") in credentials
-    assert Credentials(username=TEST_USERNAMES[1], secret="") in credentials
-    assert Credentials(username=TEST_USERNAMES[0], secret=None) in credentials
-    assert Credentials(username=TEST_USERNAMES[1], secret=None) in credentials
+    assert (
+        Credentials(username=TEST_USERNAMES[0], secret="", secret_type=SecretType.PASSWORD)
+        in credentials
+    )
+    assert (
+        Credentials(username=TEST_USERNAMES[1], secret="", secret_type=SecretType.PASSWORD)
+        in credentials
+    )
+    assert (
+        Credentials(username=TEST_USERNAMES[0], secret=None, secret_type=SecretType.CACHED)
+        in credentials
+    )
+    assert (
+        Credentials(username=TEST_USERNAMES[1], secret=None, secret_type=SecretType.CACHED)
+        in credentials
+    )
 
 
 def test_get_credentials__username_only_windows_false():
-    credentials = get_credentials(TEST_USERNAMES, [], False)
+    credentials = get_credentials(TEST_USERNAMES, [], [], [], False)
 
     assert len(credentials) == 2
-    assert Credentials(username=TEST_USERNAMES[0], secret="") in credentials
-    assert Credentials(username=TEST_USERNAMES[1], secret="") in credentials
+    assert (
+        Credentials(username=TEST_USERNAMES[0], secret="", secret_type=SecretType.PASSWORD)
+        in credentials
+    )
+    assert (
+        Credentials(username=TEST_USERNAMES[1], secret="", secret_type=SecretType.PASSWORD)
+        in credentials
+    )
 
 
 def test_get_credentials__username_password_windows_true():
-    credentials = get_credentials(TEST_USERNAMES, TEST_PASSWORDS, True)
+    credentials = get_credentials(TEST_USERNAMES, TEST_PASSWORDS, [], [], True)
 
     assert len(credentials) == 9
     for user in TEST_USERNAMES:
         for password in TEST_PASSWORDS:
-            assert Credentials(username=user, secret=password) in credentials
+            assert (
+                Credentials(username=user, secret=password, secret_type=SecretType.PASSWORD)
+                in credentials
+            )
+
+
+def test_get_credentials__username_lm_hash_windows_false():
+    credentials = get_credentials(TEST_USERNAMES, TEST_PASSWORDS, TEST_LM_HASHES, [], False)
+
+    assert len(credentials) == 10
+    for user in TEST_USERNAMES:
+        for lm_hash in TEST_LM_HASHES:
+            assert (
+                Credentials(username=user, secret=lm_hash, secret_type=SecretType.LM_HASH)
+                in credentials
+            )
+
+
+def test_get_credentials__username_nt_hash_windows_false():
+    credentials = get_credentials(
+        TEST_USERNAMES, TEST_PASSWORDS, TEST_LM_HASHES, TEST_NT_HASHES, False
+    )
+
+    assert len(credentials) == 14
+    for user in TEST_USERNAMES:
+        for nt_hash in TEST_NT_HASHES:
+            assert (
+                Credentials(username=user, secret=nt_hash, secret_type=SecretType.NT_HASH)
+                in credentials
+            )