Security and IAM

Brute Force AWS IAM MFA

Is it possible? Let's run a test to find out.
Matt Skillman Featured
Matt Skillman | Feb 09 2022
3 min read

Background

AWS IAM allows for you to create IAM users. These “users” essentially boil down to being a set of credentials that an application (or a person using an application) may use to make API calls to AWS services. Because IAM users are associated with an AWS account, in most circumstances access to the credentials for an IAM user entails some level of access to someone’s AWS account. These credentials can easily become a security issue if they fall into the wrong hands. To help combat this security risk, AWS offers MFA for IAM users. MFA entails the requirement that, in addition to the standard set of credentials (which are a pair of strings commonly stored in a “credentials” file on someone’s computer), a TOTP must be offered to AWS in order to temporarily be able to use that IAM user. A TOTP should be a familiar concept to all of us: think of the text messages you receive when logging into your bank which contain a six-digit code. This six-digit code would be an example of a TOTP. A TOTP can only be used once, which is why your bank sends you a different six-digit code every time you login.

In the case of MFA for IAM users, the TOTP will always be a six-digit code. This means that there are only 1 million possibilities for what this code could be. The code will change every 30 seconds, which made me wonder whether or not it would be possible to simply try all 1 million possible codes within a 30-second window to bypass AWS MFA. This post describes my findings.

Summary

Question: Given that a bad actor gains access to a user's aws_access_key_id as well as the aws_secret_access_key stored within ~/.aws/credentials file, will the actor be able to quickly gain access to that user, provided that MFA is required? (i.e. does AWS implement some sort of backoff related to failed MFA attempts?)

Assumptions: the actor does not have access to the MFA device but is able to programmatically iterate through all possible MFA code values as well as attempt a login for each possible MFA value.

Setup

import random
import pyotp
from os import environ
from itertools import permutations
from time import sleep

import boto3
from boto3_type_annotations.sts import Client

sts_client: Client = boto3.client('sts')

possible = list(
   map(
       lambda x: ''.join(map(str, x)),
       permutations(range(0,10), 6),
   )
)

attempts_count = 0
attempts_count_max = 5
sleep_time = 240
actual_code_generator = pyotp.TOTP(environ['token'])
serial = environ['serial']

# NOTE I can't iterate fast enough through all 1 million + I don't want to maybe technically
# ... dos AWS by trying to do so
while 1:
   try:
       attempts_count += 1
       result = sts_client.get_session_token(
           TokenCode=str(random.choice(possible)),
           SerialNumber=serial,
       )
       print(result)
       print('login success using random number')
   except Exception as e:
       if attempts_count == attempts_count_max:
           try:
               result = sts_client.get_session_token(
                   TokenCode=actual_code_generator.now(),
                   SerialNumber=serial,
               )
               print('login confirmed to still work')
           except Exception as e:
               print(f'locked out at {attempts_count} attempts')
   if attempts_count == attempts_count_max:
       attempts_count = 0
       print(f'waiting {sleep_time} seconds')
       sleep(sleep_time)

Results

As expected, it is not possible to quickly gain access. From what I can see, you only get 5 guesses every 4 minutes. After too many failed attempts, AWS will temporarily lock the IAM user. This means that all further attempts to access that user would be pointless as even a correct MFA code will still not allow you access. The ballpark estimate for gaining access through slowly making a handful of guesses every 4 minutes would be somewhere on the order of 1 year or more. It is not guaranteed that this “slow” method would work however as AWS may have more logic which prevents this method from working. This “slow” approach would be the topic of a different question.

Math

(1 - ( 5 / 1000000)) ^ (15 * 24 * 365) = ~ 50% chance of gaining access.

There is a 99.9995% chance that each attempt (an “attempt” being 5 rapid guesses) will be unsuccessful.

Each attempt may take place every 4 minutes, so there are 131400 attempts that can be made each year.

To find the probability of a year’s worth of attempts, we take the chance of each attempt being unsuccessful and raise that to the power of 131400. This gives us a 52% chance that a year’s worth of guessing will be unsuccessful. So conversely, there is a 48% chance of success in the first year of guessing. We can round this up to 50% and say that roughly you may expect this process to take a year.

Author
Matt Skillman Featured
Matt Skillman