两个“Medium”发现导致完整基础设施被攻破

发布: (2026年2月6日 GMT+8 00:20)
8 min read
原文: Dev.to

Source: Dev.to

你是否有过这样的感觉,当你在对安全发现进行分流时,看到积压中一堆 mediums?它们最终会被修复——大概——在 criticalshighs 以及 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..."
}

无身份验证。任意的 recipientsubject 和 HTML body

当该请求被处理时,应用会通过组织的合法邮件基础设施发送邮件。邮件来自经过授权的邮箱,并带有正确的身份验证。

实际含义

  • 邮件通过 SPFDKIMDMARC 检查
  • 发件人显示组织的官方邮件地址
  • 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

  1. 锁定公共邮件发送端点——使用严格模式、固定模板、身份验证、速率限制。
  2. 清理错误响应——永不泄露令牌或内部上下文。
  3. 监控 异常的邮件发送活动和令牌泄漏模式。

通过同时解决这两个问题,你可以切断攻击链,消除攻击者将你的基础设施用于钓鱼平台的能力。

示例:订阅端点

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

生产环境绝不应向客户端返回堆栈跟踪或应用程序上下文。

要点

两个中等严重性发现:

  1. 一个端点接受的参数过多。
  2. 另一个端点返回的信息过多。

您的漏洞扫描器分别评估了这些并给出了相应的评级。扫描器并没有错,但它的思考方式不像攻击者。

攻击者不在乎严重性评级。他们在乎的是 路径

Back to Blog

相关文章

阅读更多 »