[NISACTF 2022]babyupload

[NISACTF 2022]babyupload wp

信息搜集

进入页面:

[NISACTF 2022]babyupload_第1张图片

尝试文件上传,但是各种后缀名我都试过了,过不去。

在源码中发现提示,存在 ./source 路径:

[NISACTF 2022]babyupload_第2张图片

访问该路径得到源码:

from flask import Flask, request, redirect, g, send_from_directory
import sqlite3
import os
import uuid

app = Flask(__name__)

SCHEMA = """CREATE TABLE files (
id text primary key,
path text
);
"""


def db():
    g_db = getattr(g, '_database', None)
    if g_db is None:
        g_db = g._database = sqlite3.connect("database.db")
    return g_db


@app.before_first_request
def setup():
    os.remove("database.db")
    cur = db().cursor()
    cur.executescript(SCHEMA)


@app.route('/')
def hello_world():
    return """


Select image to upload:
""" @app.route('/source') def source(): return send_from_directory(directory="/var/www/html/", path="www.zip", as_attachment=True) @app.route('/upload', methods=['POST']) def upload(): if 'file' not in request.files: return redirect('/') file = request.files['file'] if "." in file.filename: return "Bad filename!", 403 conn = db() cur = conn.cursor() uid = uuid.uuid4().hex try: cur.execute("insert into files (id, path) values (?, ?)", (uid, file.filename,)) except sqlite3.IntegrityError: return "Duplicate file" conn.commit() file.save('uploads/' + file.filename) return redirect('/file/' + uid) @app.route('/file/') def file(id): conn = db() cur = conn.cursor() cur.execute("select path from files where id=?", (id,)) res = cur.fetchone() if res is None: return "File not found", 404 # print(res[0]) with open(os.path.join("uploads/", res[0]), "r") as f: return f.read() if __name__ == '__main__': app.run(host='0.0.0.0', port=80)

这道题的要求就两个:看懂 python 代码;os.path.join 绝对路径拼接。

加强 python 基础

为了能更好的看懂本次 python 代码,我推荐以下文章:

python 连接数据库

Python getattr() 函数

关于python中的查询数据库内容中用到的fetchone()函数

request.files 介绍

在 Flask 框架中,request.files 是一个代表上传文件的特殊字典对象。它允许您访问用户通过 HTTP 请求上传的文件。具体来说,在 Flask 中,当您的应用程序收到一个带有文件上传的 POST 请求时,可以使用 request.files 来获取上传文件的信息。

request.files 是一个字典,它的键是文件域( input 标签的 name 属性值),而值是一个 FileStorage 对象,该对象提供了访问和操作上传文件的方法和属性。

例如,假设您的 HTML 表单中有一个文件上传域,其 name 属性为 "file"

Select image to upload:

那么您可以通过以下方式访问上传文件:

file = request.files['file']

上面的代码将返回上传文件的 FileStorage 对象,您可以使用该对象的方法和属性来获取文件的详细信息,如文件名、内容类型、保存文件等:

filename = file.filename          # 获取上传文件的文件名
file.save('/path/to/save/file')   # 将上传文件保存到指定路径

通过 request.files,您可以轻松地处理和管理用户上传的文件数据。注意,为了使用 request.files,您的请求必须使用 enctype="multipart/form-data" 编码类型,这是用于文件上传的标准编码类型。

关于 python 的 flask 框架,在学 SSTI 模板注入时有所学习。

源码分析

# 设置了一个路由,当以 POST 方式访问 ./upload 页面时,就会触发 upload() 函数
@app.route('/upload', methods=['POST'])
def upload():
    if 'file' not in request.files:
        return redirect('/')
# 从 HTML 表单中获取 file 对象,即用户上传的文件
    file = request.files['file']
# 文件名中不能有小数点
    if "." in file.filename:
        return "Bad filename!", 403
# 调用 db() 函数连接数据库
    conn = db()
    cur = conn.cursor()
# 调用 uuid 模块以某种方式生成文件的 uid
    uid = uuid.uuid4().hex
    try:
        cur.execute("insert into files (id, path) values (?, ?)", (uid, file.filename,))
    except sqlite3.IntegrityError:
        return "Duplicate file"
    conn.commit()
# 将文件保存到 ./uploads/ 路径下
    file.save('uploads/' + file.filename)
# 返回最终保存的文件路径
    return redirect('/file/' + uid)

不过在此段代码中 file.save('uploads/' + file.filename) 并没有起到作用,推测原因是 uploads/ 路径不存在。

下面来看这段代码:

# 设定路由,当访问 /file/ 路径时触发 file 函数,其中  为用户输入的任意值,该值会被作为 file 函数的参数值
@app.route('/file/')
def file(id):
# 连接数据库
    conn = db()
    cur = conn.cursor()
# 执行 SQL 语句,根据 id 查找 path
    cur.execute("select path from files where id=?", (id,))
# fetchone 函数的作用在上面的博客中已提到,执行该语句后,res[0] 就是 SQL 语句查询到的 path ,根据源代码,path 就是传入的文件名
    res = cur.fetchone()
    if res is None:
        return "File not found", 404

    # print(res[0])
# os.path.join 函数拼接路径,再 open 打开文件
    with open(os.path.join("uploads/", res[0]), "r") as f:
        return f.read()

os.path.join 绝对路径拼接

os.path.join(path, *paths) 函数用于将多个文件路径连接成一个组合的路径。第一个参数通常包含了基础路径,而之后的每个参数都被当做组件拼接到基础路径后。

但是该函数有个特性:如果拼接的某个路径以 / 开头,那么包括基础路径在内的所有前缀路径都将被删除,该路径将被视为绝对路径。下面的示例揭示了开发者可能遇到的这个陷阱。

比如,上述代码中,若 res[0] 以 / 开头,则前面的 uploads/ 将被抹去,最后拼接的结果只有 res[0] 。

漏洞利用

据此,我们可以传入一个名为 “/flag” 的文件,那么最后经过路径拼接抹去前面的 uploads/ 路径,就会直接打开根目录下的 flag 文件。

当然,这种方法存在猜测性,前提是根目录下确实有一个 flag 文件。

文件名为 /flag ,文件内容任意:

[NISACTF 2022]babyupload_第3张图片

返回结果中给出了路径:/file/de087626346e4fc390a272957aa5b88e ,那么直接访问:

[NISACTF 2022]babyupload_第4张图片

拿到 flag 。

你可能感兴趣的:(ctf,web安全,网络安全,python)