问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
Pydash set方法原型链污染漏洞分析:以Bottle框架环境变量泄露为例
漏洞分析
NCTF遇到了一道pydash题目,似乎与SUCTF2025出的一道SU_blog都是这个知识点,遂想基于这道题分析一下链子。其实还可以结合idekctf那道题
前言 == NCTF遇到了一道pydash题目,似乎与SUCTF2025出的一道SU\_blog都是这个知识点,遂想基于这道题分析一下链子。其实还可以结合idekctf那道题 NCTF-Pydash =========== 我们首先来贴一下题目给的源码然后下载一下Pydash的源码 ```python ''' Hints: Flag在环境变量中 ''' from typing import Optional import pydash import bottle __forbidden_path__=['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__wrapped__', "Optional","render" ] __forbidden_name__=[ "bottle" ] __forbidden_name__.extend(dir(globals()["__builtins__"])) def setval(name:str, path:str, value:str)-> Optional[bool]: if name.find("__")>=0: return False for word in __forbidden_name__: if name==word: return False for word in __forbidden_path__: if path.find(word)>=0: return False obj=globals()[name] try: pydash.set_(obj,path,value) except: return False return True @bottle.post('/setValue') def set_value(): name = bottle.request.query.get('name') path=bottle.request.json.get('path') if not isinstance(path,str): return "no" if len(name)>6 or len(path)>32: return "no" value=bottle.request.json.get('value') return "yes" if setval(name, path, value) else "no" @bottle.get('/render') def render_template(): path=bottle.request.query.get('path') if len(path)>10: return "hacker" blacklist=["{","}",".","%","<",">","_"] for c in path: if c in blacklist: return "hacker" return bottle.template(path) bottle.run(host='0.0.0.0', port=8000) ``` 主要看一下这里的`set_` ```python pydash.set_(obj,path,value) ``` 我们来看一下pydash关于这一个函数是如何用的 /src/pydash/objects.py ```python def set_(obj: T, path: PathT, value: t.Any) -> T: """ Sets the value of an object described by `path`. If any part of the object path doesn't exist, it will be created. Args: obj: Object to modify. path: Target path to set value to. value: Value to set. Returns: Modified `obj`. Warning: `obj` is modified in place. Example: >>> set_({}, "a.b.c", 1) {'a': {'b': {'c': 1}}} >>> set_({}, "a.0.c", 1) {'a': {'0': {'c': 1}}} >>> set_([1, 2], "[2][0]", 1) [1, 2, [1]] >>> set_({}, "a.b[0].c", 1) {'a': {'b': [{'c': 1}]}} .. versionadded:: 2.2.0 .. versionchanged:: 3.3.0 Added :func:`set_` as main definition and :func:`deep_set` as alias. .. versionchanged:: 4.0.0 - Modify `obj` in place. - Support creating default path values as ``list`` or ``dict`` based on whether key or index substrings are used. - Remove alias ``deep_set``. """ return set_with(obj, path, value) ``` 也就是说我们往name中传入一个对象,path中传入其属性名,values传入更改的值就可以改掉其属性的值,例如这样子 ```python import pydash class Apple: def __init__(self): self.name = "apple" self.sweet = 10 a = Apple() print(f"修改前: {a.sweet}") pydash.set_(a, "sweet", 100) print(f"修改后: {a.sweet}") ```  所以我们就要利用这个来进行污染某些参数从而读取到environ。 我们先来看看要污染什么值,首先从这个源码看不到可以读取environ的地方,所以我们应该去找一下import的库,可以看到最后面`return bottle.template(path)`return了一个值,利用bottle来渲染模板,遂看其源码。  我们直接搜索到对应的函数,看到这一行 ```python adapter = kwargs.pop('template_adapter', SimpleTemplate) lookup = kwargs.pop('template_lookup', TEMPLATE_PATH) ``` 可以看到默认的模板引擎是SimpleTemplate,lookup就是模板的搜索路径也就是`TEMPLATE_PATH`这个变量 默认的TEMPLATE\_PATH的值为`['./','./views/']`,所以会去这里面去lookup 然后接着交给`SimpleTemplate`去解析 ```python TEMPLATES[tplid] = adapter(source=tpl, lookup=lookup, **settings) else: TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings) ``` 跟进这个解析器,然后因为后面传入的是name是一个path而不是一个模板,所以会走到BaseTemplate这里先寻找并解析目录下的模板文件再扔给SimpleTemplate进行渲染解析。  这里的name就是传入的`bottle.template(path)`的值,这里假设我们path传的是`environ`,然后通过`abspath()`方法得到TEMPLATE\_PATH的绝对路径。接着进入search方法(name=environ)  search方法中  最后把name拼接到lookup中的每个绝对路径中,然后读到文件后就返回这个fname,接着走到`SimpleTemplate`的`prepare`方法 ```python class SimpleTemplate(BaseTemplate): def prepare(self, escape_func=html_escape, noescape=False, syntax=None, **ka): self.cache = {} enc = self.encoding self._str = lambda x: touni(x, enc) self._escape = lambda x: escape_func(touni(x, enc)) self.syntax = syntax if noescape: self._str, self._escape = self._escape, self._str ``` 这个方法的作用就是初始化模板的字符处理逻辑,支持 HTML 转义或直接输出原始 HTML,也就是解析这个传入的template变成html 然后回到template结尾 ```python TEMPLATES[tplid].render(kwargs) ``` 进入render方法  走到execute方法  构建了env执行环境,然后在env中执行预编译的模板也就是这里的self.c  如果模板调用 `rebase()` 继承父模板就递归渲染父模版 这个时候就会去读取environ模板文件  然后在render中返回解析后的内容  至此整个链子就分析完了,最后整理一下链子逻辑 ```php template:adapter()->class:BaseTemplate:search()->class:SimpleTemplate:prepare()->render()->exec()->stdout ``` 所以很简单,我们只需要将模板文件改成linux的proc让他去读environ即可,利用`set_`方法更改掉TEMPLATE\_  其默认的值为`./`和`./views/`,我们需要读取其environ,就可以利用`../../../proc/self/environ`来读取,只需要我们将TEMPLATE\_PATH改为`../../../proc/self/`然后把environ放入到其中进行渲染即可得到环境变量。 接下来去看setval函数 ```python def setval(name:str, path:str, value:str)-> Optional[bool]: if name.find("__")>=0: return False for word in __forbidden_name__: if name==word: return False for word in __forbidden_path__: if path.find(word)>=0: return False obj=globals()[name] try: pydash.set_(obj,path,value) except: return False return True ``` 所以结合黑名单,playload应该是这样子的 ```python setval.__globals__.bottle.TEMPLATE_PATH=['../../../../../proc/self/'] ``` 但是pydash是不允许去修改**globals**属性的,去看⼀下代码 在helpers.py中  所以我们要先污染这个`RESTRICTED_KEYS`之后再去污染bottle的值即可。  接着  最后渲染  得到flag
发表于 2025-04-02 09:46:42
阅读 ( 281 )
分类:
WEB安全
0 推荐
收藏
0 条评论
请先
登录
后评论
梦洛
9 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!