This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. You are now authenticated. Still, it has some interesting advantages. This could be a problem if a load balancer in front of the STS API makes routing decisions based on the Host header, but blind testing against the STS host did not lead to any success. The Vault server sends the pre-signed requests to the STS host and extracts the AWS IAM information out of the result. "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. out of the token header and tries to find a google-wide oAuth key with the same identifier. Both iam and gce are built on top of JWT. For infrastructure that runs on a supported cloud provider, using the provider's IAM platform for authentication is a logical choice. parseGetCallerIdentityResponse is called on every response received from STS as long as the status code is 200. vault login -method=aws role=qa-role, token 395db08a-6b47-e6cf-d0b4-2b1e4daf4fd5 As the attacker can just use a service account in their own project, it is straightforward to just grant this permission to the GCP identity Vault is running under or even allUsers. can now be used to grant the dbclient role access to the database secret. This mechanism makes it possible to pre-sign a request and forward it to another party to allow a limited form of impersonation. In addition to the normal JWT claims (sub, aud, iat, exp), the tokens returned from the metadata server also contains a special compute_engine claim that lists details about the instance, which are processed as part of the auth process: "google":{"compute_engine":{"instance_creation_timestamp":1594641932,"instance_id":"671398237781058X, XXX","instance_name":"vault","project_id":"fwilhelm-testing-XXXX","project_number":950612XXXX,"zone":"europe-west3-c"}}. A vault policy can now be used to grant the dbclient role access to the database secret. If verification succeeds, Vault fills out the loginInfo struct that is later used to grant or deny access. For example, AWS might decide to put STS behind a load balancing frontend, which uses the Host header for routing decisions. If you're using Lambdas, then inferencing isn't something you should use. This could be a problem if a load balancer in front of the STS API makes routing decisions based on the Host header, but blind testing against the STS host did not lead to any success. Vaults aws auth method supports two different authentication mechanisms internally: iam and ec2. We can test if everything is setup correctly by sending a direct request to the STS AssumeRoleWithWebIdentity action using the (signed) token from step 3 and the RoleArn used in step 2: 'https://sts.amazonaws.com/?DurationSeconds=900&Action=AssumeRoleWithWebIdentity&Version=2011-06-15&RoleSessionName=web-identity-federation&RoleArn=arn:aws:iam::XZY::YOUR-OIDC-ROLE&WebIdentityToken=YOURTOKEN'. For the aws auth backend to work, it needs to have a couple of additional privileges. Connect to your instance with SSH. bound_iam_principal_arn from the CLI tools, but it just wants a raw string. token_meta_canonical_arn arn:aws:iam::362381645759:role/chef-dev For infrastructure that runs on a supported cloud provider, using the provider's IAM platform for authentication is a logical choice. The next section describes how GCP authentication for Vault is implemented and how a simple logic flaw can lead to an authentication bypass in many configurations. 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_accessor ca210b40-745e-9804-c25f-f21392f2e3dc With the policy created, we can now create the role. The server-side part of this flow is implemented in pathLoginUpdate in builtin/credential/aws/path_login.go: func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {, method := data.Get("iam_http_request_method"). 1. A strong developer might be able to reason about all security boundaries, requirements and pitfalls of their own software, but it becomes very difficult once a complex external service comes into play. A decoded example token could look like this: iss, azp and aud match the details specified in the step 2. sub contains our spoofed response, identifying us as the AWS IAM account arn:aws:iam::superprivileged-aws-account. (string), // In the future, might consider supporting GET, rawUrlB64 := data.Get("iam_request_url"). the second statement allows vault to use sts to do the same validation in other accounts. (string)), return nil, errwrap.Wrapf("unable to parse signed JWT: {{err}}", err), key, err := b.getSigningKey(ctx, jwtVal, signedJwt. Instead of attaching some form of authentication token or credential to API requests, AWS requires clients to. token_renewable true As mentioned above, all of this code is shared between the iam and gce auth methods. "arn:aws:iam::111111111111:role/vaultawsauth", "arn:aws:iam::222222222222:role/vaultawsauth", path "acg/globals/data/*" { token_meta_account_id 362381645759 You do NOT need to run "vault login" Announcing the Fuzzilli Research Grant Program. See here for a simple proof-of-concept script that takes care of most of the details. An end-to-end attack against a vulnerable configuration will look like this: Create a service account in a GCP project you control and generate a private key using gcloud: gcloud iam service-accounts keys create key.json --iam-account sa-name@project-id.iam.gserviceaccount.com. 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. Both vulnerabilities (, were addressed by HashiCorp and are fixed in Vault versions 1.2.5, 1.3.8, 1.4.4 and 1.5.1 released in, Interfacing with Vault requires authentication and Vault supports role-based access control to govern access to stored secrets. Vault Similar to its AWS counterpart, the auth method supports two different authentication mechanisms: mechanism supports arbitrary service accounts and can be used from services such as App Engine or Cloud Functions. This part of my projected worked just fine, but it left me with another problem. These issues can lead to an authentication bypass in configurations that use the, auth methods, and demonstrate the type of issues you can find in modern cloud-native software. Here are the commands you should run on the Vault server for this example: You can authenticate from a dev instance to Vault with: We will use the iam method in the next section. The root cause seems to be the merging of two separate authentication flows into a single code path in the parseAndValidateJwt function, which makes it difficult to reason about all security requirements when writing or reviewing the code. , you can create a mapping between certain IAM users or roles to Vault roles. After some trial and error, I decided to target the. auth is built on top of an AWS API method called. Depending on the database backend, this secret could be a static user-password combination, a short lived client certificate or even a, However, this operational simplicity is only possible because of hidden complexity in the AWS iam auth method. Finding a reflected parameter that is not constrained to alpha-numeric characters turns out to be tricky. One way to achieve this is to manipulate the Vault server into sending a request to a host we control, bypassing the hardcoded endpoint host. Sharing the same master key creates an issue as well, since somebody leaving the team requires a key rotation across all instances. token_meta_inferred_aws_region n/a 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. claim that lists details about the instance, which are processed as part of the auth process: JWT has a number of design choices that make it very prone to implementation errors (see. The final step is to convert this request into the form expected by Vault (e.g base64 encoding all required headers, the url and an empty post body) and to send it to the target Vault server as a login request on /v1/auth/aws/login. enabling the backend is straightforward. Finally, AuthorizeGCE is called to grant or deny access. I'm able to read and write to the vault cluster using the same "Vault Role + Policy", https://vault.rocks/v1/auth/aws/role/dev-role, https://www.hashicorp.com/community-guidelines.html, https://github.com/hashicorp/vault/issues, https://groups.google.com/d/msgid/vault-tool/20171216162828.GA31871%40toger.us. token_policies [default dev] What went wrong here? {'iss': 'https://oidc-test-wrbvvljkzwtfpiikylvpckxgafdkxfba.s3.amazonaws.com/'. Packer. Assign the vault-aws-authentication policy shown above to both roles. response element. We will illustrate both the ec2 and iam methods. To understand how Vault uses this method to authenticate clients we need to understand how AWS APIs perform authentication: Instead of attaching some form of authentication token or credential to API requests, AWS requires clients to calculate an HMAC signature for the (canonicalized) request using the caller's secret access key and attach this signature to the request. While the OIDC provider setup adds some complexity, we end up with a nice authentication bypass for arbitrary AWS enabled roles. "metadata":{"account_id":"242434931706","auth_type":"iam","role_id":"47faaf36-c8ab-c589-396c-2643c26e7b30"}, "lease_duration":1800000,"renewable":true,"entity_id":"447e1efe-0fd4-aa10-3a54-52405c0c69ab","token_type":"service","orphan":true}}. When the lambda function executes, it authenticates to Vault by sending a request to the, API endpoint. If this approach fails, the Vault server extracts the Subject (sub) claim from the supplied token. Vault is a widely used tool for securely storing, generating and accessing secrets such as API keys, passwords or certificates. The function uses the Golang standard XML library to decode an XML response into a GetCallerIdentityResponse structure and returns an error if decoding fails. News and updates from the Project Zero team at Google. Both vulnerabilities (CVE-2020-16250/16251) were addressed by HashiCorp and are fixed in Vault versions 1.2.5, 1.3.8, 1.4.4 and 1.5.1 released in August. And install the vault binary on your EC2 instance as described above. If you are not sure how, select your instance in the AWS Console and click the Connect button. It can be used as a shared password manager for human users, but its feature set is optimized for API based access by other services. accordingly"],"auth":{"client_token":"s.Kx3bUNw6wEc5bbkrKBiGW6WL","accessor":"TBRh0hvfd4FkYEAyFrUE3i2P","policies":["default","dev","prod"],"token_policies":["default","dev","prod"]. We are interested in the iam mechanism, which is the recommended variant and also used in our previous Lambda example. token_renewable true Interfacing with Vault requires authentication and Vault supports role-based access control to govern access to stored secrets. Sending a request to this action with a valid signed JWT will return the. This started out as pretty straightforward, adding the script I use to configure AMIs to my instance user_data instead. 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? We can now use our OIDP to sign a JWT that contains an arbitrary GetCallerIdentityResponse as part of its subject claim. Our goal is to trick Vaults submitCallerIdentityRequest function into returning an attacker controlled caller identity. It first parses the token without verifying the signature and passes the decoded token into the, "unable to get public key for signed JWT: %v". ) Still, it has some interesting advantages. Lets take a look. iam auth is built on top of an AWS API method called GetCallerIdentity, part of the AWS Security Token Service (STS). 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. While this will help me solve some problems with the few appliances that I run, I can also see this being very helpful for using with autoscaling groups as well. , part of the AWS Security Token Service (STS). This boils down to generating a RSA key pair, creating an OIDC discovery.json and key.json document and hosting the json files on a web server (see here, for an example setup using S3). Just find the instance, select it, select the Tags tab in the bottom portion of the window, click the "Add/Edit Tags" button, click the "Create Tag" button, add the correct tag, and then click the Save button. Cannot retrieve contributors at this time. This can be done with the Vault UI or CLI. 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. A vault client that wants to, authenticate, creates a signed token to prove its identity and sends it to the vault, server to get a session token back. You will need to use the Vault CLI. It then calls submitCallerIdentity to forward the request to the STS server and to fetch and parse the result in parseGetCallerIdentityResponse: func submitCallerIdentityRequest(ctx context.Context, maxRetries int, method, endpoint string, parsedUrl *url.URL, body string, headers http.Header) (*GetCallerIdentityResult, error) {, request := buildHttpRequest(method, endpoint, parsedUrl, body, headers), retryableReq, err := retryablehttp.FromRequest(request), response, err := retryingClient.Do(retryableReq), responseBody, err := ioutil.ReadAll(response.Body), callerIdentityResponse, err := parseGetCallerIdentityResponse(string(responseBody)), return nil, fmt.Errorf("error parsing STS response"), return &callerIdentityResponse.GetCallerIdentityResult[0], nil, func buildHttpRequest(method, endpoint string, parsedUrl *url.URL, body string, headers http.Header) *http.Request {, targetUrl := fmt.Sprintf("%s/%s", endpoint, parsedUrl.RequestURI()), request, err := http.NewRequest(method, targetUrl, strings.NewReader(body)). The server-side part of this flow is implemented in, The function extracts HTTP method, URL, body and headers out of the supplied request body which is stored in, to forward the request to the STS server and to fetch and parse the result in, object based on the user supplied parameters, but uses the hardcoded constant, function into returning an attacker controlled caller identity. Create a minimal OIDC IdP. The developers and QA engineers will need Vault tokens or usernames in order to authenticate to Vault and create, write, and read their secrets. Imagine that you have an AWS Lambda function and want to give it access to a database password stored in Vault. When the lambda function executes, it authenticates to Vault by sending a request to the /v1/auth/aws/login API endpoint. ranging from static credentials, LDAP or Radius, to full integration into third-party OpenID Connect (OIDC) providers or Cloud Identity Access Management (IAM) platforms. claim from the supplied token. Our second example restricts authentication to IAM roles in a particular account. When doing this, set the tag's key to "dev_role" or "qa_role" and set the tag's value to the value of tag_value that was returned when you created the dev_role or qa_role role tag in the previous section, depending on whether you you want the EC2 instance to have the dev or qa roles.. STS supports 8 different actions, but none gives us the ability to completely control the response. After finding this issue in the AWS authentication module, I decided to review its GCP equivalent. "Audience":"abcdef","Credentials":{},"PackedPolicySize":null,"Provider":"arn:aws:iam::242434931706:oidc-provider/oidc-test-wrbvvljkzwtfpiikylvpckxgafdkxfba.s3.amazonaws.com/". . Even though Vault will always create a HTTPS request pointing at the hardcoded endpoint, the attacker has full control over the Host http header (request.Host = parsedUrl.Host). Ive written before on Using Vault for SSH Connections and decided to implement that at my current job. Sign a JWT with a fake compute_engine claim describing an existing and privileged VM. Ill go into the exact layout of this request later in the post, but for now just assume that the request allows Vault to verify the AWS IAM role of the caller. Error writing data to auth/aws/role/dev-role: Error making API request. There is an easy to miss problem with this code: Vault never enforces or verifies that the STS response is actually XML encoded. The next section describes how we can turn this gap into a full authentication bypass. token_duration 768h buildHttpRequest creates a http.Request object based on the user supplied parameters, but uses the hardcoded constant https://sts.amazonaws.com to build the target URL. in addition to limiting the instances to only have access to secret paths that it needs, i also only allow read access to the policies. You could use a different domain for the iam_server_id_header_value and header_value, but they should match each other. can only be used to authenticate virtual machines running on Google Compute Engine. As discussed above, the Go XML decoder will skip all of the content before and after the GetCallerIdentityResponse object leading Vault to consider this a valid STS CallerIdentity response. Serialize a request to it while including an. This blog post describes two authentication vulnerabilities in HashiCorp Vault, a cloud-native software for secret management. For authentication, it supports pluggable auth methods ranging from static credentials, LDAP or Radius, to full integration into third-party OpenID Connect (OIDC) providers or Cloud Identity Access Management (IAM) platforms. Modern cloud IAM solutions are powerful and often more secure than comparable on-premise solutions, but they come with their own security pitfalls and a high implementation complexity. For gce, the client is expected to run on an authorized GCE VM. You do not have permission to delete messages in this group, For #2 at least, this bound_iam_principal_arn field accepts only. Copy the dev-policy.hcl and qa-policy.hcl files to your Vault server. Without this restriction, we could simply trigger a request to a server under our control and return a fake caller identity. Take AWS as an example: Almost every workload you can run in AWS executes in the context of a specific AWS IAM user. ). token_policies [default qa] You should run the command shown from a directory containing your SSH key or provide a path to the key. In this section, we use the ec2 method. 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. AssumeRoleWithWebIdentity is used to translate JSON Web Tokens (JWT) signed by an OpenID Connect (OIDC) provider into AWS IAM identities. We now want to configure the AWS auth method on the Vault server. Let's put all of this together and walk through the full attack step-by-step.