静态网站常被误认为“天然安全”,因为不涉及数据库和动态脚本,攻击面似乎更小。但实际情况是,静态站点的服务器配置、第三方资源加载、构建流程和部署环节,每一个节点都可能成为突破口。攻击者不一定非要拿到服务器权限,只要能篡改你页面里的一个JS文件、替换一张图片、注入一段不可见代码,就可能劫持访客流量、窃取表单数据、植入挖矿脚本,甚至把你的站点变成钓鱼页面的跳板。所以静态站的安全加固,重点不在于“有没有数据库”,而在于“资源完整性、传输安全、部署链路、访问控制和持续监控”这五件事是否做到位。
一、先解决传输层:强制HTTPS并启用HSTS静态站最大的风险之一是传输过程中被中间人篡改。即使你的服务器本身没问题,如果用户通过HTTP访问,运营商或公共WiFi环境中的攻击者就可以在返回的HTML里注入广告、恶意脚本或跳转代码。解决方式不是“同时支持HTTP和HTTPS”,而是彻底关闭80端口的明文服务,所有HTTP请求一律301跳转到HTTPS。更彻底的做法是启用HSTS响应头,让浏览器在本地记住“这个域名只走HTTPS”,后续即使用户手动输入http://,浏览器也会在发出请求前直接升级为https://。HSTS的配置在Nginx里只需一行:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
这里max-age建议至少一年,includeSubDomains表示子域名同样强制,preload则是申请加入浏览器内置的HSTS预加载列表。需要注意的是,一旦开启preload,你的所有子域名都必须支持HTTPS,否则会出现无法访问的情况。所以上线前务必检查证书覆盖范围。
二、内容安全策略CSP:给资源加载划一道硬边界静态网站最常见的攻击后果就是XSS。即使页面本身没有用户输入,第三方统计代码、广告脚本、CDN上的公共库、甚至一张看似正常的SVG图片,都可能成为注入入口。CSP的作用是告诉浏览器:只允许从这些来源加载资源,其他一律拦截。一个针对静态站的严格CSP可以这样写:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://img.example.com; font-src 'self' https://fonts.gstatic.com; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
这里script-src中的'unsafe-inline'要谨慎使用。很多静态站为了省事把JS直接写在HTML里,这会让CSP对XSS的防护大打折扣。建议把内联脚本全部抽离成外部文件,然后去掉'unsafe-inline'。如果确实需要内联,可以用nonce或hash的方式精确放行,而不是一刀切允许所有内联。frame-ancestors 'none'是防止你的页面被别的网站用iframe嵌入,避免点击劫持。base-uri 'self'防止攻击者通过注入base标签改变相对路径资源的解析位置。form-action 'self'则限制表单只能提交到本站,避免表单数据被劫持到外部。
三、子资源完整性SRI:防止第三方资源被篡改很多静态站会从公共CDN加载JS库或CSS框架,比如从某个第三方域名加载jQuery或Bootstrap。问题在于,如果这个第三方CDN被入侵,或者你的DNS被劫持,加载回来的文件可能已经被替换成恶意版本。SRI机制允许你在引用外部资源时附带一个哈希值,浏览器下载完文件后会计算哈希并与你提供的值比对,不一致就拒绝执行。写法如下:
<script src="https://cdn.example.com/jquery.min.js" integrity="sha384-abc123..." crossorigin="anonymous"></script>
哈希值可以用openssl命令生成:
openssl dgst -sha384 -binary jquery.min.js | openssl base64 -A
拿到结果后前面加上sha384-前缀即可。这里要提醒一点:一旦给某个资源加了SRI,第三方那边任何一次版本更新或内容变化都会导致哈希不匹配,资源加载失败。所以要么锁定具体版本号,要么在每次更新依赖后重新生成哈希。对于自己站点上的关键JS文件,也可以考虑用SRI配合CDN分发,但前提是CDN不会对文件做任何压缩或改写,否则哈希会失效。
四、安全响应头:把浏览器防护能力拉满除了HSTS和CSP,还有几个响应头对静态站的安全加固非常关键。X-Content-Type-Options: nosniff可以防止浏览器对响应内容类型进行MIME嗅探,避免攻击者上传一个伪装成图片的HTML文件被浏览器当脚本执行。X-Frame-Options: DENY是旧版浏览器的防嵌入方案,虽然CSP的frame-ancestors已经覆盖了这个功能,但加上它可以让不支持CSP的浏览器也得到保护。Referrer-Policy建议设置为strict-origin-when-cross-origin,这样跨域请求时只发送源信息,不发送完整路径,减少敏感URL泄露。Permissions-Policy可以限制浏览器特性权限,比如摄像头、麦克风、定位等,静态站一般用不到这些,直接全部禁用:
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
这些响应头在Nginx里可以统一配置在server块中,对所有静态文件生效。配置完成后用curl -I检查响应头是否完整输出,确保没有被CDN或中间层剥离。
五、静态文件本身的安全处理静态站的文件一旦上传到服务器,通常不会被修改,但这不意味着文件内容天然安全。上传的HTML、JS、CSS、SVG、JSON文件都可能包含恶意代码或敏感信息。首先,构建过程中要确保所有依赖来自可信源,并且锁定版本。package-lock.json或yarn.lock文件必须提交到版本库,防止构建时拉取到被篡改的依赖包。其次,对上传的文件做内容检查,尤其是SVG文件。SVG本质上是XML,可以内嵌script标签,如果用户上传的SVG被直接引用,就会变成存储型XSS。处理方式有两种:一是把用户上传的SVG当作附件下载而不是内联展示,二是用独立的域名提供服务,并配合CSP限制其脚本执行。另外,构建产物中不要包含.map文件,source map会暴露完整的源代码结构和注释,对攻击者来说是非常有价值的情报。如果确实需要调试,只在开发环境生成,生产构建时关闭。
六、部署链路与访问控制静态站的部署方式通常是通过Git推送触发CI/CD自动构建并上传到对象存储或服务器。这条链路如果被攻破,攻击者可以直接往你的站点里写入任意文件。首先,Git仓库的权限要最小化,部署用的token或密钥不要放在前端代码或公开仓库里。CI/CD系统的环境变量要加密存储,日志中避免输出敏感信息。其次,对象存储的Bucket权限要严格配置。很多人为了方便,把Bucket设置为公共可写,这等于把网站后台直接暴露给所有人。正确做法是:Bucket只允许CDN回源读取,写入权限只授予CI/CD服务账号,并且开启版本控制和访问日志。如果使用Nginx直接托管静态文件,要确保站点根目录下的文件权限是只读的,运行Nginx的用户没有写权限。同时关闭目录浏览功能,避免攻击者通过目录列表发现隐藏文件:
autoindex off;
另外,隐藏文件和敏感配置文件要禁止访问,比如.git目录、.env文件、备份文件等。可以在Nginx里加一条规则:
location ~ /\.(?!well-known).* {
deny all;
}
这条规则会拦截所有以点开头的路径请求,但保留.well-known目录用于证书验证等合法用途。
七、持续监控与完整性校验静态站上线后不能放任不管。攻击者可能通过服务器漏洞、DNS劫持、CDN节点入侵等方式篡改你的文件。建议在服务器上部署文件完整性监控,定期计算关键文件的哈希值并与基准值比对。可以用一个简单的脚本配合cron任务实现:
find /var/www/html -type f -exec sha256sum {} \; > /tmp/current_hashes.txt
diff /tmp/baseline_hashes.txt /tmp/current_hashes.txt
如果发现差异,立即触发告警。更完善的做法是把静态资源发布到CDN后,定期从多个地理位置发起请求,检查返回内容的哈希是否与源站一致,防止CDN节点被投毒。同时,开启CDN的访问日志和WAF功能,对异常请求模式进行分析。比如突然出现大量针对某个不存在路径的请求,可能是扫描器在探测漏洞;某个静态文件被频繁下载且来源IP集中,可能是被第三方网站盗链或滥用。针对这些情况,可以在CDN层面配置防盗链、限速和IP黑名单。
八、备份与应急响应静态站虽然结构简单,但一旦被篡改,恢复速度取决于你是否有干净的备份。建议在每次部署成功后,将构建产物打包存档,保留至少最近10个版本。对象存储可以开启版本控制,这样即使文件被覆盖或删除,也能快速回滚到之前的版本。同时准备一份应急响应清单,明确发现篡改后的处理步骤:第一时间下线站点或切换到备用源、检查服务器和CDN日志确定入侵路径、回滚到干净版本、修改所有相关密钥和密码、通知用户如果涉及数据泄露。不要直接在线上修复被篡改的文件,因为攻击者可能已经留下了后门,直接修复可能无法彻底清除。
静态网站的安全加固不是一次性工作,而是一个持续的过程。HTTPS、CSP、SRI、安全响应头、文件权限、部署链路、监控和备份,这些措施环环相扣,缺一不可。攻击者不会因为你的站点是静态的就放过你,相反,静态站往往因为缺乏动态站那样的安全团队关注,反而更容易成为长期潜伏的目标。把每一层防护都落实到位,才能让静态站真正“静态”地安全运行。
