This means we are not limited to pre-signing requests to GetCallerIdentity and can create requests to any action of the STS API. This is verified using the ServiceAccount GCP API which requires the iam.serviceAccounts.get permission in the project hosting the service account. Now that I can retrieve secrets from Vault, I can set my GIT_TOKEN and DATADOG_TOKEN.
This is a few years later, but this seems to work for me: bound_iam_principal_arn=arn:aws:iam::383372584635:role/auth-example-role20190626175122610800000005,arn:aws:iam::822220783772:role/AppRoleTestJLE. This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. However, this operational simplicity is only possible because of hidden complexity in the AWS iam auth method. The only requirement is that the attacker knows the name of an privileged AWS role in the target Vault server. You will need to use the Vault CLI.
How does the /v1/auth/aws/login API endpoint actually work and is there a way a unauthenticated attacker can impersonate a random AWS IAM role? At this point I was slowly getting frustrated and decided to take a look at Vaults response parsing code: is called on every response received from STS as long as the status code is 200. Note that all calls except the logins are done on the Vault server using a privileged token.
Enter "dev" as the policy name and paste the text from dev-policy.hcl into the policy field. for a simple proof-of-concept script that takes care of most of the details.
Finding a reflected parameter that is not constrained to alpha-numeric characters turns out to be tricky. With those tokens set, I can setup my .netrc file so that I can checkout my playbooks and roles from my private GitHub repositories and pass secrets to my Ansible. Third-party services cant easily verify pre-signed requests and AWS IAM doesnt offer any standard signing primitives that could be used to implement certificate based authentication or JWTs. token_meta_client_user_id AROAJTX274TFBJ6ZFA3YC, You can authenticate from a qa instance with: token_renewable true This part of my projected worked just fine, but it left me with another problem. At this point I was slowly getting frustrated and decided to take a look at Vaults response parsing code: func parseGetCallerIdentityResponse(response string) (GetCallerIdentityResponse, error) {, decoder := xml.NewDecoder(strings.NewReader(response)), XMLName xml.Name `xml:"GetCallerIdentityResponse"`, GetCallerIdentityResult []GetCallerIdentityResult `xml:"GetCallerIdentityResult"`, ResponseMetadata []ResponseMetadata `xml:"ResponseMetadata"`. Now simply use the token to sign-in to Vault: curl --request POST --data '{"role": "my-gce-role", "jwt" : "."}' http://vault:8200/v1/auth/gcp/login. As its name implies, GetCallerIdentity returns details about the IAM role or user whose credentials were used to call the API. token_meta_auth_type iam By enabling and configuring the aws auth method, you can create a mapping between certain IAM users or roles to Vault roles. We will illustrate both the ec2 and iam methods. The ec2 method uses the EC2 Metadata Service which enables each instance to fetch its unique AWS Instance Identity Document using its PKCS#7 signature. ranging from static credentials, LDAP or Radius, to full integration into third-party OpenID Connect (OIDC) providers or Cloud Identity Access Management (IAM) platforms. Serialize a request to it while including an Accept: application/json header and put an arbitrary GetCallerIdentityResponse XML blob into the reflected payload. "iam_http_request_method": "POST", "iam_request_body": "encoded-body", , "iam_request_headers" : "encoded-headers", "iam_request_url" : "encoded-url"}', {"request_id":"59b09a0b-f5d5-f4c4-8ed0-af86a2c1f5d4","lease_id":"","renewable":false,"lease_duration":0,"data":null,"wrap_info":null,"warnings":["TTL, of \"768h\" exceeded the effective max_ttl of \"500h\"; TTL value is capped.
However, tricks like embedding a fake userinfo (https://sts.amazonaws.com/:foo@example.com/test) and similar ideas do not work against the robust Go URL parser. using a service account private key under their control or with the projects.serviceAccounts.signJwt IAM API method. If the attacker impersonated. . auth method for deployments on Google Cloud. This token can now be used to fetch the database secret from Vault.
{'iss': 'https://oidc-test-wrbvvljkzwtfpiikylvpckxgafdkxfba.s3.amazonaws.com/'. 1. 2. This means that calling parseGetCallerIdentityResponse with a (JSON encoded) server response such as {abc : xzy
auth is built on top of an AWS API method called.
What went wrong here?
For Vault, this turns into a security issue due to a somewhat surprising feature of the Go XML decoder: The decoder silently ignores non XML content before and after the expected XML root. Next, we need to create a policy to limit what the instance has access to. For gce, the client is expected to run on an authorized GCE VM. You could use point a Vault client against your server after setting VAULT_ADDR and VAULT_TOKEN or just run the following commands on your Vault server after SSHing to it.
If the AWS ARN/UserID in our fake GetCallerIdentityResponse has privileges on the Vault server we get a valid session token back, which we can use to interact with the Vault server to fetch some secrets. Connect to your instance with SSH. It fetches a signed token by sending a request to the, of the GCP metadata server. While the OIDC provider setup adds some complexity, we end up with a nice authentication bypass for arbitrary AWS enabled roles. Both iam and gce are built on top of JWT. JWT has a number of design choices that make it very prone to implementation errors (see this blog post by securitum for an overview about typical issues), so I decided to spend a day on reviewing Vaults token processing. Here are the commands you should run on the Vault server for this example: You can authenticate from a dev instance to Vault with:
Repeat the above steps for the path qa/secrets. Knowing the key id and subject of the token, Vault fetches the public key used for signing using the service account GCP API: "Unable to get Google-wide OAuth2 Key, trying service-account public key", "unable to get public key %q for JWT subject %q: {{err}}", If verification succeeds, Vault fills out the, struct that is later used to grant or deny access. For #2, what Jason said is true. This means that calling, with a (JSON encoded) server response such as, {abc : xzy
token_accessor 30b2fd84-73c1-6eef-8f96-bc99791cc2ae With vault configured to allow ec2 instances to authenticate and pull secrets, we now need to write a user_data script to take advantage of the access. the disallow_reauthentication is set to true so that the instance can only authenticate once. For valid tokens, this claim contains the email address of the signing service account. 'sub': '
By enabling and configuring the. can now be used to grant the dbclient role access to the database secret. We want to create some secrets for members of the dev and qa teams. If the attacker impersonated, a GCE instance with the right attributes (project, label, zones..) everything works out well and. can only be used to authenticate virtual machines running on Google Compute Engine. This brings us really close to our goal of spoofing an arbitrary caller identity: We just need to find a STS action that reflects attacker controlled text as part of its API response.
For infrastructure that runs on a supported cloud provider, using the provider's IAM platform for authentication is a logical choice. However, the complete lack of validation for URL path, query, POST body and HTTP headers still looks like a promising attack surface. Vault
token_meta_inferred_entity_type n/a We can now use our OIDP to sign a JWT that contains an arbitrary GetCallerIdentityResponse as part of its subject claim. Still, an attacker can just create their own OIDC Identity Provider (IdP), register it on an AWS account they own and sign arbitrary tokens with their own keys. In the end, Hashicorp fixed the vulnerability by enforcing an allowlist of HTTP headers, restricting requests to the GetCallerIdentity action and stronger validation of the STS response, which is hopefully enough to protect against unexpected changes to the STS implementation or HTTP parser differences between STS and Golang. This will work for GCE metadata tokens, but not for tokens signed by a service account: func (b *GcpAuthBackend) getSigningKey() (interface{}, error) {, b.Logger().Debug("Getting signing Key for JWT"), return nil, errors.New("expected token to have exactly one header"), b.Logger().Debug("kid found for JWT", "kid", kid), k, gErr := gcputil.OAuth2RSAPublicKey(ctx, kid), b.Logger().Debug("Found Google OAuth2 provider key", "kid", kid). The function extracts HTTP method, URL, body and headers out of the supplied request body which is stored in data. curl -X POST "https://vault-server/v1/auth/aws/login" -d '{"role":"dev-role-iam".
"SubjectFromWebIdentityToken":"
If authentication succeeds, Vault returns a short-lived API token for the dbclient role back to the lambda function. I have a bunch of lambdas in different accounts that would like to auth against this role. A decoded example token could look like this: match the details specified in the step 2. contains our spoofed response, identifying us as the AWS IAM account, 'https://oidc-test-wrbvvljkzwtfpiikylvpckxgafdkxfba.s3.amazonaws.com/', '