The lab environment should not be used for activities not described or requested in the learning materials you encounter. It is not designed to serve as a playground to test additional items that are out of the scope of the Learning Module.
The lab environment should not be used to take action against any asset external to the lab. This is specifically noteworthy because some Modules may describe or even demonstrate attacks against vulnerable cloud deployments for the purpose of describing how those deployments can be secured.
Existing rules and requirements against sharing OffSec training materials still apply. Credentials and other details of the lab are not meant to be shared. OffSec monitors activity in the Public Cloud Labs (including resource usage) and monitors for abnormal events that are not related to activities described in the learning modules.
Activities that are flagged as suspicious will result in an investigation. If the investigation determines that a student acted outside of the guidelines described above, or otherwise intentionally abused the OffSec Public Cloud Labs, OffSec may choose to rescind that learner's access to the OffSec Public Cloud Labs and/or terminate the learner's account.
Reconnaissance of Cloud Resources on the Internet
Accessing the Lab
Deployment will take a few minutes to complete and, once complete, the additional services might take 5-10 minutes to start.
Once the lab finishes its deployment, we'll receive some pieces of information we'll need later while working in the lab.
Public DNS IP Address
Domain name of the target
Credentials for the IAM user attacker
ACCESS_KEY_ID
SECRET_ACCESS_KEY
We must configure the DNS in our local environment to use the public DNS set for this lab:
We should also keep in mind that the public IP address of the DNS server will change every time we restart the lab, and we'll need to run this configuration again.
Domain and Subdomain Reconnaissance
Querying the authoritative DNS servers for the offseclab.io domain:
kali@kali:~$ host -t ns offseclab.io
offseclab.io name server ns-1536.awsdns-00.co.uk.
offseclab.io name server ns-512.awsdns-00.net.
offseclab.io name server ns-0.awsdns-00.com.
offseclab.io name server ns-1024.awsdns-00.org.
kali@kali:~$ host www.offseclab.io
www.offseclab.io has address 52.70.117.69
Performing a reverse DNS lookup:
kali@kali:~$ host 52.70.117.69
69.117.70.52.in-addr.arpa domain name pointer ec2-52-70-117-69.compute-1.amazonaws.com
kali@kali:~$ whois 52.70.117.69 | grep "OrgName"
OrgName: Amazon Technologies Inc.
Using dnsenum to auomate some of this dns reconnaissance:
kali@kali:~$ dnsenum offseclab.io --threads 100
dnsenum VERSION:1.2.6
----- offseclab.io -----
Host's addresses:
__________________
offseclab.io. 60 IN A 52.70.117.69
Name Servers:
______________
ns-1536.awsdns-00.co.uk. 0 IN A 205.251.198.0
ns-0.awsdns-00.com. 0 IN A 205.251.192.0
ns-512.awsdns-00.net. 0 IN A 205.251.194.0
ns-1024.awsdns-00.org. 0 IN A 205.251.196.0
Mail (MX) Servers:
___________________
Trying Zone Transfers and getting Bind Versions:
_________________________________________________
Trying Zone Transfer for offseclab.io on ns-512.awsdns-00.net ...
AXFR record query failed: corrupt packet
Trying Zone Transfer for offseclab.io on ns-1024.awsdns-00.org ...
AXFR record query failed: corrupt packet
Trying Zone Transfer for offseclab.io on ns-0.awsdns-00.com ...
AXFR record query failed: corrupt packet
Trying Zone Transfer for offseclab.io on ns-1536.awsdns-00.co.uk ...
AXFR record query failed: corrupt packet
Brute forcing with /usr/share/dnsenum/dns.txt:
_______________________________________________
mail.offseclab.io. 60 IN A 52.70.117.69
www.offseclab.io. 60 IN A 52.70.117.69
...
Service-specific Domains
Not much to add, of note Amazon ec2 instances tend to have a URL beginning with ec2. Same goes for s3 buckets.
cloud-enum is a useful tool to perform OSINT across multiple CSPs. The cloud-enum tool will search through several public CSPs for resources containing a keyword specified using the --keyword KEYWORD (-k KEYWORD) parameter. We can specify multiple keyword arguments, or we can specify a list with the --keyfile KEYFILE (-kf KEYFILE) parameter.
We can also use the --mutations (-m) option to specify a file to add extra words to the keyword. If we don't specify any file, the /usr/lib/cloud-enum/enum_tools/fuzz.txt file is used by default. We can disable this option using the --quickscan (-qs) parameter.
Using cloud-enum to search for more buckets belonging to offseclab.io:
kali@kali:~$ cloud_enum -k offseclab-assets-public-axevtewi --quickscan --disable-azure --disable-gcp
...
Keywords: offseclab-assets-public-axevtewi
Mutations: NONE! (Using quickscan)
Brute-list: /usr/lib/cloud-enum/enum_tools/fuzz.txt
[+] Mutated results: 1 items
++++++++++++++++++++++++++
amazon checks
++++++++++++++++++++++++++
[+] Checking for S3 buckets
OPEN S3 BUCKET: http://offseclab-assets-public-axevtewi.s3.amazonaws.com/
FILES:
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/offseclab-assets-public-axevtewi
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/amethyst-expanded.png
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/amethyst.png
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/logo.svg
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/pic02.jpg
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/pic05.jpg
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/pic13.jpg
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/ruby-expanded.png
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/ruby.jpg
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/saphire-expanded.png
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/saphire.jpg
Elapsed time: 00:00:00
[+] Checking for AWS Apps
[*] Brute-forcing a list of 1 possible DNS names
Elapsed time: 00:00:00
[+] All done, happy hacking!
Using a custom keyfile for cloud-enum:
kali@kali:~$ for key in "public" "private" "dev" "prod" "development" "production"; do echo "offseclab-assets-$key-axevtewi"; done | tee /tmp/keyfile.txt
offseclab-assets-public-axevtewi
offseclab-assets-private-axevtewi
offseclab-assets-dev-axevtewi
offseclab-assets-prod-axevtewi
offseclab-assets-development-axevtewi
offseclab-assets-production-axevtewi
kali@kali:~$ cloud_enum -kf /tmp/keyfile.txt -qs --disable-azure --disable-gcp
...
Keywords: offseclab-assets-public-axevtewi, offseclab-assets-private-axevtewi, offseclab-assets-dev-axevtewi, offseclab-assets-prod-axevtewi, offseclab-assets-development-axevtewi, offseclab-assets-production-axevtewi
Mutations: NONE! (Using quickscan)
Brute-list: /usr/lib/cloud-enum/enum_tools/fuzz.txt
[+] Mutated results: 6 items
++++++++++++++++++++++++++
amazon checks
++++++++++++++++++++++++++
[+] Checking for S3 buckets
OPEN S3 BUCKET: http://offseclab-assets-public-axevtewi.s3.amazonaws.com/
FILES:
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/offseclab-assets-public-axevtewi
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/amethyst-expanded.png
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/amethyst.png
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/logo.svg
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/pic02.jpg
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/pic05.jpg
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/pic13.jpg
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/ruby-expanded.png
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/ruby.jpg
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/saphire-expanded.png
->http://offseclab-assets-public-axevtewi.s3.amazonaws.com/sites/www/images/saphire.jpg
Protected S3 Bucket: http://offseclab-assets-private-axevtewi.s3.amazonaws.com/
Elapsed time: 00:00:06
[+] Checking for AWS Apps
[*] Brute-forcing a list of 6 possible DNS names
Elapsed time: 00:00:00
[+] All done, happy hacking!
Objects inside a bucket can be publicly accessible even if the bucket is not publicly accessible.
Reconnaissance via Cloud Service Provider's API
Preparing the Lab - Configure AWS CLI
Configuring awscli:
kali@kali:~$ sudo apt update
...
kali@kali:~$ sudo apt install -y awscli
...
The following NEW packages will be installed:
awscli docutils-common python3-awscrt python3-docutils python3-jmespath python3-roman
(Reading database ... 461429 files and directories currently installed.)
...
kali@kali:~$ aws configure --profile attacker
AWS Access Key ID []: AKIAQO...
AWS Secret Access Key []: cOGzm...
Default region name []: us-east-1
Default output format []: json
kali@kali:~$ aws --profile attacker sts get-caller-identity
{
"UserId": "AIDAQOMAIGYU5VFQCHOI4",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/attacker"
}
Publicly Shared Resources
Listing all AMIs that our account can read, that are provided by Amazon that are public, including self-owned public AMIs:
Steps to find the account ID from a publicly available S3 bucket:
Create an IAM user with a policy that only allows them to ls the S3 bucket if the account ID begins with x where x is 0-9.
As that user, attempt to ls the S3 bucket. If it's able to, then move to the next digit. Otherwise change the first digit for the account ID in teh policy and reapply.
Rinse & repeat.
There are also tools like s3-account-search to do this, however that tool uses roles rather than users to link the policy to the condition.
Similar to the last section, we can use policies to determine if IAM accounts exist when we know an account ID. In this case we would assign the policy to our own private bucket. If no error returns after running the command to apply the policy, that means that the cloudadmin user exists in the target account.
kali@kali:~$ aws --profile attacker s3api put-bucket-policy --bucket offseclab-dummy-bucket-28967-25641-13328 --policy file://grant-s3-bucket-read-userDoNotExist.json
An error occurred (MalformedPolicy) when calling the PutBucketPolicy operation: Invalid principal in policy
We can automate this process using pacu and a prebuilt list of users we'd like to test:
# Generating our list of users.
kali@kali:~$ echo -n "lab_admin
security_auditor
content_creator
student_access
lab_builder
instructor
network_config
monitoring_logging
backup_restore
content_editor" > /tmp/role-names.txt
# Installing pacu.
kali@kali:~$ sudo apt update
kali@kali:~$ sudo apt install pacu
# Starting pacu in interactive mode.
kali@kali:~$ pacu
....
Database created at /root/.local/share/pacu/sqlite.db
What would you like to name this new session? offseclab
Session offseclab created.
...
# Setting keys from the AWS CLI, specifying the attacker role.
Pacu (offseclab:No Keys Set) > import_keys attacker
Imported keys as "imported-attacker"
# Listing available modules.
Pacu (offseclab:imported-attacker) > ls
...
[Category: RECON_UNAUTH]
iam__enum_roles
iam__enum_users
...
# Getting information about the iam__enum_roles module
Pacu (offseclab:imported-attacker) > help iam__enum_roles
iam__enum_roles written by Spencer Gietzen of Rhino Security Labs.
usage: pacu [--word-list WORD_LIST] [--role-name ROLE_NAME] --account-id
ACCOUNT_ID
This module takes in a valid AWS account ID and tries to enumerate existing
IAM roles within that account. It does so by trying to update the
AssumeRole policy document of the role that you pass into --role-name if
passed or newlycreated role. For your safety, it updates the policy with an
explicit deny against the AWS account/IAM role, so that no security holes
are opened in your account during enumeration. NOTE: It is recommended to
use personal AWS access keys for this script, as it will spam CloudTrail
with "iam:UpdateAssumeRolePolicy" logs and a few "sts:AssumeRole" logs. The
target account will not see anything in their logs though, unless you find
a misconfigured role that allows you to assume it. The keys used must have
the iam:UpdateAssumeRolePolicy permission on the role that you pass into
--role-name to be able to identify a valid IAM role and the sts:AssumeRole
permission to try and request credentials for any enumerated roles.
...
# Running the module with our custom name list.
Pacu (offseclab:imported-attacker) > run iam__enum_roles --word-list /tmp/role-names.txt --account-id 123456789012
Running module iam__enum_roles...
...
[iam__enum_roles] Targeting account ID: 123456789012
[iam__enum_roles] Starting role enumeration...
[iam__enum_roles] Found role: arn:aws:iam::123456789012:role/lab_admin
[iam__enum_roles] Found 1 role(s):
[iam__enum_roles] arn:aws:iam::123456789012:role/lab_admin
[iam__enum_roles] Checking to see if any of these roles can be assumed for temporary credentials...
[iam__enum_roles] Role can be assumed, but hit max session time limit, reverting to minimum of 1 hour...
[iam__enum_roles] Successfully assumed role for 1 hour: arn:aws:iam::123456789012:role/lab_admin
[iam__enum_roles] {
"Credentials": {
"AccessKeyId": "ASIAQOMAIGYUWZXRMMO2",
"SecretAccessKey": "2UU80dtizqx3DUa9mn6033AjXKb13GXOMCy+tOUt",
"SessionToken": "FwoGZXIvYXdzEO///////////wEaDCv5...",
"Expiration": "2023-08-18 22:07:49+00:00"
},
"AssumedRoleUser": {
"AssumedRoleId": "AROAQOMAIGYUR5KMGWT7V:dCkQ0O1y6n9KSQmGBaKJ",
"Arn": "arn:aws:sts::123456789012:assumed-role/lab_admin/dCkQ0O1y6n9KSQmGBaKJ"
}
}
Cleaning up the PacuIamEnumRoles-XbsIV role.
Now that we were able to assume the role, let's set environment variable and query the environment:
kali@kali:~$ export AWS_ACCESS_KEY=ASIAQOMAIGYUWZXRMMO2
kali@kali:~$ export AWS_SECRET_ACCESS_KEY=2UU80dtizqx3DUa9mn6033AjXKb13GXOMCy+tOUt
kali@kali:~$ export AWS_SESSION_TOKEN=FwoGZXIvYXdzEO///////////wEaDCv5...
# We don't need to include profile anymore as it will use our environment variables.
kali@kali:~$ aws ec2 describe-vpcs --region us-east-1
Initial IAM Reconnaissance
Accessing the Lab
Credentials for access as the target user.
Target ACCESS KEY ID
Target SECRET ACCESS KEY
Credentials for access as the challenge user.
Challenge ACCESS KEY ID
Challenge SECRET ACCESS KEY
Credentials for access as the monitor user.
Management Console login URL
Username
Password
Examining Compromised Credentials
The get-caller-identity subcommand is a good way to identify the account and identity of the credentials, and this action will never return an AccessDenied error. However, we should be aware that this action is logged in Cloudtrail's event history. As defenders, we should establish alerts for these types of calls as they are typically executed by attackers once they've compromised credentials.
Getting information in a stealthy way from error messages via a lambda function:
kali@kali:~$ aws --profile target lambda invoke --function-name arn:aws:lambda:us-east-1:123456789012:function:nonexistent-function outfile
An error occurred (AccessDeniedException) when calling the Invoke operation: User: arn:aws:iam::123456789012:user/support/clouddesk-plove is not authorized to perform: lambda:InvokeFunction on resource: arn:aws:lambda:us-east-1:123456789012:function:nonexistent-function because no resource-based policy allows the lambda:InvokeFunction action
Checking inline and managed policies for the group discovered:
kali@kali:~$ aws --profile target iam list-group-policies --group-name support
{
"PolicyNames": []
}
kali@kali:~$ aws --profile target iam list-attached-group-policies --group-name support
{
"AttachedPolicies": [
{
"PolicyName": "SupportUser",
"PolicyArn": "arn:aws:iam::aws:policy/job-function/SupportUser"
}
]
}
While AWS Managed Policies offer flexibility for IAM management, they tend to be overly-permissive and there is an inherent security risk when they are used alone.
Determining the version of the AWS managed policy applied to the support group:
Filter command via the aws help to show what all we can do:
kali@kali:~$ aws --profile target iam help | grep -E "list-|get-|generate-"
o generate-credential-report
o generate-organizations-access-report
o generate-service-last-accessed-details
o get-access-key-last-used
o get-account-authorization-details
o get-account-password-policy
o get-account-summary
o get-context-keys-for-custom-policy
o get-context-keys-for-principal-policy
o get-credential-report
o get-group
o get-group-policy
o get-instance-profile
o get-login-profile
o get-open-id-connect-provider
o get-organizations-access-report
o get-policy
o get-policy-version
o get-role
o get-role-policy
o get-saml-provider
o get-server-certificate
o get-service-last-accessed-details
o get-service-last-accessed-details-with-entities
o get-service-linked-role-deletion-status
o get-ssh-public-key
o get-user
o get-user-policy
o list-access-keys
o list-account-aliases
o list-attached-group-policies
o list-attached-role-policies
o list-attached-user-policies
o list-entities-for-policy
o list-group-policies
o list-groups
o list-groups-for-user
o list-instance-profile-tags
o list-instance-profiles
o list-instance-profiles-for-role
o list-mfa-device-tags
o list-mfa-devices
o list-open-id-connect-provider-tags
o list-open-id-connect-providers
o list-policies
o list-policies-granting-service-access
o list-policy-tags
o list-policy-versions
o list-role-policies
o list-role-tags
o list-roles
o list-saml-provider-tags
o list-saml-providers
o list-server-certificate-tags
o list-server-certificates
o list-service-specific-credentials
o list-signing-certificates
o list-ssh-public-keys
o list-user-policies
o list-user-tags
o list-users
o list-virtual-mfa-devices
Getting a summary of the IAM-related information in the account, tee'ing it to avoid interacting with the AWS API again:
Then we can run the get-policy-version subcommand to read the policy document for each managed policy found.
Alternatively, if we have Get* permissions, we can just run get-account-authorization-details to gather the same information of all the previous commands.
Userful search listing Users, groups, and attached managed policies:
aws --profile target iam get-account-authorization-details --filter User Group --query "UserDetailList[].{Name: UserName,Path: Path,Groups: GroupList, AttachedManagedPolicies:AttachedManagedPolicies}"