一次对象存储被植入恶意页面后的安全治理复盘
说明:为避免暴露真实业务及调查线索,文中的公司名称、业务域名、Bucket 名称、目录、恶意外链和第三方主体均已匿名化或泛化。相关 IOC、日志及证据仅在内部留存。
一、背景:域名被拦截,问题却不一定出在网站代码
我们的部分业务静态资源托管在对象存储中,并通过业务域名或其关联域名对外提供访问。近期,主域名及多个子域名突然在某社交平台的内置浏览器中被判定存在风险,用户访问时会直接进入安全拦截页。
第一反应通常是排查官网、管理后台和近期上线内容。但检查应用代码和数据库后,并没有发现明显的违规页面。继续沿着域名解析、CDN 回源和对象存储链路排查,最终发现:对象存储中被写入了一批不属于正常业务的网页和无后缀文件。
这些文件可以通过受信任的业务域名直接访问。攻击者并不需要入侵应用服务器,只要拿到一个权限过宽或可被滥用的上传凭证,就能把对象存储变成恶意页面的托管空间。风险链接一旦被外部传播,平台风控看到的并不是“某个孤立文件有问题”,而是“这个域名正在持续提供风险内容”,最终可能连带拦截主域名及其子域名。
这也是本次事件最容易被忽略的坑:域名被拦截只是结果,对象存储写入链路失守才是源头。
二、事件排查:从异常文件反推攻击链
我们对全部对象存储空间进行了人工排查,并结合文件类型、写入时间、目录分布和源码特征进行分类。
异常文件主要集中在两个历史业务目录,其中一个目录曾长期用于前端直传,因此被列为重点凭证泄露或接口滥用排查对象。下载部分样本并分析源码后,发现其行为主要包括:
- 构造寄生页面,借助正常域名获取搜索引擎收录;
- 劫持搜索流量,根据访问来源动态跳转;
- 连接外部 WebSocket 或中控地址,远程获取跳转目标;
- 使用混淆脚本、动态加载和条件判断规避静态检查;
- 在普通扩展名、无扩展名文件中隐藏可执行网页内容。
另一个历史 Bucket 中还存在 .php、.jsp 和无后缀文件。按照当前业务设计,这类文件本不应出现在静态资源 Bucket 中,但部分文件写入时间较早,不能仅凭扩展名直接认定为恶意文件。为避免误删历史业务源码,我们采用了分级处置:
| 文件类型 | 判断依据 | 处置方式 |
|---|---|---|
| 已确认恶意 | 存在外部中控、动态跳转、混淆劫持等行为 | 留存证据后隔离并删除 |
| 高风险但无法立即定性 | 类型异常、来源不明、业务无人认领 | 先隔离,人工复核 |
| 历史业务疑似文件 | 写入时间早,可能属于旧系统 | 禁止公网访问,联系负责人确认 |
| 正常业务文件 | 来源、用途、责任人均可确认 | 保留并补齐审计信息 |
这里有一个很重要的经验:安全处置不是看到可疑扩展名就批量删除。 对于可能涉及业务资产或后续取证的文件,应先保存对象元数据、内容哈希、写入时间、访问日志和样本,再进行隔离或删除。
三、攻击链是怎样形成的
结合接口代码、访问日志和异常文件特征,整个风险链路可以概括为:
问题通常不是某一个开关没有打开,而是多层薄弱环节叠加:
- 历史上传接口仍可访问,且调用成本较低;
- 上传授权范围过宽,客户端可以控制目录或完整对象 Key;
- 临时凭证有效期、上传大小或并发次数缺少严格限制;
- 只校验扩展名,没有验证真实文件类型和内容;
- 上传成功后直接公开访问,没有隔离区和二次审核;
- 缺少“用户、IP、凭证、对象 Key、请求时间”的关联审计;
- 异常监控滞后,直到外部平台拦截后才发现问题。
四、攻击者真正想利用的是什么
在分析异常文件源码后,可以发现,这些文件本身并不是传统意义上的 WebShell,也没有直接攻击业务系统的行为,而是更多承担了流量入口的作用。
绝大多数样本都包含了重定向、动态加载或远程控制逻辑。当用户访问这些文件时,并不会停留在对象存储页面,而是根据访问环境、来源或攻击者远程下发的策略,被跳转到指定的网站。
典型流程如下:
用户访问业务域名
│
▼
对象存储中的恶意页面
│
▼
执行 JavaScript / WebSocket / 中控逻辑
│
▼
获取最新跳转地址
│
▼
302 或 JavaScript 跳转
│
▼
攻击者控制的网站这类攻击的真正目标通常不是对象存储本身,而是借用可信业务域名作为流量入口。
结合样本分析,大致可以归纳为以下几类用途:
(一)借助可信域名为自身网站引流
这是最常见的一类。
攻击者将恶意页面上传到企业对象存储后,通过业务域名进行访问,例如:
https://static.xxx.com/xxxxx.html由于访问地址属于企业正常业务域名,用户、搜索引擎以及部分平台都会天然赋予其更高的可信度。
当用户打开后,页面会立即执行重定向逻辑,将访问流量导向攻击者真正控制的网站。
整个过程中,业务域名仅作为**流量跳板(Traffic Relay)**存在,而最终内容实际上全部来自攻击者控制的服务器。
(二)借助企业信誉伪装非法网站
另一类风险更高。
攻击者并不是单纯获取访问流量,而是利用企业域名提升非法网站的可信度。
例如:
-
赌博网站
-
色情网站
-
虚假投资平台
-
钓鱼页面
-
仿冒登录页面
-
虚假客服页面
由于用户最初访问的是企业域名,很多用户甚至不会注意后续发生的跳转,从而降低警惕性。
对于社交平台、搜索引擎以及浏览器安全系统而言,它们首先看到的也是:
企业域名正在持续提供风险内容。
最终导致整个域名信誉下降,甚至被平台整体封禁。
(三)利用企业基础设施降低自身成本
对象存储、CDN 和业务域名本身就属于高可用、高带宽、高信誉的互联网基础设施。
攻击者上传的页面虽然很小,但却能够直接利用:
-
企业对象存储的高可用能力;
-
CDN 全球节点提供的高速访问;
-
企业域名长期积累的信誉;
-
HTTPS 证书带来的可信访问体验。
换句话说,攻击者几乎不需要投入自己的基础设施,就能够获得一套成熟的内容分发能力。
因此,他们真正消耗的并不是对象存储容量,而是在**“借用企业基础设施为自己的业务服务”**。
(四)通过动态跳转规避检测
本次样本中还有一个共同特点,就是大量使用动态获取跳转地址,而不是直接写死目标网址。
例如:
-
WebSocket 获取最新地址;
-
请求中控接口返回目标域名;
-
JavaScript 动态拼接 URL;
-
根据来源 Referer、User-Agent 或访问地区决定是否跳转。
这样做有几个目的:
-
被封一个域名后,只需修改中控即可恢复;
-
静态扫描难以直接发现最终目标;
-
可以针对搜索引擎和普通用户返回不同内容;
-
可以按地区、时间或来源动态切换跳转策略。
因此,即使删除一个目标域名,也无法真正消除风险,真正需要切断的是对象存储上传入口。
从本次事件来看,攻击者真正需要的并不是对象存储空间本身,而是企业长期积累的域名信誉、CDN 分发能力以及对象存储的托管能力。对象存储中的恶意文件只是整个攻击链的入口,其真正价值在于让攻击者能够借助可信业务域名承载自己的流量分发和内容跳转逻辑。一旦上传链路失守,企业的基础设施就可能在不知情的情况下成为非法内容传播、流量劫持甚至钓鱼攻击的一部分。这样的问题不仅影响对象存储本身,更会直接损害企业域名信誉,并触发搜索引擎、社交平台及浏览器安全机制的整体拦截。
五、应急处置:先断入口,再清文件
发现异常文件后,最危险的操作是只删除文件、不关闭入口。只要上传能力仍然存在,攻击者很快就能重新写入,清理工作会变成没有尽头的打地鼠。
本次应急处置按以下顺序进行:
- 立即下线存在风险的旧上传接口;
- 作废可能泄露的访问密钥和临时凭证,收紧 RAM 权限;
- 暂停高风险目录的外部写入,阻断新增风险文件;
- 对全部 Bucket 进行扫描,而不是只处理已暴露的目录;
- 对恶意样本、对象元数据、访问日志和请求记录进行证据留存;
- 隔离并清理确认恶意的网页、脚本和无后缀文件;
- 联系云厂商协助核查访问日志、凭证调用和异常来源;
- 确认风险源已经清除后,再提交域名复核与解封申请。
“先断入口,再清文件”看似简单,却决定了处置是一次止血,还是反复复发。
六、上传授权接口的重新设计
整改后的核心思路是:前端不再获得宽泛的上传能力,后端只为当前这一次上传签发一个短时、单文件、强约束的授权。
6.1 完整调用流程
6.2 授权必须足够窄
后端签发上传授权前,需要同时校验:
- 当前用户是否已登录、账号状态是否正常;
- 上传场景是否属于服务端白名单;
- Bucket 和目录是否由服务端配置,禁止客户端自由指定;
- 文件扩展名、声明类型、大小是否符合当前业务场景;
- 用户维度和 IP 维度是否触发频率或并发限制;
- 同一请求是否存在重放、覆盖已有对象等风险。
对象 Key 也不应由前端直接传入,而应由服务端生成,例如:
业务目录/用户标识/日期/随机标识.扩展名上传策略应进一步限制为:
- 仅允许写入一个确定的对象 Key;
- 只授予上传能力,不授予读取、删除、列举等额外权限;
- 限制最小和最大文件大小;
- 凭证保持短时有效,过期后必须重新申请;
- 默认禁止覆盖已有对象;
- 密钥、签名、Token 和完整 Policy 不写入业务日志。
6.3 限流不能只看单一维度
只限制 IP 容易误伤公司出口、校园网和运营商 NAT;只限制用户则可能被批量注册账号绕过。因此建议至少同时控制:
- 用户固定窗口申请次数;
- IP 固定窗口申请次数;
- 用户当前并发凭证数;
- IP 当前并发凭证数;
- 连续参数校验失败次数。
初始阈值应根据正常用户上传行为设置得相对保守,再结合监控逐步调整。例如,普通图片上传场景可以从“单用户每分钟十次以内、同时持有少量有效授权”起步,而不是一开始给出几百次的宽松额度。
并发控制建议使用带唯一租约标识和过期时间的 Redis 结构。请求获得授权时创建租约,上传完成或授权失效后释放;释放时必须校验租约标识,避免旧请求误删新请求的并发占位。所有限流 Key 都要设置过期时间,防止异常流程产生永久脏数据。
6.4 真实 IP 获取必须建立信任边界
在容器、反向代理或网关环境中,应用直接读取到的远端地址通常是内网 IP。直接信任客户端传入的 X-Forwarded-For 同样危险,因为攻击者可以伪造该请求头绕过 IP 限流。
正确方式是:
- 由最外层可信网关覆盖或清洗来源 IP 请求头;
- 应用只在请求来自可信代理地址时解析转发头;
- 按代理链规则选择第一个非可信地址作为客户端 IP;
- 同时记录客户端 IP、远端 IP 和经过清洗的代理链,便于溯源;
- 不把公网 IP 识别逻辑散落在各个业务接口中,统一由网关或公共组件处理。
七、从扩展名校验升级为内容治理
扩展名黑名单只能挡住最直接的攻击。恶意网页可以没有后缀,也可以伪装成图片、文本甚至业务数据文件,因此更稳妥的方案是“按业务场景建立白名单 + 上传后检测”。
建议将上传文件先写入不可公开访问的隔离区,完成以下检查后再发布:
- 使用文件头特征识别真实类型,而不是只信任扩展名和
Content-Type; - 校验文件大小、图片尺寸、编码和解码结果;
- 检测 HTML、脚本标签、动态跳转、外部中控、WebSocket、混淆代码等特征;
- 对压缩包、SVG、Office 文档等高风险格式执行专门解析;
- 接入病毒或恶意内容扫描能力;
- 对无法确认的文件进入人工审核,而不是默认放行。
对外访问层也应增加防护:不可信上传内容使用独立资源域名,与主站登录态和 Cookie 隔离;响应中设置正确的 Content-Type、Content-Disposition 和 X-Content-Type-Options: nosniff,降低浏览器把普通文件当作网页执行的风险。
八、异常扫描与持续巡检
一次性人工排查只能解决当前问题,长期治理需要自动化扫描。扫描任务可以每隔数分钟增量读取最近写入的对象,重点关注:
.html、.htm、.php、.jsp等不符合业务设计的扩展名;- 无后缀文件、双扩展名和异常
Content-Type; - 新出现的一级目录或高随机度目录;
- 短时间内由同一用户或来源 IP 批量写入的对象;
- 包含外部跳转、动态脚本、WebSocket、中控请求和混淆特征的内容;
- 历史对象被覆盖、访问权限突然变更等操作。
扫描结果至少要记录 Bucket、对象 Key、ETag、内容哈希、文件大小、最后修改时间、风险规则和处理状态。对明确恶意的新增文件可以自动隔离;对历史文件或判断不充分的文件,只告警不自动删除。
九、告警要汇总,也要能重试
上传接口的安全事件通常具有突发性。如果每次拒绝都发送一封邮件,很快会形成告警风暴,甚至触发邮件服务自身的发送限制。因此我们采用“事件入队、定时汇总、冷却抑制”的方式:
事件队列、统计窗口、分布式锁和冷却标记都必须设置合理的过期时间。一个可落地的初始配置是:
- 每 5 分钟执行一次汇总任务;
- 统计最近 10 分钟的安全事件;
- 连续校验失败达到 3 次,或出现限流事件时进入告警候选;
- 相同类型告警设置约 30 分钟冷却期;
- 事件队列保留约 60 分钟,并限制最大长度;
- 邮件发送失败时不删除事件,待下一周期重试。
这些数字不是固定标准,应该依据正常流量、攻击密度和邮件通道能力调整。关键是同时做到:真正的攻击不能漏掉,同一波攻击也不能把通知通道打爆。
十、权限与审计:让每一次写入都能解释
对象存储权限应遵循最小授权原则:
- 应用使用短期凭证,不把长期访问密钥下发到客户端;
- 按环境、业务和 Bucket 拆分账号与角色;
- 上传角色只允许写入指定前缀,不允许列举、删除或修改权限;
- 定期轮换密钥,发现泄露迹象立即作废;
- 开启对象存储访问日志、操作审计和必要的 CDN 日志;
- 日志中建立用户、IP、请求 ID、授权 ID、对象 Key 和时间的关联关系。
只有当一次异常写入能够回答“谁申请了授权、从哪里申请、被允许写到哪里、最终写了什么、随后被谁访问”,溯源才不再依赖猜测。
十一、整改后的整体防线
最终方案不应只依赖某一个接口校验,而是建立多层防线:
这套设计的目标不是承诺“永远不会出现恶意文件”,而是让攻击者更难获得写入能力,让单次凭证的破坏范围足够小,让风险文件在公开传播前被发现,并让所有异常行为都有证据可追溯。
十二、复盘总结
需要特别说明的是,安全整改应区分“应急措施”和“长期能力”,不能因为方案设计完成就宣称风险已经永久消失。本次工作的实际推进状态如下:
| 阶段 | 当前状态 |
|---|---|
| 全量人工排查、恶意文件清理、风险入口下线、风险凭证处置 | 已完成 |
| 新上传授权接口的鉴权、限流、并发控制、审计和汇总告警 | 后端已完成,等待客户端逐步切换 |
| 历史疑似文件归属确认、云厂商协查、证据链补全 | 持续进行 |
| 隔离区、上传完成确认、内容检测和自动化增量扫描 | 作为下一阶段纵深防御能力建设 |
在新旧接口切换完成前,应继续保留人工巡检和高频日志观察;在历史文件尚未确认前,应坚持隔离优先,避免直接批量删除。
这次事件带来了几条很实在的教训:
- 对象存储一旦绑定受信任域名,就不再只是一个“放图片的地方”,而是完整的内容发布系统;
- 删除恶意文件只能治标,关闭旧入口、撤销凭证和收紧权限才是在止血;
- 只做扩展名黑名单远远不够,必须结合白名单、真实类型识别和内容检测;
- 来源 IP 必须在可信代理链中解析,否则限流和溯源都会失真;
- 上传授权应该短时、单对象、不可覆盖,并与用户和请求全链路关联;
- 监控要早于外部平台投诉,告警需要汇总、冷却和失败重试;
- 清理前要先留存证据,特别是对象元数据、内容哈希、访问日志和异常调用记录;
- 对历史遗留文件应分级处置,不能为了快速清理制造新的业务事故。
安全事故最麻烦的地方,往往不是某段代码写错了,而是一个曾经“够用”的历史方案,在业务规模、攻击方式和平台风控都发生变化后,悄悄变成了风险入口。
对象存储直传依然是高效、成熟的架构,但前提是把它当作一条需要认证、授权、审核、监控和追溯的内容供应链,而不是一个拿到签名就可以随意写入的文件夹。