보안 경보 및 모니터링의 일부인 Linux 프로세스 및 세션 모델

illustration-radar-security.png

Elastic 내에 제공된 Linux 프로세스 모델을 사용하면 사용자가 매우 표적화된 경보 규칙을 작성하고 Linux 서버와 데스크톱에서 정확히 무슨 일이 일어나고 있는지에 대한 더 심층적인 인사이트를 얻을 수 있습니다.

이 블로그에서는 Linux 워크로드가 표현되는 방식에서 핵심적인 부분을 차지하는 Linux 프로세스 모델의 배경을 설명합니다.

초기 POSIX 문서에서 setsid() 시스템 호출이 도입된 것으로 판단할 때 Linux는 1970년대 출시되어 1980년대 개념 세션으로 보강된 Unix 프로세스 모델을 따릅니다.

Linux 프로세스 모델은 프로그램이 실행되는 컴퓨터 워크로드를 기록하고 이러한 이벤트에 대응하는 규칙을 작성하는 데 유용한 추상화입니다. 경보, 규정 준수 및 위협 헌팅을 위해 누가 언제 어떤 서버에서 무엇을 했는지 명확하게 보여줍니다.

프로세스 생성, 권한 상승 및 수명 주기를 파악하면 애플리케이션 및 서비스가 구현되는 방식과 정상적인 프로그램 실행 패턴에 대한 깊은 인사이트를 얻을 수 있습니다. 정상적인 실행 패턴이 파악되면 비정상적인 실행 패턴이 발생할 때 경보를 보내도록 규칙을 작성할 수 있습니다.

상세한 프로세스 정보가 있으면 경보에 대해 매우 표적화된 규칙을 작성할 수 있으므로 오탐과 경보 피로가 줄어듭니다.  또한 Linux 세션을 다음 중 하나로 분류할 수도 있습니다.

    • 부팅 시 시작되는 자율 서비스(예: cron)
    • 원격 액세스를 제공하는 서비스(예: sshd)
    • 대화형(아마도 인간) 원격 액세스(예: SSH를 통해 시작된 bash 터미널)
    • 비대화형 원격 액세스(예: SSH를 통한 Ansible 설치 소프트웨어)

이러한 분류로 매우 정확한 규칙과 검토가 가능해집니다. 예를 들어 선택한 기간에 특정 서버의 모든 대화형 세션을 검토할 수 있습니다.

이 문서에서는 Linux 프로세스 모델이 어떻게 작동하며 워크로드 이벤트에 대한 경보 및 대응 규칙을 작성하는 데 어떤 도움을 주는지 설명합니다. Linux 프로세스 모델을 이해하는 것은 컨테이너와 이를 구성하는 네임스페이스 및 cgroup을 이해하는 데 필수적인 첫 번째 단계이기도 합니다.

프로세스 모델 캡처와 시스템 호출 로그

새 프로세스, 새 세션, 종료 프로세스 등의 관점에서 세션 모델에 대한 변경 사항을 캡처하는 것은 해당 변경 사항을 실행하는 데 사용되는 시스템 호출을 캡처하는 것보다 더 간단하고 명확합니다. Linux에는 약 400개의 시스템 호출이 있으며 일단 릴리즈되면 이러한 시스템 호출을 리팩토링하지 않습니다. 이 접근 방식은 안정적인 ABI(애플리케이션 바이너리 인터페이스)를 유지합니다. 즉, 수년 전에 Linux에서 실행되도록 컴파일된 프로그램이 소스 코드에서 다시 빌드하지 않고도 오늘날 Linux에서 계속 실행된다는 의미입니다.

기존 시스템 호출을 리팩토링하는 대신 기능이나 보안을 개선하기 위해 새로운 시스템 호출이 추가되었습니다(ABI 유지). 결론은 시스템 호출 및 해당 파라미터의 시간 순서 목록을 수행하는 논리적 작업에 매핑하려면 상당한 전문 지식이 필요하다는 것입니다. 또한 io_uring과 같은 최신 시스템 호출은 커널과 사용자 공간 간에 매핑된 메모리를 사용하여 추가 시스템 호출 없이 파일과 소켓을 읽고 쓸 수 있습니다.

이와 대조적으로 프로세스 모델은 안정적이지만(1970년대 이후 크게 변경되지 않음), 파일 액세스, 네트워킹 및 기타 논리 연산이 포함될 때 시스템에서 수행되는 작업을 여전히 포괄적으로 다루고 있습니다.

프로세스 형성: 부팅 후 첫 번째 프로세스는 init

Linux 커널이 시작되면 “init 프로세스”라는 특수 프로세스가 생성됩니다. 프로세스는 하나 이상의 프로그램의 실행을 구현합니다. init 프로세스는 항상 프로세스 ID(PID)가 1이며, 사용자 ID가 0(루트)인 상태로 실행됩니다. 대부분의 최신 Linux 배포판은 init 프로세스의 실행 프로그램으로 systemd를 사용합니다.

init의 작업은 데이터베이스, 웹 서버 및 원격 액세스 서비스(sshd 등)와 같은 구성된 서비스를 시작하는 것입니다. 이러한 서비스는 일반적으로 자체 세션 내에 캡슐화됩니다. 이는 각 서비스의 모든 프로세스를 단일 세션 ID(SID)로 그룹화하여 서비스 시작 및 중지를 간소화합니다.

SSH 프로토콜을 통해 sshd 서비스에 액세스하는 등의 원격 액세스는 액세스하는 사용자를 위한 새로운 Linux 세션을 생성합니다. 이 세션은 처음에 원격 사용자가 요청한 프로그램(주로 대화형 셸)을 실행하며 관련 프로세스는 모두 동일한 SID를 갖습니다.

프로세스를 생성하는 메커니즘

init 프로세스를 제외한 모든 프로세스에는 단일 부모 프로세스가 있습니다. 각 프로세스에는 부모 프로세스의 프로세스 ID인 PPID가 있습니다(init의 경우 0/부모 없음). 부모 프로세스가 자식 프로세스는 종료하지 않는 방식으로 종료되는 경우 부모 재지정이 발생할 수 있습니다.

부모 재지정은 대개 init를 새 부모로 선택하며, init에는 채택된 자식이 종료될 때 이를 정리하는 특수 코드가 있습니다. 이러한 채택 및 정리 코드가 없으면 고아 자식 프로세스는 ‘좀비’ 프로세스가 될 것입니다(농담이 아닙니다!). 이러한 자식은 부모가 종료 코드를 검사하기 위해 이를 수거할 때까지 서성거립니다. 이는 자식 프로그램이 작업을 성공적으로 완료했는지 여부를 나타내는 지표입니다.

‘컨테이너’, 특히 pid 네임스페이스의 출현으로, init 이외의 프로세스를 ‘하위 리퍼’(고아 프로세스를 채택하려는 프로세스)로 지정하는 기능이 필요했습니다. 일반적으로 하위 리퍼는 컨테이너의 첫 번째 프로세스입니다. 이는 컨테이너의 프로세스가 조상 pid 네임스페이스의 프로세스를 ‘볼 수’ 없기 때문에 필요한 작업입니다(즉, 부모가 조상 pid 네임스페이스에 있다면 해당 PPID 값이 의미가 없음).

자식 프로세스를 생성하기 위해 부모는 fork() 또는 clone() 시스템 호출을 통해 자신을 복제합니다. fork/clone 후 실행은 부모 자식 모두에서 즉시 계속되지만(vfork() 및 clone()의 CLONE_VFORK 옵션 무시) fork()/clone()의 반환 코드 값에 의해 다른 코드 경로를 따라 진행됩니다.

정확히 읽으셨습니다. fork()/clone() 시스템 호출 하나가 두 개의 서로 다른 프로세스에 반환 코드를 제공합니다! 부모는 반환 코드로 자식의 PID를 받고, 자식은 0을 수신하므로, 부모와 자식의 공유 코드가 해당 값을 기준으로 분기될 수 있습니다. 다중 스레드 부모와 효율성을 위한 쓰기 시 복사 메모리에는 약간의 복제 뉘앙스가 있으며 여기에서 자세히 설명할 필요가 있습니다. 자식 프로세스는 부모 및 열린 파일, 네트워크 소켓 및 제어 터미널(있는 경우)의 메모리 상태를 상속합니다.

일반적으로 부모 프로세스는 자식의 PID를 캡처하여 해당 수명 주기를 모니터링합니다(위의 수거 참조). 자식 프로세스의 동작은 자신을 복제한 프로그램에 따라 달라집니다(fork()의 반환 코드를 기반으로 따를 실행 경로를 제공합니다).

nginx와 같은 웹 서버는 자신을 복제하여 http 연결을 처리하기 위한 자식 프로세스를 생성할 수 있습니다. 이와 같은 경우, 자식 프로세스는 새 프로그램을 실행하지 않고 단순히 동일한 프로그램에서 다른 코드 경로를 실행하여 http 연결을 처리합니다. 복제나 포크의 반환 값은 자식에게 자신이 자식임을 알려주므로 이 코드 경로를 선택할 수 있다는 점을 기억하세요.

SSH 세션의 대화형 셸 프로세스(예: 제어 터미널이 있는 bash, sh, fish, zsh 등)는 명령이 입력될 때마다 자신을 복제합니다. 여전히 부모/셸의 코드 경로를 실행 중인 자식 프로세스는 해당 프로세스 내에서 다른 프로그램을 실행하기 위해 자식의 코드 경로가 execve() 시스템 호출 또는 이와 유사한 호출을 하기 전에 IO 리디렉션을 위한 파일 설명자를 설정하고, 프로세스 그룹을 설정하는 등 많은 작업을 수행합니다.

셸에 ls를 입력하면 셸이 포크되고, 위에서 설명한 설정이 셸/자식에 의해 수행된 다음, ls 프로그램이 실행되어(보통 /kr/usr/bin/ls 파일에서) 해당 프로세스의 내용을 ls의 머신 코드로 대체합니다. 셸 작업 제어 구현에 관한 이 문서는 셸 및 프로세스 그룹의 내부 작동에 대한 깊이 있는 인사이트를 제공합니다.

프로세스가 execve()를 두 번 이상 호출할 수 있으므로 워크로드 캡처 데이터 모델도 이를 처리해야 한다는 점에 유의해야 합니다. 이는 프로세스는 종료되기 전에 다양한 프로그램이 될 수 있음을 의미합니다. 선택적으로 하나의 프로그램이 뒤따르는 부모 프로세스 프로그램만 될 수 있는 것은 아닙니다. 셸에서 이 작업을 수행하는 방법은 셸 exec 기본 제공 명령을 참조하세요(즉, 동일한 프로세스에서 셸 프로그램을 다른 프로그램으로 교체).

프로세스에서 프로그램을 실행하는 또 다른 측면은 일부 열린 파일 설명자(close-on-exec로 표시된 것)가 새 프로그램을 실행하기 전에 닫힐 수 있는 반면, 다른 파일 설명자는 새 프로그램에서 계속 사용할 수 있다는 것입니다. 단일 fork()/clone() 호출은 두 프로세스, 즉 부모 프로세스와 자식 프로세스에 반환 코드를 제공한다는 점을 기억하세요. execve() 시스템 호출은 성공적인 execve()에 성공을 위한 반환 코드가 없다는 점에서도 이상한데, 그 이유는 새 프로그램 실행으로 이어지기 때문에 execve()가 실패할 때를 제외하고는 반환할 곳이 없기 때문입니다.

새 세션 생성

Linux는 현재 새 세션 리더가 되는 프로세스에 의해 호출되는 단일 시스템 호출인 setsid()를 사용하여 새 세션을 생성합니다. 이 시스템 호출은 해당 프로세스에서 다른 프로그램을 실행하기 전에 실행되는 복제된 자식 코드 경로의 일부인 경우가 많습니다(즉, 부모 프로세스의 코드에 의해 계획되고 포함됩니다). 세션 내의 모든 프로세스는 동일한 SID를 공유하며, 이는 세션 리더라고도 알려진 setsid()라는 프로세스의 PID와 동일합니다. 즉, 세션 리더는 SID와 일치하는 PID를 가진 프로세스입니다. 세션 리더 프로세스가 종료되면 직계 자식 프로세스 그룹이 종료됩니다.

새 프로세스 그룹 생성

Linux는 프로세스 그룹을 사용하여 세션 내에서 함께 작동하는 프로세스 그룹을 식별합니다. 이들은 모두 동일한 SID와 프로세스 그룹 ID(PGID)를 갖습니다. PGID는 프로세스 그룹 리더의 PID입니다. 프로세스 그룹 리더에게는 특별한 상태가 없습니다. 프로세스 그룹의 다른 구성원에게는 영향을 주지 않고 종료될 수 있으며 해당 PID를 가진 프로세스가 더 이상 존재하지 않더라도 동일한 PGID를 유지합니다.

pid-wrap(사용 중인 시스템에서 최근에 사용된 pid 재사용)을 사용하더라도, Linux 커널은 해당 프로세스 그룹의 모든 구성원이 종료될 때까지 종료된 프로세스 그룹 리더의 pid가 재사용되지 않도록 보장합니다. 즉, PGID가 실수로 새 프로세스를 참조할 수 있는 방법은 없습니다.

프로세스 그룹은 다음과 같은 셸 파이프라인 명령에 유용합니다.

cat foo.txt | grep bar | wc -l

이는 서로 다른 세 가지 프로그램(cat, grep, wc)에 대한 세 가지 프로세스를 생성하고 파이프와 연결합니다. 셸은 ls와 같은 단일 프로그램 명령에 대해서도 새 프로세스 그룹을 생성합니다. 프로세스 그룹의 목적은 일련의 프로세스에 대한 신호 타겟팅을 허용하고, 세션의 제어 터미널에 대한 모든 읽기 및 쓰기 액세스가 허용되는 일련의 프로세스(포그라운드 프로세스 그룹)를 식별하는 것입니다.

다시 말해, 셸의 control-C는 포그라운드 프로세스 그룹의 모든 프로세스에 인터럽트 신호를 보냅니다(신호의 pid 대상이 그룹과 프로세스 그룹 리더 프로세스 자체를 구별하므로 음수 PGID 값). 제어 터미널 연결은 터미널에서 입력을 읽는 프로세스가 서로 경쟁하지 않고 문제를 일으키지 않도록 보장합니다(터미널 출력은 포그라운드가 아닌 프로세스 그룹에서 허용될 수 있음).

사용자와 그룹

위에서 언급한 것처럼 init 프로세스의 사용자 ID는 0(루트)입니다. 모든 프로세스에는 연관된 사용자 및 그룹이 있으며 이를 사용하여 시스템 호출 및 파일에 대한 액세스를 제한할 수 있습니다. 사용자와 그룹에는 숫자 ID가 있으며 root 또는 ms와 같은 관련 이름이 있을 수 있습니다. 루트 사용자는 무엇이든 할 수 있는 슈퍼 사용자이며 보안상의 이유로 꼭 필요한 경우에만 사용해야 합니다.

Linux 커널은 ID에만 관심이 있습니다. 이름은 선택 사항이며 사람의 편의를 위해 /etc/passwd/etc/group 파일을 통해 제공됩니다. Name Service Switch(NSS)를 사용하면 이러한 파일을 LDAP 및 기타 디렉터리의 사용자 및 그룹으로 확장할 수 있습니다(/kr/etc/passwd와 NSS 제공 사용자의 조합을 보려면 getent passwd 사용).

각 프로세스에는 여러 사용자 및 그룹(실제 그룹, 유효 그룹, 저장된 그룹, 보충 그룹)이 연결되어 있을 수 있습니다. 자세한 내용은 man 7 자격 증명을 참조하세요.

루트 파일 시스템이 컨테이너 이미지로 정의되는 컨테이너의 사용이 증가함에 따라 /kr/etc/passwd 및 /kr/etc/group이 없거나 사용 중인 사용자 및 그룹 ID의 일부 이름이 누락될 가능성이 높아졌습니다. Linux 커널은 이러한 이름에는 관심이 없고 ID에만 관심이 있으므로 괜찮습니다.

요약

Linux 프로세스 모델은 서버 워크로드를 표현하는 정확하고 간결한 방법을 제공하므로 매우 표적화된 경보 규칙 및 검토가 가능합니다. 브라우저에서 프로세스 모델을 이해하기 쉽게 세션별로 렌더링하면 서버 워크로드에 대한 인사이트를 강화할 수 있습니다.

Elastic Cloud 14일 무료 체험판을 통해 시작하실 수 있습니다. 또는 자체 관리형 버전의 Elastic Stack을 무료로 다운로드하실 수도 있습니다.

자세히 알아보기

Linux man 페이지는 훌륭한 정보 소스입니다. 아래 man 페이지에는 위에서 설명한 Linux 프로세스 모델에 대한 자세한 내용이 나와 있습니다.