[목차]
1. Sandbox란?

2. SECCOMP란
    2-1. SECCOMP 모드
    	2-1-1. STRICT_MODE
        2-1-2. FILTER_MODE
    2-2. 코드 적용 방법
        2-2-1. seccomp 라이브러리 사용
        2-2-2. BPF문법을 사용

3. seccomp-tools

 

1. Sandbox란

  • 외부의 공격으로부터 시스템을 보호하기 위해 설계된 기법
  • 애플리케이션이 꼭 필요한 시스템 콜만 실행하게 하거나, 파일의 접근만을 허용하게 하여 
      공격을 어렵게 하기 위한 보안 메커니즘 중 하나

 

(2가지 방법)

  • Allow List : 전체를 차단한 후 원하는 시스템 콜만 허용
  • Deny List : 전체를 허용한 후 차단할 시스템 콜만 선택

 

(sandbox에서 주의점)

- 각 애플리케이션은 실행 목적과 기능이 천차만별이기 때문에 정확한 이해가 있는 개발자가 샌드박스를 직접 명시해야한다.
- if) 샌드박스를 적용 시에 애플리케이션에 대한 기능과 보안 관련 지식이 부족하다면?

=> 서비스의 접근성을 과하게 해치거나 일부 기능이 정상적으로 실행되지 않거나, 우회가능

 

 

2. SECCOMP이란?

(SECure COMputing mode)

  • 리눅스 커널에서 샌드박스 메커니즘을 제공하는 컴퓨터 보안 기능
  • 이 기능을 사용시 애플리케이션에서 불필요한 시스템 콜의 호출을 방지가능

(SECCOMP 구성 코드)

int __secure_computing(const struct seccomp_data *sd) {
  int mode = current->seccomp.mode;
  int this_syscall;
  ... 
  this_syscall = sd ? sd->nr : syscall_get_nr(current, task_pt_regs(current));
  switch (mode) {
    case SECCOMP_MODE_STRICT:
      __secure_computing_strict(this_syscall); /* may call do_exit */
      return 0;
    case SECCOMP_MODE_FILTER:
      return __seccomp_filter(this_syscall, sd, false);
    ...
  }
}

 

[SECCOMP이 생긴 이유]

- 애플리케이션에서 외부의 시스템 명령어를 실행하지 않는다면 execve와 같은 시스템 콜이 실행될 필요가 없다.
- 보통 이 시스템 콜은 공격시 임의 명령어를 실행하기 위해 사용된다.
- 따라서 SECCOMP 기능을 설정하면 이런 시스템 콜은 허용되지 않아서 즉시 종료되어 
애플리케이션이 취약점이 존재해도 외부의 공격으로부터 피해를 최소화 가능하다.

 

(SECCOMP mode)

  1. STRICT_MODE
    - read, write, exit, sigreturn 시스템 콜의 호출만을 허용
  2. FILTER_MODE
    - 원하는 시스템 콜의 호출을 허용하거나 거부할 수 있음

 

(SECCOMP 코드 적용 방법)

  • seccomp 라이브러리 함수를 이용한 방법
  • 필터링에 주로 쓰이는 BPF문법을 적용하는 방법
    <BPF - Berkeley Packet Filter>

 

(적용 예시)

// Name: strict_mode.c
// Compile: gcc -o strict_mode strict_mode.c
#include <fcntl.h>
#include <linux/seccomp.h>
#include <sys/prctl.h>
#include <unistd.h>

void init_filter() { prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT); }

int main() {
  char buf[256];
  int fd = 0;
  
  init_filter();
  
  write(1, "OPEN!\n", 6);
  
  fd = open("/bin/sh", O_RDONLY);
  write(1, "READ!\n", 6);
  
  read(fd, buf, sizeof(buf) - 1);
  write(1, buf, sizeof(buf));
  
  return 0;
}
root@74de726ce0af:/home/user/study# ./strict_mode 
OPEN
Killed

=> 허용되지 않는 open 시스템콜이 실행되었기 때문에 "OPEN!" 출력이후 프로그램이 종료

 

2-1. SECCOMP 모드

2-1-1. STRICT_MODE

: read, write, exit, sigreturn 시스템 콜의 호출만을 허용하여 
이외에 시스템 콜 호출 요청이 들어오면 SIGKILL 시그널을 발생하고 프로그램을 종료

 

[주의점]
해당 mode는 매우 적은 시스템 콜만을 허용하여 
다양한 기능을 수행하는 애플리케이션에서 적용할 수 없음

 

(동작 순서)

  • 애플리케이션에서 시스템 콜이 호출
  • __secure_computing함수에 먼저 진입
  • 해당 함수에서 전달된 시스템 콜 번호가 model_syscalls, model_syscalls_32에 
      미리 정의된 번호와 일치하는지 검사
  • 일치하지 않는다면 SIGKILL시그널을 전달하고 SECCOMP_RET_KILL을 반환
    <model_syscall배열 - 시스템 콜의 번호를 저장하고 있는 변수>

 

2-1-2. FILTER_MODE

  • 원하는 시스템 콜의 호출을 허용하거나 거부 가능
  • 애플리케이션 기능에 맞게 유연하게 시스템 콜의 실행을 허용, 거부 가능

 

(알아야하는 SECCOMP에서 제공하는 함수)

 

## 2-2. 코드 적용 방법

### 2-2-1. SECCOMP 라이브러리 사용

 

(ALLOW_LIST)

// Name: libseccomp_alist.c
// Complie: gcc -o libseccomp_alist libseccomp_alist.c -lseccomp
#include <fcntl.h>
#include <seccomp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <unistd.h>

void sandbox() {
  scmp_filter_ctx ctx;
  ctx = seccomp_init(SCMP_ACT_KILL);
  if (ctx == NULL){
    printf("seccomp error\n");
    exit(0);
  }

  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
  seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0);
  seccomp_load(ctx);
}

int banned() {fork();} //

int main(int argc, char *argv[]){
  char buf[256];
  int fd;

  memset(buf, 0, sizeof(buf));
  sandbox();

  if(argc < 2){ // ????
    banned();
  }

  fd = open("/bin/sh", O_RDONLY);
  read(fd, buf, sizeof(buf) -1);
  write(1, buf, sizeof(buf));

}
  • "SCMP_ACT_KILL"을 통해 모든 시스템 콜의 호출을 차단
  • seccomp_rule_add함수의 3번째 인자로 허용할 시스템콜 추가

=> 가변인자로 값을 전달하지 않으면 fork함수가 실행되어 프로그램 종료

 

 

(DENY_LIST)

// Name: libseccomp_dlist.c
// Compile: gcc -o libseccomp_dlist libseccomp_dlist.c -lseccomp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <fcntl.h>
#include <sys/prctl.h>
#include <seccomp.h>

void sandbox(){
    scmp_filter_ctx ctx;
    ctx = seccomp_init(SCMP_ACT_ALLOW); // 모든 시스템콜을 허용
    if(ctx == NULL){
        exit(0);
    }

    // Deny규칙 추가
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(open), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(openat), 0);

    seccomp_load(ctx);
}

int main(int argc, char *argv[]){
    char buf[256];
    int fd;
    
    memset(buf, 0, sizeof(buf));
    sandbox();

    fd = open("/bin/sh", O_RDONLY);
    read(fd, buf, sizeof(buf)-1);
    write(1, buf, sizeof(buf));
}
  • "SCMP_ACT_ALL"을 통해 모든 시스템 콜의 호출을 허용
  • seccomp_rule_add함수의 3번째 인자로 차단할 시스템콜 추가

 

2-2-2. BPF문법을 사용

: 라이브러리 함수를 통해 규칙을 정의한 것과 같이 BPF를 사용해서 
특정 시스템 콜 호출시에 어떻게 처리할지 명령어를 통해 구현할 수 있음

 

BPF란?
커널에서 지원하는 Virtual Machine(VM)으로, 
본래에는 네트워크 패킷을 분석하고 필터링하는 목적으로 사용
=> 임의 데이터를 비교하고, 결과에 따라 특정 구문으로 분기하는 명령어를 제공
<VM인만큼 다양한 명령어와 타입이 존재>

 

(SECCOMP을 적용하는데 알아야하는 명령어)

 

BPF Macro

(1) BPF_Macro

BPF_STMT(opcode, operand)

oprand에 해당하는 값을 명시한 opcode로 값을 가져온다.
< opcode - 인자로 전달된 값에서 몇번째 인덱스에서 몇바이트를 가져올 것인지를 지정가능 >

 

(2) BPF_JUMP

BPF_JUMP(opcode, operand, true_offset, false_offset)
  • BPF_STMT매크로를 통해 저장한 값과 oprand를 opcode에 정의한 코드로 비교
  • 비교 결과에 따라 특정 오프셋으로 분기

 

(ALLOW LIST)

ex)

BPF를 통해 지정한 시스템 콜의 호출만을 허용하는 예제 코드

// Name: secbpf_alist.c
// Compile: gcc -o secbpf_alist secbpf_alist.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/mman.h>
#include <sys/prctl.h>
#include <stddef.h>
#include <fcntl.h>

#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h> // 왜 따로 쓰지?

#define ALLOW_SYSCALL(name)                               \
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_##name, 0, 1), \
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW)

#define KILL_PROCESS BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL)

#define syscall_nr (offsetof(struct seccomp_data, nr))

#define arch_nr (offsetof(struct seccomp_data, arch))

/* architecture x86_64 */
#define ARCH_NR AUDIT_ARCH_X86_64

int sandbox() {
  struct sock_filter filter[] = {
      /* Validate architecture. */
      BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_nr),
      BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARCH_NR, 1, 0),
      BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL),

      /* Get system call number. */
      BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),

      /* List allowed syscalls. */
      ALLOW_SYSCALL(rt_sigreturn),
      ALLOW_SYSCALL(open),
      ALLOW_SYSCALL(openat),
      ALLOW_SYSCALL(read),
      ALLOW_SYSCALL(write),
      ALLOW_SYSCALL(exit_group), // 그냥 exit가 아니라 exit_group?
      KILL_PROCESS,
  };
  struct sock_fprog prog = {
      .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
      .filter = filter,
  };
  if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
    perror("prctl(PR_SET_NO_NEW_PRIVS)\n");
    return -1;
  }
  if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
    perror("Seccomp filter error\n");
    return -1;
  }
  return 0;
}
void banned() { fork(); }
int main(int argc, char* argv[]) {
  char buf[256];
  int fd;
  memset(buf, 0, sizeof(buf));
  sandbox();
  if (argc < 2) {
    banned();
  }
  fd = open("/bin/sh", O_RDONLY);
  read(fd, buf, sizeof(buf) - 1);
  write(1, buf, sizeof(buf));
  return 0;
}

=> sandbox함수 - filter 구조체에 BPF코드가 작성되어이있음

 

1 - 아키텍처 검사

=> 현재 아키텍처가 x86_64가 아니라면 프로그램 종료

 

2 - 시스템 콜 검사

  • 호출된 시스템 콜의 번호를 저장
  • 이후 ALLOW_SYSCALL매크로로 허용할 시스템콜 저장

 

(DENY_LIST)

ex)

BPF를 통해 지정한 시스템 콜을 호출하지 못하도록 하는 예제코드

// Name: secbpf_dlist.c
// Compile: gcc -o secbpf_dlist secbpf_dlist.c

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <unistd.h>

#include <sys/mman.h>
#include <sys/prctl.h>
#include <fcntl.h>

#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>

#define DENY_SYSCALL(name) \
    BPF_JUMP(BPF_RET + BPF_JEQ + BPF_K, __NR_##name, 0, 1), \
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL)

#define MAINTAIN_PROCESS \
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW)

#define syscall_nr \
    (offsetof(struct seccomp_data, nr))

#define arch_nr \
    (offsetof(struct seccomp_data, arch))

/* architecture x86_64 */
#define ARCH_NR AUDIT_ARCH_X86_64

int sandbox(){
    struct sock_filter filter[] = {
        /* Validate architecture */
        BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_nr),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARCH_NR, 1, 0),
        BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL),

        /* Get system call number */
        BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),

        /* List allowed syscalls */
        DENY_SYSCALL(open),
        DENY_SYSCALL(openat),
        MAINTAIN_PROCESS,
    };

    struct sock_fprog prog = {
        .len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
        .filter = filter,
    };

    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1){
        perror("prctl(PR_SET_NO_NEW_PRIVS)\n");
        return -1;
    }

    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1){
        perror("Seccomp filter error\n");
        return -1;
    }

    return 0;

}

int main(int argc, char* argv[]){
    char buf[256];
    int fd;

    memset(buf, 0, sizeof(buf));
    sandbox();

    fd = open("/bin/sh", O_RDONLY);
    read(fd, buf, sizeof(buf)-1);
    write(1, buf, sizeof(buf));
    return 0;
}

 

1 - 아키텍처 검사

=> 현재 아키텍처가 x86_64가 아니라면 프로그램 종료

 

2 - 시스템 콜 검사

  • 호출된 시스템 콜의 번호를 저장
  • 이후 DENY_SYSCALL매크로로 차단할 시스템콜 지정

 

3. seccomp-tools

SECCOMP가 적용된 바이너리의 분석을 도울뿐만 아니라
BPF 어셈블러/디스어셈블러를 제공하는 유용한 도구

 

(설치 방법)

$ sudo apt install gcc ruby-dev
$ gem install seccomp-tools

=> 관련 자세한 내용 및 사용법(https://github.com/david942j/seccomp-tools)

 

(사용 방법)

$ seccomp-tools dump [파일경로]

=> ALLOW_LIST - 허용하는 시스템 콜이 초록색으로 표시되어서 나옴
=> 추가적인 사용방법은 따로 찾아보고 정리해야할듯

 


## 참고링크

https://dreamhack.io/lecture/courses/263