Unveiling the Risks of sts:GetFederationToken, You Probably Don't Need It
2024 May 02

Let’s talk about sts:GetFederationToken and why we should disable it within our AWS Accounts. This call allows the creation of temporary access credentials that can be associated to any user identity. These credentials are difficult to revoke from the console, and cannot be revoked using the standard deny all credentials created before X time policy. It is much better to use standard built in identity providers like the OpenID Connect for CICD Operations or IAM Identity Center for federated user access. We’ll walk through how this call works, what it was originally intended for, and some threat detection.

The AWS docs state the following about this API call:

“Returns a set of temporary security credentials (consisting of an access key ID, a secret access key, and a security token) for a user. A typical use is in a proxy application that gets temporary security credentials on behalf of distributed applications inside a corporate network.”

In the past, this call was handy, but today we have much better solutions for federation that come packaged. Let’s consider a malicious use case as follows:

To illustrate an example of this attack let’s consider the following scenario:

  1. Alice is an administrator at the company who wants to blame Bob as a internal threat.
  2. She uses sts:GetFederationToken to pretend to be his federated user
  3. She performs some action that causes major downtime that due to a sloppy investigation Bob is fingered as the target.

sts:GetFederationToken

This is but one scenario. Another scenario could be a threat actor trying to hide their actions as legitimate traffic within the environment.

We are going to be running as my administrative user here, with the standard administrator policy. There’s no permission boundary or other trickery at work here to prevent any high privilege tokens from generated.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        }
    ]
}

Coding Demo

Let’s get started by creating a boto3 sts client.

import boto3

sts_client = boto3.client('sts')

We have our STS client. Next, we need to call the get_federation_token function and get a set of credentials. You can pass whatever kind of policy you want into here. It could be a list of managed policies or you can include an in-line policy. It’s whatever you want here.

my_creds = sts_client.get_federation_token(
    Name="bob.jones@example.com",
    PolicyArns=[{
        'arn': 'arn:aws:iam::aws:policy/AdministratorAccess'
    }]
)

# Just show that the keys exist in the returned object for later use'
my_creds['Credentials'].keys()
dict_keys(['AccessKeyId', 'SecretAccessKey', 'SessionToken', 'Expiration'])

Next let’s create a session to use with these credentials:

fed_session = boto3.session.Session(
    aws_access_key_id=my_creds['Credentials']['AccessKeyId'],
    aws_secret_access_key=my_creds['Credentials']['SecretAccessKey'],
    aws_session_token=my_creds['Credentials']['SessionToken']
)
fed_sts_client = fed_session.client('sts')
fed_sts_client.get_caller_identity()
{'UserId': 'XXX:bob.jones@example.com',
 'Account': 'XXX',
 'Arn': 'arn:aws:sts::XXX:federated-user/bob.jones@example.com',
 'ResponseMetadata': {'RequestId': 'af1ba5ad-b94a-4fab-ba04-bf9c1164dbf5',
  'HTTPStatusCode': 200,
  'HTTPHeaders': {'x-amzn-requestid': 'af1ba5ad-b94a-4fab-ba04-bf9c1164dbf5',
   'content-type': 'text/xml',
   'content-length': '441',
   'date': 'Thu, 02 May 2024 19:27:45 GMT'},
  'RetryAttempts': 0}}

From the above content we see our user id is “bob.jones@example.com.” It’s not possible for us to differentiate between a set of credentials created this way, and a SAML or AD created federated session. This token methods helps an attacker evade detection by pretending to be another user. This can be very difficult for a incident response team to track down the malicious actor.

Next let’s do something that will show up in cloudtrail using our federated credentials. We’ll go ahead and list all the accounts in my organization. Then attempt to find the event.

fed_org_client = fed_session.client('organizations')

# Author's note we don't care about the response
# We just care that is succeeded
resp = fed_org_client.list_accounts()

Time to create our cloudtrail client as our regular user and query for the event of interest. For the purposes of this exercise we’ll pretend we don’t know the access key or the user name falsely assumed. We’ll just search by the service. We do have to be careful with this API call since it’s heavily rate limited.

from datetime import datetime, timedelta

cloudtrail_client = boto3.client('cloudtrail')

# Author's note. I have pretty printed the results below.
resp = cloudtrail_client.lookup_events(
    LookupAttributes=[{
        'AttributeKey': 'EventSource',
        'AttributeValue': 'organizations.amazonaws.com'
    }],
    StartTime=datetime.now() - timedelta(minutes=5)
)

resp['Events'][0].keys()
dict_keys(['EventId', 'EventName', 'ReadOnly', 'AccessKeyId', 'EventTime', 'EventSource', 'Username', 'Resources', 'CloudTrailEvent'])

The event in question is shown below for illustration purposes:

{
    "eventVersion": "1.09",
    "userIdentity": {
        "type": "FederatedUser",
        "principalId": "XXX:bob.jones@example.com",
        "arn": "arn:aws:sts::XXX:federated-user/bob.jones@example.com",
        "accountId": "XXX",
        "accessKeyId": "ASIA4NLLQK5QUXXXXXX",
        "sessionContext": {
            "sessionIssuer": {
                "type": "IAMUser",
                "principalId": "AIDA4NLLQK5QVAGSOXXXX",
                "arn": "arn:aws:iam::XXX:user/hschmale",
                "accountId": "XXX",
                "userName": "hschmale"
            },
            "attributes": {
                "creationDate": "2024-05-02T17:33:26Z",
                "mfaAuthenticated": "false"
            }
        }
    },
    "eventTime": "2024-05-02T17:56:03Z",
    "eventSource": "organizations.amazonaws.com",
    "eventName": "ListAccounts",
    "awsRegion": "us-east-1",
    "sourceIPAddress": "XXX.XXX.XXX.XXX",
    "userAgent": "Boto3/1.34.34 md/Botocore#1.34.34 md/awscrt#0.19.19 ua/2.0 os/linux#6.6.26-1-MANJARO md/arch#x86_64 lang/python#3.11.8 md/pyimpl#CPython cfg/retry-mode#legacy Botocore/1.34.34",
    "requestParameters": null,
    "responseElements": null,
    "requestID": "fa0c5c73-3d6d-4765-b1dd-62c530705924",
    "eventID": "adcae203-5b60-4efc-98f4-4d623913dc95",
    "readOnly": true,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "XXX",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "organizations.us-east-1.amazonaws.com"
    }
}

Looking at the userIdentity.sessionContext field we can see more details about how the session was executed, but there’s no easy way to filter for these events inside of the native AWS tools without paying a fee. As such you need to pay for proper CSPM solution to detect these events and alert on them, or implement a kludge using Athena.

SCP Example Code

Before you go ahead and implement any changes to your SCP, you should first check your cloudtrail logs to make sure the API call isn’t in use somewhere in your organization. I’m sure some company out there is using this for something and AWS is very good about making sure APIs are not deprecated.

To disable it apply the following SCP to whatever organizational units need this policy:

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "Statement1",
			"Effect": "Deny",
			"Action": [
			    "sts:GetFederationToken"
            ],
			"Resource": "*"
		}
	]
}

Feel free to add a Condition block if necessary to allow certain calls to pass through but otherwise I would avoid using this API as there are much better solutions like IAM Identity Center and other builtin user federation/SSO solutions built into AWS than rolling your own through this API.

Conclusion

While sts:GetFederationToken is not novel, it is often overlooked. Many people have spoken at length previously about this API. This is your reminder that you likely don’t need this API call in your organization. If you have a valid use case for it that is not met through other services, I would love to hear about it. I may not work for AWS, but I love learning about a new service or use case I might not have considered.

Additional Notes & Further Reading

Even though I censored my account ids, this is a good time to remind you AWS considers account ids to be public information. It’s also relatively easy to identify an account id if there’s an s3 bucket exposed anywhere in that account you should review this excellent blog post by Sam Cox for more details.


Remember you can also subscribe using RSS at the top of the page!

Share this on → Mastodon Twitter LinkedIn Reddit

A selected list of related posts that you might enjoy:

*****
Written by Henry J Schmale on 2024 May 02
Hit Counter