爲什麼說在移動App中使用OAuth API密鑰是不安全的?

對於移動應用程序來說,使用密鑰來訪問後端API服務來獲取數據是非常常見的。那麼,如何將密鑰安全地包含在移動應用程序中呢?往短了說,你做不到。往長了說,請看這篇文章的剩餘部分。

保存在本地和移動應用程序中的密鑰被提取

從JavaScript應用程序中提取API密鑰非常簡單。只要你加載包含JavaScript應用程序的網頁,瀏覽器就會下載整個源代碼。你所要做的就是單擊“查看源代碼”,就可以看到整個源代碼(包括API密鑰)。JavaScript代碼通常會被壓縮或縮小,看懂源代碼可能並沒有那麼容易,但應用程序中定義的所有字符串都是可見的。

對於原生移動應用程序來說也是一樣。這些應用程序在運行之前也會被下載到設備上,只是你下載的是二進制文件,而不是未編譯的源代碼。

讓我們來做一個有趣的快速演示。如果你使用的是Mac,並且安裝了1Password,請運行下面這個命令。你可以針對Mac上的任意一個二進制文件運行這個命令,但1Password應用程序中恰好有一些非常容易讀懂的數據。

strings /Applications/1Password\ 7.app/Contents/MacOS/1Password\ 7 | grep 1Password

strings命令將會顯示二進制文件的所有嵌入字符串,然後我們使用grep查找與1Password匹配的字符串,結果得到了在應用程序中使用的一堆文本!

...
Restarting 1Password...
1Password failed to connect to 1Password
Please quit 1Password and start it again.
Add Account to 1Password
An update to Safari is required in order to use the 1Password extension.
Welcome to 1Password!
...

如果應用程序中嵌入了API密鑰,strings命令也會將它們顯示出來。

讓我們從頭開始寫一個簡單的程序來證明這一點。我們將編寫一個輸出“hello world”的C語言程序。首先,創建一個叫作hello.c的文件,並寫入以下內容:

#include <stdio.h>

char hello[] = "hello";
char world[] = "world";

int main()
{
  printf("%s %s", hello, world);
  return 0;
}

在這段代碼中,我們聲明一個叫作example的字符串,值爲“hello world”,然後在程序運行時打印它。你可以使用任何一個C語言編譯器(如gcc)編譯它:

$ gcc hello.c

結果會得到一個叫作a.out的二進制文件,然後你可以運行它:

$ ./a.out
hello world

讓我們針對這個二進制文件運行strings命令:

$ strings a.out
%s %s
hello
world

它很容易就能發現二進制文件中的文本。

現在你可能會想:“如果我將API密鑰分成幾個部分並將它們分散在代碼中會怎樣?”這可能會爲你贏得一些時間,但仍然會被一個真正有決心的人找出來。

即使是Twitter也無法阻止這種情況發生!2013年,Twitter官方應用程序的API密鑰就是通過這種方式泄露的,讓攻擊者可以冒充合法的Twitter應用程序。

關鍵問題在於,一旦你將包含應用程序需要使用的字符串的內容發送給用戶,總有人會提取它們。解決這個問題唯一的方法是使用“硬件安全模塊”,祕鑰被存儲在微處理器中,微處理器無法通過編程的方式提取任密鑰,它可以對數據進行加密簽名,而不是發送密鑰本身。

總的來說,如果你以未編譯或二進制形式向用戶發送代碼,他們就有可能看到其中的內容。

HTTPS請求在移動應用程序端被攔截

即使你認爲你使用了最厲害的混淆技術,並且確信沒有人能夠從應用程序二進制文件中提取密鑰,但總有人可以通過另一種方式找到密鑰。

與運行在數據中心服務器上的應用程序不同,移動應用程序運行在用戶手中的設備上,經過各種網絡。用戶可能是通過自家的網絡安裝應用程序,然後連接到咖啡店的網絡並打開它,又或者在通過身份認證之前連接到酒店的網絡。這些網絡都是不可信任的,並且很有可能會出問題,或者攻擊者會試圖攔截數據!

你可能會想:“HTTPS會保護傳輸中的數據!”在正常情況下確實如此。只要應用程序在發出請求時正確驗證HTTPS證書,處於手機和服務器之間的攻擊者幾乎不可能看到流量。

但是,我們擔心的不是這個問題。如果有人願意,他們可以爲你的API URL提供自己的HTTPS證書,在請求離開手機之前攔截自己的HTTPS連接。網上有很多教程教你如何做到這一點!實際上,這一項很好的技術,在開發應用程序時,可以用它來測試自己的應用程序,也是人們對Instagram等私有API進行反向工程的常見方式。

如果你有興趣嘗試一下,可以看看Charles Proxy(https://www.charlesproxy.com/)或者免費的mitmproxy(https://mitmproxy.org/)。

在手機上安裝了自己的證書授權程序後,它就可以爲任何一個域頒發HTTPS證書,對於你的手機來說,一切看起來都很正常。只是你的手機實際上是在向運行在筆記本電腦上的軟件發出HTTPS請求,然後你的筆記本電腦再向真實的API發起新的HTTPS請求。這樣你的筆記本電腦就可以看到手機發送給API的所有內容。

當然,攻擊者不會通過這種方式隨機攔截用戶的傳輸數據,但如果有人想要知道應用程序使用的密鑰,他們就可以通過這種方式輕鬆查看應用程序通過網絡發送的所有數據。這意味着儘管你盡最大努力在源代碼中隱藏應用程序密鑰,仍然會在通過網絡傳輸時被攔截。

這與OAuth有什麼關係?

我們已經看到了從移動應用中提取API密鑰的兩種方法,但這與OAuth有什麼關係呢?

傳統上,OAuth 2.0應用程序在開發人員註冊應用程序時會發出client_id和client_secret。當應用程序在Web服務器上運行時,這沒問題,因爲應用程序用戶永遠無法訪問源代碼,因此無法查看密鑰。但是,當我們在JavaScript或原生應用程序中使用OAuth 2.0時,顯然會有問題,因爲正如我們所看到的那樣,它們沒有保密的能力。

在OAuth 1中,每個API請求都需要使用一個密鑰,這是它的主要缺點之一,也是它被OAuth 2.0取代的主要原因。OAuth 1是在移動應用程序開始流行之前出現的,所以它並沒有考慮到這些限制。

隨着OAuth 2.0的出現,這種情況發生了變化,特別是在引入PKCE(證明密鑰交換)擴展之後。我喜歡把PKCE看作是一個“動態”的客戶端密鑰。PKCE沒有采用向移動應用程序傳遞client_secret的方式,而是在每次啓動OAuth請求流時,應用程序都會創建一個新的隨機密鑰。這樣就不存在需要提前傳遞的密鑰,攻擊者也沒有什麼東西可竊取。

OAuth仍然會通過網絡發送訪問令牌,如果使用了mitmproxy之類的東西,那麼它們對你仍然是可見的,但不同的是,這個令牌是動態發出的,並且特定於使用它的用戶!這樣一來,源代碼中就沒有密鑰了,如果有人想從他們自己的設備上攔截流量,他們看到的只是一個訪問令牌!他們無法訪問應用程序已經無法訪問的東西。

如何保護移動應用中的密鑰

希望你現在已經瞭解爲什麼在移動應用程序中發佈API密鑰或其他密鑰是不安全的。那麼你會怎麼做呢?

OAuth解決了這個問題,它沒有向移動應用程序傳遞任何密鑰,而是讓用戶參與獲取應用程序訪問令牌的過程。這些訪問令牌在用戶每次登錄時都是唯一的。PKCE擴展提供了在移動應用程序上安全執行OAuth請求流的解決方案,即使沒有使用預先準備的密鑰。

如果你需要從移動應用程序中訪問API,但願可以支持OAuth和PKCE!幸運的是,有關PKCE的大部分繁瑣的任務都是由像AppAuth這樣的SDK來處理的,所以你不需要自己編寫所有的代碼。如果你使用的是像Okta這樣的API,那麼Okta的SDK會自動執行PKCE,你完全不需要操心。

英文原文:https://developer.okta.com/blog/2019/01/22/oauth-api-keys-arent-safe-in-mobile-apps

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章