两个“Medium”发现导致完整基础设施被攻破
Source: Dev.to
你是否有过这样的感觉,当你在对安全发现进行分流时,看到积压中一堆 mediums?它们最终会被修复——大概——在 criticals、highs 以及 PM 已经提了三个冲刺的那个功能之后。
关键是:攻击者不会按严重程度分流,他们按 chains together 来分流。
我想要逐步讲解我们最近记录的一个漏洞链,它将两个完全不起眼的发现组合在一起,形成能够进行 authenticated phishing 并对 Microsoft 365 环境实现 persistent access 的能力。
Neither finding would make anyone panic. Together, they’re a full compromise.
Source: …
发现 #1:功能过度的新闻通讯接口
每个 Web 应用都有发送邮件的接口——新闻通讯订阅、联系表单、密码重置、事务性通知。
这些接口必须公开才能工作(这正是它们的目的),但它们也需要 严格的输入验证 来防止滥用。
易受攻击的模式
POST /api/newsletter/subscribe HTTP/1.1
Content-Type: application/json
{
"recipient": "victim@target.com",
"subject": "Urgent: Security Alert",
"body": "...phishing content..."
}
无身份验证。任意的 recipient、subject 和 HTML body。
当该请求被处理时,应用会通过组织的合法邮件基础设施发送邮件。邮件来自经过授权的邮箱,并带有正确的身份验证。
实际含义
- 邮件通过 SPF、DKIM 和 DMARC 检查
- 发件人显示组织的官方邮件地址
- Gmail 会因合法来源自动标记为 “重要”
- 邮件进入收件箱主页面,而非垃圾邮件
你刚刚把目标自己的基础设施变成了钓鱼平台。
查找这些接口并不难
site:target.com newsletter
site:target.com "sign up"
site:target.com contact
未在主导航中链接的页面往往仍被索引并且可以正常使用。
发现 #2:泄露令牌的错误信息
第二个发现涉及生产环境中的冗长错误处理。你之前已经见过这种模式:
POST /api/newsletter/subscribe
Content-Type: application/json
{
"recipient": "test@test.com"
// missing required fields
}
响应
{
"error": "ValidationError",
"stack": "...",
"context": {
"oauth_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"service": "graph.microsoft.com",
...
}
}
为什么错误响应会包含 OAuth 令牌?
在许多应用程序中,内部服务通过存储在应用上下文中的令牌相互认证。冗长的错误处理会将该上下文转储给客户端,意外地暴露令牌。
在此案例中,泄露的令牌是针对 Microsoft Graph API 的。
根据作用域,这可以授予以下访问权限:
- 邮件(读取 & 发送)
- 日历
- Teams 对话
- SharePoint 和 OneDrive 文件
- 用户目录和组织结构图
- 有时还有 Azure 资源和 Intune
“但是令牌在一小时后会过期。”
确实如此。但你可以再次触发错误以获取新的令牌。该漏洞变成了一个 令牌分发器——无需凭证即可持久化。

链条
第1阶段:令牌提取
攻击者发现详细错误条件,获取有效的 Graph 令牌,从而在不触发登录失败警报的情况下获得已认证的 M365 访问权限。
第2阶段:侦察
使用该令牌,他们枚举:
# Get org chart
GET https://graph.microsoft.com/v1.0/users
# Get user details
GET https://graph.microsoft.com/v1.0/users/{id}
# Get manager chain
GET https://graph.microsoft.com/v1.0/users/{id}/manager
员工姓名、职务、项目、汇报结构、内部术语——所有用于制作可信钓鱼邮件的必要信息。
第3阶段:针对性钓鱼
他们利用邮件端点发送钓鱼活动。这些并非通用的“点击此处验证账户”邮件;而是使用真实的项目名称、准确的组织结构和内部术语精心制作,并且来自组织自己的邮件服务器。
第4阶段:升级
收集到的凭证让攻击者进一步渗透——管理员账户、Azure 资源、生产基础设施。

第5阶段:持久化
只要详细错误仍然存在,攻击者就能重新生成令牌。凭证轮换无济于事——漏洞本身就是持久化机制。
修复
Email endpoint
# Bad: accepts arbitrary input
@app.post("/api/newsletter/subscribe")
def subscribe(data: dict):
send_email(
to=data.get("recipient"),
subject=data.get("subject"),
body=data.get("body")
)
# Good: strict schema, single purpose
class SubscribeRequest(BaseModel):
email: EmailStr
@app.post("/api/newsletter/subscribe")
def subscribe(request: SubscribeRequest):
# Only allow sending a predefined template to the supplied email
send_email(
to=request.email,
subject="Thank you for subscribing!",
body=render_template("newsletter_welcome.html")
)
- 验证 输入使用严格的模式(例如 Pydantic)。
- 限制 端点为单一、不可配置的用途(例如,仅发送固定模板)。
- 对 内部调用者进行身份验证(如果可能);否则,实施速率限制和日志记录。
Verbose error handling
- 永不泄露 错误响应中的内部上下文(尤其是令牌)。
- 向客户端返回通用错误信息,并在服务器端记录详细的堆栈跟踪。
- 轮换并使用短生命周期的令牌,并强制执行最小权限范围。
- 实现一个集中式错误处理中间件,对响应进行清理。
TL;DR
- 锁定公共邮件发送端点——使用严格模式、固定模板、身份验证、速率限制。
- 清理错误响应——永不泄露令牌或内部上下文。
- 监控 异常的邮件发送活动和令牌泄漏模式。
通过同时解决这两个问题,你可以切断攻击链,消除攻击者将你的基础设施用于钓鱼平台的能力。
示例:订阅端点
@app.post("/subscribe")
def subscribe(data: SubscribeRequest):
add_to_mailing_list(data.email)
send_confirmation_email(data.email) # fixed template
如果是新闻通讯注册,只接受电子邮件地址——仅此而已。
错误处理
# Bad: dumps everything to client
@app.exception_handler(Exception)
def handle_error(request, exc):
return JSONResponse({
"error": str(exc),
"stack": traceback.format_exc(),
"context": app.state.__dict__ # tokens live here
})
# Good: generic client response, detailed server logs
@app.exception_handler(Exception)
def handle_error(request, exc):
logger.error(f"Error: {exc}", exc_info=True) # server‑side only
return JSONResponse({
"error": "An error occurred",
"request_id": generate_request_id()
})
生产环境绝不应向客户端返回堆栈跟踪或应用程序上下文。
要点
两个中等严重性发现:
- 一个端点接受的参数过多。
- 另一个端点返回的信息过多。
您的漏洞扫描器分别评估了这些并给出了相应的评级。扫描器并没有错,但它的思考方式不像攻击者。
攻击者不在乎严重性评级。他们在乎的是 路径。