由一次安全掃描引起的思考:如何保障 api 接口的安全性?

引言

前段時間,公司對運行的系統進行了一次安全掃描,使用的工具是 IBM 公司提供的 AppScan 。javascript

這個正所謂不掃沒關係,一掃嚇一跳,結果就掃出來這麼個問題。前端

咱們的一個年老失修的內部系統,在登陸的時候,被掃描出來安全隱患,具體學名是啥記不清了,大體就是咱們在發送登陸請求的時候,有個字段名是 password , AppScan 認爲這個是不安全的,大概就是下面:java

6cca27ac89d743f8ac904ff5d2b7ae5b

我第一個反應是把這個字段名字改一下,畢竟能簡單解決就簡單解決嘛,結果固然是啪啪啪打臉。算法

這個名字我無論是換成 aaa 仍是 bbb ,再次掃描都還會報一樣的問題,惟一不一樣的地方就是安全報告上的字段名換一換。數據庫

這個就有意思了,這個問題是來者不善啊,通過我一翻查找(別問我怎麼查的,問就是瞎猜的),找到緣由的所在了。json

由於咱們這個系統是一個內部系統,當時作登陸這我的比較圖懶,就在頁面上簡單的作了個 form 表單提交,就好比這樣:瀏覽器

e7cc26e543f94cb180e449aa6372f649

這個代碼我曾經在大學的大做業上這麼寫過,沒想到時隔多年我居然又見到了這樣的代碼,居然讓我有一種老鄉見老鄉的特殊情感。安全

這個問題具體的緣由是 AppScan 是直接檢測頁面上 type='password' 的輸入框,而後再檢查請求中是否有對應的字段,別問我咋知道的,由於我幹過把這裏改爲 type='text' 就不報錯了,惟一的缺點就是頁面上的密碼框將會明碼顯示密碼。服務器

雖然我能夠經過 js 來把輸入框裏的值動態的替換成任何我想要的樣子,好比點啊、星號啊以及一些其餘的樣式,可是這麼幹總歸有點不道德。網絡

問題找到了,那麼怎麼改呢?

到這裏就涉及到了我今天要聊的內容了,如何保障 API 接口的安全性?

首先這個問題咱們分紅兩個部分來看,客戶端和服務端。

服務端

由於我自己是作服務端開發的,這個問題固然要從服務端聊起。

我的以爲安全措施主要體如今兩個方面,一個是如何保證數據在傳輸過程當中的安全性,另外一個是如何在數據已經到達服務端後,服務端如何識別數據,保證不被***。

下面咱們一條一條來聊:

1. HTTP 請求中的來源識別

HTTP 請求中的來源識別就是,服務端如何識別當前的請求是由本身的客戶端發起的,而不是由第三方模擬的請求。

咱們先看下一個正常的 HTTP 請求的頭裏面會有什麼內容:

b018b864382b4d0bb74c5b34bbf9dc8c

我打開百度的首頁,經過 network 隨便抓了一個請求,查看這個請求的請求頭,這裏面我要說的幾個字段都用紅框框起來了:

  • Origin:用於指明當前請求來自於哪一個站點。

  • Referer:用於指明當前的請求是從哪一個頁面連接過來的

  • User-Agent:用於標識當前的請求的瀏覽器或者系統的一些信息。

咱們通常會對 HTTP 請求頭中的 Origin 和 Referer 作白名單域名校驗,先判斷這個請求是否是由咱們本身的域發出來的,而後再對 User-Agent 作一次校驗,用來保證當前的請求是由瀏覽器發出來的,而不是由什麼雜七雜八的模擬器發出來的。

當前,因爲前端是徹底不可被信任的,上面這幾個字段都是能夠被篡改和模擬的(我在前面寫爬蟲的文章中絕對寫過),可是,能作的校驗儘可能作,咱們不能一次把全部的漏洞都堵上,可是至少能堵上一部分。

2. 數據加密

數據在傳輸過程當中是很容易被抓包的,若是直接傳輸好比經過 http 協議,那麼用戶傳輸的數據能夠被任何人獲取;因此必須對數據加密。

常見的作法對關鍵字段加密好比用戶密碼直接經過 md5 加密;如今主流的作法是使用 https 協議,在 http 和 tcp 之間添加一層加密層( SSL 層),這一層負責數據的加密和解密。

3. 數據簽名

增長簽名就是咱們在發送 HTTP 請求的時候,增長一個沒法僞造的字符串,用來保證數據在傳輸的過程當中不被篡改。

數據簽名使用比較多的算法是 MD5 算法,這個算法是將要提交的數據,經過某種方式組合成一個字符串,而後經過 MD5 算法生成一個簽名。

我用前面那個登陸的接口舉個簡單的例子:

srt:name={參數1}&password={參數2}&$key={用戶密鑰}
MD5.encrypt(str)

這裏的 key 是一個密鑰,由客戶端和服務端各持有一份,最終登陸請求要提交的 json 數據就會是下面這個樣子:

{
	"name": "test",
	"password": "123",
	"sign": "098f6bcd4621d373cade4e832627b4f6"
}

密鑰是不參與數據提交,不然請求被劫持後,第三方就能夠經過密鑰本身生成簽名,固然,若是以爲單純的 MD5 不夠安全的話,還能夠在 MD5 的時候加鹽和加 hash ,進一步下降請求被劫持後存在模擬的風險。

4. 時間戳

時間戳機制主要用來應對非法的 DDOS ***,咱們的請求通過的加密和簽名後,已經很難進行逆向破解了,可是有的***者他在抓包後,並不在乎裏面的具體數據,直接拿着抓的包進行***,這就是臭名昭著的 DDOS ***。

咱們能夠在參數中加上當前請求的時間戳,服務端拿到這個請求後會拿當前的時間和請求中的時間作比較,好比在 5 分鐘以內的纔會流轉到後面的業務處理,在 5 分鐘之外的直接返回錯誤碼。

這裏要注意的是客戶端的時間和服務端的時間基本上是不可能一致的,加上請求本省在網絡中傳輸還有耗時,因此時間限制的閥值不能設定的過小,防止合法的請求沒法訪問。

我仍是拿上面的登陸舉例子,到這一步,咱們的請求數據會變成下面這個樣子:

{
	"name": "test",
	"password": "123",
	"timestamp": 1590334946000,
	"sign": "098f6bcd4621d373cade4e832627b4f6"
}

5. AppID

不少時候,咱們一個 API 接口可能並非只會有一個客戶端進行調用,可能調用方會有很是多,咱們的服務端爲了驗證合法的調用用戶,能夠添加一個 AppID 。

想要調用咱們的 API 接口,必須經過線下的方式像我申請一個 AppID ,只有當這個 AppID 開通後,才能對個人接口進行合法的訪問,在進行接口訪問的時候,這個 AppID 須要添加到請求參數中,與其餘數據一塊兒提交。

到了這一步,咱們上面那個登陸接口的傳入參數就變成了下面這樣:

{
	"appid": "geekdigging",
	"name": "test",
	"password": "123",
	"timestamp": 1590334946,
	"sign": "098f6bcd4621d373cade4e832627b4f6"
}

6. 參數總體加密

咱們上面對請求的參數進行了一系列的處理,整體思想是防止第三方進行抓包和破解,可是若是我不是第三方呢,好比我就在瀏覽器的 network 中進行抓包,這個請求中的數據我就能看的清清楚楚明明白白,***者能夠先經過正規的途徑進行訪問,當分析清楚咱們的套路後再對請求進行僞造,開始***,這時咱們前面的努力好像就都白費了。

不要說什麼沒有人會這麼作,我就舉一個例子,支付寶網頁端的登陸接口,若是能搞清楚其中請求的發送規則,***者就可使用買來的用戶數據庫,進行批量撞庫測試,經過請求響應的結果就能夠驗證一批的支付寶的帳號密碼(固然不會有這麼簡單哈,我只是舉例子)。

咱們接下來還能再對請求作一次總體加密,如今主流的加密方式有對稱加密不對稱加密兩種。

對稱加密:對稱密鑰在加密和解密的過程當中使用的密鑰是相同的,常見的對稱加密算法有 DES , AES , RC4 , Rabbit , TripleDes 等等。優勢是計算速度快,缺點是在數據傳送前,發送方和接收方必須商定好祕鑰,而後使雙方都能保存好祕鑰,若是一方的祕鑰被泄露,那麼加密信息也就不安全了。

不對稱加密:服務端會生成一對密鑰,私鑰存放在服務器端,公鑰能夠發佈給任何人使用。優勢就是比起對稱加密更加安全,可是加解密的速度比對稱加密慢太多了。普遍使用的是 RSA 算法。

對上面咱們提交的數據作一次 DES 加密,密鑰使用 123456 ,咱們能夠獲得這樣一個結果:

U2FsdGVkX18D+FiHsounFbttTFV8EToywxEHZcAEPkQpfwJqaMC5ssOZvf3JJQdB
/b6M/zSJdAwNg6Jr8NGUGuaSyJrJx7G4KXlGBaIXIbkTn2RT2GL4NPrd8oPJDCMk
y0yktsIWxVQP2hHbIckweEAdzRlcHvDn/0qa7zr0e1NfqY5IDDxWlSUKdwIbVC0o
mIaD/dpTBm0=

而後咱們把這個字符串放到請求中進行請求,咱們剛纔的登陸請求就會變成這樣:

78cf54936a0e422692e4707291379642

相信這樣一來,超過 99% 的***者看到 network 中這樣的抓包請求都會放棄,可是還剩下 1% 的人會打開 Chrome 提供的開發者工具進行一行一行的 debug ,針對這部分人,咱們下面在客戶端的段落裏再聊如何對付他們。

7. 限流

不少時候,在某些併發比較高的場景下,基於對業務系統的保護,咱們須要對請求訪問速率進行限制,防止訪問速率太高,把業務系統撐爆掉。

尤爲是一些對外的接口,給客戶或者供應商使用的接口,由於調用方咱們本身沒法控制,天知道對方的代碼會怎麼寫。

我曾經見過供應商把咱們提供的修改數據的接口拿來當作批量接口跑批,天天晚上都能把那個服務跑掛掉,後來直到咱們去問,供應商他們才說天天晚上會用這個接口作上千萬的數據同步,我也是醉了。

出於安全的角度考慮,在服務端作限流就顯得十分有必要。

服務端限流的算法常見的有這麼幾種:令牌桶限流、漏桶限流、計數器限流。

令牌桶限流:令牌桶算法的原理是系統以必定速率向桶中放入令牌,填滿了就丟棄令牌;請求來時會先從桶中取出令牌,若是能取到令牌,則能夠繼續完成請求,不然等待或者拒絕服務;令牌桶容許必定程度突發流量,只要有令牌就能夠處理,支持一次拿多個令牌。

漏桶限流:漏桶算法的原理是按照固定常量速率流出請求,流入請求速率任意,當請求數超過桶的容量時,新的請求等待或者拒絕服務;能夠看出漏桶算法能夠強制限制數據的傳輸速度。

計數器限流:計數器是一種比較簡單粗暴的算法,主要用來限制總併發數,好比數據庫鏈接池、線程池、秒殺的併發數;計數器限流只要必定時間內的總請求數超過設定的閥值則進行限流。

實現方面來說, Guava 提供了 RateLimiter 工具類是基於基於令牌桶算法,有須要的同窗能夠本身度娘一下。

8. 黑名單

黑名單機制已經有點風控的概念了,咱們能夠對非法操做進行定義。

好比記錄每一個 AppID 的訪問頻次,若是在 30 分鐘內,發生了 5 次或者以上的超頻訪問而且超出了 10 倍以上的訪問量,這時能夠將這個 AppID 放入黑名單中, 24 小時之後或者調用方線下聯繫才能將這個 AppID 取出。

再好比記錄 AppID 超時訪問次數,正常來說,超時訪問不會頻繁發生,若是在某個時間段內,大量的出現超時訪問,這個 AppID 必定存在問題,也能夠將其先放入黑名單中讓它冷靜冷靜。

黑名單實際上更多的是應用在業務層面,好比你們可能碰到過的拼爹爹的風控,直接把帳戶扔到黑名單裏面,禁止這個帳戶對某些補貼商品的下單。

客戶端

在當今的互聯網時代,網頁和 APP 成爲了主流的信息載體。

其中 APP 是可使用一些加固技術對 APP 進行加固,防止別人進行暴力破解。

而網頁就比較困難了,網頁的動態都是依靠 JavaScript 來完成的,邏輯是依賴於 JavaScript 來實現的,而 JavaScript 又有下面的特色:

  • JavaScript 代碼運行於客戶端,也就是它必需要在用戶瀏覽器端加載並運行。

  • JavaScript 代碼是公開透明的,也就是說瀏覽器能夠直接獲取到正在運行的 JavaScript 的源碼。

基於這兩點,致使了 JavaScript 代碼是不安全的,任何人均可以讀取、分析、盜用、篡改 JavaScript 代碼。

因此說, JavaScript 若是不進行一些處理,無論使用瞭如何高超的加解密方案,在被人找到其中的邏輯後,被模擬或者複製將變得在所不免。

前端 JavaScript 常見的加固方案有這麼幾種:壓縮、混淆、加密 。

1. 壓縮

代碼壓縮,就是去除 JavaScript 代碼中沒必要要的空格、換行等內容,把一些可能公用的代碼進行處理實現共享,最後輸出的結果都壓縮爲一行或者幾行內容,代碼可讀性變得不好,同時也能提升網站加載速度,就想下面這樣:

24049ab037264fbaa982fee3f7ee4bbf

這個是我從百度的頁面上隨便找了一個 js 截出來。

若是是單純從去除空行空格這個角度上來對代碼進行壓縮,其實幾乎是沒有任何防禦做用的,由於這種壓縮方式僅僅是下降了代碼的直接可讀性。

咱們能夠經過各類工具對代碼進行格式化,包括 Chrome 瀏覽器自己就提供了這個功能。

目前主流的前端技術都會使用 Webpack 進行打包,Webpack 會對源代碼進行編譯和壓縮,輸出幾個打包好的 JavaScript 文件,其中咱們能夠看到輸出的 JavaScript 文件名帶有一些不規則字符串,同時文件內容可能只有幾行內容,變量名都是一些簡單字母表示。

這其中就包含 JavaScript 壓縮技術,好比一些公共的庫輸出成 bundle 文件,一些調用邏輯壓縮和轉義成幾行代碼,這些都屬於 JavaScript 壓縮。另外其中也包含了一些很基礎的 JavaScript 混淆技術,好比把變量名、方法名替換成一些簡單字符,下降代碼可讀性。

總體上來說, JavaScript 壓縮術只能在很小的程度上起到防禦做用,要想真正提升防禦效果還得依靠 JavaScript 混淆和加密技術。

2. 混淆

JavaScript 混淆是徹底是在 JavaScript 上面進行的處理,它的目的就是使得 JavaScript 變得難以閱讀和分析,大大下降代碼可讀性,是一種很實用的 JavaScript 保護方案。

JavaScript 混淆器大體有兩種:

  • 經過正則替換實現的混淆器

  • 經過語法樹替換實現的混淆器

第一種實現成本低,可是效果也通常,適合對混淆要求不高的場景。第二種實現成本較高,可是更靈活,並且更安全,更適合對抗場景。

經過語法樹替換實現的混淆器,這種混淆方式的實現有點複雜了,我這裏就不展開去聊了,有興趣的同窗能夠參考這篇文章:https://www.zhihu.com/question/47047191/answer/121013968 。

針對修改語法樹進行混淆的方式,目前有一家作的比較好而且提供商業服務的是 jscrambler ,他們的官網地址:https://jscrambler.com/ 。

總之,以上方案都是 JavaScript 混淆的實現方式,能夠在不一樣程度上保護 JavaScript 代碼。

在通常的場景中,第一種混淆方式足夠咱們使用,如今 JavaScript 混淆主流的實現是 javascript-obfuscator 這個庫,利用它咱們能夠很是方便地實現頁面的混淆,它與 Webpack 結合起來,最終能夠輸出壓縮和混淆後的 JavaScript 代碼,使得可讀性大大下降,難以逆向。

3. 加密

不一樣於 JavaScript 混淆技術,JavaScript 加密技術能夠說是對 JavaScript 混淆技術防禦的進一步升級,其基本思路是將一些核心邏輯使用諸如 C/C++ 語言來編寫,並經過 JavaScript 調用執行,從而起到二進制級別的防禦做用。

其加密的方式如今有 Emscripten 和 WebAssembly 等,其中後者愈來愈成爲主流。

感興趣的同窗能夠自行度娘瞭解下。

小結

上面介紹了這麼多,只是爲了咱們的程序可以更加安全穩定的運行,減小由於***而產生的損失(加班)。

相關文章
相關標籤/搜索