谷歌警告云存储桶劫持攻击
黑客正瞄准被遗忘或配置错误的云存储桶,寻找敏感数据或资源,以便向他人提供恶意软件。谷歌分享了其防止悬挂存储桶接管的最佳实践,并敦促开发人员保护其云环境。
谷歌警告称,悬垂存储桶攻击是指开发人员删除存储桶,但应用程序代码、移动应用程序、公共文档或其他地方仍然存在对它的引用。
该科技巨头表示:“攻击者只需在自己的项目中声明相同的存储桶名称,即可有效劫持您的旧地址,从而可能为恶意软件提供服务,并窃取那些在不知情的情况下仍然依赖不再正式使用的存储桶的用户的数据。”
Google 建议对此类云存储采取谨慎的退役流程,并分享了有关保护悬空存储桶的四步建议。
首先,在删除任何存储桶之前,云管理员应该“花时间了解谁以及什么仍在访问该存储桶”。
日志会显示最近的流量,如果新请求来自旧版本的应用、第三方服务或用户,则应进行调查。来自机器人、数据爬虫和扫描器的流量可以放心忽略。
谷歌警告说:“要特别注意试图提取可执行代码、机器学习模型、动态网络内容(如 JavaScript)和敏感配置文件的请求。”
其次,Google 建议至少等待一周再删除存储桶。观察整个活动周期,例如每周报告、批处理作业以及不频繁的用户访问,将有助于增强信心。
帖子中写道:“在您确认至少一周内没有合法流量进入存储桶,并且您已更新所有遗留代码后,您就可以继续删除存储桶了。”
谷歌提醒用户,删除 Google Cloud 项目也会删除与其相关的所有资源,包括存储桶。
接下来,主动发现悬空的存储桶引用至关重要,包括分析日志中的 404 错误和扫描代码库中的过时引用。
“对同一个不存在的存储桶名称的大量失败请求是一个重大危险信号。”
Google 建议扫描代码库和文档,查找任何可能不再使用的存储桶名称的引用。如果开发人员发现不再拥有的悬空存储桶,应立即删除硬编码引用,并向用户部署修复程序。
“如果您发现一个悬空的存储桶名称可能对您或您的客户构成安全风险,请迅速采取行动。”
公司仍然拥有的悬空存储桶可以通过在其控制的安全项目中创建同名的新存储桶来回收。这些存储桶应使用严格的 IAM 策略锁定。
谷歌还分享了可能有助于开发人员识别悬空存储桶的检测脚本。
https://cloud.google.com/blog/products/identity-security/best-practices-to-prevent-dangling-bucket-takeovers
import re
import sys
from typing import Optional, Setimport requests
from requests.exceptions import RequestExceptiondef check_bucket(bucket_name: str) -> Optional[requests.Response]:try:with requests.Session() as session:response = session.head(f"https://storage.googleapis.com/{bucket_name}")return responseexcept RequestException as e:print(f"An error occurred while checking bucket {bucket_name}: {e}")return Nonedef sanitize_bucket_name(bucket_name: str) -> Optional[str]:# Remove common prefixes and quotesbucket_name = bucket_name.replace("gs://", "")bucket_name = bucket_name.replace("\"", "")bucket_name = bucket_name.replace("\'", "")bucket_name = bucket_name.split("/")[0]# Validate the bucket name format according to GCS naming conventionsif re.match("^[a-z0-9-._]+$", bucket_name) is None:return Nonereturn bucket_namedef extract_bucket_names(line: str) -> Set[str]:all_buckets: Set[str] = set()pattern = re.compile(r'gs://([a-z0-9-._]+)|'r'([a-z0-9-._]+)\.storage\.googleapis\.com|'r'storage\.googleapis\.com/([a-z0-9-._]+)|'r'([a-z0-9-._]+)\.commondatastorage\.googleapis\.com|'r'commondatastorage\.googleapis\.com/([a-z0-9-._]+)',re.IGNORECASE)for match in pattern.finditer(line):# The first non-empty group is the bucket nameif raw_bucket := next((g for g in match.groups() if g is not None), None):if sanitized_bucket := sanitize_bucket_name(raw_bucket):all_buckets.add(sanitized_bucket)return all_bucketsdef main(filename: str) -> None:with open(filename, 'r') as f:for i, line in enumerate(f, 1):bucket_names = extract_bucket_names(line)for bucket_name in bucket_names:response = check_bucket(bucket_name)if response.status_code == 404:print(f"Dangling bucket found: {bucket_name} (line {i}), {line}")if __name__ == "__main__":if len(sys.argv) != 2:print("Usage: python find_dangling_buckets.py <filename>")sys.exit(1)main(sys.argv[1])