From 2b096080e174bcdb1c04b045471beb981746da47 Mon Sep 17 00:00:00 2001 From: Tatsuya Murase Date: Fri, 2 Oct 2020 15:42:10 -0700 Subject: [PATCH] Fix API issue, add auto org mode --- bfs.py | 96 +++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 31 deletions(-) diff --git a/bfs.py b/bfs.py index 34b1afa..7e51008 100644 --- a/bfs.py +++ b/bfs.py @@ -3,6 +3,8 @@ import sys import json import subprocess +from time import sleep +import shlex dangerous_permissions = [ "dataproc.clusters.create", @@ -24,6 +26,7 @@ "resourcemanager.folders.setIamPolicy", "resourcemanager.projects.setIamPolicy", ] + def bfs_search(org, base_id): token = subprocess.check_output("gcloud auth print-access-token".split(" ")).decode("utf-8") token = token.strip() @@ -53,33 +56,57 @@ def bfs_search(org, base_id): } } } +# currently getting this error response from GCP +# we need to split up dangerous_permissions and run multiple requests +# {"error": {"code": 400, "message": "Some specified value(s) are invalid.", "status": "INVALID_ARGUMENT", "details": [{"@type": "type.googleapis.com/google.rpc.BadRequest", "fieldViolations": [{"field": "access_selector", "description": "In one request, the total number of roles and permissions should be equal or less than 10. If you have more than that, please split your request into multiple ones."}]}]}} + perms_added = 0 for dangerous_permission in dangerous_permissions: - JSON_REQUEST["analysisQuery"]["accessSelector"]["permissions"].append(dangerous_permission) - res = requests.post("https://cloudasset.googleapis.com/v1p4beta1/organizations/{}:analyzeIamPolicy".format(org), headers=headers, json=JSON_REQUEST) - results = res.json() - if "analysisResults" in results["mainAnalysis"]: - for result in results["mainAnalysis"]["analysisResults"]: - recipient = result["attachedResourceFullName"] - target = recipient.split("/")[-1] - if recipient.startswith("//cloudresourcemanager.googleapis.com/projects"): - if target not in visited_projects: - visited_projects.append(target) - command = "gcloud iam service-accounts list --format json --project {}".format(target) - project_service_accounts = json.loads(subprocess.check_output(command.split(" ")).decode("utf-8")) - for project_service_account in project_service_accounts: - sa_email = project_service_account["email"] - if sa_email not in visited and sa_email not in queue: - queue.append(sa_email) - info[sa_email] = target - print("Adding {} by means of {} with {}".format(sa_email, service_account, result["iamBinding"]["role"])) + if len(JSON_REQUEST["analysisQuery"]["accessSelector"]["permissions"]) < 10 and perms_added < len(dangerous_permissions): + JSON_REQUEST["analysisQuery"]["accessSelector"]["permissions"].append(dangerous_permission) + perms_added += 1 + else: + sleep(0.15) + res = requests.post("https://cloudasset.googleapis.com/v1p4beta1/organizations/{}:analyzeIamPolicy".format(org), headers=headers, json=JSON_REQUEST) + res.raise_for_status() + results = res.json() + json_formatted_str = json.dumps(results) + JSON_REQUEST["analysisQuery"]["accessSelector"]["permissions"].clear() + + if "analysisResults" in results["mainAnalysis"]: + for result in results["mainAnalysis"]["analysisResults"]: + recipient = result["attachedResourceFullName"] + target = recipient.split("/")[-1] + if recipient.startswith("//cloudresourcemanager.googleapis.com/projects"): + if target not in visited_projects: + visited_projects.append(target) + command = "gcloud iam service-accounts list --format json --project {}".format(target) + sleep(0.05) + project_service_accounts = json.loads(subprocess.check_output(command.split(" ")).decode("utf-8")) + for project_service_account in project_service_accounts: + sa_email = project_service_account["email"] + if sa_email not in visited and sa_email not in queue: + queue.append(sa_email) + info[sa_email] = target + print("Adding {} by means of {} with {}".format(sa_email, service_account, result["iamBinding"]["role"])) - elif recipient.startswith("//iam.googleapis.com/projects/"): - if target not in visited and target not in queue: - queue.append(target) - info[target] = recipient.split("/")[-3] - print("Adding {} by means of {} with {}".format(target, service_account, result["iamBinding"]["role"])) + elif recipient.startswith("//iam.googleapis.com/projects/"): + if target not in visited and target not in queue: + queue.append(target) + info[target] = recipient.split("/")[-3] + print("Adding {} by means of {} with {}".format(target, service_account, result["iamBinding"]["role"])) + return visited, info +def visit(org_id, base_id): + visited, info = bfs_search(org_id, base_id) + + print("\n\n~~~~~~~{} can move laterally to the following identities ~~~~~~~~~~~".format(base_id)) + for service_account in visited: + if service_account != base_id: + print("{} from project {}".format(service_account, info[service_account])) + print("\n\n") + return + if __name__ == "__main__": parser = argparse.ArgumentParser(description='bfs tool for GCP service account exploitation.') @@ -87,20 +114,27 @@ def bfs_search(org, base_id): help='The starting point for your bfs search') parser.add_argument('--org_id', dest="org", help='Your org ID') + parser.add_argument('--auto', action="store_true", + help='Automatically step through every project default compute service account in the organization') args = parser.parse_args() - if not args.source: + if not args.source and not args.auto: print("Need the starting point --source ") sys.exit() elif not args.org: print("Need the org ID --org_id ") sys.exit() - base_id = args.source org_id = args.org - visited, info = bfs_search(org_id, base_id) - - print("\n\n~~~~~~~{} can move laterally to the following identities ~~~~~~~~~~~".format(base_id)) - for service_account in visited: - if service_account != base_id: - print("{} from project {}".format(service_account, info[service_account])) + if args.source: + base_id = args.source + visit(org_id, base_id) + elif args.auto: + command = 'gcloud projects list --format "value(projectNumber)"' + process = subprocess.run(shlex.split(command), capture_output=True, text=True) + process.check_returncode() + project_list = process.stdout.splitlines() + for number in project_list: + base_id = number + "-compute@developer.gserviceaccount.com" + visit(org_id, base_id) + sleep(20)