WannaCry: Finding where SMB is allowed in AWS

WannaCry is the latest ransomware to sweep the internet and cause lots of excitement. As occasionally happens with well publicized security events like this I got to hear a former firewall admins favorite words: “Can you please take away a bunch of network access?” What fun!

I love blocking traffic as much as the next guy, but it’s not a great idea to just change firewall rules willy nilly. You should always spend a little time thinking about the impacting and looking at what access it’s prudent to remove. In this post I’ll list a couple of the commands I used to poke around our AWS Security groups and find where SMB was allowed.
Because time was a factor, I decided to do all of this in the AWS Powershell tools. As usual, you’ll need to set default credentials and an AWS region before running any of these commands.
The first one is pretty straightforward. The sequence of commands will spit out any security groups in your region that allow SMB traffic inbound in the default ports (137-139 and 445).
Fun fact, apparently AWS built this section of the powershell tools before you could control egress permissions in a group. The inbound rules are named “IpPermissions” while the outbound rules have the more describe “IpPermissionsEgress”. Other places (like cloudformation templates) name inbound rule sets “Ingress” to match the outbound version.
get-ec2securitygroup | foreach {foreach ($ingress in $_.ippermissions) {if(($ingress.fromport -le 445 -and $ingress.toport -ge 445) -or $ingress.fromport -le 137 -and $ingress.toport -ge 139) -or $ingress.ipprotocol -eq -1){$_.GroupName}}} | select-object  -unique

I apologize for the length of this single line, but it should show you anywhere your instances are accepting SMB, including rules that allow all traffic.

Next it can also be helpful to look for rules allowing SMB outbound. You might want to restrict this traffic too. This command is almost the same as the first

PS C:\Users\bolson\Documents\AWS> get-ec2securitygroup | foreach {foreach ($egress in $_.ippermissionsegress) {if(($egress.fromport -le 445 -and $egress.toport -ge 445) -or ($egress.fromport -le 137 -and $egress.toport -ge 137) -or $egress.ipprotocol -eq -1){$_.GroupName}}} | select-object  -unique

Now that you’ve got a listing of where SMB is allowed in your AWS account, you may want to remove specific security groups from instances. If you’re looking to do one or two instances, that can be done pretty easily through the console. If you’re looking to pull a few security groups off of every instance, you can use the example below, updating the security group IDs.

We used this example for removing a “temp setup” group that we use in our environment to allow extra access for configuring a new instance.

set-defaultawsregion us-east-1
set-awscredentials -profilename PHI
(Get-EC2Instance -filter @( @{name='instance.group-id';values="sg-11111","sg-22222"})).instances | foreach {
write-host "Instance Name: $(($_.tags | where {$_.key -eq "Name"}).value) - $($_.InstanceId)";
$finalGroups = @();
$finalGroupNames = @();
foreach ($group in $_.SecurityGroups) {
write-host $group.groupid
if($group.groupid -ne "sg-11111" -and $group.groupid -ne "sg-22222") {
write-host "$($group.groupid -ne 'sg-333333')"
$finalGroups += $group.groupid;
$finalGroupNames += $group.groupname
}
}
Edit-EC2InstanceAttribute -InstanceId $_.InstanceId -group $finalGroups
write-host "Finalgroups: $($finalGroupNames)"
}

Hopefully that helps you do some analysis in your environment!

Auditing AWS IAM Users

Like any other company with sensitive data we go through audits pretty regularly. The latest one included some questions about accounts that have access to sensitive data, and the number of auth factors required to log into them.

As usual I started digging around in the AWS Powershell Tools to find a way to make this job easier than just manually looking through accounts, and I quickly found Request-IAMCredentialReport and Get-IAMCredentialReport.

These two commands are a pretty interesting combination. The first tells AWS to generate a credential report. Request-IAMCredentialReport is the step required to generate the report on AWS’s end. There’s some pretty good documentation on how that section works. The most interesting point to me is that AWS will only generate a report every 4 hours. This is important to note if you’re making changes, and re-running reports to double check they fixed an issue.
The second command, Get-IAMCredentialReport actually downloads the report that’s generated. From what I’ve seen, if you haven’t run Request-IAMCredentialReport in the last 4 hours to have a fresh report, this command will fail.
I found the output of this command to be the most useful when I included the -AsTextArray option. That returns an array of lines of the report with the columns separated by columns. I won’t include an sample output from our accounts for obvious reasons, but check the documentation for an example of what that looks like.
Now that we’ve got all of the components to download this report, it’s pretty trivial powershell work to do some parsing and logic.
The script example below creates an array of maps for each line of the report, letting you iterate over it and check for different conditions.
The one I’m testing for now is IAM accounts that have passwords enabled, but do not have an MFA device activated, but you can see it would be pretty easy to add additional criteria that would be tested against the report.
$awsProfiles = @("FirstProfileName","SecondProfileName");
set-DefaultAWSRegion us-east-1;
foreach ($awsProfile in $awsProfiles) {
write-host "Running audit on $awsProfile";
Set-AWSCredentials -ProfileName $awsProfile;

# Attempt to get an IAM credential report, if one does not exist, sleep to let one generate
$reportResult = Request-IAMCredentialReport -Force;

# Sleep for 15 seconds to allow the report to generate
start-sleep -s 15

try {

# Get IAM Credential report
$credReports = Get-IAMCredentialReport -AsTextArray;
} catch {
write-host "No credential report exists for this account, please run script again in a few minutes to let one generate";
exit;
}
# Empty list that will contain parsed, formatted credential reports
$credReportList = @();

# Get the headings from the report
$headings = $credReports[0].split(",");

# Start processing the report, starting after the headings
for ($i = 1; $i -lt $credReports.length; $i++) {

# Break up the line of the report by commas
$splitLine = $credReports[$i].split(",");
$lineMap = @{};

# Go through the line of the report and set a map key of the header for that column
for ($j = 0; $j -lt $headings.length; $j++) {
$lineMap[$headings[$j]] = $splitLine[$j];
}

# Add the formatted line to the final list
$credReportList += , $lineMap;
}

# Iterate over the report, using rules to evaluate the contents
foreach($credReport in $credReportList) {
# Check for users that have an active password, but not an active MFA device
if($credReport['password_enabled'] -eq "TRUE" -and $credReport['mfa_active'] -eq "FALSE") {
write-host "ALERT: User: $($credReport['user']) has a password enabled, but no MFA device"
}
}
write-host "";
}

This script assumes you have created AWS Powershell tools profiles that match the array on the first line.

And here is some example output of users I had to go have a chat with today to activate their MFA devices.

NOTE: You may need to run this script a couple times, if you haven’t generated an IAM Credential Report in a while.

AWS Powershell Tools Snippets: Powershell Pipes

I’m on another AWS Powershell tools rant. Hopefully after reading this blog post you’ll share my appreciation for how useful they are.

Powershell takes the idea of piping commands together (sending that output of one command directly to the input of another) to a whole new level of useful. If you aren’t familiar with the concept, it’s a great way to make your commands dynamic and intricate. Let’s walk through an example.

Let’s say you want to list all of your Opsworks instances in an AWS account. You’d probably start by going to the AWS Powershell Tools commandlet reference and find the Get-OPSInstances commandlet. But running it alone will give you the not-so-helpful output below
Looking a little more closely at the documentation you’ll find a “StackId” parameter that will give you instances for that stack. Obviously you could go to the console, find a stack, copy the ID, and paste it into the shell, but that feels like a lot of steps.
Instead you could use two commands, connected by a pipe to link they’re results together. For example “Get-OpsStacks” piped into 
“Get-OpsInstances”. In the command below I’m using the “count” property of powershell lists to find the number of instances returned, which is more than the zero by my first command.
Pretty cool! I was able to pull back instances without going to the console to look up a Stack ID.
This is a pretty basic example, but once you’re comfortable with the concept the possibilities are endless. Let’s say we wanted to find only Opsworks Stacks that contain the number “901” (we use the “900” range for test instances at my company). You could pipe “Get-OpsStacks” into the “Where” commandlet to filter the Opsworks stacks before you get the instances.
In the example below, I’ve also used “foreach” commandlet to only print the hostname to keep the output a little smaller.
It’s pretty clear that we’ve got instances from multiple stacks here, so we can make our where filter a little more sophisticated. Let’s find instances that are only part of the NodeJS webserver stack.
You can repeat this pattern of piping commands together as many times as you need to get the results you’re looking for. Eventually these commands can get long and complicated enough it’s better to put them into a file for some formatting and white space, but for quick and dirty work they’re pretty useful.

Describing AWS Instances

Amazon uses the idea of “describing” resources using filters quite a bit.

A filter in python terms is a list of maps, and each value in the map’s key-value pairs can be a list. The documentation is a little light on details, intentionally I believe, because the format is extremely flexible. A few examples of using this in Python and Powershell are below

If you wanted to get all of the instances that have a Name tag with the value webserver, you would use

filters = [{‘Name’:’tag:Name’, ‘Values’: [‘*Webserver*’]}]

Here is an example of using this concept in a python script:


https://gist.github.com/LenOtuye/ed4617b19217a3f903dc0161524dcd91.js

And here is the same thing in powershell. https://gist.github.com/LenOtuye/c7c36710be9a153ea1998045c64806a9.js

And just for extra fun, once you have the instance objects in Powershell it’s pretty easy to pipe them to other commandlets, like “where” to filter by instance type: https://gist.github.com/LenOtuye/71d800f6807bda0d501a157ec6a50757.js