那天早上,所有访客都看到了一个红色警告页
一个跑了四年的内容站,某天上午流量突然掉到平时的三成。查访问日志,正常;查服务器,正常;查 CDN,正常。后来在读者群里有人发了一张截图:Chrome 打开首页,整屏红色,写着「您要访问的网站包含欺骗性内容」或「此网站可能被入侵」,底下只有一个「返回安全连接」按钮。
这就是 Google Safe Browsing 的拦截页。对个人站长来说,它是所有「网站事故」里最不讲道理的一种——站点可能完全正常,代码没改、服务器没换、什么也没做,但浏览器一刀切地认为你有问题,而所有的搜索结果里你的站都带着「此网站可能损害您的计算机」的标签。流量在几个小时内塌掉,而且恢复速度比下跌还慢。
更要紧的是:大多数人的第一反应是错的。 登录服务器各种排查、改 Nginx、清缓存、换 IP,一周过去发现什么也没变。真正有效的那条路——提交复审申请——反而没人走,或者走了但写错了。
这篇按「先判断是不是它 → 立即止血 → 找到真实原因 → 提交复审 → 恢复后的长期防护」讲。全部流程都在真实站点上走过一遍。
第一步:确认拦截来源,别瞎猜
「网站被标记不安全」这个说法下面藏着至少四种完全不同的故障,处理方式也完全不同。先分清楚:
- Safe Browsing 拦截(恶意软件 / 欺骗性内容 / 有害程序)。 整屏红色警告,页面里有「继续前往(不安全)」的小字链接。这是本文的主题。
- HTTPS 证书问题。 警告文案是「您的连接不是私密连接」「NET::ERR_CERT_DATE_INVALID」,整页灰色+红色三角。这是证书过期或配置错误,和 Safe Browsing 毫无关系。
- 混合内容警告。 地址栏左侧的锁图标变成一个「ⓘ」,点开显示「此网站的部分内容不安全」。页面能正常打开,只是 HTTPS 页面里加载了 HTTP 资源。
- 浏览器层面的「不安全」标记。 纯 HTTP 站点,地址栏显示「不安全」三个字。这不是惩罚,只是提示你没有加密。挂个免费证书就好。
区分方法很简单,看地址栏和页面主体:整屏红色 = Safe Browsing;只有图标变化 = 证书或混合内容。别在证书问题上查了两天 Safe Browsing 的历史记录,那是白费力气。
确认是 Safe Browsing 之后,用官方的诊断接口查历史,这比看浏览器截图有用得多:
# Google Transparency Report 的公开查询接口
# 替换 %2F 编码后的域名
curl -s "https://transparencyreport.google.com/transparencyreport/api/v3/safebrowsing/status?site=example.com" \
| head -c 500返回的 JSON 里 "2" 通常表示「未发现不安全内容」,其它值对应不同类型的问题。更直观的做法是直接打开 https://transparencyreport.google.com/safe-browsing/search?url=example.com,看它给出的分类——「恶意软件」「不需要的软件」「社会工程学(钓鱼)」「误导性内容」。这个分类决定了你要修什么。
同时查另外两个源,因为它们独立判定、需要分别申诉:
# Microsoft Bing / SmartScreen
# https://www.bing.com/webmasters/ 里的 "Site Security" 或
curl -s "https://www.bing.com/webmaster/api/..." # 需 API key,一般人工查看更快
# VirusTotal 聚合了 70+ 家引擎(含 Google、Yandex、Sophos 等)
curl -s "https://www.virustotal.com/vtapi/v2/url/report?apikey=YOUR_KEY&resource=www.example.com" \
| python3 -m json.tool | head -40VirusTotal 需要免费 API key(注册即得,每分钟 4 次配额够了)。它的价值在于一次性看到所有引擎的判定,能把「只有 Google 报」和「多家都报」区分开——后者通常意味着真的有问题,前者更可能是误报或撞了某个黑名单。
第二步:止血——能不能让访客先进来
申诉流程通常要 24~72 小时,期间流量在流血。有几个动作可以立刻做:
1. 检查是不是整站都被标记。 有时候只有某个特定 URL 或某个子目录被标记(典型是被挂了赌博/色情页面的 /go/ 或 /temp/)。如果只是路径级,删掉那个路径、清掉索引,主站很快就能恢复,不必等复审。
# 用 site: 查是否所有页面都被标
# 在 Google 搜索框输入 site:example.com,看结果里有没有警告前缀
# 看内部有没有来历不明的目录
find /var/www/html -type d -newermt '30 days ago' -not -path '*/\.git/*'
find /var/www/html -name '*.php' -newermt '30 days ago' -not -path '*/\.git/*' | head -302. 换域名不是解法,但可以过渡。 别急着换域名。换域名意味着所有收录清零,这个代价远大于等两天复审。如果确实需要给老读者一个能访问的入口,可以临时挂一个静态说明页在另一台机器上,告诉读者发生了什么、预计多久恢复。
3. 不要试图绕过去。 有些文章会教你「加 meta 标签解除拦截」「用 JS 检测后跳转」,这些都没有用——拦截页是浏览器在网络层直接替换的,你的 HTML 根本没被加载。还有些人建议「把首页做成纯静态躲过扫描」,这在 Safe Browsing 的持续爬取下也撑不了多久,而且会掩盖真正的问题。
4. 提前告诉搜索引擎不要抓了,如果确实是内容问题。 这一条要慎重。如果确认站点被注入了大量垃圾页面,可以临时在 robots.txt 里 Disallow 那些路径,避免它们继续被收录和传播。但不要 Disallow 全站——那会让恢复后的重新收录变得更加困难。
第三步:找出真实原因——90% 的「误报」其实不是误报
这一节是全文最重要的部分。个人站长常说的「我什么都没做,就被误报了」,真实情况往往是下面几种之一,而它们都有共同特征:从外部看你的站确实有问题,只是你看不到。
原因一:站点被注入(最常见)
攻击者拿到写权限后,会在正常页面里插入隐藏的恶意 iframe、跳转脚本或者 SEO 垃圾链接。你在浏览器里看页面一切正常,是因为注入的代码只对搜索引擎爬虫或者特定 Referer 生效——这叫「cloaking」,是黑帽 SEO 和被入侵站点最典型的标记。
# 用爬虫 UA 抓页面,看返回的内容和普通 UA 是否不同
curl -s -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
"https://example.com/" -o /tmp/as_bot.html
curl -s -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
"https://example.com/" -o /tmp/as_user.html
diff /tmp/as_bot.html /tmp/as_user.html | head -40如果 diff 有输出,恭喜你找到了确切证据。常见的注入形态:
# 一次性扫全站可疑特征
grep -rl --include='*.php' --include='*.html' --include='*.js' \
-E 'eval\s*\(\s*base64_decode|eval\s*\(\s*gzinflate|document\.write\s*\(\s*unescape|atob\s*\(\s*['"'"'"]' \
/var/www/html/ 2>/dev/null | head -20
# 找出最近修改过的静态文件和模板
find /var/www/html -name '*.php' -mtime -60 -printf '%TY-%Tm-%Td %p\n' | sort -r | head -30还有一类注入藏在 .htaccess 和 Nginx 配置里,比如把所有 /?from=xxx 的请求重定向到外部站点:
grep -rnE 'RewriteRule|proxy_pass|return 30[12]' /var/www/html/.htaccess 2>/dev/null
grep -rnE 'rewrite|return 30' /etc/nginx/sites-enabled/ 2>/dev/null原因二:站点上有被举报的第三方内容
这一条容易被忽略:Safe Browsing 判定的是「这个 URL 上承载的内容」,不是「你是不是恶意」。如果你的站有评论区、留言板、用户投稿、文件上传、短链跳转,而有人利用它在你的域名下发布钓鱼页面或者挂了一个恶意软件下载,被标记的是你的域名。
# 检查有没有异常上传目录、异常扩展名的文件
find /var/www/html -type f \( -name '*.exe' -o -name '*.apk' -o -name '*.scr' -o -name '*.bat' \) 2>/dev/null
find /var/www/html -type d -name 'upload*' -o -type d -name 'tmp*' 2>/dev/null
# 看访问日志里有没有大量来自陌生来源、命中奇怪路径的请求
awk '{print $7}' /var/log/nginx/access.log \
| grep -vE '\.(css|js|png|jpg|gif|webp|ico|woff2?)$' \
| sort | uniq -c | sort -rn | head -30典型事故:站上有「网址导航」或「短链生成」功能,被人拿去跳转到钓鱼页;或者评论区允许贴外链,被批量刷了几百条指向恶意站点的链接。这些内容在页面里可能很小、很难发现,但爬虫会看到。
原因三:广告位和第三方脚本
这是最让人无奈的一类:你自己的站干干净净,但挂的广告联盟里某个素材(或者它链向的落地页)被判为恶意。Safe Browsing 的判定是「URL 及其引用的资源」,如果页面上加载的某个脚本、iframe、图片来自被标记的域名,整个页面可能被牵连。
# 列出页面引用的所有外部域名(含脚本、iframe、图片)
curl -s "https://example.com/" \
| grep -oE '(src|href)="https?://[^"/]+' \
| sed -E 's#.*https?://##' \
| sort | uniq -c | sort -rn把出现的外部域名逐个丢给 VirusTotal 或 Transparency Report 查。如果其中一个被标记了,那就是元凶。处理方式是暂时摘掉那个广告位或者脚本,然后申诉。
顺带说一句,「免费统计代码」「免费 CDN」「免费图床」这类服务是这条的高危区。它们的域名一旦被挂马,所有引用它的站都会被牵连。这也是我后来把外部依赖收敛到极少的直接原因。
原因四:域名历史或共享主机邻居问题
如果域名是买的二手域名,或者站被放在共享主机上,可能你和「问题」根本不在同一个位置。二手域名可能多年前做过灰色业务,历史记录还挂在黑名单里。共享主机则是同 IP 的其他站点被入侵导致的连带标记。
# 看同 IP 上有多少个站点(反向解析 + 证书透明度日志)
# 用 crt.sh 的公开接口查同 IP 的证书
curl -s "https://crt.sh/?q=example.com&output=json" | python3 -m json.tool | head -20
# 检查域名注册时间(很短 = 可疑,但对老站无意义)
whois example.com | grep -iE 'created|creation'如果是 IP 连带问题,在申诉时明确说明「本站使用共享主机,同 IP 的其他站点问题与本站无关,已向主机商反馈」,并附上证据(你的站没有相关内容的截图、宿主机的服务说明)。这一条对复审通过率影响不小。
第四步:提交复审——写什么、怎么写
Google 的正式入口只有一个:
https://search.google.com/search-console/security-issues
# 或者在 Transparency Report 的查询结果页点 "Request a review"前置条件:站点必须在 Search Console 里验证过所有权。没验证的话先去验证(DNS TXT 或 HTML 文件最稳),这一步不能省。
复审申请是一个文本框,字数有限。大多数人在这里失败的原因是写得像求情:「我是一个小站长,没有恶意,请尽快恢复」。这些信息对审核员没有任何价值。 有效的复审申请要包含四个要素:
- 结论:问题是什么(或明确说明是误报)。 一句话说清。
- 证据:你怎么查的,查到了什么。 具体到文件、日志、日期。
- 动作:你做了什么来修。 已删除什么、已修补什么、已改了什么配置。
- 防复发:如何避免再次发生。 一句话即可,但要具体。
一份实际通过的申请(被注入类型)大致长这样:
Site: https://example.com
Issue: Harmful content detected on 2026-10-14 due to a malicious
redirect script injected into theme header.
Root cause: An outdated plugin (upload.php) allowed arbitrary file
write. Attackers modified /themes/default/header.php on 2026-10-13
03:12 UTC, inserting a cloaked iframe that only rendered for
search engine user agents.
Actions taken:
1. Removed all injected code from header.php and 4 other files;
restored from a clean backup dated 2026-10-01.
2. Upgraded the vulnerable plugin to 2.4.1 and rotated all
database and SSH credentials.
3. Scanned the entire document root: no remaining suspicious
patterns (eval/base64_decode/unescape).
4. Added a WAF rule blocking POST requests to /upload.php from
unauthenticated clients.
Prevention: Weekly file-integrity diff against a known-good manifest;
automatic security updates enabled.
Verified with: curl requests using Googlebot and desktop user agents
return identical HTML as of 2026-10-16.几个提高通过率的细节:
- 用英文写。 审核团队是全球化处理的,英文的描述准确度最高。中文申请不是不能过,但描述歧义带来的来回次数更多。
- 给具体时间戳(UTC)。 「最近」这种词没用,「2026-10-13 03:12 UTC」有用。
- 不要撒谎。 如果确实是自己的问题,承认并说清怎么修的,通过率远高于坚称「我什么都没做」。审核员能看到爬虫抓到的实际内容。
- 只申诉一次,别连刷。 重复提交会进队列末尾,反而更慢。
微软的入口在 https://www.bing.com/webmasters/ 的 Site Security 里,逻辑类似但审核更慢,通常 3~7 天。它是独立系统,Google 解除不代表必应解除——很多站长只申诉了 Google,结果 Edge 用户还是一片红。
第五步:解除之后,为什么流量不会立刻回来
这是第二波打击,也是最容易让人放弃的地方。拦截页消失不等于流量恢复,原因有三层:
1. 搜索引擎的标记是滞后的。 Google 搜索结果里的「此网站可能损害您的计算机」标签,在复审通过后通常还要 24~72 小时才会消失。这期间点击率极低。可以在 Search Console 的「安全性问题」里看状态是否变成「未检测到问题」。
2. 排名已经掉了,恢复需要重新积累信号。 拦截期间爬虫抓取失败、用户点击骤降,这些都会体现在排名里。恢复后排名回升通常要一到四周,取决于站点权重和竞争度。
3. 部分用户被缓存了警告。 浏览器的 Safe Browsing 本地数据库有更新周期,一些用户可能还会看到几小时的旧警告。这个无法干预,只能等。
加速恢复的几件实际有用的事:
# 1. 重新提交 sitemap,给爬虫明确的抓取信号
# Search Console > Sitemaps > 重新提交
# 2. 用 URL 检查工具对首页和几个重要页面请求重新抓取
# Search Console > URL 检查 > 请求编入索引
# 3. 确认服务器对爬虫返回 200 且内容完整
for ua in "Googlebot/2.1" "bingbot/2.0"; do
curl -s -o /dev/null -w "%{http_code} %{size_download} $ua\n" \
-A "Mozilla/5.0 (compatible; $ua)" "https://example.com/"
done
# 4. 检查 robots.txt 没有被自己误伤
curl -s https://example.com/robots.txt第 4 条不是多余的。我见过至少两次「恢复后一直不收录」的案例,最后发现是事故期间为了止血加了 Disallow: /,忘了删。这个错误代价是几周。
第六步:防复发——把「发现」的时间从一周缩到一小时
这次事故真正暴露的问题不是「被入侵」,而是从被标记到发现隔了整整一天。老板是先从读者那里知道的,而不是从监控。所以最该补的不是安全加固,是发现速度。
监控项一:文件完整性。 把文档根目录的文件哈希存一份基线,每天比对。
#!/bin/bash
# /usr/local/bin/site-integrity.sh
ROOT="/var/www/html"
BASE="/var/lib/site-integrity/baseline.sha256"
TODAY="/var/lib/site-integrity/today.sha256"
mkdir -p "$(dirname "$BASE")"
find "$ROOT" -type f \( -name '*.php' -o -name '*.js' -o -name '*.html' \) \
-not -path '*/.git/*' -print0 \
| sort -z | xargs -0 sha256sum > "$TODAY"
if [ ! -f "$BASE" ]; then
cp "$TODAY" "$BASE"
echo "baseline created: $(wc -l < "$BASE") files"
exit 0
fi
DIFF=$(diff "$BASE" "$TODAY" | grep -E '^[<>]' | awk '{print $1, $3}' | sort -u)
if [ -n "$DIFF" ]; then
echo "=== FILE CHANGES DETECTED ==="
echo "$DIFF" | head -30
echo "=== count: $(echo "$DIFF" | wc -l) ==="
# 这里接 webhook / ntfy 推送
fi挂到 cron 每天跑一次。注意:正常的发布也会触发变更,所以这个脚本的价值不在「零告警」,而在「你有了一份变更清单」。当你哪天说不清为什么页面变了,这份清单能直接指向文件。
监控项二:伪装爬虫对比。 每周跑一次上面的 bot/user-agent diff。这是发现 cloaking 最直接的手段,而且成本极低。真实页面不应该因为 UA 不同而返回不同内容(响应式设计是 CSS 层面的事,不是服务端吐不同 HTML)。
监控项三:外链引用清单。 定期导出页面里所有外部域名,和上周的对比。多出来的那个陌生域名,往往就是出事的地方。
监控项四:Transparency Report 定期查询。 用一个脚本每周查一次自己的域名,状态变了就推送。
#!/bin/bash
# /usr/local/bin/sb-check.sh example.com
D="$1"
R=$(curl -s "https://transparencyreport.google.com/transparencyreport/api/v3/safebrowsing/status?site=$D")
echo "$(date -Is) $D $R" >> /var/log/sb-check.log
# 状态码非 "safe" 时推送告警
case "$R" in
*'"2"'*) ;; # 通常表示无问题
*) echo "ALERT: $D status $R" ;; # 接 webhook
esac这一条的性价比最高:每周一个 curl,能在「被标记后的第一个小时」而不是「第二天从读者那里」知道。
最后一点实话
Safe Browsing 的判定机制不公开,这意味着「申诉」这件事永远带有不确定性。但不确定性集中在「什么时候解除」,而不在「为什么被标记」——只要真的去查,原因几乎总能在日志、文件、外部引用里找到确切痕迹。所谓「莫名其妙被误报」,绝大多数是因为排查的方向停在了自己的视角里:我看网站是好的,所以它是好的。而拦截系统看的是爬虫视角、外部引用、历史记录,这些你从来不看。
那台站从被标记到复审通过用了三天,流量完全回到事故前水平用了将近五周。这五周里最有用的一件事,是那次事故之后建立的文件完整性基线——它后来抓到了一次更早阶段的注入尝试,那时候页面还没被标记。真正省下流量的,从来不是申诉写得漂亮,而是下一次不用申诉。