漏洞

SMF 图片代理替论坛用户签下了一次内网请求(CVE-2026-61520

启用图片代理的 Simple Machines Forum 会把登录用户写入帖子的图片地址加工成带 HMAC 的代理请求,旧版校验没有完整约束域名解析结果与重定向目的地,使这张由服务器亲自签发的“取图票据”可以把 HTTP 请求带入内网。

暖纸手绘的 SMF 3.0 修复闭环:DNS 的 A 与 AAAA 结果汇入地址集合,全局范围闸门拒绝私网及 NAT64 转私网目的地;3xx 响应返回 URL 再做一次判断。
文章导航

研究依据SOSEC Web 安全研究 · 源码固定于 90ff9400d641 ,分别复核 release-2.1 与 release-3.0 的请求生成、HMAC 校验、解析、连接和重定向链路

来源GitHub Advisory Database 未审条目 / CVE 记录 / SMF 2.1 与 3.0 修复提交 / SMF 源码 / SOSEC 源码复核

1 帖子里看似普通的一张图,最终使用的是论坛服务器自己的网络位置

论坛用户在编辑器里写下一个远程图片地址时,浏览器通常直接向图片站点发起请求。SMF 的图片代理改变了这条路线:论坛先把远程图抓到本地缓存,再把缓存内容交给读者。这样可以减少混合内容、隐藏访问者地址,也能控制图片大小和类型。与此同时,远端看到的请求来源从读者浏览器变成了论坛服务器。

CVE-2026-61520 正好出现在这次身份转换里。能发帖的认证用户选择地址,SMF 使用站点秘密为地址计算 HMAC,然后 proxy.php 验证签名并在服务端取图。签名能够证明 URL 是 SMF 自己加工出来的,却没有证明它解析到公网。应用替用户补上了本来无法伪造的凭据,也替用户跨过了浏览器与服务器网络权限的差异。

危险请求不需要在帖子页面展示出有效图片才成立。服务器只有完成网络连接、接收响应之后,才能检查 MIME 类型和体积。即使目标返回的是文本、错误页或空内容,连接行为已经发生。对云主机元数据地址、内部管理接口、环回服务和只允许内网来源的 HTTP 端点而言,这一步本身就具有安全意义。

GitHub Advisory Database 在 2026 年 7 月 14 日发布了由 NVD 提供的未审条目,记录 Moderate、CVSS 6.3;它不是 Simple Machines 厂商安全公告。SOSEC 将仓库固定到 90ff9400d6415bc9368fc91c61123bd7bca885d8,并分别读取 3.0 修复 4bf35cf9e45573a5f55a6f52995086c1da89c096 与 2.1 修复 b4d23dfd74a511587c605f9d294cefc3a75b4b26。下面的因果关系均回到这些固定源码中的字段、函数与分支内容。

这份报告把问题压回一条可验证的请求路径:帖子中的 URL 进入 BBCode 图片校验,get_proxied_url() 生成 requesthash,代理入口检查 HMAC,缓存函数调用通用 Web 获取器,获取器解析域名、建立连接并处理重定向。两项修复在已追踪的 HTTP 路径上,于代理入口、共享抓取入口、Curl/Socket 请求入口和下一跳处理中增加各自分支的目的地判断;FTP 族不纳入这项证明。

1.2 图片代理把 BBCode URL 变成服务器签发的抓取请求,也改变了由谁的网络去连接

论坛页面可能通过 HTTPS 提供,而老帖里的图片仍使用 HTTP。浏览器会警告或拦截混合内容。图片代理把远程对象搬到论坛域名下,读者只访问同源 HTTPS 地址。它还避免图片站点直接收集每位读者的 IP、User-Agent 和访问时间,这对活跃社区有现实价值。

代理也为缓存提供统一入口。SMF 可以把图片正文、内容类型、长度和缓存时间保存到服务器,后续阅读不必重复访问源站。管理员可以设置最大尺寸,失效后重新拉取。这个设计把一个展示功能变成了带持久缓存的服务端 HTTP 客户端,攻击面随之从 HTML 渲染延伸到 DNS、IPv4、IPv6、重定向和网络路由。

浏览器受到同源策略、私有网络访问策略和用户本机网络位置影响;论坛进程受到服务器路由表、防火墙、云平台网络与服务账号影响。两者看到的世界并不相同。公网用户无法直连的地址,可能对论坛服务器完全可达。代理设计必须在连接前重新建立一条“只允许访问全局可路由目的地”的规则。

因此,评估这类功能时不能只问“用户能否控制 URL”。还要逐一回答:谁解析域名、解析得到哪些地址、谁跟随重定向、连接从哪个网络命名空间发出、响应检查发生在连接前还是连接后、缓存是否改变后续执行。CVE-2026-61520 的完整解释正是这些答案连在一起后的结果。

SMF 2.1 的相关入口位于 Sources/Subs.php。帖子内容经过 BBCode 解析,图片标签中的 URL 被提取和规范化。站点启用图片代理后,代码调用 get_proxied_url($url)。这里的 $url 仍然源自帖子作者;权限关系尚未改变,变化发生在下一步。

get_proxied_url() 读取 $boardurl$image_proxy_enabled$image_proxy_secret 和当前用户信息。代理未启用或访问者被识别为机器人时,函数可以保留原始路径。正常论坛用户创建内容时,函数会产生指向本站 proxy.php 的 URL,其中携带原始地址和基于站点秘密的散列。

$proxied_url = https_board_url . '/proxy.php'
  . '?request=' . urlencode($url)
  . '&hash=' . hash_hmac('sha1', $url, $image_proxy_secret);

用户无需读取 $image_proxy_secret。关键在于应用接受了用户选择的消息,再使用高权限秘密替消息背书。HMAC 本身没有被破解;它按照设计工作。安全问题来自被签消息的含义过宽,任何能够进入图片标签的目的地都可能获得一张服务器认可的代理票据。

生成后的代理地址会随帖子内容交给读者。当帖子被查看、预览或由相关渲染流程触发时,请求落到论坛自己的代理入口。利用条件因此包含“能够提交会进入该解析路径的图片内容”和“站点启用了图片代理”。公开数据库条目将攻击者前提描述为认证用户,部署侧应按实际发帖权限、私信、签名档和其他复用 BBCode 的功能继续缩小范围。

从受控 HTTP BBCode 图片地址,经 get_proxied_url HMAC 签名、checkRequest 校验,再到服务端抓取的四步请求链。
签名只回答“这是不是 SMF 生成的代理 URL”;目的地是否适合由服务器连接,需要另一套独立判断。

proxy.php 不应成为任意公开 HTTP 代理,因此它要求 requesthash 同时存在,并使用同一秘密重新计算 HMAC。外部访客若直接篡改 request,散列将不匹配。这个机制有效阻止了没有合法内容生成路径的人随意提交地址。

然而,认证发帖用户本来就能够驱动内容生成路径。上游函数替其计算了正确散列。换句话说,安全判断从“知道秘密的人才能创建请求”变成了“任何能让应用签名某个地址的人都能创建请求”。代码审查若只停留在代理入口,会看到严谨的 HMAC 比较,却看不到签名消息如何产生。

类似模式常见于缩略图服务、PDF 转换器、Webhook 中继、邮件预览和链接展开器。签名 URL 能防止接口滥用和参数篡改,但无法自动限制签名前的用户数据。安全评审必须沿反向数据流找到所有签发点,确认每个签发点都把目的地主机、协议、端口和解析结果限制在预期集合内。

这也解释了为何简单轮换 $image_proxy_secret 不能修复漏洞。新秘密会让旧链接失效,却仍会为新帖子中的同类地址签发有效链接。有效措施需要改变可签消息的安全条件,或者在代理和底层连接处拒绝非公网目的地。

2 checkRequest() 从票据校验走到网络 I/O,缓存未命中便会真正出网

2.1 的代理类在构造时读取功能开关、最大图片体积、缓存目录和站点秘密。checkRequest() 随后确认代理已启用,要求 hashrequest 同时存在,检查 URL 语法、本站主机和 HMAC。这些判断能证明票据格式正确、内容未被篡改,却还没有回答远端地址是否适合由服务器连接。

执行顺序决定缓存对风险的影响。两个漏洞父提交都没有新的目的地谓词:有效缓存可以直接返回,缓存未命中才进入网络链。修复后,2.1 在缓存判断前调用 is_fetch_safe($request),3.0 在同一位置调用 WebFetchApi::isFetchSafe($request)。命中缓存仍可能留下安全 DNS 判断,但不会重新连接远端或读取正文。

未命中时,cacheImage() 才计算缓存文件名并调用共享抓取器。MIME 与长度检查发生在响应正文到达之后,所以它们可以拒绝不合格缓存,却不能撤销已经完成的解析、连接和请求。这正是“最后没有显示图片”仍可能构成 SSRF 的原因。

2.1 通过 fetch_web_data() 兼容不同 PHP 环境,3.0 则由 WebFetchApi::fetch() 分派到 CurlFetcher 或 SocketFetcher。业务入口知道这是一张代理图片,共享入口知道要选择哪种协议后端,具体获取器最接近套接字;补丁把判断放在这些不同状态上,避免只修一层后被另一条调用路径绕开。

本文能够确认的是已追踪 HTTP/HTTPS 路径:3.0 覆盖代理入口、共享入口、Curl/Socket 请求和重定向,2.1 覆盖代理入口、共享抓取与旧 cURL 重定向。FTP/FTPS 是独立调用面,不在这项 HTTP 结论中;不需要就关闭,需要则另做控制连接、数据连接和出口验证。

部署核查因此不能只搜索 proxy.php 里有没有一条新判断。还要确认生产实际启用的 PHP 后端来自完整修复包,主题或插件没有复制旧函数,也没有直接实例化缺少保护的获取器;测试环境与生产的 cURL、socket、IPv6 和网络策略必须一致。

2.1 两条修复都检查完整 DNS 结果,但各自采用不同的地址语义

漏洞前并非毫无防护:URL 必须具备可接受的结构,代理会排除本站主机,部分字面 IP 和 localhost 形式也会失败。缺口在于这些文本级规则看不到普通域名背后的实际连接地址。

解析器可能同时返回多条 A 与 AAAA,连接库会根据协议栈、顺序和失败情况选择其中一条。只要候选集合里混入环回、私网、链路本地或其他受限地址,“另有一条公网地址”就不能授权整个主机。因此两条修复都先得到完整候选集合,再以本分支的谓词作一次整体决定。

3.0 的 WebFetchApi::isFetchSafe() 先检查已注册协议和调用者给出的更窄协议集,拒绝空主机与保留后缀;字面 IP 直接成为候选,域名则通过 dns_get_record(..., DNS_A | DNS_AAAA) 收集地址,无回答时失败关闭。候选随后交给项目 IP 语义与 FILTER_FLAG_GLOBAL_RANGE,任一未通过便拒绝。

3.0 还在 Sources/IP.php 规范化以 64:ff9b:: 开头的 NAT64 表示,把末尾 32 位还原为点分 IPv4 后再分类。这样,外观看似 IPv6 的地址若实际承载私有或环回 IPv4,范围判断仍能看见它的网络含义。

2.1 的 is_fetch_safe($url) 使用旧式 IRI 解析,允许 HTTP、HTTPS、FTP 与 FTPS,并对每个候选应用 FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_NO_PRIV_RANGE。它同样在空结果时拒绝,却没有 3.0 的 GLOBAL_RANGE 定义与 NAT64 修改;两条分支不能用一句“全球可路由”抹平成同一策略。

SMF 3.0 修复与目标防御模型示意:URL 经 A 与 AAAA 解析,全局范围闸门拒绝私网及 NAT64 转私网地址,HTTP 重定向再做目的地判断。
本图只表达 3.0 的全局范围与 NAT64 模型;2.1 使用不同 PHP 标志,必须按自己的运行时结果验收。

因此,回移与测试都要忠于具体分支:记录规范化后的候选数组、实际谓词结果和最终获取器是否建立连接。DNS64/NAT64、IPv4 映射 IPv6 以及 PHP 与 cURL 的解释差异应在隔离环境中验证;部署出口策略继续承担更严格的统一拒绝,但不能被写成应用函数本身已经具备的语义。

2.2 HTTP 重定向会生成新的目的地,上一跳的许可不能沿用

公网图片可以返回 301302307,Location 又可能是绝对 URL、协议相对地址或相对路径。获取器补全新地址后会发起下一次请求;第一跳在公网,只能证明第一跳安全,不能替它选择的第二跳背书。

3.0 CurlFetcher 禁用 libcurl 自动跟随,由 sendRequest() 读取状态与 Location,getRedirectUrl() 生成完整 URL,再由 redirect() 调用 WebFetchApi::isFetchSafe()。判断失败便停止递归,不把当前响应当作成功图片。

SocketFetcher 在取得 Location 后构造新 Url 并重新进入受保护的 request();2.1 的旧 cURL 类则在跟随前调用自己的分支谓词。3.0 cURL 的重定向判断没有携带初始 HTTP/HTTPS 窄列表,所以跨协议 Location 仍应作为负向用例,并由出口策略兜底。

验收要观察连接事实:公网记录器收到第一跳,受禁记录器必须保持零次连接。相对跳转、跨主机、协议相对、多跳、循环、畸形 Location 与跳数上限分别测试;仅看到最终函数返回 false,不足以证明第二个套接字从未建立。

2.3 MIME、体积和缓存规则保护响应,却来不及替目的地授权

cacheImage() 在取得正文后识别 MIME,要求 image/ 前缀,并执行管理员配置的长度上限。这些规则能防止明显的非图片或超大对象进入缓存,是必要的内容侧控制。

SSRF 的关键动作更早发生:DNS、TCP、可选 TLS 与 HTTP 请求都先于正文分类。内部服务即使只返回文本,也已经收到来自论坛网络位置的请求;若某个遗留端点把 GET 当作状态变更,攻击者甚至不需要读取响应。

真正返回图片也不能证明来源安全。内部监控、摄像头或图表服务本来就可能输出图片,公网服务也能伪造内容类型。目的地策略回答“能不能连接”,响应策略回答“能不能保存和展示”,二者必须分别成立。

到这里,机制已经闭合:SMF 为用户选择的地址签发票据,缓存未命中把票据送进服务端抓取,DNS 与重定向可以改变真实目标,而响应检查只能在连接后介入。下一章只讨论两条维护分支怎样关闭这条路径,不再重走请求链。

3 两条维护分支沿不同代码结构收住同一个 HTTP 控制点

SMF 同时维护 2.1 与 3.0。3.0 使用命名空间、类型声明、UrlWebFetchApi;2.1 保留全局函数和传统文件布局。两项修复都要在连接前完成目的地判断,却分别依赖项目全局范围模型和 PHP 的非私有、非保留标志。

仓库包含关系给出了稳定归属:git branch --contains 4bf35cf9... 指向 release-3.0,变更落在 ProxyServer、WebFetchApi、Url、IP 与命名空间获取器;git branch --contains b4d23df... 指向 release-2.1,变更落在 Subs.php、proxy.php 与旧 cURL 类。

GitHub Advisory Database 未审条目的文字映射看起来把两个散列对应的版本线对调了。本文不推测原因,只采用可以重做的仓库证据,按 4bf35cf9→3.0、b4d23df→2.1 书写。

这项校准直接影响资产判断:扫描器若采用反向映射,既可能误报,也可能漏报。下游提交可以使用不同散列,但必须证明它保留本分支的函数、谓词、调用位置与行为,不能让 2.1 和 3.0 互相充当等价补丁。

3.1 3.0 以对象化公共控制点收口,2.1 在旧函数链上补齐同样的时机

3.0 的 ProxyServer::checkRequest() 在业务入口拒绝不安全目标,WebFetchApi::fetch() 在选择后端前再次判断,CurlFetcher 与 SocketFetcher 又在最接近连接的位置复核;每个 HTTP 重定向生成的新 Url 都重新进入判断。HMAC 继续负责票据完整性,目的地函数负责网络授权。

这条分支的地址决定由 WebFetchApi::isFetchSafe()IP.php 共同完成:协议和保留后缀先筛选,A/AAAA 候选按 GLOBAL_RANGE 分类,NAT64 表示先规范化。CurlFetcher 的下一跳使用通用谓词而未继承初始窄协议集,因此跨协议仍由负向测试和出口策略处理。

2.1 在 Sources/Subs.php 新增 is_fetch_safe($url),由 proxy.php::checkRequest()、公共 fetch_web_data() 和旧 cURL 重定向分别调用。它在相同的业务、共享库与目的地变化时机阻断请求,但地址结论严格来自 PHP 的非私有、非保留过滤。

长期运行的社区常修改 Subs.php、proxy.php 或获取器,合并冲突可能保留 HMAC 和 MIME 逻辑,却丢掉某个安全调用。升级或发行方回移只有在符号、调用位置和连接记录器测试同时通过时才可接受;版本字符串与单独一段复制代码都不够。

两条修复共享的 HTTP 不变量很简单:不符合本分支目的地规则的 URL,在初始请求和每个下一跳上都不能到达 cURL 或 HTTP socket 的连接动作。FTP/FTPS 不包含在这项证明中,必须关闭或另有独立证据。源码机制至此结束,后文只讨论暴露、处置与验收。

4 实际影响由论坛进程能到达的网络,以及目标如何信任这条来源决定

SSRF 不自动等于“读取全部内网数据”。结果取决于可用协议、响应是否可见、缓存行为、目标认证、云元数据防护、网络 ACL 和目标接口语义。源码能够证明服务器会发出请求,具体部署仍要用资产与日志判断它能得到什么。

第一类后果是通过延迟、状态和缓存差异探测主机或服务;第二类是访问只按来源网段放行的内部 HTTP 端点;第三类是触达本机管理面或云元数据。现代平台可能要求令牌、专用方法或 Hop Limit,这些条件会收窄后果,却不能替论坛修复入口。

若内部接口允许 GET 改变状态,连接本身就可能产生影响;若正文通过图片类型和体积检查,还可能经代理缓存被观察。非图片正文通常不会正常展示,但时间、错误和目标日志仍能证明网络动作,不能把“页面没有图片”当作未触发。

因此影响评估应落到资产表:记录 SMF 主机、容器或 Pod 的出站路由、可达子网、元数据模式、服务网格、内部 HTTP 依赖与源地址信任,再叠加代理开关和内容权限。优先级来自这组交集,而不是一条对所有安装都相同的夸张结论。

4.1 五项条件可以逐一核实:身份、内容路径、代理状态、出站可达性和目标信任

第一项是某个用户或流程能创建经过图片 BBCode 与 get_proxied_url() 的内容。用户组、审核、私信、签名档、导入和插件都会改变这组主体,调用图与路由测试比“只有公开帖子会用”更可靠。

第二项是图片代理或其他共享抓取调用者处于可用状态。关闭 image_proxy_enabled 会切断命名的图片路径,但头像导入、MIME 检测、预览、计划任务与插件仍可能使用 fetch_web_data()WebFetchApi::fetch()

第三项是论坛进程具有到目标的出站路由。没有公网入站的容器仍可能访问集群服务、宿主网关和元数据;反向代理与 WAF 通常只观察入站流量,不能证明主动请求被阻断。

第四项是可达目标会信任这个来源或在无需强认证时产生有价值的响应。严格的 egress proxy、强化元数据模式和每个内部接口的强身份会缩小风险;依赖源 IP 或“只有内网可达”的服务会放大风险。

第五项是运行代码确实位于修复之前。3.0 要查看 isFetchSafe() 及 ProxyServer、共享入口、Curl/Socket 与下一跳调用;2.1 要查看 is_fetch_safe()、proxy.php、fetch_web_data() 与旧 cURL 重定向。定制包的版本字符串不能代替函数证据。

4.2 出站网络把应用判断失误变成可阻断、可观察的事件

论坛进程至少应同时禁止 IPv4 与 IPv6 的私网、环回、链路本地、保留、元数据、集群控制面和管理网段;允许的公网 HTTP/HTTPS 经受控 egress proxy 出口,由代理记录最终连接 IP 并重复目的地分类。

受控出口还能缩小 DNS 检查与实际连接之间的时间差。高敏部署可以固定已审核地址,同时保留原域名的 Host 与 TLS SNI,并为合法多地址与 CDN 轮换设计明确回退,避免用破坏证书或业务语义的 URL 替换来“模拟”固定。

规则必须绑定实际 SMF 进程、容器或专用抓取身份,并在真实网络路径上测试。IPv4 ACL 不会自动覆盖 IPv6 或 NAT64,配置文件写着拒绝也不等于数据包一定经过对应防火墙;应用补丁和基础设施策略应各自留下独立证据。

4.3 检测把内容作者、代理票据、目的地决定、连接与缓存接成一条事件

签发点记录内容对象、作者、功能、规范主机、协议、端口和脱敏 URL 哈希,并生成贯穿代理与抓取日志的请求 ID。完整查询可能含敏感值,应按字段脱敏,而不是把原始 URL 无限制复制到每个日志系统。

目的地日志记录分支、谓词、解析结果类别与拒绝阶段;获取日志记录后端、重定向跳数、实际连接 IP、状态、字节、MIME、耗时和缓存状态。解析、应用、出口与目标侧日志用同一关联键,才能证明拒绝发生在套接字之前。

一次指向元数据或管理网的尝试就值得升级。数量规则用于补充上下文:新账号短时提交多个主机、端口、重定向器或连续制造缓存未命中,更像在使用代理探测网络。判断围绕目的地与行为展开,不依赖单一公开字符串。

4.4 历史追查从已签票据走向当时的 DNS、真实连接与影响层级

先固定时区并保全数据库、Web/PHP 日志、代理缓存、解析日志和出站流量。提取 proxy.php?request=... 后只在离线工具中解码与规范化,按作者、主机、端口、时间和缓存键聚类,绝不在分析工作站自动访问目的地。

当前 DNS 不能代表事件时刻。使用自有递归日志、权威记录或合适的历史解析资料恢复内容创建与代理访问附近的 A/AAAA;再把缓存创建、修改和过期状态放进时间线,因为漏洞父提交的缓存命中可以不出网,未命中或过期刷新才进入完整抓取。

网络侧搜索论坛进程、容器节点、NAT 或 egress 对内部 HTTP 的连接,并与代理访问和缓存时间配对。目标服务日志是判断方法、路径、来源与响应的最强证据;单个 404 只能说明应用结果,不能独自证明有没有连接。

结论分层写成:签发了危险票据、完成解析、尝试或建立连接、收到响应、触达敏感内容或状态、后续使用凭据。若目标属于元数据、Secrets 或内部凭据接口,立即轮换并检查后续调用,不等待正文是否成功作为图片展示。

4.5 处置从关闭入口、保全证据一路推进到分批恢复

不能立即升级时,关闭图片代理和非必要远程抓取,并在出站侧拒绝非公网、元数据与管理网目标。业务配置减少新调用,网络策略覆盖遗漏调用者;记录准确生效时间、负责人、用户影响和回滚条件。

暂时限制低信任用户创建远程图片只能减慢新票据产生。旧帖子仍可能含有效代理 URL,首次阅读或缓存过期会再次触发;外层拒绝 /proxy.php 能切断命名入口,却会造成破图,也不覆盖共享抓取器的其他调用者。

清理前先快照数据库、日志与缓存。可疑文件移入只读隔离、计算哈希并限制访问;一旦证据指向秘密或凭据接口,就按相应暴露级别轮换密钥、失效会话并审查云端或目标系统日志。

升级和验证通过后按批次恢复代理,持续观察目的地拒绝、图片失败、DNS 延迟、连接量与缓存重建。明确旧缓存是继续服务、隔离还是分批刷新,避免一次清空让多年历史图片同时重新出网。

5 升级验收既看函数覆盖,也看危险目的地是否真的没有收到连接

3.0 实例应具备 WebFetchApi::isFetchSafe()、ProxyServer 与共享入口调用、CurlFetcher/SocketFetcher 的请求与下一跳判断,以及 IP.php 的 NAT64 规范化;2.1 实例应同时具备 is_fetch_safe()、proxy.php、fetch_web_data() 与旧 cURL 重定向修改。发行方回移以这些语义和文件哈希验收,不以版本横幅替代。

在隔离网络中配置受控 DNS 与 HTTP 记录器:一个名称指向允许的测试服务,一个指向受禁记录器,一个返回混合集合,一个公网记录器重定向到受禁记录器;IPv6 组覆盖环回、ULA、链路本地、映射和 NAT64。记录器只返回固定小图片或 302,不提供任何真实管理能力。

使用正常认证账号创建图片内容,记录生成票据、分支谓词、DNS 数组、后端、缓存和连接计数。正常公网图片应成功;被部署策略禁止的单一或混合目标不得收到连接;重定向用例只允许第一跳记录器收到一次。应用谓词和更严格的出口结果分栏,不能强迫 2.1 与 3.0 输出同一分类。

最后按生产实际配置强制运行 cURL 与 socket,覆盖支持的 PHP、IPv4-only、dual-stack 和容器镜像,同时回归 HTTPS 图片、国际化域名、合法多地址 CDN、相对重定向、缓存命中与过期、最大体积和非图片拒绝。保存解析器、应用、记录器与防火墙日志,作为发布证据。

6 一张图暴露的不是 HMAC 失效,而是签名与网络授权回答了两道不同问题

CVE-2026-61520 的关键转折发生在应用替用户签票的那一刻:HMAC 能证明代理 URL 由 SMF 生成,却不能证明其中的主机适合由论坛服务器连接。旧代码在票据完整性上做对了事,却没有把域名解析和重定向后的真实目的地纳入同一决定。

3.0 通过 WebFetchApi::isFetchSafe()、项目 IP 语义和已追踪 Curl/Socket 路径收口,2.1 通过 is_fetch_safe()、共享抓取入口与旧 cURL 重定向补齐时机;两条分支都需要自己的函数与行为证据,不能只看“2.1”“3.0”或数据库中的散列标签。

运营上的闭环也由此清楚:确认正确分支修复,保留缓存和历史日志,限制论坛进程出站,追查曾经签发的危险目的地,再用隔离 DNS 与记录器证明修复后的网络结果。若触及敏感目标,按事件证据推进凭据与目标系统响应。

图片代理仍然可以继续提供 HTTPS、隐私和缓存价值。可持续的做法是让签名认证消息来源,让目的地策略授权服务器要去的地方,再让出口网络与日志独立证明这项决定确实生效。

研究记录

7证据、对象与来源

下面保留本文实际使用的标识、时间和原始材料,便于继续调查。

7.1研究对象

报告涉及的产品、行为者、技术、受影响对象和控制点。

漏洞编号CVE-2026-61520

SMF 图片代理认证后 SSRF

GitHub Advisory Database 条目GHSA-9wvj-rhhh-8292

NVD 提供的未审条目,2026 年 7 月 14 日公开

SMF 3.0 修复4bf35cf9e45573a5f55a6f52995086c1da89c096

release-3.0 中的 WebFetchApi 安全收口

SMF 2.1 修复b4d23dfd74a511587c605f9d294cefc3a75b4b26

使用 PHP 非私有、非保留过滤的旧分支修复

复核快照90ff9400d6415bc9368fc91c61123bd7bca885d8

SOSEC 本地源码固定提交

入口字段proxy.php?request={url}&hash=HMAC-SHA1(url, image_proxy_secret)

服务端为用户选择的图片地址签名

关键函数get_proxied_url → checkRequest → cacheImage → fetch_web_data/WebFetchApi::fetch

完整请求路径

HTTP 修复性质all A/AAAA answers pass the branch predicate; HTTP next hops rechecked

应用与出口证据共同证明禁止 Curl/Socket 连接为零

7.2事件时间

  1. 数据库条目公开

    GitHub Advisory Database 发布由 NVD 提供的 CVE-2026-61520 未审条目 GHSA-9wvj-rhhh-8292。

  2. SOSEC 完成双分支复核

    固定源码并打通签发、代理、DNS、连接与重定向函数链。

  3. SOSEC 完成调用链复核

    SOSEC 固定相关调用点,对比分支修复决策,并把临时控制与零连接验收衔接起来。

7.3来源与材料

  1. GitHub Advisory Database 未审条目 GHSA-9wvj-rhhh-8292https://github.com/advisories/GHSA-9wvj-rhhh-8292
  2. CVE-2026-61520 记录https://www.cve.org/CVERecord?id=CVE-2026-61520
  3. 公开 v2.1.7 标签提交 d49255b9https://github.com/SimpleMachines/SMF/commit/d49255b955cbeeb3a702d77009c85aae517ed1e4
  4. 公开 v3.0-alpha.4 标签提交 49e49a03https://github.com/SimpleMachines/SMF/commit/49e49a035728fbed888b38807d80c1d050c7b014
  5. SMF 3.0 修复提交 4bf35cf9https://github.com/SimpleMachines/SMF/commit/4bf35cf9e45573a5f55a6f52995086c1da89c096
  6. SMF 2.1 修复提交 b4d23dfdhttps://github.com/SimpleMachines/SMF/commit/b4d23dfd74a511587c605f9d294cefc3a75b4b26
  7. SMF 3.0 提交 4bf35cf9 中的已修 WebFetchApihttps://github.com/SimpleMachines/SMF/blob/4bf35cf9e45573a5f55a6f52995086c1da89c096/Sources/WebFetch/WebFetchApi.php#L130-L268
  8. SMF 3.0 提交 4bf35cf9 中的已修 ProxyServerhttps://github.com/SimpleMachines/SMF/blob/4bf35cf9e45573a5f55a6f52995086c1da89c096/Sources/ProxyServer.php#L117-L161
  9. SMF 3.0 提交 4bf35cf9 中的已修 CurlFetcher 请求入口https://github.com/SimpleMachines/SMF/blob/4bf35cf9e45573a5f55a6f52995086c1da89c096/Sources/WebFetch/APIs/CurlFetcher.php#L226-L253
  10. SMF 3.0 提交 4bf35cf9 中的已修 SocketFetcher 请求入口https://github.com/SimpleMachines/SMF/blob/4bf35cf9e45573a5f55a6f52995086c1da89c096/Sources/WebFetch/APIs/SocketFetcher.php#L165-L181
  11. SMF 3.0 提交 4bf35cf9 中的 IP 与 NAT64 规范化https://github.com/SimpleMachines/SMF/blob/4bf35cf9e45573a5f55a6f52995086c1da89c096/Sources/IP.php#L91-L116
  12. SMF 2.1 提交 b4d23df 中的公共抓取与安全函数https://github.com/SimpleMachines/SMF/blob/b4d23dfd74a511587c605f9d294cefc3a75b4b26/Sources/Subs.php#L6122-L6360
  13. SMF 2.1 提交 b4d23df 中的已修图片代理入口https://github.com/SimpleMachines/SMF/blob/b4d23dfd74a511587c605f9d294cefc3a75b4b26/proxy.php#L116-L158
  14. SMF 2.1 提交 b4d23df 中的已修 cURL 重定向https://github.com/SimpleMachines/SMF/blob/b4d23dfd74a511587c605f9d294cefc3a75b4b26/Sources/Class-CurlFetchWeb.php#L341-L357
  15. RFC 1918:私有 IPv4 地址空间https://www.rfc-editor.org/rfc/rfc1918
  16. RFC 4193:IPv6 Unique Local Addresshttps://www.rfc-editor.org/rfc/rfc4193
  17. RFC 6052:IPv6 中嵌入 IPv4 地址https://www.rfc-editor.org/rfc/rfc6052
  18. PHP dns_get_record 文档https://www.php.net/manual/en/function.dns-get-record.php
  19. PHP filter_var IP 范围标志文档https://www.php.net/manual/en/function.filter-var.php