电脑上打开自家网站一切正常,用户用手机点进来却被弹到陌生页面,或者莫名弹出好几个窗口——这种 PC 正常、手机异常的情况在站长圈里叫移动端劫持。手机网页被挂马怎么办?先别急着推翻重做模板,劫持可能来自服务器下发的判断逻辑、移动端模板里被注入的代码、CDN 边缘缓存,也可能来自运营商链路上的插入,层级不同,处理手法完全不一样。
一、先确认劫持发生在哪一层
做法是把同一个地址用不同身份各请求一次再对比。用带手机 User-Agent 的请求抓一遍页面,再用电脑的 User-Agent 抓一遍,两个结果不一致,说明代码在按访问者身份分发内容。换一个网络环境再试,比如从公司宽带换成手机热点,如果只有某个网络下才跳,问题多半出在该运营商链路的插入,不在你的服务器上。两种情形分开记录,后面的处理路径完全不同。
下面这段脚本用手机 User-Agent 请求页面,并在返回的 HTML 里搜索跳转和外部脚本注入的典型特征,命中就把上下文片段打印出来,方便判断是移动端模板被改写,还是页面运行中又被额外插了脚本。把 URL 换成你的移动端首页地址即可。
# 用手机 User-Agent 抓取页面,搜索移动端常见的跳转与劫持特征
import re, urllib.request
URL = "https://www.example.com/" # 改成你的移动端页面地址
UA_MOBILE = ("Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1")
PAT = re.compile(
rb"(window\.location|document\.location|location\.replace|"
rb"http-equiv=[\"']?refresh|"
rb"<script[^>]+src=[\"']http://)",
re.I,
)
req = urllib.request.Request(URL, headers={"User-Agent": UA_MOBILE})
html = urllib.request.urlopen(req, timeout=15).read()
for m in PAT.finditer(html):
s = max(0, m.start() - 80)
print("命中片段:", html[s:m.end() + 80].decode("utf-8", "ignore").replace("\n", " "))
跑完分三种结果处理。命中片段出现在你熟悉的模板文件里,就顺着片段定位到具体文件,比对干净版本还原;命中出现在页面尾部、模板里根本搜不到,说明是运行时注入或 CDN 边缘改写,往程序入口文件和 CDN 规则上查;完全没有命中却仍在真机上跳转,重点转向运营商链路和 DNS 解析。
二、移动端代码常藏的四个入口
第一个是移动端模板目录,很多程序把 m、mobile、wap 目录单独放一套模板,电脑模板查干净了这里却漏了。第二个是公共 JS 文件,劫持代码常被塞进统计脚本或公共头尾引入的文件里。第三个是 CDN 的缓存与边缘规则,被改写过的页面会被缓存住,只清源站没用,必须刷新 CDN 缓存。第四个是程序入口文件的 UA 判断逻辑,攻击者写一段只有手机访问才执行的代码,站长在电脑上自查基本发现不了。
三、清理顺序
先做一次全量备份并临时挂维护页,再用文件修改时间把最近被改动的文件列出来,逐个比对还原;还原之后按上面的脚本复抓一次,确认跳转特征消失。用了 CDN 的,清理完成后必须刷新全站缓存,否则用户拿到的还是被改写过的旧副本。最后改掉后台、数据库、服务器三处密码,把入口文件权限收紧到 644,模板目录设为只读。
四、加固与复检
移动端页面可以在响应头里加内容安全策略,限制脚本只能从自己的域名加载,能挡住大部分外链脚本注入。程序升级前先比对模板文件哈希,发现被改过的立刻还原。复检时用安卓和 iOS 各测一台真机,别只靠浏览器模拟。全部通过后去站长平台提交修复反馈,并在监控里加一条移动端页面的定时抓取,UA 固定成手机,出现跳转特征就告警。移动端劫持的排查核心就是“用移动端的身份去抓、去比”,坚持用手机 UA 复抓一遍,问题基本藏不住。
相关阅读:
申请创业报道,分享创业好点子。点击此处,共同探讨创业新机遇!
