CVE-2026-7025 是 Typecho Pingback 功能中的一个服务端请求伪造(SSRF)漏洞,影响 Typecho 1.3.0 及以下版本。
发布文章时,Typecho 会自动请求文章中的外链,然后读取对方返回的 X-Pingback 响应头或页面里的 <link rel="pingback"> 地址,再向这个地址发起 XML-RPC 请求。问题出在这一步:这个地址完全由对方站点决定,Typecho 却没做任何校验。
攻击者只要在自己的页面返回:
X-Pingback: http://127.0.0.1:6379/或者
X-Pingback: http://169.254.169.254/latest/meta-data/就能让你的服务器去访问本机 Redis、内网服务,甚至云服务器的元数据接口。
| 项目 | 说明 |
|---|---|
| 🔖 漏洞编号 | CVE-2026-7025 / AVD-2026-7025 |
| 🎯 影响版本 | Typecho ≤ 1.3.0 |
| 🧨 漏洞类型 | SSRF(服务端请求伪造) |
| 📍 漏洞位置 | var/Widget/Service.php → sendPingHandle() |
| 🚪 利用入口 | 远端返回的 X-Pingback 响应头 / <link rel="pingback"> |
| ⚠️ 危害 | 探测内网、访问本机服务、读取云元数据 |
原始代码(简化后):
$spider->setTimeout(10)->send($url);
// 直接信任远端返回的地址
if (!($xmlrpcUrl = $spider->getResponseHeader('x-pingback'))) {
if (preg_match("/<link[^>]*rel=[\"']pingback[\"'][^>]*href=[\"']([^\"']+)[\"'][^>]*>/i",
$spider->getResponseBody(), $out)) {
$xmlrpcUrl = $out[1];
}
}
if (!empty($xmlrpcUrl)) {
// 没有任何校验,直接对该地址发起请求
$xmlrpc = new \IXR\Client($xmlrpcUrl);
$xmlrpc->pingback->ping($permalink, $url);
}整个流程有三处外发请求:抓取文中外链、请求 Pingback 地址、发送 Trackback,三处都没有校验目标地址。
有意思的是:接收 Pingback 的一侧(XmlRpc::pingbackPing)其实调用了Common::checkSafeHost()校验来源地址,只是发送一侧漏掉了。
| 修复点 | 说明 |
|---|---|
| 🛡️ 协议白名单 | 只允许 http / https,拦截 gopher://、file://、dict:// 等 |
| 🛡️ 内网拦截 | 复用官方 Common::checkSafeHost(),拦截回环、内网和保留地址 |
| 🛡️ 账号信息拦截 | 拒绝 http://user@host 这类可能造成解析歧义的地址 |
| 🎯 三处全覆盖 | 抓取外链前、请求 Pingback 地址前、发送 Trackback 前 |
| 🧯 异常兜底 | 单个外链请求失败时跳过这一条,不再中断整个循环 |
| 📦 改动极小 | 只修改 var/Widget/Service.php 一个文件 |
/**
* 校验外发请求地址:仅允许 http/https,且主机不能解析到内网/保留地址(防 SSRF)
*/
private function isSafeRemoteUrl(string $url): bool
{
$params = parse_url($url);
if (false === $params || empty($params['host']) || empty($params['scheme'])) {
return false;
}
if (!in_array(strtolower($params['scheme']), ['http', 'https'], true)) {
return false;
}
/** 禁止携带账号信息,避免 http://a@b 之类的解析歧义 */
if (isset($params['user']) || isset($params['pass'])) {
return false;
}
$host = trim($params['host'], '[]');
return Common::checkSafeHost($host);
}// 1. 抓取文章外链前
if (!$this->isSafeRemoteUrl($url)) {
continue;
}
// 2. 使用远端返回的 X-Pingback / link 地址前(漏洞核心)
if (!empty($xmlrpcUrl) && $this->isSafeRemoteUrl($xmlrpcUrl)) {
$xmlrpc = new \IXR\Client($xmlrpcUrl);
$xmlrpc->pingback->ping($permalink, $url);
}
// 3. 发送 Trackback 前
if (!$this->isSafeRemoteUrl($url)) {
continue;
}为什么不用担心 302 跳转绕过?
Typecho 自带的 HTTP 客户端没有开启CURLOPT_FOLLOWLOCATION,不会跟随重定向,所以没法先用公网地址通过校验、再 302 跳到内网。
cp var/Widget/Service.php var/Widget/Service.php.bak📥 下载地址:https://kiwl.lanzout.com/ilLnf4dcl7tc
压缩包结构:
typecho-ssrf-fix.zip
└── var/
└── Widget/
└── Service.php上传到 Typecho 根目录(config.inc.php 所在目录),直接解压覆盖即可:
unzip -o typecho-ssrf-fix.zipphp -l var/Widget/Service.php
# No syntax errors detected in var/Widget/Service.php⚠️ 如果你自己改过 Service.php,请不要直接覆盖,按上面的「调用位置」手动合并。在网站根目录执行下面的命令。它只在内存里加载 Typecho 的类并调用校验方法,不写入文件,也不对外发请求:
php <<'EOF'
<?php
define('__TYPECHO_ROOT_DIR__', getcwd());
spl_autoload_register(function ($c) {
$f = 'var/' . str_replace(['\\', '_'], '/', $c) . '.php';
if (is_file($f)) require_once $f;
});
require_once 'var/Typecho/Common.php';
$r = new ReflectionClass('Widget\Service');
$o = $r->newInstanceWithoutConstructor();
$m = $r->getMethod('isSafeRemoteUrl');
$m->setAccessible(true);
// 1 = 应放行,0 = 应拦截
$cases = [
'http://127.0.0.1:6379/' => 0,
'http://localhost/' => 0,
'http://169.254.169.254/latest/meta-data/' => 0,
'http://10.0.0.1/' => 0,
'http://192.168.1.1/' => 0,
'http://172.16.0.1/' => 0,
'http://0.0.0.0/' => 0,
'http://2130706433/' => 0,
'http://[::1]/' => 0,
'http://user@127.0.0.1/' => 0,
'http://a@www.baidu.com/' => 0,
'gopher://127.0.0.1:6379/_x' => 0,
'file:///etc/passwd' => 0,
'dict://127.0.0.1:11211/' => 0,
'https://www.baidu.com/' => 1,
'https://typecho.org/' => 1,
];
$ok = 0;
foreach ($cases as $url => $want) {
$got = $m->invoke($o, $url);
$pass = ($got === (bool)$want);
$ok += $pass;
printf("%-45s want=%s got=%s %s\n", $url,
$want ? 'allow' : 'block', $got ? 'allow' : 'block', $pass ? 'PASS' : 'FAIL');
}
echo "RESULT: $ok/" . count($cases) . "\n";
EOF| 类型 | 测试地址 | 结果 |
|---|---|---|
| 本机 / 回环 | 127.0.0.1、localhost、0.0.0.0、[::1]、十进制 IP 2130706433 | ✅ 拦截 |
| 云元数据 | 169.254.169.254 | ✅ 拦截 |
| 内网段 | 10.x、192.168.x、172.16.x | ✅ 拦截 |
| 带账号信息的 URL | user@127.0.0.1、a@www.baidu.com | ✅ 拦截 |
| 危险协议 | gopher://、file://、dict:// | ✅ 拦截 |
| 正常外链 | https://www.baidu.com/、https://typecho.org/ | ✅ 放行 |
RESULT: 16/1616 项测试全部通过!
CURLOPT_RESOLVE 把校验时拿到的 IP 固定下来,改动会更大,这个修复包暂时没有做。| 链接 | 地址 |
|---|---|
| 漏洞详情 | https://avd.aliyun.com/detail?id=AVD-2026-7025 |
| 修复包下载 | https://kiwl.lanzout.com/ilLnf4dcl7tc |
| Typecho 官网 | https://typecho.org/ |
isSafeRemoteUrl() 外发地址校验方法如果这篇文章对你有帮助,欢迎分享给身边使用 Typecho 的朋友! Made with ❤️ by YouChuang Open Source Community · Powered by PHP
我用的typecho的二开版本,昨天已经有博主注意到了这个漏洞,经询问作者,二开版本没有这个漏洞。
作者确认过没问题就安心用吧。不过建议:用不到 Pingback 的话在后台顺手关掉,再留意官方修复版,出正式版后升级更保险。(官方不知道何时修。。。。。)
确认自己用的版本不受影响就安心了,不过修复包还是先收藏备用。
等官方修复版出来还是建议升级,更彻底。