前端必須懂的計算機網路知識系列文章:
- 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攻擊就發生了。下面圖片中是獲取了本地的部分cookie資訊: 上面只是模擬攻擊,通過alert獲取到了個人的cookie資訊。一般黑客會注入一段第三方的js程式碼,然後將獲取到的cookie資訊存到他們的伺服器上,拿到我們的身份認證做一些違法的事情了。儲存型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的功能實現主要依賴於三類基本演算法:
- 非對稱加密實現身份認證和金鑰協商
- 對稱加密演算法採用協商的金鑰對資料加密
- 雜湊函式驗證資訊的完整性,針對於金鑰洩露的不安全性 結合三類演算法的特點,TLS 的基本工作方式:
- 客戶端使用非對稱加密與伺服器進行通訊,實現身份驗證並協商對稱加密使用的金鑰
- 然後對稱加密演算法採用協商金鑰對資訊以及資訊摘要進行加密通訊,不同的節點之間採用的對稱金鑰不同,從而可以保證資訊只能通訊雙方獲取
對稱加密
- 相同的金鑰可以用於資訊的加密和解密,掌握金鑰才能獲取資訊,能夠防止資訊竊聽,通訊方式是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伺服器
結語
前端必須懂的計算機網路知識系列已經全部結束,歡迎大家研究討論!