传统LVFS S3存储桶接管和CVE-2020-10759转发签名验证绕过

2020-06-10 10:08:13

PermalLink GitHub是5000多万开发人员的家园,他们一起工作,共同托管和审查代码、管理项目和构建软件。

报名。

属于LVFS(Linux供应商固件服务)的悬挂S3存储桶。悬挂存储桶被fwupd版本<;1.0.6(2018年发布)的默认配置引用。

综上所述,这些漏洞将允许声称拥有S3存储桶的攻击者向运行旧版fwupd的Linux台式机和服务器提供恶意固件更新。

绕过PGP验证存在于fwupd 1.4.0之前的版本中,以及fwupd从1.4.0开始使用的libjcat中。通过向后移植https://github.com/fwupd/fwupd/commit/21f2d12,,可以在1.4.0之前的fwupd中修复该漏洞,在1.4.0及更高版本的fwupd中,可以使用包含https://github.com/hughsie/libjcat/commit/839b89f的libjcat修复该漏洞。

尽管悬而未决的S3存储桶只影响具有旧配置的fwupd客户端,但这些客户端的安装基数很大。例如,Debian Stretch(目前是oldtable&34;)仍然发布了一个引用存储桶的版本。在26天的时间里,S3存储桶收到了来自500,000多个唯一IP地址的250多万个更新请求。

下图显示了每周每小时看到的平均更新请求数。

GNOME-SOFTWARE通常每天刷新一次fwupd数据,第一张图中的24小时周期性流量模式反映了这一点。第二张图中的每日更新请求数约为100,000(不包括周末),因此我估计至少有100,000个唯一的fwupd客户端到达悬而未决的S3存储桶。

尽管存在悬而未决的S3存储桶问题,签名绕过漏洞仍可被以下攻击者利用:

在公共fwupd元数据存储库(如LVFS)中处于特权位置;或。

在本地元数据镜像(如属于企业的镜像)中处于特权位置。

一些Linux发行版,如Red Hat Enterprise Linux(RHEL),据报道没有附带包含LVFS元数据存储库的配置。

fwupd不会自动为用户安装固件更新-用户必须手动接受更新。

固件安装只能使用在fwupd本身内实现的规定协议来完成。在固件安装期间,更新不可能执行自定义代码,例如专有闪存实用程序。但是,该漏洞确实能够向以超级用户身份运行的XMLparser和.cab文件解析器提供恶意输入。这些代码路径没有被彻底检查。

一些硬件设备执行自己的固件更新的片上验证(基于硬件供应商自己持有的密钥),这可以防止安装一些恶意固件映像。

2015年4月-发布fwupd 0.1.2;包括引入Pgp签名验证绕过漏洞(https://github.com/fwupd/fwupd/commit/36a8890))的提交

2016年8月-发布fwupd 0.7.3;包括指示客户端计算机从lvfsbucket S3存储桶(https://github.com/fwupd/fwupd/commit/96e1ea6))下载固件元数据的提交。

2018年3月-发布fwupd 1.0.6;包括指示客户端计算机离开lvfsbucket S3存储桶(https://github.com/fwupd/fwupd/commit/4ee0836))的提交。

2018年11月-报告显示,lvfsbucket S3存储桶返回未找到文件的404&34;个错误(https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=912414)。

2019年11月-这是LVFS项目声称删除S3存储桶的时候。

2020年4月-fwupd 1.4.0发布;包括从pgp签名转向jcat签名(https://github.com/fwupd/fwupd/commit/d5aab65))的提交。

2020年5月28日-fwupd项目收到悬挂式S3存储桶和签名绕过漏洞的通知。

2020年5月29日-lvfsbucket s3桶移交给fwupd项目保管。

2020年5月30日-就libgpgme中的边缘案例处理与GnuPG项目联系

2020年6月2日-GnuPG项目响应-这不会被认为是libgpgme中的错误。

2020年6月10日-对本建议进行了更新(供应商参考和GnuPG的文档更改)。

附录附录A-S3桶接管+绕过签名验证的理论攻击场景。

允许硬件供应商上传固件更新的安全门户。所有主要的Linux发行版都使用该站点来提供元数据工具,如fwupdmgr和GNOME软件。

固件更新文件(.cab文件)的URL,这些文件可以直接托管在LVFS上,也可以托管在第三方网站上。

元数据将发布为名为firware.xml.gz的文件。可以使用使用LVFS PGP密钥(0x48A6D80E4538BAC2)或签名包(PGP和PKCS7)生成的分离PGP签名来验证此文件,本建议稍后将对此进行说明。

一个简单的守护进程,允许会话软件更新本地计算机上的设备固件。它专为台式机设计,但也可用于手机和无头服务器。您可以使用GNOMESoftware之类的GUI软件管理器、命令行工具或System D-Businterface直接查看和应用更新。

检查来自存储库(如LVFS)的固件元数据的PGP(fwupd<;1.4.0)或jcat(fwupd&>=1.4.0)签名

通知用户元数据中描述的可用于其硬件的固件更新,例如BIOS或Thunderbolt控制器更新。

固件安装通过在fwupd内实现的有限数量的协议进行,例如DFU或FASTBOOT(https://fwupd.org/lvfs/docs/metainfo/protocol).。重要的是,单个固件更新不可能在用户机器上运行自定义代码,例如专有的闪存实用程序。

在LVFS内,固件供应商适用于LVFS,并被授予上载某些硬件供应商-id值(https://blogs.gnome.org/hughsie/2019/12/11/improving-the-security-model-of-the-lvfs/))的固件元数据的权限。

LVFS将所有供应商的元数据整理到一个文件(firware.xml.gz)中,该文件包括每个.cab文件的加密散列。

LVFS对此文件进行签名以生成分离的PGP签名(firware.xml.gz.asc)和jcat(PGP&;&;PKCS7)签名(firware.xml.gz.jcat)。

在客户端计算机上,客户端实用程序通过D-BUS将每个文件的文件描述符发送给Fwupd,以执行更新元数据操作(例如https://github.com/fwupd/fwupd/blob/1.4.1/libfwupd/fwupd-client.c#L1420-L1466)。

FWUPD检查分离的PGP(例如,https://github.com/fwupd/fwupd/blob/1.3.9/src/fu-keyring-gpg.c#L279-L316)或JCAT签名),这建立了信任锚。元数据包含各个固件更新的加密散列(例如,SHA),并且元数据作为一个整体由LVFS签名。此模式(包含单个校验和的已签名元数据)类似于APT模型(https://wiki.debian.org/SecureApt#Secure_apt_groundwork:_checksums)。

假设签名验证成功,fwupd使用AppStream组件规范(https://lvfs.readthedocs.io/en/latest/metainfo.html))的子集解析XML元数据

fwupd根据firware.xml.gz中的散列验证.cab文件的加密散列,将其解包,然后刷新固件。

Firmware.xml.gz描述各种硬件设备可用的更新,提供每个更新(.cab文件)的下载URL,并提供.cab文件的加密散列。

如果固件.cab文件的散列与firware.xml.gz中列出的散列匹配,并且固件.xml.gz的签名正确,则可以认为该.cab文件是可信的。

考虑这样一种情况:攻击者控制了firware.xml.gz的内容,并且可以对文件签名或绕过签名验证过程。攻击者可能会广告各种共享软件产品的恶意固件更新。如果用户选择安装恶意更新,攻击者可能会危害用户的硬件,破坏用户系统的完整性。

fwupd版本附带固件存储库的URL,如LVFS。这些URL应从其中下载有关固件更新的信息。

此更改发生在Commit https://github.com/fwupd/fwupd/commit/4ee0836(28(2018年2月),以允许LVFS项目从AmazonS3迁移到替代CDN。

据报道,某些Linux发行版(例如Red Hat Enterprise Linux)没有附带配置为使用LVFS元数据URL的fwupd。

2018年11月(https://github.com/fwupd/fwupd/issues/831))向该项目报告,从旧URL下载失败,出现404个错误。该项目的解决方案是让用户更新fwupd;或修改他们的发行版以支持修复。针对Debian Stretch的漏洞(目前是老版本)于2018年10月在https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=912414上开放,在撰写本文时尚未解决。

离开Amazon S3托管安排后,LVFS项目删除了lvfsbucket S3存储桶。LVFS项目在披露过程中通知我,S3存储桶在2019年11月被删除。

删除存储桶后,任何AmazonAWS客户都可以免费注册。然后,注册存储桶的用户将收到来自运行遗留fwupd客户端的用户的呼叫总部更新请求。

我在2020年5月注册了存储桶,并启用了存储桶上的S3访问日志记录(https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerLogs.html)。

在26天的时间里,S3存储桶收到了来自500,000多个唯一IP地址的250多万个更新请求。

请注意,在I控制S3存储桶期间,没有从存储桶发布任何内容。对于向其发出的每个请求,存储桶都返回";403禁止";。

要使用这些原语,我们将需要使用受信任的LVFS密钥对firware.xml.gz进行签名;否则,我们将需要一种方法来绕过签入fwupd的PGP签名。

悬挂的S3吊桶已经转移到LVFS项目。LVFS项目应保持对此存储桶的控制,直到不再在现场安装旧版本的fwupdale。在安全删除存储桶之前,不应删除该存储桶。

fwupd<;1.4.0中基于PGP的元数据签名验证存在漏洞,可以可靠地绕过。由任何密钥签名的任何未分离的签名都将使验证逻辑失效。

鉴于此漏洞,声称上述悬空S3存储桶或将自己置于fwupd客户端和元数据存储库之间的攻击者可能已经向fwupd客户端提供了签名的恶意固件更新信息。

该漏洞取决于分离的和非分离的PGP签名之间的差异。附录A提供了关于PGP签名类型的入门读物。

以fwupd 1.3.9中的签名验证码为例。

/*加载文件数据*/[snip-将元数据数据读入';data';]//[snip-将元数据签名读入';sig';]/*Verify*/rc=gpgme_op_ify(self>;ctx,sig,data,null);if(rc!=gpg_err_no_error){g_set_error(error,FWUPD_ERROR,FWUPD_ERROR。无法验证数据:%s";,gpgme_strerror(Rc);返回NULL;}/*验证结果*/RESULT=gpgME_OP_VERIFY_RESULT(Self>;CTX);IF(RESULT==NULL){g_set_error_cripal(ERROR,FWUPD_ERROR,FWUPD_ERROR_INTERNAL,";;libgpgme";没有结果记录);Return NULL;}/*Look。s!=null;s=s->;Next){g_debug(";返回签名指纹%s";,s-&>;fpr);如果(!fu_keyring_gpg_check_sign(s,error))return null;/*保存结果的密钥详细信息*/if(Gint64)s->;timeamp>;timeamp_newest){timeamp_newest=(Gint64)s->;timeamp;timeamp;g_string_assign(Authority_newest,s->;fpr);}}return Fu_keyring_result(g_object_new(Fu_type_。,TIMESTAMP_NEWEST,";AUTHORITY";,AUTHORITY_NEWEST->;字符串,NULL));}。

签名验证由gpgme_op_Verify()完成,gpgme_op_Verify()由libgpgme提供,并记录在https://www.gnupg.org/documentation/manuals/gpgme/Verify.html中。

结果中的签名被循环。对于每个签名,如果fu_keyring_gpg_check_sign()不满意(例如,公钥未知;签名不正确;签名密钥过期),则验证失败。

提供一些数据(XML)和sig(Asc)数据,它们:使gpgme_op_verify_result()(上面的步骤2)成功并返回零签名的结果。

由于没有签名,因此将完全跳过for循环(上面的步骤3),因此不会调用fu_keyring_gpg_check_sign(),这允许我们跳过细粒度签名检查

由于GPGme(GnuPG MadeEasy)中的边缘情况,当GPGme(GnuPG MadeEasy)试图像验证分离签名一样验证正常签名时,我们能够执行此攻击。

显式检查结果-&>签名是否是非零(即,gpgme_op_ify()至少观察到一个签名);和/或。

反转单个签名验证逻辑。目前,代码进行检查以确保没有错误的签名。相反,更安全的做法是确保至少有一个好的签名。这是Flatpak https://github.com/flatpak/flatpak/blob/1.7.2/common/flatpak-oci-registry.c#L2073-L2085)采取的方法。

GPGme由GnuPG项目提供。它是一个包装gpg命令行实用程序并向软件开发人员公开GnuPG功能的库。

当它验证分离的PGP签名时(fwupd通常是这种情况):

在验证普通或明文PGP签名时,纯文本应该是一个可写数据对象,它将包含验证成功后的明文";

虽然fwupd通常使用gpgme来验证分离的PGP签名,但在恶意情况下不能保证这一点-攻击者可以为签名文件提供任何数据。

任何作为SIGNED_TEXT的数据(这是对gpgme的一个提示,即应该分离sig)

如果赋予的是fwupd不知道签名密钥的分离签名,则验证码会正确拒绝签名,如下所示:

/*Verify*/rc=gpgme_op_Verify(self>;ctx,sig,data,null);IF(rc!=GPG_ERR_NO_ERROR){g_set_error(ERROR,FWUPD_ERROR,FWUPD_ERROR_INTERNAL,";,gpgme_strerror(Rc));返回NULL;}/*验证结果*/result=gpgme_op。IF(RESULT==NULL){g_SET_ERROR_TEXAL(ERROR,FWUPD_ERROR,FWUPD_ERROR_INTERNAL,";无libgpgme";的结果记录);return null;}/*[+]调试跟踪gdb-peda$print*result$1={签名=0x5593c66668b0,file_name=0x0,is_mime=0x0,_unuse=0x0}g.。签名$2={NEXT=0x0,摘要=GPGME_SIGSUM_KEY_MISSING,fPR=0x5593c666d290";88C202B687A57ED3D0109BAFAC92351AF277A464";,Status=0x9,NOTIONS=0x0,TIMESTAMP=0x5ecf199e,EXP_TIMESTAMP=0x0,WRONG_KEY_USAGE=0x0,PKA_TRUST=0x0,CHAIN_MODEL=0x0,IS_DE_VS=0x0,_UNUSED=0x0,VALIDATION=GPGME_VALITY_UNKNOWN,VALIDATION_REASON=0x0,pubkey_algo=GPGME_PK_RSA,HASH_ALGO=。key=0x0}*//*查看每个签名*/(s=result->;签名;s!=null;s=s-&>;Next){g_debug(";返回签名指纹%s";,s-&>;fpr);如果(!fu_keyring_gpg_check_sign(s,error))return null;/*保存结果的密钥详细信息*/if(Gint64)s->;timeamp>;timeamp_newest){timeamp_newest=(Gint64)s->;timeamp;timeamp;g_string_assign(Authority_newest,s->;fpr);}}return Fu_keyring_result(g_object_new(Fu_type_。,TIMESTAMP_NEWEST,";AUTHORITY";,AUTHORITY_NEWEST->;字符串,NULL));}。

调用gpgme_op_verify_result()返回一个签名的列表,我们可以看到该签名的摘要是GPGME_SIGSUM_KEY_MISSING,这将使fu_keyring_gpg_check_sign()在细粒度检查forloop中不愉快。

将这与如果我们给出一个fwupd仍然不知道签名密钥的非分离签名所发生的情况进行比较。验证码错误接受签名,如下所示:

/*Verify*/rc=gpgme_op_Verify(self>;ctx,sig,data,null);IF(rc!=GPG_ERR_NO_ERROR){g_set_error(ERROR,FWUPD_ERROR,FWUPD_ERROR_INTERNAL,";,gpgme_strerror(Rc));返回NULL;}/*验证结果*/result=gpgme_op。IF(RESULT==NULL){g_SET_ERROR_TEXAL(ERROR,FWUPD_ERROR,FWUPD_ERROR_INTERNAL,";无libgpgme";的结果记录);return null;}/*[+]调试跟踪gdb-peda$print*result$3={签名=0x0,文件名=0x0,is_mime=0x0,_unuse=0x0}*//*查看每个签名*。s=s->;Next){g_debug(";返回签名指纹%s";,s->;fpr);如果(!fu_keyring_gpg_check_sign(s,error))return null;/*保存结果的密钥详细信息*/if(Gint64)s->;timeamp>;timeamp_newest){timeamp_newest=(Gint64)s->;timeamp;timeamp;g_string_assign(Authority_newest,s->;fpr);}}return Fu_keyring_result(g_object_new(Fu_type_。,TIMESTAMP_NEWEST,";AUTHORITY";,AUTHORITY_NEWEST->;字符串,NULL));}。

这将导致忽略细粒度的for循环检查,因此根本不会调用fu_keyring_gpg_check_sign()。验证函数将错误地返回成功结果。

顺便说一句,在Fwupd项目的公开过程中,项目注意到https://github.com/fwupd/fwupd/commit/f69a4810中添加的回滚/重放保护应该可以阻止这种攻击。在零签名的情况下(例如我们可以触发的情况),验证过程将向调用函数返回默认时间戳值0,并且调用函数应该将该值作为arollback尝试来拒绝。

然而,对于验证函数返回时间戳为0的情况,在https://github.com/fwupd/fwupd/commit/f69a4810#diff-300ca38c94199c9239dc835920e466ecR1550there是一个特定的分割。这使此特定情况的回滚保护失效。

严格来说,这并不是回滚保护中的漏洞,撇开签名验证绕过不谈,攻击者尝试回滚攻击时,将会重播LVFS签名的旧元数据。此重放签名将始终具有与其关联的时间戳,因此攻击者应该不能触发时间戳为0的恶意回滚攻击。

此反回滚错误已通过披露过程中的https://github.com/fwupd/fwupd/pull/2150during修复。它破坏了本建议中描述的利用漏洞,并可能破坏任何针对此漏洞的利用。但是,此反回滚修复程序不应被视为绕过漏洞的根本原因修复程序。

说得非常清楚--这个概念证明从未使用Real lvfsbucket执行过。这是在脱机环境中通过调整fwupd配置文件完成的。

通过将fwupd配置为指向您控制的服务器,可以在实验室中执行以下概念验证。我。

..