依赖项扫描

适用于 Azure DevOps 的 GitHub Advanced Security 中的依赖项扫描可检测源代码中使用的开源组件,并检测是否存在任何相关漏洞。 从开源组件中发现的任何漏洞都将被标记为警报。

适用于 Azure DevOps 的 GitHub Advanced Security 可与 Azure Repos 配合使用。 如果要将 GitHub Advanced Security 与 GitHub 存储库配合使用,请参阅 GitHub Advanced Security

关于依赖项扫描

依赖项扫描将为代码所依赖的任何被发现易受攻击的直接或可传递的开源组件生成警报。 直接漏洞是代码直接使用的库。 可传递依赖项是直接依赖项使用的库或其他软件。

关于依赖项扫描检测

每当存储库的依赖项关系图发生更改时,都将存储组件的新快照,之后将执行包含生成新代码的依赖项扫描任务的管道。

对于在使用中检测到的每个易受攻击的组件,组件和漏洞都将在生成日志中列出,并在“Advanced Security”选项卡中显示为警报。只有经过 GitHub 审核并添加到 GitHub 公告数据库的公告才会创建依赖项扫描警报。 生成日志包含指向单个警报的链接,以供进一步调查。 有关警报详细信息的更多信息,请查看“修复依赖项扫描警报”。

生成日志还包含有关每个检测到的漏洞的基本信息。 这些详细信息包括严重性、受影响的组件、漏洞的名称和关联的 CVE。

依赖项扫描生成输出的屏幕截图

支持的包生态系统

对于所有受支持的包生态系统,依赖项扫描同时支持直接依赖项和传递依赖项。

程序包管理器 语言 支持的格式
Cargo Rust Cargo.toml, Cargo.lock
CocoaPods Swift Podfile.lock
Go 模块 Go go.mod, go.sum
Gradle Java *.lockfile
Maven Java pom.xml
npm JavaScript package-lock.json, package.json, npm-shrinkwrap.json, lerna.json
NuGet C# *.packages.config, *.project.assets, *.csproj
pip Python setup.py, requirements.txt
pnpm JavaScript package.json
RubyGems Ruby Gemfile.lock
Yarn JavaScript package.json

关于依赖项扫描警报

Azure DevOps 中 Repos 中的“Advanced Security”选项卡是查看安全警报的中心,默认情况下显示依赖项扫描警报。 可以按分支、管道、包和严重性进行筛选。 可以选择进入警报以获取更多详细信息,包括修正指南。 此时,警报中心不会显示针对已为 PR 分支完成的扫描的对应警报。

当在存储库中检测到易受攻击的包时,修复依赖项扫描警报通常涉及升级到更高的包版本或移除有问题的包。 此建议适用于直接和可传递(或间接)依赖项。 “Advanced Security”选项卡中的默认视图是存储库默认分支的活动警报。

重命名管道或分支不会对结果产生影响 - 最多可能需要 24 小时才能显示新名称。

存储库的依赖项扫描警报视图的屏幕截图

当在安装了依赖项扫描任务的任何管道的最新生成中不再检测到易受攻击的组件时,警报的状态将自动更新为 Closed。 要查看已解决的警报,请利用主工具栏中的 State 筛选器并选择 Closed

查看已关闭的依赖项扫描警报的屏幕截图

如果关闭了存储库的 Advanced Security,则无法访问“Advanced Security”选项卡和生成任务中的结果。 生成任务不会失败,但在禁用了 Advanced Security 的情况下,与该任务一起运行的生成的任何结果都会隐藏,并且不会保留。

警报详细信息

还可以通过单击进入特定警报和修正指南来深入了解有关警报的详细信息。

显示依赖项扫描警报详细信息的屏幕截图

部分 说明
建议 建议文本直接来自漏洞数据提供程序 GitHub 公告数据库。 通常,指南建议将已标识的组件升级到不易受攻击的版本。
位置 位置部分详细说明了依赖项扫描任务发现了正在使用的易受攻击组件的路径。 如果文件可以从基础生成扫描解析为源中的已提交文件,则“位置”卡显示为可单击的链接。 如果文件是作为生成(例如生成工件)的一部分生成的,则无法单击该链接。 查看生成日志以更好地了解是如何将组件引入到生成的。
说明 说明由 GitHub 公告说明提供。

检测

检测选项卡下列出的管道用于发现易受攻击的组件。 每一行都详细说明了受影响管道的最新生成和首次引入包的日期。 如果易受攻击的包已在某些(但不是全部)管道中得到修复,你将看到部分已修复的行。

未修复警报的依赖项扫描检测视图的屏幕截图

解决警报后,警报将自动更改为 Closed 状态,并且“检测”选项卡下的最新运行管道将显示一个绿色复选标记,这意味着包含更新后的组件的代码已在该管道中运行:

警报的依赖项扫描检测视图的屏幕截图

严重性

GitHub 公告数据库提供了 CVSS 分数,然后通过以下指南将其转换为警报严重性“低”、“中”、“高”或“严重”:

CVSS 分数 严重性
1.0 < 分数 < 4.0
4.0 < 分数 < 7.0 中型
7.0 < 分数 < 9.0
分数 >= 9.0 严重

查找详细信息

查找详细信息下通常包含两个部分:易受攻击的包和根依赖项。 易受攻击的包是潜在易受攻击的组件。 根依赖项部分包含对导致漏洞的依赖项链负责的顶级组件。

如果将易受攻击的包仅作为直接依赖项引用,则你只会看到“易受攻击的包”部分。

如果将易受攻击的包作为直接依赖项和可传递依赖项引用,则该包将同时显示在“易受攻击的包”和“根依赖项”部分。

如果将易受攻击的包仅作为可传递依赖项引用,则该包显示在“易受攻击的包”部分,引用易受攻击的包的根依赖项显示在“根依赖项”部分。

管理依赖项扫描警报

查看存储库的警报

任何拥有存储库参与者权限的人都可以在Repos>Advanced Security中查看存储库所有警报的摘要。

默认情况下,警报页显示存储库默认分支的依赖项扫描结果。

警报的状态反映默认分支和最新运行管道的状态,即使该警报存在于其他分支和管道上也是如此。

修复依赖项扫描警报

直接依赖项是存储库中包含的组件。 可传递或间接依赖项是由直接依赖项使用的组件。 无论漏洞是在直接依赖项还是可传递依赖项中发现的,项目仍然易受攻击。

修复易受攻击的可传递依赖项通常采用显式替代用于每个已识别直接依赖项的易受攻击组件的版本的形式。 根依赖项将其对易受攻击组件的使用升级到安全版本后,就可以升级每个根依赖项,而不是多个单独的替代。

更新 Yarn/Npm 的依赖项

假设此包有两个漏洞。 其中一个漏洞是针对 axios 的直接依赖项,而另一个是针对 acorn 的可传递依赖项(也称为间接依赖项或依赖项的依赖项)。

{
 "name": "my-package",
 "version": "1.0.0",
 "dependencies": {
   "axios": "0.18.0",
   "eslint": "5.16.0",
 }
}

axios 的当前版本存在拒绝服务 (DoS) 漏洞,建议更新到 v0.18.1 或更高版本。 因为它是直接依赖项,因此你可以控制所使用的 axios 版本;只需更新引入的 axios 的版本。 更新后的 package.json 外观如下所示:

{
  "name": "my-package",
  "version": "1.0.0",
  "dependencies": {
    "axios": "0.19.2",
    "eslint": "5.16.0",
  }
}

现在,显示的 package.json 中的 eslint 版本取决于 acorn(正则表达式拒绝服务 (ReDoS) 漏洞)的版本,建议更新到版本 5.7.4, 6.4.1, 7.1.1 或更高版本。 如果从依赖项扫描工具收到警报,它应会告诉你需要易受攻击的依赖项的根依赖项。

Yarn

如果你使用 Yarn,可以使用 yarn why 来查找完整的依赖项链。

> $ yarn why acorn
 yarn why v1.22.4
 [1/4] Why do we have the module "acorn"...?
 [2/4] Initialising dependency graph...
 [3/4] Finding dependency...
 [4/4] Calculating file sizes...
 => Found "acorn@6.4.0"
 info Reasons this module exists
   - "eslint#espree" depends on it
   - Hoisted from "eslint#espree#acorn"
 info Disk size without dependencies: "1.09MB"
 info Disk size with unique dependencies: "1.09MB"
 info Disk size with transitive dependencies: "1.09MB"
 info Number of shared dependencies: 0
 Done in 0.30s.

完整的依赖项链为 eslint>espree>acorn。 了解了依赖项链后,就可以使用 Yarn 的另一项功能(选择性依赖项解析)来替代所使用的 acorn 版本。

使用 package.json 中的解析字段来定义版本替代。 显示了三种替代包的不同方法,按从最差到最佳的顺序排列:

{
  "name": "yarn-resolutions",
  "version": "1.0.0",
  "license": "MIT",
  "dependencies": {
    "axios": "0.19.2",
    "eslint": "5.16.0"
  },
  "resolutions": {
    // DO NOT USE!
    "**/acorn": "6.4.1",
    // BETTER
    "eslint/**/acorn": "6.4.1",
    // BEST
    "eslint/espree/acorn": "6.4.1"
  }
}

使用 **/acorn 模式将替代跨所有依赖项对 acorn 包的所有使用。 这样做不安全,并且会在运行时中断。 因此,已在 Yarn v2 中将其移除。

使用 eslint/**/acorn 模式将替代 eslint 包下的 acorn 包及其所依赖的任何包中的所有用法。 它比替代所有依赖项的包更安全,但如果包的依赖项关系图很大,它仍然存在一些风险。 如果有许多子包使用易受攻击的包并且为单个子包定义替代不切实际,则建议使用此模式。

使用模式 eslint/espree/acorn 仅替代 eslint 包中 espree 包中 acorn 的使用。 它专门面向易受攻击的依赖项链,并且是替代包版本的建议方法。

npm

如果使用的是 npm 8.3 或更高版本,则可以使用 package.json 中的替代字段

如果需要对可传递依赖项进行特定更改,请添加替代。 例如,你可能需要添加替代以替换具有已知安全性问题的依赖项的版本,将现有的依赖项替换为分支,或者确保在所有位置都使用相同版本的包。

{
  "name": "npm-overrides",
  "version": "1.0.0",
  "license": "MIT",
  "dependencies": {
    "axios": "0.19.2",
    "eslint": "5.16.0"
  },
   "overrides":{
       "eslint": {
        "espree": {
          "acorn": "6.4.1"
        }
    }
   }
}

显示的替代示例演示了 npm 表达“仅替代 eslint 包中 espree 包中的 acorn 的使用”的方式。它专门面向易受攻击的依赖项链,并且是替代包版本的建议方法。 替代是 npm 的原生功能。 它提供了一种用于将依赖关系树中的包替换为另一个版本或完全替换为另一个包的方法。

设置替代后,必须删除 package-lock.jsonnode_modules,并再次运行 npm install

不能为直接依赖的包设置替代,除非依赖项和替代本身具有完全相同的规范。例如,假设 axios: "0.18.0" 存在漏洞,我们希望升级到 axios: "0.19.2"。 直接更改依赖项版本,而不是使用替代。

{
  "name": "npm-overrides",
  "version": "1.0.0",
  "license": "MIT",
  "dependencies": {
    "axios": "0.18.0"
  },
  "overrides": {
    // BAD, will throw an EOVERRIDE error
    // "axios": "0.19.2",
  }
}

在不设置替代的情况下更新依赖项的版本:

{
  "name": "npm-overrides",
  "version": "1.0.0",
  "license": "MIT",
  "dependencies": {
    "axios": "0.19.2"
  }
}

更新 Maven 的依赖项

依赖项解析机制并不像 Yarn 中使用的那样复杂。 因此,项目中只能有一个依赖项的单一版本。 为了解决此问题,Maven 使用了“选择最近项”算法。 也就是说,它使用依赖关系树中与项目最接近的依赖项的版本。

例如,你有以下依赖项关系图:

your-project --- A:1.0.0 --- B:2.0.0
      \
       \__ B:1.0.0

your-project 依赖于 A:1.0.0,后者又依赖于 B:2.0.0,但项目也具有对 B:1.0.0 的直接依赖项。 因此,依赖项关系图中有两个不同版本的依赖项 B,但依赖项 B 的 1.0.0 版本优先,因为它“最接近”你的项目。

在某些情况下,如果版本兼容,则此方案起作用。 但是,如果 A:1.0.0 依赖于 B 的某些功能,,而这些功能仅在版本 2.0.0 中可用,则此行为不起作用。 在最坏的情况下,此项目可能仍然可以编译,但在运行时却失败。

让我们来看一个实际的示例。

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.microsoft.customer360</groupId>
  <artifactId>maven-dependencies</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>maven-dependencies</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>com.fasterxml.jackson.jaxrs</groupId>
      <artifactId>jackson-jaxrs-json-provider</artifactId>
      <version>2.10.3</version>
    </dependency>
</project>

假设所依赖的 com.fasterxml.jackson.core:jackson-databind 版本依赖于具有不受信任数据反序列化漏洞com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider 版本。

你可以使用 Maven 依赖项插件验证此依赖项。 在这种情况下,你将运行 mvn dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind 并获得以下输出:

> $ mvn dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind
 [INFO] Scanning for projects...
 [INFO]
 [INFO] ------------< com.microsoft.customer360:maven-dependencies >------------
 [INFO] Building maven-dependencies 1.0-SNAPSHOT
 [INFO] --------------------------------[ jar ]---------------------------------
 [INFO]
 [INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ maven-dependencies ---
 [INFO] com.microsoft.customer360:maven-dependencies:jar:1.0-SNAPSHOT
 [INFO] \- com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:jar:2.10.3:compile
 [INFO]    \- com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:jar:2.10.3:compile
 [INFO]       \- com.fasterxml.jackson.core:jackson-databind:jar:2.10.3:compile
 [INFO] ------------------------------------------------------------------------
 [INFO] BUILD SUCCESS
 [INFO] ------------------------------------------------------------------------
 [INFO] Total time:  0.928 s
 [INFO] Finished at: 2020-04-27T14:30:55+02:00
 [INFO] ------------------------------------------------------------------------

首先,检查是否有新版本的 com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider 不依赖于易受攻击的 com.fasterxml.jackson.core:jackson-databind 版本。 如果是这样,可以升级 com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider 并到此为止。 如果不是这样,请替代 com.fasterxml.jackson.core:jackson-databind 的版本。

如代码片段中所示,当使用“选择最近项”Maven 时,解决方案是向 com.fasterxml.jackson.core:jackson-databind 添加直接依赖项以修复漏洞。

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.microsoft.customer360</groupId>
  <artifactId>maven-dependencies</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>maven-dependencies</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>com.fasterxml.jackson.jaxrs</groupId>
      <artifactId>jackson-jaxrs-json-provider</artifactId>
      <version>2.10.3</version>
    </dependency>
    <!-- Dependency resolutions -->
    <!-- jackson-jaxrs-json-provider -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.10.4</version>
    </dependency>
  </dependencies>
</project>

可通过再次运行 mvn dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind 来验证该解决方案是否有效。

$ mvn dependency:tree -Dincludes=com.fasterxml.jackson.core:jackson-databind
[INFO] Scanning for projects...
[INFO]
[INFO] ------------< com.microsoft.customer360:maven-dependencies >------------
[INFO] Building maven-dependencies 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ maven-dependencies ---
[INFO] com.microsoft.customer360:maven-dependencies:jar:1.0-SNAPSHOT
[INFO] \- com.fasterxml.jackson.core:jackson-databind:jar:2.9.10.4:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.827 s
[INFO] Finished at: 2020-04-27T14:32:42+02:00
[INFO] ------------------------------------------------------------------------

建议在依赖项解析旁边添加注释,这样后面的人就知道这里存在依赖项的原因。 一旦根依赖项使用了新版本,就可以将其移除,否则会积累依赖项。

在实际项目中,将依赖项添加到尽可能高的链上。 例如,可以在父 POM 文件(而不是在每个项目 POM 文件)中添加解析。

更新 NuGet 的依赖项

NuGet 中使用的依赖项解析算法与 Maven 类似,因为只能使用单一版本的依赖项。 但是,NuGet 不会固定依赖项版本。

例如,如果有依赖项 <PackageReference Include="A" Version="1.2.3" />,你可能希望此包等同于 = 1.2.3,但它实际上意味着 >= 1.2.3。 为了固定准确的版本,你应该使用 Version="[1.2.3]"。 有关详细信息,请参阅 NuGet 版本范围文档

除了默认范围行为外,NuGet 还将还原最低适用版本以满足某个范围。 此行为意味着,在许多情况下,你必须定义一个范围。

让我们来看看以下示例项目,该项目具有对 Microsoft.AspNetCore.App 的依赖项:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <RootNamespace>NuGet.Dependencies</RootNamespace>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.14" />
  </ItemGroup>
</Project>

它取决于易受远程代码执行 (RCE) 漏洞攻击的 Microsoft.AspNetCore.Http.Connections 的版本。

首先,你应该检查是否存在依赖于较新版本的 Microsoft.AspNetCore.Http.ConnectionsMicrosoft.AspNetCore.App 的更新后的版本。 如果是这样,可以升级 Microsoft.AspNetCore.App 并到此为止。 如果不是这样,必须替代它所依赖的 Microsoft.AspNetCore.Http.Connections 版本。

NuGet 没有内置的 yarn why 或 mvn dependency:tree 的等效项,因此访问 nuget.org 通常是查看依赖关系树的最简单方法。如果你访问 Microsoft.AspNetCore.App 的 NuGet 页面,就会发现它依赖于 Microsoft.AspNetCore.Http.Connectionsversion >= 1.0.4 && < 1.1.0。 或者,在 NuGet 版本范围内,具有代表性的语法是 [1.0.4,1.1.0)

Microsoft.AspNetCore.Http.Connections 中的 RCE 漏洞已在版本 1.0.15 中修复,因此你需要将版本范围替代为 [1.0.15, 1.1.0)

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <RootNamespace>NuGet.Dependencies</RootNamespace>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" Version="2.2.8" />
  </ItemGroup>

  <ItemGroup Label="Dependency Resolutions">
    <!-- Microsoft.AspNetCore.App -->
    <PackageReference Include="Microsoft.AspNetCore.Http.Connections" Version="[1.0.15,1.1.0)" />
  </ItemGroup>
</Project>

建议在依赖项解析旁边添加注释,这样后面的人就知道这里存在依赖项的原因。 一旦根依赖项使用了新版本,就可以将其移除。 否则,将积累依赖项。

如果没有可用的修补程序,该怎么办?

如果没有可用的已知修补程序,以下选项可用作其他修正方法,直到升级后的组件可用:

  • 停止使用组件并将其从代码中移除,在安装了依赖项扫描任务的下一个生成后,将检测到此移除
  • 为组件本身提供修补程序。 如果组织拥有关于开源贡献的特定指南,请遵循这些指南。
  • 正在消除警报。 但是,没有已知修补程序的警报仍可能对组织构成安全威胁。 建议不要仅仅因为没有已知的修补程序而消除警报。

消除依赖项扫描警报

若要消除 Advanced Security 中的警报,你需要适当的权限。 默认情况下,只有项目管理员才能消除 Advanced Security 警报。

若要消除警报,请执行以下操作:

  1. 转到要关闭的警报并选择该警报
  2. 选择关闭警报下拉列表
  3. 如果尚未选择该选项,请选择接受的风险误报作为关闭原因
  4. 注释文本框中添加可选注释
  5. 选择关闭以提交并关闭警报
  6. 警报状态从“打开”更改为“已关闭”并显示消除原因

显示如何消除依赖项扫描警报的屏幕截图

此操作仅消除所选分支的警报。 可能包含相同漏洞的其他分支将保持活动状态,直到执行其他操作为止。 之前已消除的任何警报都可以手动重新打开。

对依赖项扫描进行故障排除

依赖项扫描任务超时

依赖项扫描任务在超时前将运行的默认时间为 300 秒(5 分钟)。 如果任务在完成之前超时,你可以设置一个管道变量 DependencyScanning.Timeout,它需要一个表示秒的整数,例如 DependencyScanning.Timeout: 600。 在默认的 300 秒超时以下的任何设置都不会生效。

要使用此变量,需添加 DependencyScanning.Timeout 作为管道变量:

- task: AdvancedSecurity-Dependency-Scanning@1
- env:
    DependencyScanning.Timeout: 600

生成任务的破窗方案

如果依赖项扫描生成任务正在阻止管道的成功执行,而你需要紧急跳过生成任务,则可以设置管道变量 DependencyScanning.Skip: true

依赖项扫描任务权限

依赖项扫描生成任务使用管道标识来调用高级安全 REST API。 默认情况下,同一项目中的管道有权提取警报。 如果从生成服务帐户中删除这些权限,或者拥有自定义设置(例如,托管在与存储库不同的项目中的管道),则必须手动授予这些权限。

向管道中使用的生成服务帐户授予 Advanced Security: View Alerts 权限。对于项目范围的管道,该帐户为 [Project Name] Build Service ([Organization Name]);对于集合范围的管道,该帐户为 Project Collection Build Service ([Organization Name])