[NISACTF 2022]babyupload_WP

POC

查看网页源码发现/source路径,访问这个路径,得到了源码,审阅源码

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 """<!DOCTYPE html>
<html>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    Select image to upload:
    <input type="file" name="file">
    <input type="submit" value="Upload File" name="submit">
</form>
<!-- /source -->
</body>
</html>"""


@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/<id>')
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)

os.path.join()

这里面有一个重要的函数,os.path.join()函数,函数定义:

智能地拼接一个或多个路径部分。 返回值是 path 和 *paths 的所有成员的拼接,其中每个非空部分后面都紧跟一个目录分隔符,最后一个部分除外,这意味着如果最后一个部分为空,则结果将以分隔符结尾。 如果某个部分为绝对路径,则之前的所有部分会被丢弃并从绝对路径部分开始继续拼接。

但是os.path.join()有个特殊的特性:相对路径的开头不能有 \ 符号,如果有这个符号,则是绝对路径,并且会丢弃前面的路径

如果在程序设计过程中忽略了这个问题则可能导致出现安全漏洞

 

分析上面的代码得到如下的思路:

上传文件的时候upload函数会先判断是否含有 “ . ” ,如果有则返回403;接着会为上传的文件分配一个uuid,并将文件名和对应的uuid写入数据库中,接着会将文件保存到upload/目录下面,然后会跳转到/file/<uuid>路径下面,由file函数处理;

file函数会在数据库中搜索该uuid对应的文件名,然后使用os.path.join()方法拼接完整的路径

漏洞就出现在拼接路径那里,如果uuid对应的文件名前面带一个斜杠,那么这个文件名实际上就是对应着一个绝对路径,而os.path.join()恰巧会因为后面是绝对路径而忽略掉前面的所有路径,导致了非放访问

EXP

上传一个名字为\flag的任意文件,当跳转到这个文件对应的uuid的时候实际是访问的/flag文件,成功获取flag