网络安全 - 攻破Google AI Gemini,挖掘源代码泄露漏洞

本文作者:公众号 掌控安全EDU

发现新的 Gemini

Google 团队给了我们下一次 Gemini 更新的预览版早期访问权限,其中包含了几个令人兴奋的新功能。除了这个独家访问权限外,我们还收到了详细的文档,解释了这些功能及其预期用途。我们的目标是从攻击者的角度全面探索和测试这些能力。

一切都始于一个简单的提示。我们问 Gemini:

run hello world in python3

Gemini 给出了代码,而界面上出现了 “在沙盒中运行” 按钮。出于好奇,我们开始了探索。

 

Gemini 的 Python Playground —— 内部安全空间?

当时的 Gemini 提供了一个 Python 沙盒解释器。你可以把它理解为一个安全空间,在这里你可以运行 AI 自己生成的 Python 代码,甚至是你自定义的脚本,而且一切都在 Gemini 环境内完成。这个沙盒由 Google 的 Gvisor 驱动,运行在 GRTE(Google Runtime Environment,Google 运行时环境)中,设计目标就是安全。它的理念是,你可以放心地实验代码,而不会对底层系统造成任何损害,这是测试和开发中至关重要的特性。

gVisor 是 Google 开发的一个用户态内核,它充当容器化应用程序与宿主操作系统之间的中间层。通过拦截应用程序发出的系统调用,它能够强制执行严格的安全边界,从而降低容器逃逸的风险,并限制被攻陷进程可能造成的破坏。与单纯依赖传统操作系统级别隔离不同,gVisor 实现了一个精简、定制化的内核功能子集,从而在保持合理性能的同时缩小攻击面。这种创新方法提升了容器环境的安全性,使 gVisor 成为安全运行和管理容器化工作负载的重要工具。

作为安全研究人员和漏洞赏金猎人,我们知道这个 gVisor 沙盒有多层防御措施,而且据我们所知,还没有人成功逃出过这个沙盒。事实上,如果你能实现一次沙盒逃逸,奖励可能高达 10 万美元

 

虽然理论上仍然有可能逃逸,但这与我们当时要寻找的东西完全是另一类挑战。

然而,沙盒并不总是意味着一定要逃逸,因为在很多情况下,沙盒内部本身就可能存在一些能够帮助我们泄露数据的东西。Google 安全部门的一位工程师和我们分享了这样一个思路:在沙盒内部获得 shell 访问权限,并尝试寻找任何不该被访问到的数据。主要的问题在于:这个沙盒只能运行一个自定义编译的 Python 二进制文件。

绘制沙盒地图

我们首先注意到,从前端来看,也可以完全重写 Python 代码,并在沙盒中运行我们任意修改的版本。我们的第一步是了解这个沙盒的结构。我们怀疑其中可能隐藏着一些有趣的文件。由于我们无法直接获取 shell,于是我们检查了这个自定义编译的 Python 二进制中有哪些可用的库。结果发现 os 模块存在!太好了,我们就可以用它来遍历文件系统。

我们写下了如下的 Python 代码:

import os

def get_size_formatted(size_in_bytes):
    if size_in_bytes >= 1024 ** 3:
        size = size_in_bytes / (1024 ** 3)
        unit = "Go"
    elif size_in_bytes >= 1024 ** 2:
        size = size_in_bytes / (1024 ** 2)
        unit = "Mb"
    else:
        size = size_in_bytes / 1024
        unit = "Ko"
    return f"{size:.2f} {unit}"

def lslR(path):
    try:
        # Determine if the path is a directory or a file
        if os.path.isdir(path):
            type_flag = 'd'
            total_size = sum(os.path.getsize(os.path.join(path, f)) for f in os.listdir(path))
        else:
            type_flag = 'f'
            total_size = os.path.getsize(path)
        
        size_formatted = get_size_formatted(total_size)
        
        # Check read and write permissions
        read_flag = 'r' if os.access(path, os.R_OK) else '-'
        write_flag = 'w' if os.access(path, os.W_OK) else '-'
        
        # Print the type, permissions, size, and path
        print(f"{type_flag}{read_flag}{write_flag} - {size_formatted} - {path}")
        
        # If it's a directory, recursively print the contents
        if type_flag == 'd':
            for entry in os.listdir(path):
                entry_path = os.path.join(path, entry)
                lslR(entry_path)
    except PermissionError:
        print(f"d-- - 0Ko - {path} (PermissionError: cannot access)")
    except Exception as e:
        print(f"--- - 0Ko - {path} (Error: {e})")

这段代码的目标是实现某种递归列出文件和目录的功能,以便查看存在哪些文件、它们的大小以及权限信息。

我们使用该函数列出了目录 lslR("/usr")

 

这个调用让我们发现了一个位于 /usr/bin/entry/entry_point 的二进制文件。

泄露 entry_point 文件

我们的下一步是提取这个文件,但由于它的大小高达 579MB,直接在前端进行 base64 编码并打印出来并不可行,这会导致整个沙盒卡死,最终超时。

我们尝试过看看能否通过 TCP、HTTP 和 DNS 请求来外传信息。有趣的是,我们的所有出站连接尝试都失败了,沙盒似乎与外部网络完全隔离。这引出了一个耐人寻味的问题:如果沙盒被隔离得如此彻底,甚至无法进行任何外部调用,那它又是如何与 Google Flights 等 Google 服务交互的呢?好吧……这个问题或许我们稍后能解答 ;D

因此,我们需要通过 在控制台中分块打印 的方式来外传这个二进制文件。为此,我们使用了 seek() 函数来逐步遍历该二进制文件,并以 10MB 的块大小分段获取完整文件。

import os
import base64

def read_and_encode(file_path, kilobytes):
    try:
        # Calculate the number of bytes to read
        num_bytes = kilobytes * 1024
        
        # Open the file and read the specified number of bytes
        with open(file_path, 'rb') as file:
            file_content = file.read(num_bytes)
        
        # Base64 encode the bytes
        encoded_content = base64.b64encode(file_content)
        
        # Print the encoded string
        print(encoded_content.decode('utf-8'))
    
    except FileNotFoundError:
        print(f"FileNotFoundError: {file_path} does not exist")
    except PermissionError:
        print(f"PermissionError: Cannot access {file_path}")
    except Exception as e:
        print(f"Error: {e}")

read_and_encode("/usr/bin/entry/entry_point", 10000)

 

我们随后使用了 Caido 在代理中捕获运行沙盒调用并获取结果的请求,然后将其发送到 Automate 功能。Automate 功能允许你批量发送请求。该功能提供了一种灵活的方式,通过使用字典对请求的某些参数进行快速修改,从而发起暴力破解/模糊测试。

当我们拿到所有 base64 分片后,就在本地重建了整个文件,准备查看其内容。

如何读取这个文件?

file 命令?

对该二进制文件运行 file 命令,结果显示其身份为 binary: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /usr/grte/v5/lib64/ld-linux-x86-64.so.2。这确认了该文件是一个二进制文件。我们能用它做些什么呢?

strings 命令?

当我们运行 strings 命令时,输出格外引人注意,因为多次出现了对 google3(Google 的内部代码库)的引用。这指向了内部数据路径和从未打算对外暴露的代码片段,清楚地表明该二进制中包含 Google 专有软件的痕迹。但这真的存在任何安全影响吗?

Binwalk 大显身手!

真正的突破来自 Binwalk。这个工具从二进制中提取出了完整的文件结构,揭示了一个全面的沙盒布局。提取得到的多个目录和文件勾勒出了内部架构的细节,并暴露出一些组件——当我们看到这些时的反应就是……天哪。

内部源代码泄露?

在深入分析 binwalk 提取的内容时,我们意外发现了内部源代码。提取结果显示出整套 Google 专有源代码目录。但它是否敏感?

包含 Python 代码的 Google3 目录

在 binwalk 提取的目录中,我们找到了一个 google3 目录,其中包含以下文件:

total 2160
drwxr-xr-x   14 lupin  staff   448B Aug  7 06:17 .
drwxr-xr-x  231 lupin  staff   7.2K Aug  7 18:31 ..
-r-xr-xr-x    1 lupin  staff   1.1M Jan  1  1980 __init__.py
drwxr-xr-x    5 lupin  staff   160B Aug  7 06:17 _solib__third_Uparty_Scrosstool_Sv18_Sstable_Ccc-compiler-k8-llvm
drwxr-xr-x    4 lupin  staff   128B Aug  7 06:17 assistant
drwxr-xr-x    4 lupin  staff   128B Aug  7 06:17 base
drwxr-xr-x    5 lupin  staff   160B Aug  7 06:17 devtools
drwxr-xr-x    4 lupin  staff   128B Aug  7 06:17 file
drwxr-xr-x    4 lupin  staff   128B Aug  7 06:17 google
drwxr-xr-x    4 lupin  staff   128B Aug  7 06:17 net
drwxr-xr-x    9 lupin  staff   288B Aug  7 06:17 pyglib
drwxr-xr-x    4 lupin  staff   128B Aug  7 06:17 testing
drwxr-xr-x    9 lupin  staff   288B Aug  7 06:17 third_party
drwxr-xr-x    4 lupin  staff   128B Aug  7 06:17 util

在 assistant 目录中,我们还发现了与 Gemini 内部 RPC 调用 相关的代码(用于处理通过 YouTube、Google Flights、Google Maps 等工具发出的请求)。目录结构如下:

.
├── __init__.py
└── boq
    ├── __init__.py
    └── lamda
        ├── __init__.py
        └── execution_box
            ├── __init__.py
            ├── images
            │   ├── __init__.py
            │   ├── blaze_compatibility_hack.py
            │   ├── charts_json_writer.py
            │   ├── format_exception.py
            │   ├── library_overrides.py
            │   ├── matplotlib_post_processor.py
            │   ├── py_interpreter.py
            │   ├── py_interpreter_main.py
            │   └── vegalite_post_processor.py
            ├── sandbox_interface
            │   ├── __init__.py
            │   ├── async_sandbox_rpc.py
            │   ├── sandbox_rpc.py
            │   ├── sandbox_rpc_pb2.pyc
            │   └── tool_use
            │       ├── __init__.py
            │       ├── metaprogramming.py
            │       └── runtime.py
            └── tool_use
                ├── __init__.py
                └── planning_immersive_lib.py

8 directories, 22 files

更深入地查看 Python 代码

在文件 google3/assistant/boq/lamda/execution_box/images/py_interpreter.py 中,有一段代码揭示了如下内容:

# String for attempted script dump detection:
  snippet = (  # pylint: disable=unused-variable
      "3AVp#dzcQj$U?uLOj+Gl]GlY<+Z8DnKh"  # pylint: disable=unused-variable
  )

这段代码似乎起到了防止未授权脚本转储的保护作用,更加凸显了这些代码从未打算对外公开。

经过全面审查后发现,这些看似内部的 Google3 代码其实是 刻意包含的……可惜了 x)

尽管这些 Python 代码中存在的反转储机制最初可能暗示了受限访问,但事实上在上线之前,Google 安全团队已经明确批准了其公开曝光。虽然这些措施最初是为了防止意外打印,但它们最终被保留了下来,因为……何乐而不为呢?

但我们并没有就此放过这个沙盒,因为我们知道自己正接近一个重大突破! ;D

深挖沙盒的主要逻辑

在更深入分析这段 Python 代码时,我们注意到,正如预期的那样,这个沙盒会与 Google 外部服务器进行通信,以执行诸如从 Google Flights 或其他 Google 服务获取数据等操作。

这是通过一个 Python 类(google3.assistant.boq.lamda.execution_box.sandbox_interface)来实现的,该类暴露了多个可调用的函数,比如 _set_reader_and_writer

def _set_reader_and_writer(
    reader_handle: io.BufferedReader | None,
    writer_handle: io.BufferedWriter | None,
) -> None:
  """Sets the reader and writer handles for rpcs.

  Should be called before running any user code that might
  import async_sandbox_rpc

  Args:
    reader_handle: the handle through which to receive incoming RpcResponses. If
      None will default to legacy behavior (/dev/fd/3)
    writer_handle: the handle through which to receive incoming RpcRequests. If.
      None will default to legacy behavior (/dev/fd/4)
  """
  with _INIT_LOCK:
    global _READER_HANDLE
    global _WRITER_HANDLE
    _READER_HANDLE, _WRITER_HANDLE = reader_handle, writer_handle
def run_tool(
    name: str, operation_id: str, parameters: str
) -> sandbox_rpc_pb2.RunToolResponse:
  """Runs a tool with the given name and id, passing in parameters.

  Args:
    name: The name of the tool.
    operation_id: The name of the operation to perform.
    parameters: The parameters to pass to the tool.

  Returns:
    A RunToolResponse containing the response from the tool.
  """
  result = make_rpc(
      sandbox_rpc_pb2.RpcRequest(
          run_tool_request=sandbox_rpc_pb2.RunToolRequest(
              name=name, operation_id=operation_id, parameters=parameters
          )
      )
  )

  if result and result.HasField("run_tool_response"):
    return result.run_tool_response
  else:
    return sandbox_rpc_pb2.RunToolResponse(response="")

我们会向这些函数提供各种数据,它们会将数据序列化为兼容 protobuf 的格式,然后通过写入本地文件描述符 5 进行 RPC 调用。响应则可以通过读取本地文件描述符 7 来获取。利用在那个庞大二进制文件中找到的 proto 文件,我们能够构建与这个 RPC 服务器之间的消息,从而直接调用这些 Google 工具。

然而,我们注意到一个有趣的现象:并非每个沙盒都能访问相同的 Google 服务。这取决于沙盒是由前端生成以运行 Python 源代码,还是由 Google Agent 生成。这里的区别是什么呢?

ReAct 研究论文!

在解释下一部分之前,需要说明 Google 团队向我们展示了 Gemini 所基于的研究论文:

  • • https://arxiv.org/pdf/2210.03629

这篇论文提出了一种当时新颖的方法,即语言模型在生成推理轨迹和执行特定动作之间交替进行,有效地将思考与行为交织在一起。实际上,这意味着当模型在解决问题时,它会创建一个透明的思维轨迹,帮助规划、跟踪并调整行动,同时在需要时与外部资源交互以收集额外数据。这种动态交互不仅通过减轻幻觉和错误传播等常见问题提升了模型性能,也让其决策过程对人类操作者更加可解释和可控。

通过整合内部推理和外部知识,ReAct 提供了一个灵活且通用的框架,在多种任务中表现出色,从问答和事实验证到基于文本的游戏和网页导航。核心而言,ReAct 利用推理与行动的综合优势,打造出更稳健、符合人类预期且多功能的语言模型。

 

好,但为什么我们需要理解这一点呢?原因是,如果按照这篇论文的逻辑,基本上 Gemini 可以多次自我提示,以完善命令并形成完整的思维链。

例如,用户问:“多伦多到纽约最早的航班是什么?”时,Gemini 的思维链大致如下:

  1. 1. 用户让我搜索多伦多到纽约的最早航班。
  2. 2. 制定计划,例如:
    1. 1. 我需要运行一段 Python 代码,连接到 Google Flights 工具。
    2. 2. 代码需要查找从今天到下周从 YYZ 机场到 JFK 机场的所有航班。
    3. 3. 代码需要按日期排序航班,并取第一个结果。
  3. 3. 计划完成后,利用 Agent 可用的工具生成相应代码。
  4. 4. 生成代码后,它会启动一个拥有正确权限的沙盒(例如,通过文件描述符访问 Google Flights 工具)。
  5. 5. 运行代码并处理输出。
  6. 6. 向用户作出响应。

因此我们的理论是:

  • • 如果我们能够在 Gemini 的规划阶段做一次二次提示注入,是否可以访问权限更高的沙盒?

在 Google 安全部门的协助下,我们测试了这一想法,并观察到根据生成种子和温度等因素(LLM 的常见概率性差异),我们有时可以访问看似权限更高的沙盒。

所谓“权限更高的沙盒”,是指能够通过两个新的文件描述符访问扩展功能。这些文件描述符始终存在,但并非总是处于监听状态。当 Agent 调用沙盒时,它们会监控对扩展(Google 服务)的调用,从而允许我们与 API 交互,而通过 Python 解释器访问时,这些扩展是不可用的。

这让我们认为存在一个真正的 P0 漏洞机会:有一个特定的消息处理器可能允许在 Google 内部基础设施上读取文件,我们有希望通过带有工具扩展的沙盒向该特定工具发起 RPC 调用。由于该攻击具有概率性,难以持续复现,我们请 Google 安全部门评估这一情况。最终,他们的审核显示,那个可疑消息处理器无法通过 RPC 调用访问,只能从外部调用。

 

即便我们的测试有限,这个核心思路如果进一步推进,仍然具有真正的潜力。在沙盒环境中运行代码并非为了赋予额外权限,它被视为不可信代码,沙盒外有安全检查,并且每次工具调用都会被过滤。但能够运行代码确实带来一些便利:

  • • 可靠性:一旦可以运行代码,就能更稳定地触发操作。
  • • 链式/复杂操作:通过纯文本控制多个工具或微调参数比较困难;代码执行可以让你构建更复杂的操作链,即便安全措施仍然存在。
  • • 工具输出操控:可能更有效地操控工具的输出。
  • • 信息泄露:环境中可能还有其他隐藏部分,如果被暴露,可能带来额外优势。

这表明我们的想法在进一步权限提升方面仍然有希望。而对于这种“泄露”的潜力,我们希望至少能验证这个理论……

我们找到了泄露 ;D

在更深入探索时,我们发现了几种泄露 proto 文件的方法。如果你不熟悉,proto 文件(Protocol Buffer 文件的简称)就像数据的蓝图,定义了消息的结构以及系统不同部分之间信息的交换方式。乍一看,它们似乎无害,但泄露这些文件可以让人相当详细地了解 Google 的内部架构。

暴露 classification.proto

事实证明,通过运行如下命令:

strings entry_point > stringsoutput.txt

然后在生成的文件中搜索 “Dogfood”,我们成功获取了部分内部 proto 片段。提取内容的一部分包含了极其敏感 proto 的元数据描述。虽然这些文件本身不包含用户数据,但它们是 Google 用于 分类 用户数据的内部类别。

出于法律原因,我们不能展示该命令的结果 x)

为什么要特别搜索字符串 “Dogfood”?在 Google,“dogfood” 指的是公司内部使用自家产品或原型的预发布版本进行测试和完善的做法。在正式发布前,这种做法允许开发人员测试产品的部署情况及潜在问题,从而在进入生产环境前进行优化。

此外,还有一个被暴露的文件 privacy/data_governance/attributes/proto/classification.proto,它详细说明了 Google 内部的数据分类方式。虽然该文件包含与相关文档的引用,但那些文档仍高度机密,不应对公众开放。

暴露内部安全 Proto 定义

相同输出还显示了许多本应隐藏的内部 proto 文件。运行命令:

cat stringsoutput.txt | grep '\.proto' | grep 'security'

可以列出若干敏感文件,包括:

security/thinmint/proto/core/thinmint_core.proto
security/thinmint/proto/thinmint.proto
security/credentials/proto/authenticator.proto
security/data_access/proto/standard_dat_scope.proto
security/loas/l2/proto/credstype.proto
security/credentials/proto/end_user_credentials.proto
security/loas/l2/proto/usertype.proto
security/credentials/proto/iam_request_attributes.proto
security/util/proto/permission.proto
security/loas/l2/proto/common.proto
ops/security/sst/signalserver/proto/ss_data.proto
security/credentials/proto/data_access_token_scope.proto
security/loas/l2/proto/identity_types.proto
security/credentials/proto/principal.proto
security/loas/l2/proto/instance.proto
security/credentials/proto/justification.proto

在二进制字符串中查找 security/credentials/proto/authenticator.proto 时,可以确认其数据确实被暴露了。

为什么这些 proto 会出现在这里?

如前所述,Google 安全部门对沙盒中的所有内容进行了彻底审查,并批准公开披露。然而,用于编译沙盒二进制的构建流水线包含一个自动化步骤:当检测到二进制可能需要这些文件以执行内部规则时,会将安全 proto 文件添加到二进制中。

在这个特定案例中,这一步其实并不必要,导致高度机密的内部 proto 被意外包含在了公开环境中!

作为漏洞赏金猎人,深入理解公司运营背后的业务规则至关重要。我们报告了这些 proto 泄露,因为我们清楚 Google 将其视为高度机密信息,绝不应公开。对目标内部运作和优先级的深入理解,有助于我们发现和标记那些可能被忽略的微妙漏洞。这种深度认知不仅帮助我们定位漏洞,也确保我们的报告符合组织的关键安全关注点。

总结

在结束之前,值得强调的是,在这些前沿 AI 系统上线前进行测试是多么重要。系统中有如此多的互联功能和炫酷特性——即便是一个可以访问不同扩展的简单沙盒——总是存在意外问题的潜在可能。我们亲眼看到,当这些组件协同工作时,即便是小小的疏忽,也可能带来新的安全隐患。因此,彻底测试不仅是最佳实践,更是确保系统安全、正常运行的唯一方法。

 申明:本账号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,所有渗透都需获取授权,违者后果自行承担,与本号及作者无关    

网络安全学习路线/web安全入门/渗透测试实战/红队笔记/黑客入门


感谢各位看官看到这里,欢迎一键三连(点赞+关注+收藏)以及评论区留言,也欢迎查看我主页的个人简介进行咨询哦,我将持续分享精彩内容~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值