前端必须懂的计算机网络知识系列文章:
- DNS服务器和跨域问题
- 计算机网络的分层模型
- IP地址和MAC地址
- 前端必须懂的计算机网络知识—(跨域、代理、本地存储)
- 前端必须懂的计算机网络知识—(TCP)
- 前端必须懂的计算机网络知识—(HTTP)
- 前端必须懂的计算机网络知识—(XSS、CSRF和HTTPS)
HTTP为什么不安全
- 可能被窃听:
- HTTP 本身不具备加密的功能,HTTP 报文使用明文方式发送
- 由于互联网是由联通世界各个地方的网络设施组成,所有发送和接收经过某些设备的数据都可能被截获或窥视。(例如大家都熟悉的抓包工具:Wireshark),即使经过加密处理,也会被窥视是通信内容,只是可能很难或者无法破解出报文的信息而已
- 认证问题
- 无法确认你发送到的服务器就是真正的目标服务器(可能服务器是伪装的)
- 无法确定返回的客户端是否是按照真实意图接收的客户端(可能是伪装的客户端)
- 无法确定正在通信的对方是否具备访问权限,Web服务器上某些重要的信息,只想发给特定用户即使是无意义的请求也会照单全收。无法阻止海量请求下的 DoS 攻击(Denial of Service,拒绝服务攻击)。
- 可能被篡改
- 请求或响应在传输途中,遭攻击者拦截并篡改内容的攻击被称为中间人攻击(Man-in-the-Middle attack,MITM)。
XSS攻击
XSS,即为(Cross Site Scripting),中文名为跨站脚本,跨站脚本的重点不在“跨站”上,而在于“脚本”上。大多数XSS攻击的主要方式是嵌入一段远程或者第三方域上的JS代码,实际上是在目标网站的作用域下执行了这段第三方域上的js代码。
反射型XSS(非持久型XSS)
特点:就像镜子反射一样,浏览器发射含XSS的url,服务器将其反射回来
- 浏览器发生请求时,XSS代码出现在请求URL中,作为参数提交到服务器,
- 服务器解析并响应,响应结果中包含XSS代码,
- 最后浏览器解析并执行。
案例:表单提交
//test.html
<body>
<textarea name="txt" id="txt" cols="80" rows="10">
<button type="button" id="test">测试</button>
<script>
var test = document.querySelector('#test')
test.addEventListener('click', function () {
var url = `/test?test=${txt.value}` //1.发送一个GET请求
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
// 3. 客户端解析JSON,并执行
var str = JSON.parse(xhr.responseText).test
var node = `${str}`
document.body.insertAdjacentHTML('beforeend', node)
} else {
console.log('error', xhr.responseText)
}
}
}
xhr.open('GET', url, true)
xhr.send(null)
}, false)
</script>
</body>
复制代码
//server.js
var express = require('express');
var router = express.Router();
router.get('/test', function (req, res, next) {
// 2.服务端解析成JSON后响应
res.json({
test: req.query.test
})
})
复制代码
现在我们通过给textarea添加一段有攻击目的的img标签:
存储型XSS(持久型XSS)
特点:黑客将XSS代码发送给服务器,然后通过服务器散播
- 黑客将XSS代码发送到服务器(不管是数据库、内存还是文件系统等。)
- 其他人请求页面的时候就会带上XSS代码了。
案例:最典型的就是留言板XSS。
- 黑客提交了一条包含XSS代码的留言到数据库。
- 当目标用户查询留言时,那些留言的内容会从服务器解析之后加载出来。
- 浏览器发现有XSS代码,就当做正常的HTML和JS解析执行。XSS攻击就发生了。
DOM型XSS
特点:DOM XSS代码不需要服务器端的解析响应的直接参与,而是完全通过浏览器端的DOM解析。
- 浏览器的代码中含有eval,new Function等将字符串内容执行的代码
- 在执行的字符串中嵌入可以执行XSS代码字符串 案例:
test.addEventListener('click', function () {
var node = window.eval(txt.value)
window.alert(node)
}, false)
//txt中的代码如下:
<img src='null' onerror='alert(123)' />
复制代码
XSS危害
- 通过document.cookie盗取cookie
- 使用js或css破坏页面正常的结构与样式
- 流量劫持(通过访问某段具有window.location.href定位到其他页面)
- Dos攻击:利用合理的客户端请求来占用过多的服务器资源,从而使合法用户无法得到服务器响应。
- 利用iframe、frame、XMLHttpRequest或上述Flash等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。
- 利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
XSS防御
XSS攻击可以看出,不能原样的将用户输入的数据直接存到服务器,需要对数据进行一些处理:
- 过滤危险的DOM节点。如具有执行脚本能力的script, 具有显示广告和色情图片的img, 具有改变样式的link, style, 具有内嵌页面的iframe, frame等元素节点。
- 过滤危险的属性节点。如on-, style, src, href等
- 对cookie设置httpOnly,但是也会导致前台无法操作cookie,不太推荐。
CSRF
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,攻击者盗用了你的身份,以你的名义发送恶意请求。
- 登录受信任网站A,并在本地生成Cookie。
- 在不登出受信任网站A的情况下,访问危险网站B。
- 危险网站会向受信任A的网站发送请求,同时会携带受信任网站A本地生成Cookie(用同一个浏览器访问同一个域的接口)
案例:
//钓鱼网站,利用用户的cookie进行权限操作转账
<body onload="steal()">
<iframe name="steal" display="none">
   <form method="POST"name="transfer" action="http://www.myBank.com/Transfer.php">
     <input type="hidden" name="toBankId" value="11">
      <input type="hidden" name="money" value="1000">
    </form>
  </iframe>
</body>
复制代码
CSRF的危害
- 篡改目标网站上的用户数据;
- 盗取用户隐私数据;
- 作为其他攻击向量的辅助攻击手法;
- 传播CSRF蠕虫。
CSRF的防御
- 验证码,因为验证码必须在受信任的网站上发送给浏览器的,并且伪造的网站和受信任的网站非同源,所以没有办法获取受信任网站发送的session,所以验证码是没有办法伪造的。
- refer,标识了当前请求的页面的源,伪造网站可以篡改成受信任的网站源,并不保险
- token,由于它是通过服务的发送给客户端的令牌,并且存储在浏览器的localstorage中,由于同源策略,并且token还有校验规则,所以token并不能轻易篡改。
Token和JWT
Token的特点
- session是通过cookie传输的,每次访问网站都会自主的带上,所以存在安全问题和浪费带宽,token存储在localstorage,可以选择性发送给后台,由于同源策略,安全性高
- session在集群服务器中很难实现共享,服务器必须存在一份sessionq清单,token不需要存储清单,共用一个密钥,发送token可以实现共享
- session销毁后会从清单消失,token的注销不好实现,没有清单证明这个token不能用,所以得用一个黑名单来标注
JWT
jwt是实现token的一种方式,一个token分3部分,3部分之间用“.”号做分隔:
- header:头部为base64字符串
- payload:载荷为base64字符串
- signature:签证 hader+payload经过key加密得出
EXP:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- JWT原理:
let jwt = {
//解码
decode(token,secret){
let [header,content,sign] = token.split('.');
let h = JSON.parse(this.formBase64ToString(header));
let c = JSON.parse(this.formBase64ToString(content));
if(sign !== this.sign([header,content].join('.'),secret){
throw new Error ('Not allowd')
}
return c
},
//加码
encode(payload,secret){
let header = this.toBase64(JSON.stringify({'type':'JWT',alg:'HS25' }));
let content = this.toBase64(JSON.stringify(payload));
let sign = this.sign([header,content].join('.'),secret);
return [header,content,sign].jion('.');
}
//转换为base64
toBase64(str){
return Buffer.from(str).toString('base64');
}
//签名
sign(str,secret){
return require('crypto').createHmac('sha256',secret).update(str);
}
}
module.exports = jwt;
复制代码
- JWT后台:
const express = require('express');
const bodyParser = require('body-parser')
const jwt = require('jwt-simple')
const userList=[
{id:1,username:'kbz',password:'123456'}
]
const SECRET = 'MISS';
let app = express();
app.use(bodyParser.json);
//因为请求头中有Authorization字段,这和跨域时要设置name传参数一样,可以参考:
//前端必须懂的计算机网络知识—(跨域、代理、本地存储)
//https://juejin.im/post/5bb1cc2af265da0ae5052496
app.use(function(req,res,next){
res.setHeader('Access-Control-Allow-Origin',"*");
res.setHeader('Access-Control-Allow-Headers',"Content-Type,Authorization");
res.setHeader('Access-Control-Allow-Methods',"GET,POST,OPTIONS");
if(req.method === 'OPTIONS'){
res.end();
}
next();
})
//登陆时发送token
app.post('/login',function(req,res,next){
let user = req.body;
userList.find(person=>(person.id === user.id));
if(user){
jwt.encode({
id:user.id,
username:user.username
},SECRET)
res.json({
code:0,
data:{
token
}
})
}else{
res.json({
code:1,
data:'用户不存在'
})
}
})
// 需要验证权限,则取token验证
// 请求头Authorization:Bearer token
app.get('/order',function(req,res,next){
let authorization = req.headers['authorization'];
if(authorization){
let token = authorization.split(' ')[1];
try{
let user = jwt.decode(token,SECRET);
req.user = user;
}catch(e){
res.status(401).send('Not Allowed')
}
}else{
res.status(401).send('Not Allowed')
}
})
app.listen(3000);
复制代码
- JWT前台
//login.html
<input type="text" id='username' class="form-control">
<input type="text" id='password' class="form-control">
<span onclick='login()'></span>
<scrpipt>
axios.post('/user').then(res=>{
localStorage.setItem('token',res.data.data.token)
location.href='/order'
})
</script>
//order.html
<scrpipt>
axios.interceptors.request.use(function(config){
let token = localStorage.getItem('token');
if(token){
config.headers.Authorization = 'Bearer '+ token
}
})
axios.get('/order').then(res=>{
console.log(res);
})
</script>
复制代码
HTTPS(HTTP+TLS/SSL)
TLS/SSL
TLS/SSL的功能实现主要依赖于三类基本算法:
- 非对称加密实现身份认证和密钥协商
- 对称加密算法采用协商的密钥对数据加密
- 散列函数验证信息的完整性,针对于密钥泄露的不安全性
- 客户端使用非对称加密与服务器进行通信,实现身份验证并协商对称加密使用的密钥
- 然后对称加密算法采用协商密钥对信息以及信息摘要进行加密通信,不同的节点之间采用的对称密钥不同,从而可以保证信息只能通信双方获取
对称加密
- 相同的密钥可以用于信息的加密和解密,掌握密钥才能获取信息,能够防止信息窃听,通信方式是1对1(前提示密钥不泄露)
- 算法公开、计算量小、加密速度快、加密效率高
- 客户端、服务器双方都需要维护大量的密钥,维护成本很高
- 因每个客户端、服务器的安全级别不同,密钥容易泄露,交易双方都使用同样钥匙,安全性得不到保证
非对称加密
- 非对称加密算法的特点加密和解密分别使用不同的密钥,客户端用公钥对请求内容加密,服务器使用私钥对内容解密,反之亦然。
- 相对来说加解密速度较慢,使用非对称加密在数据加密解密过程需要消耗一定时间,降低了数据传输效率
- 公钥是公开的,所以针对私钥加密的信息,黑客截获后可以使用公钥进行解密,获取其中的内容
- 公钥并不包含服务器的信息,使用非对称加密算法无法确保服务器身份的合法性,存在中间人攻击的风险,服务器发送给客户端的公钥可能在传送过程中被中间人截获并篡改
完整性验证算法
- 常见的有 MD5、SHA1、SHA256,该类函数特点是函数单向不可逆、对输入非常敏感、输出长度固定,针对数据的任何修改都会改变散列函数的结果,用于防止信息篡改并验证数据的完整性
- 在信息传输过程中,散列函数不能单独实现信息防篡改,因为明文传输,中间人可以修改信息之后重新计算信息摘要,因此需要对传输的信息以及信息摘要进行加密
密钥协商
身份验证
由于非对称加密无法确保服务器身份的合法性,存在中间人攻击的风险,例如:
- 客户端 C 和服务器 S 进行通信,中间节点 M 截获了二者的通信
- 节点 M 自己计算产生一对公钥pub_M和私钥 pri_M
- C 向 S 请求公钥时,M 把自己的公钥pub_M发给了 C
- C 使用公钥pub_M加密的数据能够被M解密,因为M掌握对应的私钥pri_M,而C无法根据公钥信息判断服务器的身份,从而 C 和 M 之间建立了“可信”加密连接
- 中间节点 M 和服务器S之间再建立合法的连接,因此 C 和 S 之间通信被M完全掌握,M 可以进行信息的窃听、篡改等操作。
- 服务方 Server 向第三方机构CA提交公钥、组织信息、个人信息(域名)等信息并申请认证;
- CA通过线上、线下等多种手段验证申请者提供信息的真实性,如组织是否存在、企业是否合法,是否拥有域名的所有权等;
- 如信息审核通过,CA会向申请者签发认证文件-证书。证书包含以下信息:申请者公钥、申请者的组织信息和个人信息、签发机构CA的信息、有效时间、证书序列号等信息的明文,同时包含一个签名; 签名的产生算法:首先,使用散列函数计算公开的明文信息的信息摘要,然后,采用CA的私钥对信息摘要进行加密,密文即签名;
- 客户端 Client 向服务器 Server 发出请求时,Server 返回证书文件;
- 客户端 Client 读取证书中的相关的明文信息,采用相同的散列函数计算得到信息摘要,然后,利用对应 CA的公钥解密签名数据,对比证书的信息摘要,如果一致,则可以确认证书的合法性,即公钥合法;
- 客户端还会验证证书相关的域名信息、有效时间等信息;客户端会内置信任CA的证书信息(包含公钥),如果CA不被信任,则找不到对应 CA的证书,证书也会被判定非法。
- 客户端的证书从生产出来之后就内置在主机中
搭建一个HTTPS服务器
结语
前端必须懂的计算机网络知识系列已经全部结束,欢迎大家研究讨论!