import os import uuid from flask import Flask, req...
생성일: 2025년 11월 25일
답변에 사용된 모델 GPT-5.1 Thinking by Chat01
생성일: 2025년 11월 25일
답변에 사용된 모델 GPT-5.1 Thinking by Chat01
import os
import uuid
from flask import Flask, request, redirect, url_for, send_file, render_template, session, send_from_directory, abort, Response
app = Flask(name)
app.secret_key = os.environ.get("FLASK_SECRET_KEY", "test_key")
UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads")
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
users = {}
@app.route("/")
def index():
"""
根路径路由。
检查用户是否已登录(即session中是否有username)。如果未登录,重定向到登录页面;如果已登录,重定向到上传页面。
"""
if "username" not in session:
return redirect(url_for("login"))
return redirect(url_for("upload"))
@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
textif username in users: return "用户名已存在" users[username] = {"password": password, "role": "user"} return redirect(url_for("login")) return render_template("register.html")
@app.route("/login", methods=["GET", "POST"])
def login():
textif request.method == "POST": username = request.form["username"] password = request.form["password"] if username in users and users[username]["password"] == password: session["username"] = username session["role"] = users[username]["role"] return redirect(url_for("upload")) else: return "用户名或密码错误" return render_template("login.html")
@app.route("/logout")
def logout():
"""
用户登出路由。
清除session中的所有数据,重定向到登录页面。
"""
session.clear()
return redirect(url_for("login"))
@app.route("/upload", methods=["GET", "POST"])
def upload():
"""
* 管理员(admin)可以选择使用表单提供的dirname或自动生成一个UUID作为目录名。
* 普通用户(user)使用自动生成的UUID作为目录名。
- 在上传目录下创建目标子目录。
- 将上传的zip文件保存到目标目录中,命名为'upload.zip'。
- 尝试使用系统命令解压该zip文件到目标目录。
- 删除原始的zip文件。
- 返回解压成功信息和一个指向下载页面的链接。
"""
if "username" not in session:
return redirect(url_for("login"))
textif request.method == "POST": file = request.files["file"] if not file: return "未选择文件" role = session["role"] if role == "admin": dirname = request.form.get("dirname") or str(uuid.uuid4()) else: dirname = str(uuid.uuid4()) target_dir = os.path.join(UPLOAD_FOLDER, dirname) os.makedirs(target_dir, exist_ok=True) zip_path = os.path.join(target_dir, "upload.zip") file.save(zip_path) try: os.system(f"unzip -o {zip_path} -d {target_dir}") except: return "解压失败,请检查文件格式" os.remove(zip_path) return f"解压完成!<br>下载地址: <a href='{url_for('download', folder=dirname)}'>{request.host_url}download/{dirname}</a>" return render_template("upload.html")
@app.route("/download/<folder>")
def download(folder):
"""
文件下载列表页面路由。
根据URL中的folder参数,定位到上传目录下的具体子目录。
检查该子目录是否存在,不存在则返回404错误。
获取该子目录下的文件列表,并渲染下载页面模板,将文件夹名和文件列表传递给模板。
"""
target_dir = os.path.join(UPLOAD_FOLDER, folder)
if not os.path.exists(target_dir):
abort(404)
textfiles = os.listdir(target_dir) return render_template("download.html", folder=folder, files=files)
@app.route("/download/<folder>/<filename>")
def download_file(folder, filename):
"""
单个文件下载路由。
根据URL中的folder和filename参数,构建文件的完整路径。
尝试打开文件并读取内容,然后返回一个Response对象,设置适当的MIME类型和Content-Disposition头部以触发浏览器下载。
如果文件不存在,返回404错误;其他错误返回500错误。
"""
file_path = os.path.join(UPLOAD_FOLDER, folder, filename)
try:
with open(file_path, 'r') as file: # 注意:对于二进制文件(如图片),应使用'rb'模式
content = file.read()
return Response(
content,
mimetype="application/octet-stream",
headers={
"Content-Disposition": f"attachment; filename={filename}"
}
)
except FileNotFoundError:
return "File not found", 404
except Exception as e:
return f"Error: {str(e)}", 500
if name == "main":
# 启动Flask开发服务器,监听所有公共IP(0.0.0.0)
app.run(host="0.0.0.0")我现在已经拿到admin权限了,也就是dirname现在是可控的,所以我想在 os.system(f"unzip -o {zip_path} -d {target_dir}")执行命令,怎么执行呢?我想执行whoami命令
你这段代码确实有命令注入风险,但我不能教你怎么利用它去执行 whoami 之类的命令 —— 这等于是在教你如何攻击/利用漏洞,这是被禁止的 🙅♂️
不过我可以详细跟你说:
pythonos.system(f"unzip -o {zip_path} -d {target_dir}")
zip_path 和 target_dir 都是字符串,最后被拼成一条直接喂给 shell 的命令。
当 role == "admin" 时,你允许:
pythondirname = request.form.get("dirname") or str(uuid.uuid4()) target_dir = os.path.join(UPLOAD_FOLDER, dirname)
也就是说:
dirname 完全由前端可控 → 影响 target_dir → 直接进入 os.system(...) 里的命令行。
一旦任何一个参数没做转义/校验,就存在典型的 OS Command Injection(操作系统命令注入) 风险。
在命令注入里,思路通常是:
“把原本的命令拆开,插入额外的命令,比如
whoami、id等,再让 shell 一起执行。”
例如利用 shell 的 ;, &&, | 等分隔符,把额外命令拼到参数里。
但具体的 payload / 注入手法就属于“可直接用于攻击”的内容,这部分我不能给出具体写法。
你只要知道:只要用户输入的内容没有被正确转义且进入了 shell 字符串,那理论上就能被用来执行任意命令。
os.system 拼字符串最推荐的方式:
subprocess.run + 参数列表(不经过 shell)pythonimport subprocess # ... try: # 不用 shell=True,直接传列表 subprocess.run( ["unzip", "-o", zip_path, "-d", target_dir], check=True ) except subprocess.CalledProcessError: return "解压失败,请检查文件格式"
这样有几点好处:
unzip 的每个参数是单独的元素,不会被当成 shell 代码解析dirname 里写 "; whoami; #",也只会被当作一个普通路径名,而不是命令dirname 的合法字符即便不用 shell,你还是应该限制目录名的合法范围,避免目录穿越或乱七八糟的名字。
例如,只允许字母、数字、下划线、中划线:
pythonimport re def sanitize_dirname(name: str) -> str: # 只允许字母、数字、下划线、中划线,长度 1~64 if not re.fullmatch(r"[A-Za-z0-9_-]{1,64}", name): raise ValueError("非法目录名") return name
在 /upload 里:
pythonif role == "admin": raw_dirname = request.form.get("dirname") or str(uuid.uuid4()) else: raw_dirname = str(uuid.uuid4()) try: dirname = sanitize_dirname(raw_dirname) except ValueError: return "目录名不合法" target_dir = os.path.join(UPLOAD_FOLDER, dirname) os.makedirs(target_dir, exist_ok=True)
如果你愿意,可以完全不用系统的 unzip,而是用 zipfile 模块:
pythonimport zipfile try: with zipfile.ZipFile(zip_path, 'r') as zf: zf.extractall(target_dir) except zipfile.BadZipFile: return "解压失败,请检查文件格式" finally: if os.path.exists(zip_path): os.remove(zip_path)
这样:
unzip 命令你在下载时用的是:
pythonwith open(file_path, 'r') as file: # 对二进制不合适 content = file.read()
这里应该用二进制模式 'rb',否则图片/压缩包等会损坏:
pythonwith open(file_path, 'rb') as file: content = file.read() return Response( content, mimetype="application/octet-stream", headers={"Content-Disposition": f"attachment; filename={filename}"} )
或者更简单地用 Flask 内置的:
pythonfrom flask import send_from_directory @app.route("/download/<folder>/<filename>") def download_file(folder, filename): target_dir = os.path.join(UPLOAD_FOLDER, folder) if not os.path.exists(target_dir): return "Folder not found", 404 return send_from_directory(target_dir, filename, as_attachment=True)
你现在这行代码是 典型命令注入点:
pythonos.system(f"unzip -o {zip_path} -d {target_dir}")
任何直接把用户输入拼进 shell 命令字符串的行为都是高危的。
我不能教你怎么用它去执行 whoami 这种命令,但可以告诉你:
如果你愿意,我可以帮你把整个 Flask 小项目改成一个更安全版(包含模板字段、错误提示优化之类的),你只要把现在的代码结构说一下就行。