我敢说这些嗯抄gxngxngxn那篇分析sanic博客的家伙都是要么没过脑子要么懒成狗= =
包括gxngxngxn的那篇分析,其实最关键的步骤只告诉了“要怎么做”,而没有告诉“为什么要这样做”
我就直接把自己总结的python框架的原型链污染题的解题思路在这道题中体现出来吧
学框架污染前提肯定是python原型链基础,建议可以先看这篇。如果你还不理解原型链污染,那么建议先看看js原型链污染
这里我就跳过admin绕过,因为这不是我们本篇分析的重点。这一部分可以去看gxngxngxn的分析博客
第一步,找污染点
核心肯定就是利用对pollute类的pydash._set污染,从而污染到其他我们想污染的内容。目标很明确,我们想要读取flag,也就是要读取文件。就先来找找题目源码有没有可以直接污染就能读文件的地方
很明显,我们如果能控制__file__的值,就能读取任意文件。这里我放两个repeater
repeater 1用来查看src的内容和全局变量状态
repeater 2用来发送污染数据
并且在src的return打个断点
首先访问一下src,看一下全局变量状态
很容易发现__file__,而我们要污染到全局变量的__file__,就要先知道污染点现在在哪。很明显在Pollute类,所以我们用__class__访问类,__init__访问初始化函数,__globals__访问全局变量,就能访问到__file__。具体原理参考上面贴的原型链污染基础文章
这里我先污染成我自己创建的文件试一下。先把刚才的断点放行后污染一下
在访问一遍src,就可以发现__file__值被污染
但如果我们上靶机上试,直接污染成/flag是读取不了的,很有可能是flag文件名是随机的,所以我们要想别的办法。所以__file__污染这条路是走不通的。一些wp并没有说明这点,容易误导新手
那还有什么地方可以污染吗?通篇阅读一下题目源码,会发现只有app.static这一个地方有涉及到文件路径,但很明显这里是写死的路径,我们没有办法修改./static
这个参数的值(也就是下图的file_or_directory)。但一般这类框架内置方法都不会只有短短两个参数,所以我们可以跟进一下static方法
会发现参数确实不少,在题目源码中被赋值的只有uri和file_or_directory两个参数。我们接着往下翻,就会看到一大串注释,这些注释就是介绍每个参数的功能。如果你在国赛当天写这道题,那当然是要把每个注释都看过去一遍。所以我们看到这个地方
这两个参数在题目源码里是没有被赋值的,一个意思是开启列目录的功能,另一个意思是所列目录的路径。我们可以先污染一下directory_view成true看看会发生什么。
第二步,找在哪进行污染
这里在哪进行污染并不只是admin路由下的pydash._set,更重要的是我要污染哪个变量下的哪个属性,也就是污染链。
我们可以很清楚知道__class__.__init__.__globals__
是正在执行的题目文件的全局变量环境,所以我们先来全局变量里有哪些变量可以污染。一样在src打断点访问
除了特殊变量和函数、类以外,只有两个变量。pydash是我们用来进行污染的工具,本身要污染的点肯定不在pydash里,而是在sanic。而app=Sanic(__name__)
,所以我们跟进app变量
此时你会看到一堆眼花缭乱的变量,但别着急,我们为了找到能过污染的点,必须要先清楚一个python小知识,也就是污染点必须是能够被我们写入的。所以反过来想,如果我们把不能写入的能先排除,那就可以进一步缩小范围了。而在python中,常用且不能写入的基础数据类型只有一种,那就是元组。所以我们就先不看元组数据,也就是像下面这样的子变量
并且我们其实也可以先将上面这种特殊变量先跳过,因为我们现在很明确自己要找的是和static下的路径,也就是和路由有关的内容,所以我们可以将目光放在app变量下和route类似的名字上。
(其实在这个地方我想过很久的思维链,为什么会想要找和route有关的东西?直到发文前一天晚上还在一位和很有开发经验的朋友一起研究时才告诉我,这其实只能当作一种常识记下来,就像在web开发时,其实大部分时间也是在做和路由有关的事情,所以就应该首先想到路由。如果你和我一开始一样,还是觉得这样看到route有点无厘头,觉得在实战中可能并不一定会出现在route,那么你也可以像我一样将别的变量全部检测过去一次,以防万一,甚至还有可能发现别的污染链)
可以发现一个router
接着我们继续按照上面说到的筛选条件,也就是不看元组,来继续找找可利用的变量。可以发现有name_index,routes_all,routes_static,static_routes等一众可疑变量
到这里如果是在比赛中,就必须要将每一个可以变量跟进看看能不能达到directory_view(这里需要注意,我们要污染的directory_view在static方法下,所以我们只用看static的路由)。可以的话例如我尝试跟进routes_all和routes_static
这是routes_all,可能存在污染
routes_static没有static路由,所以不可能存在污染
就像这样,将每一个变量跟一遍为止,直到找出directory_view。并逐个污染,看污染后的值会不会保留。
比如我污染routes_all试试
返回success,再次访问src验证是否污染成功
仍然是false
经过大量尝试后会发现只有在污染name_index下的变量时才能成功保留。层级如下(太长了截屏截不下,只能用表达式表示)
app.router.routes_all[('static', '<__file_uri__:path>')].handler.keywords['directory_handler'].directory_view
这也是这道题为什么在当时只有一支队做出来的原因,也是这题真正的难点。但gxngxngxn的分析博客和其他抄他的人只在wp中草草提到这个name_index是“经过查询资料”后得到的。
也正因为查了两周的大大小小各种资料仍找不到他说的这个功能,所以才决定自己复现= =也许人家是真找到了,但可以说跟风的人都没有去验证的脑子啊……
第三步,开始污染
找到了需要污染点和怎么污染的链子,就可以开始污染了。
再来回顾一下第一步的总结,我们需要污染这两个东西
directory_view已经在上一步污染成功,那么接下来要污染的就是显示的路径。我们跟进static找一下directory_handler的引用
因为static方法在一个class下,所以我们只需要在static这个方法内看其他代码对directory_handler到底做了什么就行。会发现能改变这个变量的地方只有一个
将DirectoryHandler类赋值给directory_handler。我们跟进这个类。老规矩,先看注释
会发现directory这个属性就是我们最终要污染的东西。但需要注意,他是一个Path类型的值。
通篇阅读DirectoryHandler这个类后会发现没有任何操作会影响directory这个值,所以我们想办法看能不能通过污染Path来达到目的。跟进Path
注释大概意思就是说这个类是用来在不同操作系统上调用路径的。往下看一下__new__逻辑
很简短,第一个if用来判断操作系统是什么,接着给self赋值一个父类上的_from_parts,最后判断一下操作系统支不支持这种调用方法。我们跟上去看看_from_parts
其实就是对几个值进行了赋值。这里的_parse_args类似php中的parse_str,我就不跟了。
所以我们只要在_drv,_root,_parts中找到存放路径变量的值就行了。
我们重新访问一遍src
在变量列表中我没有看到_root,所以应该不重要。很明显这里的_parts嫌疑更大,我们尝试污染
成功污染