前言
隨着前端的發展,前端應用正在迅速變化。 前端代碼承擔着與后端代碼幾乎相同的責任,可以做更多的事情,隨着公司體系越來越完善,開發框架和平台的不斷成熟,需要開發者考慮的安全問題越來越少,但並不是開發者就不需要關心項目的安全問題。
本文主要介紹幾種業務開發中經常遇到的幾種前端安全問題,由於篇幅有限本文點到為止,后續有機會會逐一展開來講,本文提供大量的圖例來說明問題。
1. XSS
XSS是跨站腳本攻擊的簡寫,攻擊者想盡一切方法 將一段腳本內容放到目標網站的目標瀏覽器上解釋執行。攻擊者將惡意腳本輸入到目標網站中。 當其他用戶訪問該網站的時候,由於瀏覽器不知道它是由網站提供服務的腳本還是攻擊者埋入的腳本,因此將執行此該腳本。攻擊者就可以很容易利用埋入的腳本進行攻擊。
- 攻擊者編寫惡意攻擊的腳本
- 攻擊者訪問前端頁面,在輸入框中輸入編寫好的惡意腳本
- 攻擊者將惡意腳本進行提交,后端將惡意腳本存儲在數據庫中
- 當某些合法用戶訪問該網站的時候,該網站會獲取存儲在數據庫中的惡意腳本,但是瀏覽器不知道它是惡意腳本所以執行了。
其實就相當於攻擊者在用戶端的頁面上注入了一段腳本,有了這段腳本攻擊者就可以為所欲為了
防范
- 永遠不要相信用戶的輸入,對用戶輸入的特殊字符串進行轉譯,針對用戶的輸入設置標簽白名單
- cookie設置HttpOnly,配合token或驗證碼防范
- 設置CSP安全策略-可以通過兩種方式設置CSP,一種是meta標簽,一種是HTTP響應頭Content-Security-Policy
2. CSRF
CSRF是跨站請求偽造的簡寫,一種誘騙受害者提交惡意請求的攻擊,攻擊者盜用了你的身份,以你的名義發送惡意請求,請求到達后端時,服務器將無法區分惡意請求和合法請求。。CSRF能夠做的事情包括:以你名義發送郵件,發消息,盜取你的賬號,甚至於購買商品,虛擬貨幣轉賬等。
CSRF攻擊必須具備兩個流程
- 登錄受信任網站A,並在本地生成Cookie。
- 在不登出A的情況下,訪問危險網站B。
防范
- 同源檢測,直接禁止外域(受信域可以開白名單)對我們發起請求
- CSRF Token,就把Token以參數的形式加入請求了,提交給服務器的時候,服務器需要判斷Token的有效性
- Samesite Cookie屬性,Samesite=Strict只允許同源網站提交請求攜帶cookie
3. 網絡傳輸安全
中間人 (Man-in-the-middle attack, MITM) 是指攻擊者與通訊的兩端分別創建獨立的聯系, 並交換其所收到的數據, 使通訊的兩端認為他們正在通過一個私密的連接與對方直接對話, 但事實上整個會話都被攻擊者完全控制. 在中間人攻擊中, 攻擊者可以攔截通訊雙方的通話並插入新的內容。
是不是覺得有了https網絡傳輸安全問題就迎刃而解了呢,即使被中間人攔截了,數據也是加密的。其實不是這樣的,不知道大家有沒有使用過charles進行抓包呢,如果數據都是加密的,為啥charles抓包后我們能夠看到傳輸的明文呢,其實這就是中間人攻擊。
charles中間人劫持
防范
- 對於個人來說防止自己被中間人攻擊最基本的就是不要亂連不信任的網絡
- 公司APP來說應該配置禁止被抓包
- APP和瀏覽器都應該嚴格校驗證書,不使用不安全的APP和瀏覽器
4. 接口加簽
通過上面的例子我們知道https並不是絕對安全的,他是會被中間人劫持的,那么我們有什么方法防止數據被串改呢?
接口加簽的目的是防止數據被串改!
舉兩個例子
例子1:正常用戶提交轉賬申請,請求中攜帶正常用戶的用戶信息,他想轉賬N金額給用戶A,這樣的請求銀行沒法拒絕會正常轉賬,因為攜帶了正常的用戶信息。但是當中間人劫持了這個請求,他修改了轉賬賬號為B,修改了轉賬金額為M,這樣我們的錢會不會轉給其他人呢?
例子2:我們辛辛苦苦寫了一個運營小游戲,違規用戶隨便玩了一下得分為0,但是他通過Charles攔截了這個請求,修改了得分為10000,然后進行提交,我們的正常服務器能否知道分數是被串改的呢?
為了解決上述問題,我們可以引入接口加簽
服務端網關首先會校驗簽是不是對的,如果不對直接拒絕請求,而簽的生成和請求參數密密相關,當接口請求中的參數被串改后,網關是沒法進行驗簽通過的,直接拒絕了請求,拋出錯誤。
5. 接口加密
有時候我們的參數根本不想被人看見是啥,我們就可以利用參數加密了
接口防重放
防重放也叫防復用,簡單來說,就是我獲取到這個請求的信息之后, 我什么也不改, 我就拿着接口的參數去重復請求這個充值的接口,也就是說我的請求是合法的,因為所有參數都是跟合法請求一模一樣的,也就是說: 服務端的 sign 驗證一定能通過。如圖上的例子,即使我們不知道登錄賬戶名密碼,即使接口參數被加簽加密了,我們依舊能夠登錄並拿到登錄信息,我們根本不用關心加密加簽的邏輯,我們只需要簡單的重放攻擊即可。
防重放設計
- 客戶端在請求中添加兩個參數
1.1 添加一個隨機不重復的字符串參數 比如uuid
至於怎么讓他不重復,可以考慮拼接時間戳,md5隨機數等
1.2 添加一個請求時間的參數 如time
值就是發送請求時的時間戳
- 服務端接收到請求之后:
2.1 去緩存里中查找 uuid 這個參數對應的值是否存在
2.2 如果不存在: 就把這個uuid的值保存到緩存中, 記錄這個請求
2.3 如果已存在: 存在那就證明, 已經請求過一次了, 就不處理這個請求了
這就是最簡單的防重放邏輯,接口只能調用一次,即使被中間人攻擊后也沒法進行重放
6. 環境檢測
是不是瀏覽器
是不是我們檢測上述變量就認為是瀏覽器環境呢,其實不是這樣的,上面的變量都是可以被串改的,所以可以作為參考,絕對不能過分的依賴!下面列舉幾項處理方案,可以看到當我們檢測這些變量的時候,這些變量都是可以被串改的。
檢測變量 | 對抗處理方案 |
---|---|
navigator.languages | Object.defineProperty(navigator, 'languages', { get: () => ["zh-CN", "zh", "en"] }); |
navigator.plugins | Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] }); |
是不是模擬器
一般我們檢測到這些變量的時候可以無腦的認為就是模擬器,比如Puppeteer
中我們啟動的時候,navigator.webdriver
這一屬性的值等於true
的,常規瀏覽器中由於沒有這個屬性navigator.webdriver
的值等於undefined
的。
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
});
攻擊者這樣串改后我們是不是就沒有辦法知道是不是webdriver
了呢?其實我們還是有辦法判斷的,因為這邊只是返回了navigator.webdriver
的值是非的,但是navigator
上依舊有webdriver
這個屬性,我們有沒有辦法檢測屬性是否存在呢?其實我們很容易拿到navigator
上所有屬性的。
var attr = window.navigator, result = [];
do {
Object.getOwnPropertyNames(attr).forEach(function(a) {
result.push(a)
})
} while (attr=Object.getPrototypeOf(attr));
當我們判斷navigator
上有webdriver
這個屬性的時候,就可以簡單的認為這個是模擬器環境,是不是覺得很完美的判斷了是不是模擬器了,其實不是的,攻擊者甚至可以刪除掉webdriver
屬性。
delete navigator.__proto__.webdriver
這樣之后就完全抹去webdriver
變量了,通過這個辦法來判斷是不是模擬器就沒有路子了。
有沒有用戶行為
通常我們可以通過判斷事件上的isTrusted
屬性來判斷是不是真實的事件,大部分情況我們都能夠很好的處理,但是攻擊者是很可怕的,這些簡單的伎倆他們能夠輕輕松松的繞過,他可以重寫事件啊,比如:
function clone(e) {
const t = {};
for (let attr in e) {
if (typeof e[attr] === "function") {
t[attr] = e[attr];
} else {
Object.defineProperty(t, attr, {
get: () => {
if (attr === 'isTrusted') {
return true;
}
return e[attr];
},
set: v => {
e[attr] = v;
}
});
}
}
return t;
}
const oldAEL = document.addEventListener;
window.addEventListener = document.addEventListener = function (e, func, c) {
const newFunc = function (event) {
const newEvent = clone(event);
return func(newEvent);
};
return oldAEL.call(this, e, newFunc, c);
};
通過上面的例子我們發現,不管我們怎么攻防,攻擊者都是有辦法繞過去的。其實上面還都是簡單的攻防,攻擊者甚至可以自己定制瀏覽器,當我們的頁面跑在攻擊者定制的瀏覽器中的時候,通過上面的那些方法我們真的無能為力了,那么是不是我們只能放棄了呢,其實不是的。
辨別機器行為還是得需要驗證碼
7. 無處不在的驗證碼
驗證碼這個名詞真正被發明出來是在2003年,這比很多概念晚多了,比如神經網絡70年代就已經有很多人在研究。卡內基梅隆大學的Luis von Ahn,Manuel Blum, Nicholas J.Hopper等人首次提出了“CAPTCHA”這個詞。他們對驗證碼系統做的很深刻的研究,並且將其付諸程序化。自此大量的驗證碼開始被應用到網站中,有效的阻止了黃牛軟件的肆虐。時至今日,每天有過億的驗證碼被人們不斷地輸入着。
傳統驗證碼
傳統驗證碼易被各圖像識別軟件、打碼平台輕易破解,人工智能飛速發展,因此扭曲的文本驗證方式也不再是一個可靠的方法,據說已經能夠解決99.8%的圖片字符型驗證碼。由此誕生了很多新型的驗證碼類型,其中國內最具代表的就是極驗,國外的就是谷歌的reCAPTCHA,他們帶來了一種全新的模式。
新型驗證碼
新型驗證碼不僅很難破解,他的交互會更加的友善,甚至做到無驗證碼,只有在需要進行驗證的時候才出來。下面是網易易盾的產品流程圖,其他產品都基本類似。背后依托強大的機器學習判斷行為到底是不是人。
8. 代碼加密混淆
代碼加密混淆大大降低了前端代碼的可讀性,同時一定程度上會增加代碼的體積。但是對於非常核心的業務邏輯,代碼加密是非常有必要的,比如:
- 前端加簽代碼,由於加簽是在前端進行的,前端必須存有秘鑰和加簽規則,但是一旦被第三方知道加簽的秘鑰和規則,加簽也就不公而破了,所以加簽的前端代碼必須得加密。
- 新型驗證碼用戶行為采集代碼,新型驗證碼涉及很多用戶行為的前端采集,然后提交后端分析,如果采集規則被第三方知道,那么攻擊者也就很好的進行攻擊行為,所以采集代碼也是需要加密的。
歡迎關注公眾號:前端復習課,一起分享交流前端知識