import os import uuid from flask import Flask, req...

Creato il: 25 novembre 2025

Risposto usando GPT-5.1 Thinking da Chat01

Domanda

import os
import uuid
from flask import Flask, request, redirect, url_for, send_file, render_template, session, send_from_directory, abort, Response

创建Flask应用实例,__name__用于确定应用根目录以便查找资源

app = Flask(name)

设置应用的密钥,用于加密会话(session)。优先从环境变量FLASK_SECRET_KEY获取,若未设置则使用默认值'test_key'(生产环境应避免使用默认值)

app.secret_key = os.environ.get("FLASK_SECRET_KEY", "test_key")

定义文件上传的目录路径,这里设置为当前工作目录下的"uploads"文件夹

UPLOAD_FOLDER = os.path.join(os.getcwd(), "uploads")

确保上传目录存在,exist_ok=True表示如果目录已存在也不会引发错误

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"]

text
if 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():

text
if 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"))

text
if 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)

text
files = 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命令

Pensiero

Explaining secure fixes for code vulnerability

I can't help exploit vulnerabilities, but I can explain how to fix them. For instance, use Python's zipfile, avoid os.system, and use shlex.quote. You should sanitize directories, ensuring they follow safe patterns. It's crucial to use proper security testing tools or consult professionals. A secure code example could replace vulnerable lines with subprocess.run and sanitize directory inputs.

Thought for 35s

Risposta

你这段代码确实有命令注入风险,但我不能教你怎么利用它去执行 whoami 之类的命令 —— 这等于是在教你如何攻击/利用漏洞,这是被禁止的 🙅‍♂️

不过我可以详细跟你说:

  • 为什么这一行有问题
  • 攻击者大概可以怎么利用(概念层面)
  • 你应该怎么修 & 加固这段代码

1. 问题出在这里

python
os.system(f"unzip -o {zip_path} -d {target_dir}")

zip_pathtarget_dir 都是字符串,最后被拼成一条直接喂给 shell 的命令

role == "admin" 时,你允许:

python
dirname = request.form.get("dirname") or str(uuid.uuid4()) target_dir = os.path.join(UPLOAD_FOLDER, dirname)

也就是说:
dirname 完全由前端可控 → 影响 target_dir → 直接进入 os.system(...) 里的命令行。

一旦任何一个参数没做转义/校验,就存在典型的 OS Command Injection(操作系统命令注入) 风险。


2. 概念层面:攻击者会怎么想?

在命令注入里,思路通常是:

“把原本的命令拆开,插入额外的命令,比如 whoamiid 等,再让 shell 一起执行。”

例如利用 shell 的 ;, &&, | 等分隔符,把额外命令拼到参数里
但具体的 payload / 注入手法就属于“可直接用于攻击”的内容,这部分我不能给出具体写法。

你只要知道:只要用户输入的内容没有被正确转义且进入了 shell 字符串,那理论上就能被用来执行任意命令。


3. 正确做法:完全不要用 os.system 拼字符串

最推荐的方式:

  1. 不用 shell 解析命令字符串
  2. 用参数列表的方式调用命令
  3. 对所有用户输入进行校验

✅ 用 subprocess.run + 参数列表(不经过 shell)

python
import subprocess # ... try: # 不用 shell=True,直接传列表 subprocess.run( ["unzip", "-o", zip_path, "-d", target_dir], check=True ) except subprocess.CalledProcessError: return "解压失败,请检查文件格式"

这样有几点好处:

  • unzip 的每个参数是单独的元素,不会被当成 shell 代码解析
  • 哪怕用户在 dirname 里写 "; whoami; #",也只会被当作一个普通路径名,而不是命令

4. 再进一步:限制 dirname 的合法字符

即便不用 shell,你还是应该限制目录名的合法范围,避免目录穿越或乱七八糟的名字。

例如,只允许字母、数字、下划线、中划线:

python
import 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 里:

python
if 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)

5. 其他顺手可以改的点(安全/健壮性)

5.1 解压尽量用 Python 自带的库,而不是系统命令

如果你愿意,可以完全不用系统的 unzip,而是用 zipfile 模块:

python
import 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 命令
  • 更加可控,也更少安全坑

5.2 二进制文件下载方式

你在下载时用的是:

python
with open(file_path, 'r') as file: # 对二进制不合适 content = file.read()

这里应该用二进制模式 'rb',否则图片/压缩包等会损坏:

python
with open(file_path, 'rb') as file: content = file.read() return Response( content, mimetype="application/octet-stream", headers={"Content-Disposition": f"attachment; filename={filename}"} )

或者更简单地用 Flask 内置的:

python
from 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)

6. 总结一下

  • 你现在这行代码是 典型命令注入点

    python
    os.system(f"unzip -o {zip_path} -d {target_dir}")
  • 任何直接把用户输入拼进 shell 命令字符串的行为都是高危的。

  • 我不能教你怎么用它去执行 whoami 这种命令,但可以告诉你:

    • 这是严重漏洞
    • 概念上是命令注入
    • 正确做法是:不用 shell 拼字符串 → 用参数列表 / 标准库 → 校验用户输入

如果你愿意,我可以帮你把整个 Flask 小项目改成一个更安全版(包含模板字段、错误提示优化之类的),你只要把现在的代码结构说一下就行。

Condividi questa Q&A