Attacking AWS Developers: Stealing Creds from AWS Toolkit
In the previous post, we reviewed accessing AWS through the use of cleartext credential files. The credentials Access Key ID and Secret Access Key values stored in the files can be used to facilitate programmatic access to the AWS infrastructure under the permissions of the credential owner. The permissions are defined in the AWS IAM service and provide granular control regarding the AWS services a user can interact. Now, these credentials are not able to give you direct access to ec2 instances via, for example, SSH or SMB. However, they can be used to interact and query services like DynamoDB, AWS' noSQL database. Of course, depending on what permissions the compromised account has, this access may or may not be available. Like a lot of things in the pentesting world, "It depends."
In this post, I'll show you that, given the proper alignment of stars, that your pursuit of AWS credentials could lead you to ec2 access. Specifically, we'll talk about attacking Visual Studio users who have installed and are running the AWS Toolkit plugin.
The AWS Toolkit is on official Amazon plugin for Visual Studio. The extension allows you to do everything through Visual Studio that you could do through the AWS web portal. It wraps a nice GUI around the entire AWS SDK for easy management directly in the IDE. Here's what it looks like:
Of course, before a developer can use the AWS Toolkit they must configure their user information. This is done directly in the Toolkit, replicating the inputs the CLI uses with the configure
subcommand.
So a developer walks through that process on initial installation (or anytime a new profile needs to be created) and merrily goes on their way developing the next great thing.
Decrypting Local AWS Credentials
So, you may ask, why are we dedicating a whole blog post to this topic? Well, the AWS Toolkit actually encrypts the AWS Access Key ID and Secret Access Key values when saving it to disk. The Toolkit saves the encrypted credentials to C:\Users\USER\AppData\Local\AWSToolkit
in a file named RegisteredAccounts.json
. Here is what the file contents look like:
Furthermore, according the the AWS Toolkit documentation, the credentials are encrypted on a per-machine basis, meaning that you can't simply take the encrypted file, move it to your attacking machine, and use it with a local instance of the AWS Toolkit. The only way to decrypt the creds is to do so on the machine on which they were encrypted. And yes, I'm aware that my screenshot above blurs out the encrypted credentials despite not being decryptable (is that a word?) elsewhere. Consider me paranoid.
The RegisteredAccounts.json
file, by nature of its location on disk, is only accessible to its owner or an Administrator with full file system access. If you are on the workstation, you've likely already got this type of access. Assuming you do, how do you go about decrypting the file contents? Well, we look to the code. Fortunately, the AWS Toolkit code is open source and available on GitHub. We are primarily interested in the logic used to encrypt and decrypt the credentials. After some digging, you'll find that the logic leads back to a file named UserCrypto.cs. Within this file, you'll find a function named Decrypt
that issues a call to another function named CryptUnprotectData
:
Searching for CryptUnprotectData
we find that it's an externally implemented function available in Crypt32.dll
, which is a standard Microsoft DLL used for all sorts of crypto stuff.
Unfortunately, it's not as simple as just passing our ciphertext string to the correct Crypt32.dll
function. You'll see that the Toolkit code first prepares the data by converting the ciphertext ASCII string to a list of bytes and then decodes the resulting cleartext byte array to a string.
After some digging, you'll find that we can implement a call to unprotect (decrypt) the data using the following function call (C# prototype shown):
public static byte[] Unprotect(
byte[] encryptedData,
byte[] optionalEntropy,
DataProtectionScope scope
)
Simplest way to call this is through PowerShell. So I created the following script to decrypt a given string. Note the string, taken directly from our RegisteredAccounts.json
file is hardcoded in the $encrypted
variable.
Add-Type -AssemblyName System.Security
$encrypted = "01000000D08C9DDF011[...snipped...]B2B06596FAC41E7168957DCD5435AEA505A95C6F3461C7F7"
$dataIn = New-Object System.Collections.ArrayList
for ($i = 0; $i -lt $encrypted.Length; $i = $i + 2) {
$data = [System.Convert]::ToByte($encrypted.Substring($i, 2), 16)
[void]$dataIn.Add($data)
}
$entropy = New-Object Byte[] 0
$decryptedBytes = [System.Security.Cryptography.ProtectedData]::Unprotect(
$dataIn.ToArray(),
$entropy,[System.Security.Cryptography.DataProtectionScope]::LocalMachine)
$clear = [System.Text.Encoding]::Unicode.GetString($decryptedBytes)
Write-Host $clear
We add the System.Security
assemble to give us access to the Unprotect
function. We then duplicate the logic of the AWS Toolkit, converting the given string to a list of bytes. We pass those bytes into a call to Unprotect
, decode the result to a string, and print the string to the screen. Here is the result:
PS C:\Users\Administrator\Desktop> .\decrypt.ps1
AK[...redacted...]
Despite the redaction of the result, I can confirm that the value was indeed decrypted properly, showing the cleartext version of my Access Key ID. I repeat the process for the encrypted Secret Access Key and confirm that it, too, decrypts properly. Again, decryption only works on the machine from which the credentials were encrypted. However, now that we've decrypted the values we are free to transfer them to our local, attacking machines for use with our own AWS CLI installation.
Ok, we've gotten to the same point as our last post - getting AWS credentials. But let's look at a scenario where you may be able to take this a step further and gain access to ec2 instances.
Attacking an EC2 Instance
The AWS Toolkit let's you deploy ec2 instances. In the process, you're given the option to create or assign a key-pair to the instance. This allows developers to leverage public-key authentication to SSH into the instances. The following image shows the creation of a key pair:
As we work through the steps, we're prompted to save our private key in AWS Toolkit:
By saving the private key in the AWS Toolkit, a developer can right-click an ec2 instance and quickly connect via SSH. It'll automatically use the key pair the dev assigned to that ec2 instance for the subsequent connection:
Enough about that process. How do we exploit that as an attacker? Well, if we poke around the file system we find that the private key is saved within a subdirectory in C:\Users\USER\AppData\Local\AWSToolkit\keypairs
. The file name is of the structure keypairname.pem.encrypted
. Sure enough, you open the file only to find what is obviously not a PEM-encoded private key:
The format, including the first several bytes of data, looks similar to the encrypted values in our RegisteredAccounts.json
file. And yes indeed, if we pass the contents of that encrypted PEM file to our PowerShell script we are give the private key:
Ok, so we have a private key. We know from the file name that the key-pair was named test
. Although I didn't show it, we also know from the directory structure to which AWS region the key-pair belonged. But we don't actually know where that key-pair is used. Remember, in this blog we have the luxury of seeing the AWS Toolkit GUI in Visual Studio which lists ec2 instances and their key-pairs. During a pentest we're unlikely to have such luxury. However, since we have the AWS credentials, we can issue a CLI query from our attacking machine to identify which ec2 instances use that same key-pair. Run this query:
$ aws ec2 describe-instances --query 'Reservations[*].Instances[*].{Key:KeyName,DNS:PublicDnsName,IP:PublicIpAddress,State:State.Name}'
We're given a list of ec2 instances and the key-pairs we're using for each:
Now that we have a private key and IP address, we can authenticate to the given ec2 instance from the attacking machine:
Conclusion
This blog post demonstrated how to leverage a developer's access, using a viable SDK attack vector, to jump the cloud gap and gain access to AWS resources. We are going to continue posting various methods to jump the cloud gap in hopes that this increases post-ex as it pertains to Cloud Provider Networks. Hope this was interesting and that it is useful on your next adversarial gig. Always forward.