web 安全是值得每一个做 web 开发的前、后端的开发人员深入研究的一个话题。只有理解可能存在的安全问题,并加以防范,我们才能构建出一个安全的网站服务。
传输安全控制
web 网站是一个基于 HTTP 协议之上的 B/S 架构软件,客户端与服务端之间的通讯是通过 TCP 建立连接的网络请求实现的,因此数据从服务端传输到客户端,或者从服务端下发数据到客户端时,如果是明文传输就存在中间人攻击的风险。因此,我们需要保证我们在传输过程中数据信息是安全的。以下有几个安全规则可以在我们开发中实施。
HTTPS
网站后者 API 使用 HTTPS 协议进行传输。我们可以借助 let-encrypt 为免费签发 https 证书,然后配置 nginx 即可。
HTTP Strict Transport Security
HTTP Strict Transport Security (HSTS) 是一个 HTTP header 配置,它通知浏览器只能通过 HTTPS 连接请求到我们的网站资源获 API。如果存在 HTTP 请求,也会强制转换所有升级到 HTTPS。
HSTS header 设置由一个强制性的参数(max-age)和两个可选参数( includeSubDomains 和 preload) 用分号分隔组成。
- max-age: 重定向到HTTPS多长时间,以秒为单位
 - includeSubDomains: 子域名是否应该升级请求
 - preload: 是否包含升级请求本站预加载的请求
 
max-age 最小值必须要 6 个月(15768000, 但是建议设置更长的时间,比如两年(63072000)。需要注意的是,当值被设置后,网站必须要在设置的时间断内支持 HTTPS 连接。
includeSubDomains 通知浏览器,所有子域也升级到 HSTS 发送请求。例如,在 domain.mozilla.com 上设置 includeSubDomains,所有请求到 host1.domain.mozilla.com 或者 host2.domain.mozilla.com 的请求均会主动升级到 HTTPS 请求,这也要求子域名也需要长期支持 HTTPS 请求。
preload 设置预加载资源升级到 HTTPS 请求。
示例:
连接到本网站的所有请求升级到 HTTPS 协议
1  | Strict-Transport-Security: max-age=63072000  | 
连接到本网站包括子域名及预加载资源均升级到 HTTPS 协议发送请求
1  | Strict-Transport-Security: max-age=63072000; includeSubDomains; preload  | 
HTTP 重定向
HTTP 默认是监听 80 端口,HTTPS 是 443 端口,我们可以通过 nginx 配置,强制重定向所有 HTTP 请求到 HTTPS。nginx 配置示例如下:
1  | server {  | 
内容安全控制(CSP)
内容安全控制主要是未来防止跨站脚本攻击及跨站注入攻击。CSP 是一个 HTTP header,允许细粒度的控制资源可以从哪些网站加载。这个头的使用是最好的方法来防止跨站点脚本(XSS)漏洞。
示例:
禁止不安全的 inline/eval,只允许加载位于 HTTPS 服务的资源(图片、字体、脚本等)
只做此配置不能达到 XSS 保护
1  | Content-Security-Policy: default-src https:  | 
禁用不安全的使用 inline/eval 及插件执行
1  | Content-Security-Policy: default-src *; object-src 'none'  | 
禁用不安全的使用 inline/eval 及插件执行,允许图片通源及从 imgur 加载
1  | Content-Security-Policy: default-src 'self'; img-src 'self' https://i.imgur.com; object-src 'none'  | 
禁用不安全安全 inline/eval 和插件加载,只有从同源加载脚本和样式表,从谷歌字体,从同源和 imgur 加载图片。这是值得推荐的做法。
1  | Content-Security-Policy: default-src 'none'; font-src 'https://fonts.googleapis.com';  | 
已存在的网站是用来了太多的 inline 来加载资源,但是想要改造为只允许通过 https 加载,并且禁止插件
1  | Content-Security-Policy: default-src https: 'unsafe-eval' 'unsafe-inline'; object-src 'none'  | 
需限制 CSP 策略,只是上报违反规则的资源加载
1  | Content-Security-Policy-Report-Only: default-src https:; report-uri /csp-violation-report-endpoint/  | 
禁用任何资源和禁用的加载框架,建议使用api
1  | Content-Security-Policy: default-src 'none'; frame-ancestors 'none'  | 
Cookies 安全
所有 Cookies 都应该尽可能的限制它被访问到,因为 Cookies 通常用来存储一些敏感的信息,限制 Cookies 访问,可以帮助我们减少跨站脚本攻击(XSS)。
设置 Cookies 注意事项
- Name: Cookies 大部分会采用 __Secure- 或 __Host- 作为前缀,从而避免被不安全的来源重写覆盖了
- 使用 __Host- 前缀设置特定域名的 Cookies,如果不包含子域名 Path 需要设置为 /
 - 使用 __Secure- 前缀设置允许发送到所有 https 协议下的 Cookies
 
 - Secure: 所有cookie必须设置安全标志,表明他们只应该送到HTTPS
 - HttpOnly: 如果 Cookies 不允许 javascript 访问,他们应该被这设置为 HttpOnly 标记
 - Expiration: 设定过期时间,如果是 session 等会话标示,应该设置尽可能快的过期时间,防止会话泄露
- Expires: 设置一个 Cookies 到期日期
 - Max-Age: 设置一个相对创建多久之后失效的过期时间 (IE8 以下不支持)
 
 - Domain: 设置 Cookies 允许随着请求发送到哪些域名,设置应该尽可能严格
 - Path: Cookies 应在哪些路径下跟随请求发送,大部分的网站会设置根目录,也就是所有路径均发送
 - SameSite: 禁止通过跨源发送 Cookies 请求,例如 
<img>标签实现跨站请求伪造(CSRF)- SameSite=Strict: 只发送 Cookies 时直接导航到网站
 - SameSite=Lax: 当从另一个网站导航到你的网站时允许发送 Cookies
 
 
Cookies 设置示例:
Session 会话标识符 Cookies 仅可在该域名上访问,用户关闭浏览器时被清除
1  | Set-Cookie: MOZSESSIONID=980e5da39d4b472b9f504cac9; Path=/; Secure; HttpOnly  | 
Session 会话标识符的 mozilla.org 网站,在30天内到期使用,并且使用 __Secure- 作为前缀
这个 Cookies 不允许跨域访问是携带发送,只允许导航至 mozilla.org 时携带发送
1  | Set-Cookie: __Secure-MOZSESSIONID=7307d70a86bd4ab5a00499762; Max-Age=2592000; Domain=mozilla.org; Path=/; Secure; HttpOnly; SameSite=Lax  | 
当用户接收服务条款时设置一个长久的 cookies 记住用户的行为
这个 Cookies 不允许跨域访问是携带发送,只允许导航至 mozilla.org 时携带发送
1  | Set-Cookie: __Host-ACCEPTEDTOS=true; Expires=Fri, 31 Dec 9999 23:59:59 GMT; Path=/; Secure; SameSite=Lax  | 
用于安全站点的会话标识符,例如 bugzilla.mozilla.org。 它不允许从跨域请求发送,也不允许从另一个站点导航到bugzilla.mozilla.org 时发送。 与其他反 CSRF 措施结合使用,这是保护站点免受CSRF攻击的一种非常有效的方法。
1  | Set-Cookie: __Host-BMOSESSIONID=YnVnemlsbGE=; Max-Age=2592000; Path=/; Secure; HttpOnly; SameSite=Strict  | 
跨域资源共享
Access-Control-Allow-Origin 是一个HTTP标头,用于定义允许哪些外部来源通过使用 XMLHttpRequest 之类的脚本通过脚本访问域中页面的内容。
示例:
允许任何站点请求你的网站资源
1  | Access-Control-Allow-Origin: *  | 
允许 https://random-dashboard.mozilla.org 读取此 API 的返回结果,
1  | Access-Control-Allow-Origin: https://random-dashboard.mozilla.org  | 
CSRF
跨站点请求伪造(Cross-site request forgeries)是一类攻击。未经授权的请求从受信任的用户发送到网站。因为发送请求会携带用户的 cookie 及存储在 cookie 中的会话信息,所以服务端会把它理解为有效的请求并执行。 CSRF 攻击可能如下所示:
<!- 尝试删除用户的帐户 ->
<img src =“ https://accounts.mozilla.org/management/delete?confirm=true”>
当用户访问带有该HTML片段的页面时,浏览器将尝试向该URL发出GET请求。如果用户已登录,浏览器将提供其会话cookie,并且帐户删除尝试将成功。
阻止 CSRF 攻击有两种方式:
- 通过设置 SameSite cookie 来防御,但是并不是所有浏览器都支持,所以不能做到完全安全。
 - 使用反 CSRF token。token 需要具备唯一且不可预测,切需要定期清除,每次有破坏性的请求是均需要携带上 CSRF token,服务端验证通过之后才执行命令。
 
示例:
下发 from 表单是增加一个 hidden input,存储阻止 CSRF token 请求的密匙
服务端设置一个 CSRF cookie
1  | Set-Cookie: CSRFTOKEN=1df93e1eafa42012f9a8aff062eeb1db0380b; Path=/; Secure; SameSite=Strict  | 
Client-side, have JavaScript add it as an X header to the
客户端如果是通过 XMLHttpRequest 发送的请求则读取 cookies 重的 CSRF tokken 需要携带在 header 中
1  | var token = readCookie(CSRFTOKEN); // 读取 CSRF token  | 
Referrer Policy
当用户通过超链接导航到站点或网站加载外部资源时,浏览器通过使用HTTP Referer(sic)标头将请求的来源通知目标站点。 尽管这对于多种用途很有用,但也会使用户的隐私受到威胁。 HTTP Referrer Policy允许站点对浏览器如何以及何时传输HTTP Referer标头进行细粒度控制。
在正常操作中,如果https://example.com/page.html上的页面包含`<img src =“ https://not.example.com/image.jpg”>`,则浏览器将发送这样的请求 :
1  | GET /image.jpg HTTP/1.1  | 
在每次请求中均会携带上 Referer 字段,但是如果请求的资源是外部,但是你不想透露你是从哪里来的请求,则可以通过控制 Referer 来避免来源泄露。
设置规则:
- no-referrer: 从不发送 referer
 - same-origin: 只在同源请求中携带 referer
 - strict-origin: 允许所有请求携带 referer,但是 referer 中不包含子路径 (比如: https://example.com/)
 - strict-origin-when-cross-origin: 同源下发送完整路径,不同源不包含自路径
 
示例:
在example.com上,仅在加载或链接到其他example.com资源时发送Referer标头
1  | Referrer-Policy: same-origin  | 
同源下发送完整路径,不同源不包含自路径
1  | Referrer-Policy: strict-origin-when-cross-origin  | 
浏览器不支持 strict-origin-when-cross-origin 策略时禁止发送 Referer
如果支持 strict-origin-when-cross-origin 则使用此策略
1  | Referrer-Policy: no-referrer, strict-origin-when-cross-origin  | 
1  | <!-- 通过 meta 标签设置策略 -->  | 
robots.txt
robots.txt是放置在网站根目录中的文本文件,通过指示爬虫机器人(例如,搜索引擎使用的索引器)不要对网站上的某些路径进行索引,从而告诉爬虫机器人如何操作。 尽管禁用了自动生成的内容的索引编制功能,这对于减少网站负载特别有用。 对于无法从搜索中受益的资源,这也有助于防止污染搜索结果。
网站可以选择使用robots.txt,但仅应将其用于这些目的。 不应将其用作防止泄露私人信息或隐藏网站部分的方法。 尽管这样做确实可以阻止这些网站出现在搜索引擎中,但是它并不能阻止其被攻击者发现,因为robots.txt通常用于侦听。
示例:
停止所有搜索引擎爬取此站点
1  | User-agent: *  | 
使用 robots.txt 隐藏某些目录
1  | User-agent: *  | 
X-Content-Type-Options
X-Content-Type-Options是Internet Explorer,Chrome和Firefox 50+支持的标头,告诉标头除非服务器指示正确的MIME类型,否则不要加载脚本和样式表。 如果没有此标头,这些浏览器可能会错误地将文件检测为脚本和样式表,从而导致XSS攻击。 这样,所有站点都必须为其提供的文件设置X-Content-Type-Options标头和适当的MIME类型。
示例:
Prevent browsers from incorrectly detecting non-scripts as scripts
1  | X-Content-Type-Options: nosniff  | 
X-Frame-Options
X-Frame-Options是一个HTTP标头,可让网站控制您的网站在iframe中的显示方式。 点击劫持(Clickjacking是一种实用的攻击,它使恶意网站可以诱骗用户单击您网站上的链接,即使这些链接似乎根本不在您的网站上。
请注意,X-Frame-Options已被内容安全政策的frame-ancestors指令所取代,该指令可对允许构架网站的来源进行更精细的控制。由于IE11和更早版本,Edge,Safari 9.1(台式机)和Safari 9.2(iOS)尚不支持帧祖先,因此建议网站除了使用CSP外,还应使用X-Frame-Options。
配置指令
- DENY: 禁止网站内嵌到 iframe 中
 - SAMEORIGIN: 允许内嵌到同源网站中
 - ALLOW-FROM uri: 已废弃; 请使用 CSP’s frame-ancestors 设置
 
示例:
使用 X-Frame-Options 和 CSP 策略禁止网站内嵌到 iframe 中
1  | Content-Security-Policy: frame-ancestors 'none'  | 
只允许网站自己内嵌到自己的 iframe 中
1  | Content-Security-Policy: frame-ancestors 'self'  | 
只允许你的网站内嵌到 framer.mozilla.org 这个网站的 iframe 中
如果浏览器不支持 CSP2+,你的网站会不被允许内嵌到 framer.mozilla.org 中
1  | Content-Security-Policy: frame-ancestors https://framer.mozilla.org  | 
X-XSS-Protection
X-XSS-Protection是Internet Explorer和Chrome的一项功能,当页面检测到反射的跨站点脚本(XSS)攻击时,该页面将阻止加载页面。 尽管当站点实施强大的内容安全策略以禁止使用内联JavaScript(“不安全内联”)时,在现代浏览器中这些保护在很大程度上是不必要的,但是它们仍可以为尚不支持CSP的旧版Web浏览器的用户提供保护。 。
新网站应使用此标头,但由于误报的风险很小,因此仅建议用于现有网站。 对于API而言,此标头不是必需的,而应仅返回一个限制性的“内容安全策略”标头。
示例:
Block pages from loading when they detect reflected XSS attacks
1  | X-XSS-Protection: 1; mode=block  |