CSRF
参考资料:浅谈CSRF攻击方式
是什么
CSRF(cross-site request forgery),中文名:跨站请求伪造。
假如黑客在自己的站点上放置了其他网站的外链,例如"www.weibo.com/api
,默认情况下,浏览器会带着weibo.com
的cookie访问这个网址,如果用户已登录过该网站且网站没有对CSRF攻击进行防御,那么服务器就会认为是用户本人在调用此接口并执行相关操作,致使账号被劫持。
原理
要完成一次CSRF攻击,需要两个条件:
- 用户访问了某个登录网站A;
- 在不登出A网站的情况下,去访问危险网站B。
危险网站B就会利用浏览器会自动带上cookie的原理来向A网站发出恶意请求。
攻击方式可以是:利用img标签,iframe等,实际CSRF攻击是源于web的隐式身份验证机制。隐式身份验证虽然可以保证请求来自用户浏览器,但无法保证是用户批准的请求。
CSRF的防御
CSRF的防御可从服务端和客户端入手,不过现在一般的CSRF防御都在服务端。
服务端的防御总的思想:在客户端页面增加伪随机数。
防范;
- 验证
Token
:浏览器请求服务器时,服务器返回一个token,每个请求都需要同时带上token和cookie才会被认为是合法请求; - 验证
Referer
:通过验证请求头的Referer来验证来源站点,但请求头很容易伪造 - 设置
SameSite
:设置cookie的SameSite,可以让cookie不随跨域请求发出,但浏览器兼容不一
扩展:chrome自76版本给cookie添加了SameSite属性,用于防止CSRF攻击,该属性可以限制第三方的cookie,可以设置的值有:
- strict:完全禁止第三方cookie,跨域时,任何情况下都不会发送第三方 Cookie。
lax(默认):多数情况不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外
// 导航到目标网址的 Get 请求只包括三种情况:
<a href="..."></a>
<link rel="prerender" href="..."/>
<form method="GET" action="...">
none
XSS
参考资料:Web安全测试之XSS
是什么
XSS(Cross Site Scripting,CSS/XSS),中文名跨站脚本攻击。通过向网页注入可执行脚本进行攻击,如获取用户cookie。
分为存储型(攻击代码存储进服务器了)、反射型(攻击代码放在URL请求参数中)、DOM型(将攻击脚本写在URL中但不经过服务器)
防范
是什么
什么是跨域:浏览器有个同源策略,这是浏览器最基本的安全功能。如果缺少了同源策略就容易受到XSS CORS的攻击,所谓的同源指的就是“协议+域名+端口”三者都相同。当三者任一不同时,就产生了不同域,那不同域之间的数据请求访问就是称作跨域。
同源策略限制以下几种行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 JS对象无法获得
-
为什么
因为浏览器出于安全的考虑,会对发出的请求做校验,校验不成功就有跨域问题。
发出的请求,只要协议、端口、域名不一样,就是跨域。
发出的请求若不是XMLHttpRequest,浏览器不会认为是跨域。
不受限制的外域资源加载情况有以下几种: script
- link
- img
- video
- object embed applet
- font-face 有的浏览器允许, 有的禁止
-
解决跨域的几种方式
JSONP:利用script标签中src带上请求然后回调,只能发送get请求
- iframe + form:可以发送post请求
- CORS(Cross-origin resource sharing, 跨域资源共享),普通请求只需设置后端Access-Control-Allow-Origin;若带cookie请求,则前后端都要设置。
- 代理:通过将前端的请求转发到后端的域名来避免跨域,Nginx,对Nginx配置即可;
nginx跨域原理:同源策略是浏览器的安全策略而不是HTTP的安全策略,服务器调用HTTP接口使用HTTP协议,而不会执行JS脚本,就不需要同源策略。JSONP原理
会跨域的情况
- 服务端: ```javascript var http = require(“http”); var PORT = 8888;
var server = http.createServer((req, res) => { res.end(“hello world”); });
server.listen(PORT, () => { console.log(“服务启动成功.”); });
- 客户端
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>实现JSONP</title>
</head>
<body>
<h3>实现跨域</h3>
</body>
<script>
let request = new XMLHttpRequest();
request.open("get", "http://localhost:8888/");
request.send();
</script>
</html>
服务端可返回定义变量
首先修改服务端代码:
res.end("var app = 12345");
然后调整前端代码:
<script>
// 第一次因为还没有引入外部 js 所以打印 undefined
console.log(window.app)
// 1 秒后, 外部 js 加载完成, 能打印出后端返回的变量定义
setTimeout(() => {console.log(window.app)}, 1e3)
</script>
<script src="http://localhost:8888/"></script>
服务端改为返回一个函数
res.end("callback({name:'joy',age:18})")
前端代码:
<script>
// 由于后端返回的内容即将调用函数 callback, 那我们就预先定义一个呗, 这东西就叫回调函数
function callback(params) {
console.log('后端返回的参数是: ', params)
}
</script>
<script src="http://localhost:8888/"></script>
CORS
CORS:跨域资源共享。CORS允许浏览器向服务器发出跨域的XML请求,从而克服了AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。目前所有浏览器都支持,IE需要IE10以上。
整个CORS通信过程,都是由浏览器自动完成,不需要用户参与。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,但用户不会有所感觉。
所以实现CORS关键在于服务器的配置,只要服务器实现了CORS接口,就可以跨源通信。
CORS的两种请求
浏览器将CORS分为两类:简单请求和非简单请求。不同的请求浏览器的处理方式也不同。
简单请求满足的条件:
简单请求
对于简单请求,浏览器直接发出CORS请求。具体就是在头信息中,增加一个Origin字段。在Origin字段中写明了来源(“协议+域名+端口”),服务器收到这个值,就可以决定是否同意这次的请求。
- 如果Origin中的来源不在服务器的许可范围内,服务器会返回一个正常的HTTP请求。浏览器接收后发现没有Access-Control-Allow-Origin字段,就知道出错了,此时需要使用XMLHttpRequest的onerror回调函数来捕获错误。因为这种错误无法通过状态码识别,状态码可能返回的是200。
如果Origin中的来源在服务器的许可范围内,服务器返回的响应会多出几个头信息字段。
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
(1)Access-Control-Allow-Origin
该字段是必须的。要么是Origin里的值,要么是*,表示接收任意域名的请求。- (2)Access-Control-Allow-Credentials
可选。表示是否允许发送Cookie,设置为true表示允许浏览器发送Cookie给服务器。不要则直接删除该字段。
另一方面,开发者必须在AJAX请求中打开withCredentials属性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策
- (3)Access-Control-Expose-Headers
可选。CORS请求时,如果想拿到其他字段,必须在这里指定。非简单请求
非简单请求是对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单的请求,会在正式通信之前,增加一次HTTP查询请求,称为“预检”请求。
“预检请求”:浏览器会先询问服务器,当前域名是否在服务器的可访问名单中,以及可以使用的HTTP动词和头信息字段,得到肯定答复后才发送XML请求。
“预检”请求的HTTP头信息:
服务器收到“预检”请求后,检查Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段,确认允许跨源请求,就可以作出回应:OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
如果服务器否定了“预检”请求,会返回一个正常的HTTP回应,但没有任何与CORS相关的头字段。浏览器收到之后就检查一下没有CORS相关的头信息字段就知道出错了,可以用XML对象的onerror回调函数捕获。HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。Cookie
参考资料:
Cookie是什么
cookie是由客户端保存的小型文本文件,内容为一系列的键值对。Cookie是由HTTP服务器所设置的,保存在浏览器中。当用户访问网站的其它页面时,就会在HTTP请求中附上Cookie字段。
Cookie可以设置过期时间,若没有设置,默认为关闭浏览器即失效;若设置了过期时间,则将Cookie存储在硬盘中,下次打开浏览器访问依旧是登录状态。cookie可以跨越一个域名下的多个网页,但不能跨越多个域名使用。
用途
- 保存用户登录信息(常用)、用户个性化设置
- 创建购物车
-
工作机制
以登录状态举例子:首次登录时,服务器生成一个标识用户身份的id,返回给客户端,客户端将该id存储在cookie中。之后再次访问该网站时,浏览器将带有用户身份的id的cookie添加到请求头(request header)上随着请求发送给服务端,服务端确认后返回登录成功的信息,则客户端无需重新登录。
Cookie的发送
服务端发送cookie
通过设置 HTTP 的 Set-Cookie 消息头,可以向客户端发送Cookie。
格式如下:
Set-Cookie: name=value[; expires=GMTDate][; domain=domain][; path=path][; secure]
// 注意这些参数是服务器给浏览器的指示,因此客户端发送cookie给服务端时,是不会带有可选参数的,只有name和value会发送给服务端
例子:如设置了Set-Cookie:user=ZhangSan
,客户端将接收到字符串user=ZhangSan
作为 cookie。
可以在node中设置res.writeHead对头部进行编辑。还可以通过服务器对Cookie进行删除,即设置 Set-Cookie 消息的Expires/Max-age属性,让时间在当前时间之前,表示Cookie已经过期,浏览器会自动删除。
如果有多个cookie,需设置多个Set-Cookie。
- 客户端发送cookie
Cookie存在且条件允许时,会在接下来的每个请求都附上Cookie字段发送给服务器。客户端向服务端发送的 HTTP 请求中设置 Cookie 消息头。
document.cookie = "name=value[; expires=GMTDate][; domain=domain][; path=path][; secure][;sameSite]"
例子:如设置了 Cookie:user=ZhangSan
,服务端接受到字符串user=ZhangSan
作为 cookie
若想要添加多个cookie,只能重复执行 document.cookie
,这里需要注意以下规则:
- 针对不同名cookie,执行的是添加操作;
- 若是同名cookie,同domain和path属性下,则为修改操作;即不能修改domain、path属性;
若同名cookie不同domain或path,则为添加操作;
Cookie的缺陷
明文传递,Cookie可能被访问、篡改,所以要求服务器对Cookie进行验证。
- 会被附加在每个请求中发送给服务端,增加网络流量;
-
Cookie的属性
Name、Value、Domain、Path、Expires/Max-age、Size、HttpOnly、Secure、Samesite
Name和Value:表示cookie的“键值”,如设置
"userAge=18"
,则Name为userAge,Value为18;- Domain和Path:共同决定了Cookie能被哪些页面共享,Domain控制对哪个域有效,Path控制Cookie发送指定域的路径,默认为”/“;如设置
"domain=.jd.com; path=/"
; - expires和max-age:表示cookie的失效时间,时间到了cookie就失效被删除。
- 不设置则默认为“session”,表示当前会话结束(关闭浏览器)即失效;
- 可设置expires为
"expires=``Sat, 04 Nov 2017 16:00:00 GMT``"
,这是个绝对时间,表示cookie将在这个时刻失效; - 可设置max-age为
"max-age=10800"
,以秒为单位,表示Cookie将在xx秒后失效;新的协议更多采用这个来取代expires;若max-age为-1,表示会话结束即失效;max-age为0,表示删除cookie
- secure:表示cookie的安全标志,设置后cookie只有在使用SSL/STL连接才会发送到服务器;
- httponly:用来限制客户端js对cookie的访问与修改,设置了以后客户端将无法通过document.cookie来获取cookie,设置这个可以有效防止XSS攻击;
- sameSite:chrome_76版本新增的属性,用于防止CSRF攻击,值有三类:strict、lax、none
Cookie的作用域
有两点要注意:
1. 在 setcookie 中省略 domain 参数,那么 domain 默认为当前域名。
2.domain 参数可以设置父域名以及自身,但不能设置其它域名,包括子域名,否则 cookie 不起作用。
另外,一个有效的 cookie 的作用域为: domain 本身以及 domain 下的所有子域名。
如②blog.zyday.com 域名下设置 cookie:
domain 参数 | zydya.com | blog.zyday.com | one.blog.zyday.com |
---|---|---|---|
setcookie(‘name’,1,time() +1) |
× | √ | √ |
setcookie(‘name’,1,time()+1,’/’,’zyday.com’) | √ | √ | √ |
setcookie(‘name’,1,time()+1,’/’,’blog.zyday.com’) | × | √ | √ |
setcookie(‘name’,1,time()+1,’/’,one.blog.zyday.com’) | × | × | × |
Session
参考资料:
Session是什么
Session对象由服务端生成,保存在服务器的内存、缓存、硬盘或数据库中。其数据结构是一个散列表(哈希表),通过Session id进行查找。
Session将变量的值保存在服务器上,用session id来进行区分。这个session id通过用户浏览器在请求时返回给服务器。当用户禁用Cookie时,还能用get来返回给服务器。
工作机制
当用户访问服务器时,服务器先检查用户的请求中是否包含session id(可能保存在cookie中,或者url中),若包含session id说明用户之前登陆过并且服务器为其创建了session id,那服务器就会用该session id去查找对应的session数据(若查找不到,可能为其新建一个);若客户端请求里不包含session id,则为该用户创建session和session id,然后给客户端返回session id,客户端将其保存到cookie中,待下次访问服务器时,附上cookie。
Q1:若浏览器禁止了cookie,该如何发送session id?
A1:作为查询字符串附加在URL后面:http://…../xxx?jSession=ByOK3vjFD75
Session的缺点
session数据存储在服务端,用户并发访问量巨大时会占用服务器大量内存;而cookie保存在用户本地,没有此问题。
session和cookie的区别
存放位置不同:cookie在本地,session在服务器;
- 保存的数据格式支持不同:cookie只能是简单的字符串,session可以是任何类型的数据;
- 安全性不同:cookie明文传输且存放于浏览器本地,易被读取、篡改,不适合存放敏感信息(如账号密码);session在服务器安全性大于cookie。但google、baidu对于敏感信息可以进行信息加密,在服务端解密,这样传输过程中不用担心被读取、篡改。
- 对服务器的压力不同
- 数据有效期不同:cookie可设置expires/max-age来控制有效时间,session则是会话结束则失效;
- 跨域支持不同:cookie支持跨域名访问,只需设置domain属性;session不支持。
JWT
参考文档:
(1)基于session;(2)基于token;(3)基于JWT的token
PS:图中将jwt存入cookie,可能存在潜在的CSRF攻击风险。另一篇文章中是存到请求头里的Authorization
字段中。形式为:
'Authorization': 'Bearer ' + token
加密
对称加密:
双方公用一个密钥进行加密、解密,简单高效。
缺点:密钥在传输时容易被获取,就会造成数据被窃听、修改等问题。
非对称加密
双方都有各自的公钥和私钥,公钥可以给对方,私钥自己留着。要发送数据时,使用对方的公钥进行加密发送出去,那么对方就可以使用自身的私钥进行解密得到数据。解决了对称加密容易被破解的缺点。
缺点:如果在获取对方公钥时被攻击者截获公钥并替换公钥,客户端和服务端都无法发现,就会造成数据窃听、修改的问题。(公钥传输问题,攻击者变身“中间代理人”)
数字签名、数字证书
数字签名(解决数据是否被篡改):服务器先将数据利用hash生成一段摘要h1,然后再用服务器私钥进行加密,生成了数字签名。将数字签名附在数据的尾部,一起发给客户端。客户端收到后,对数据进行hash生成h2,然后利用服务器公钥对数字签名进行解密,得到里面的摘要h1,若h1=h2,则数据没被篡改,公钥有效。
缺点:如果客户端本地存储的服务器公钥被替换成攻击者的公钥,然后攻击者伪装成服务器用自己本身的私钥对数据加密,也是存在风险。于是需要一个机制(数字证书)来验证本地的公钥是否是正确的服务器公钥。
数字证书(解决服务器公钥是否伪造):服务器去找CA为自己的公钥做认证,CA使用自己的私钥对服务器公钥和相关信息进行加密,生成“数字证书”。然后在数据上附上“数字签名”和“数字证书”发给客户端。客户端收到后,使用内置的CA公钥对“数字证书”进行解密拿到了服务器的公钥,然后再利用该公钥验证“数字签名”。
数字证书里含有数字签名的技术,可以保证数字证书不被掉包。
HTTP的风险
HTTP通信不加密,可能有三大风险:
- 数据被窃听
- 数据被篡改
- 对方可能是冒充的
SSL/TLS协议就是为了解决这三大风险而设计的:
- 历史:TLS可以认为就是SSL的版本升级,SSL原先由NetScape公司设计,后来互联网标准化组织接手,发布了TLS。
- 基本运行过程:
- 基本思路是公钥加密法。但要解决两个问题:如何保证公钥不被篡改,公钥加密私钥解密计算量大耗时间。解决第一个问题就是使用数字证书进行认证,第二个问题就是使用对话密钥(session key)来对通信进行加密(即对称加密)。
- 所以基本运行过程:
- 客户端向服务器索要公钥并利用证书验证公钥;
- 双方协商生成“对话密钥”;
- 双方采用“对话密钥”进行通信加密。
- 为什么期间是三个随机数?为了增加随机性
握手阶段的详细过程分为5步:
- 客户端给出协议版本号和一个随机数,以及客户端支持的加密方法,向服务器索要公钥;
- 服务器确认加密方法,并发送数字证书和一个随机数;
- 客户端验证证书并得到里面的公钥,并生成一个随机数,使用服务器公钥加密这个随机数发给服务器;
- 服务器收到后用自己的私钥解密,获得第三个随机数;
- 双方根据约定的加密方法,使用三个随机数生成“对话密钥”,用来加密之后的对话过程。
于是HTTPS加密机制就是:非对称加密+数字签名+数字证书+对称加密
参考:
HTTP和HTTPS区别
- HTTPS使用443端口,而HTTP使用80
- HTTPS需要申请证书
- HTTP是超文本传输协议,是明文传输;HTTPS是经过SSL加密的协议,传输更安全
- HTTPS比HTTP慢,因为HTTPS除了TCP握手的三个包,还要加上SSL握手的九个包