密碼硬編碼(Hard-Coded Secrets)是指將敏感信息(如數據庫密碼、API密鑰、加密密鑰、第三方服務憑證等)以明文形式直接寫入應用程序的源代碼、配置文件或固件中的行為。
常見的密鑰硬編碼場景:
- 密鑰放在環境變量、配置
- 在配置文件中:
<add key="ConnectionString" value="Server=...;User Id=sa;Password=password;" />
- 在配置文件中:
- 密鑰加密存儲在代碼里
- 在Python代碼中:
db_password = "MySuperSecretPassword123!" - 在Java代碼中:
String apiKey = "AKIAIOSFODNN7EXAMPLE";
- 在Python代碼中:
自查方法:
通過關鍵搜索
String pass="fadfdafad"
# Password key pwd pass jdbc encryption key Cipher key 等關鍵字搜索
危害說明
硬編碼是安全實踐中的大忌,主要原因如下:
- 喪失機密性: 源代碼通常會被存儲在版本控制系統(如Git)中,可能會被多位開發者訪問,也可能意外泄露到公共平臺(如GitHub)。一旦泄露,所有硬編碼的密碼都將暴露。
- 難以輪換: 如果一個密碼被泄露或需要定期更換,開發人員必須修改代碼、重新測試、重新部署整個應用程序。這個過程非常繁瑣、耗時,且容易出錯,導致密碼輪換策略無法有效執行。
- 權限擴散: 任何有權訪問代碼的人(包括不應知道生產環境密碼的開發者)都獲得了敏感信息的訪問權限,違反了“最小權限原則”。
- 缺乏審計: 無法追蹤和審計是誰、在什么時候、使用了哪個密鑰訪問了關鍵服務。
防護核心
核心點在于密鑰、代碼、開發人員分離,盡可能少的讓密鑰被人接觸到。(只是減緩因人導致的安全風險,無法杜絕研發人員惡意通過各種手段去獲取到真實Key并利用。)
總結:將秘密與代碼分離;
- 分離配置與代碼: 敏感信息不屬于代碼的一部分。代碼是邏輯,配置是環境依賴。兩者應該完全分開。
- 使用安全的憑據管理系統: 將秘密集中存儲在專門設計的安全系統中,如KMS、HashiCorp Vault、Azure Key Vault等。應用程序在運行時動態地從這些系統獲取憑據。
- 最小權限原則: 應用程序和訪問秘密的系統身份(如IAM角色)只應被授予完成其功能所必需的最小權限。
- 環境隔離: 為開發、測試、生產等不同環境使用不同的憑據。開發環境的數據庫密碼絕不能和生產環境相同。
- 加密與審計: 對靜態存儲(At-Rest)和傳輸中(In-Transit)的秘密進行加密,并記錄所有對敏感憑據的訪問和操作日志。
企業應制定安全策略:
密鑰的安全策略中,密鑰分層、密鑰定期更換是重要的防護策略,因為你無法知道在何時何處密鑰泄漏過,因此定期更換密鑰能降低風險。
不同場景下的防范實踐
場景一:擁有KMS或專業 vault(推薦的最佳實踐)
當你的云平臺或基礎設施提供了成熟的密鑰管理服務(如AWS KMS, GCP KMS, Azure Key Vault)或專業的秘密管理工具(如HashiCorp Vault)時,應采用以下模式:
實踐方法:
- 存儲秘密: 將數據庫密碼、API密鑰等秘密加密后存儲在安全的地方(如云平臺的 Secrets Manager服務)或直接由Vault管理。
- 身份認證: 應用程序(例如運行在ECS、EC2、Kubernetes Pod中)需要一個身份來訪問這些服務。這個身份不應是寫死的AK/SK,而應是:
- 云上: 分配給應用程序運行環境的 IAM角色。例如,給EC2實例分配一個IAM角色,該角色被授權讀取特定的Secrets Manager中的內容。
- 自建: 給Kubernetes Pod分配一個 Service Account,該賬戶有權訪問Vault。
- 動態獲取: 應用程序在啟動時,憑借其身份(由云平臺元數據服務或Kubernetes自動提供)安全地訪問KMS/Vault,動態獲取解密后的秘密,然后使用它來建立數據庫連接等。
- 代碼示例(概念):
# Python示例:使用Boto3從AWS Secrets Manager獲取數據庫密碼 import boto3 import json from botocore.exceptions import ClientError def get_secret(): secret_name = "MyApp/Database" region_name = "us-west-2" session = boto3.session.Session() client = session.client(service_name='secretsmanager', region_name=region_name) try: get_secret_value_response = client.get_secret_value(SecretId=secret_name) except ClientError as e: ... # 處理異常 else: secret = get_secret_value_response['SecretString'] # 解析JSON字符串,假設secret存儲的是JSON:{"username":"...","password":"..."} secret_dict = json.loads(secret) return secret_dict['username'], secret_dict['password'] # 主程序 db_user, db_password = get_secret() # 然后用獲取到的user和password去連接數據庫
場景二:沒有KMS/加密機等高級工具的“簡陋”環境
即使在沒有高級工具的情況下,也絕對不應該硬編碼到源代碼里。可以采用以下“雖不完美,但遠勝于硬編碼”的實踐方法:
實踐方法:
-
使用環境變量:
- 將密碼通過環境變量傳遞給應用程序。
- 優點: 簡單、通用,與代碼完全分離。部署時(Docker、系統d服務、手動啟動)注入變量。
- 缺點: 環境變量可能在意外輸出日志時被記錄下來,或者在進程信息中可見(
ps -ef)。切記不要將環境變量文件(如.env)提交到版本控制。 - 示例:
# 啟動腳本中 export DB_PASSWORD="super_secret_pass" python my_app.py# my_app.py中 import os db_password = os.environ.get('DB_PASSWORD') if not db_password: raise RuntimeError("DB_PASSWORD environment variable not set!")
-
使用外部配置文件:
- 將秘密放在一個獨立的配置文件(如
config.ini,secrets.json)中。 - 關鍵: 此文件必須被加入到
.gitignore中,防止誤提交。并通過部署工具(Ansible, SaltStack)或手動方式安全地分發到生產服務器。 - 示例 (
config.json):{ "database": { "host": "localhost", "user": "myuser", "password": "mysecretpassword" } }
- 將秘密放在一個獨立的配置文件(如
-
嚴格的訪問控制:
- 即使使用了上述“簡陋”方法,也必須對存儲密碼的服務器、文件和環境變量設置嚴格的權限。
- 文件權限: 確保只有運行該應用程序的用戶和必要的管理員有權限讀取該配置文件。例如:
chmod 600 config.json(僅所有者可讀可寫)。 - 基礎設施安全: 保護好你的服務器,限制SSH訪問,做好基礎的安全防護。
即使資源有限,也應當優先選擇環境變量或受權限嚴格保護的外部配置文件,并逐步升級到更專業的密鑰管理方案。
浙公網安備 33010602011771號