Linux 보안 기초
사용자(User)
Linux에서 각 사용자는 고유한 User ID를 부여받는다. 이 정보는 /etc/passwd 파일에 저장된다.
id 명령어로 현재 사용자의 실제(Real) UID와 유효(Effective) UID, 그룹 ID를 확인할 수 있다.
사용자 추가는 두 가지 방법으로 가능하다:
/etc/passwd에 직접 추가adduser명령어 사용
다른 사용자로 전환하려면 su 명령어를 사용한다.
그룹(Group)
그룹은 여러 사용자를 묶어 권한을 일괄 관리하는 단위이다. 한 사용자는 여러 그룹에 속할 수 있으며, 기본(primary) 그룹은 /etc/passwd에 기록된다.
그룹 관리 명령어:
권한과 접근 제어
파일에 대한 접근 권한:
- read (r): 파일 내용 읽기
- write (w): 파일 내용 수정
- execute (x): 프로그램/스크립트 실행
디렉토리에 대한 접근 권한:
- read (r): 디렉토리 내용 나열 (
ls) - write (w): 파일/하위 디렉토리 생성
- execute (x): 디렉토리 진입 (
cd)
chmod 명령어로 파일 권한을 변경한다.
umask는 새 파일의 기본 권한을 결정한다.
주의: umask 연산은 XOR이나 단순 AND가 아니다. 보수(complement)와의 AND 연산이다.
**ACL (Access Control List)**은 전통적인 권한 모델과 공존하며, 개별 사용자/그룹에 세밀한 권한을 부여한다.
getfacl로 ACL 조회:
setfacl로 ACL 설정:
권한 상승 방법
Linux에서 권한이 필요한 명령을 실행하는 세 가지 방법:
- sudo
- Set-UID 프로그램
- POSIX Capabilities
sudo 사용
sudo(Super-user Do)는 슈퍼유저 권한으로 명령을 실행한다. 사용자는 /etc/sudoers에 등록되어야 한다.
Ubuntu 20.04에서는 root 계정이 잠겨 있어 직접 로그인이 불가능하다. root 쉘을 얻으려면:
sudo -ssudo bashsudo su
권장사항: root 쉘에서 작업하는 것보다 개별 명령에 sudo를 붙여 실행하는 것이 안전하다.
다른 사용자 권한으로 명령 실행:
POSIX Capabilities
root 권한을 세분화한 단위이다. man capabilities로 전체 목록을 확인할 수 있다.
주요 Capabilities:
- CAP_CHOWN: 파일 UID/GID 변경
- CAP_DAC_OVERRIDE: 파일 읽기/쓰기/실행 권한 검사 우회
- CAP_DAC_READ_SEARCH: 파일 읽기 및 디렉토리 검색 권한 우회
- CAP_NET_RAW: RAW/PACKET 소켓 사용
Capability 설정:
실제 사례 - Wireshark: 패킷 스니핑 도구인 Wireshark에서 GUI 부분은 일반 권한으로, 실제 스니핑을 담당하는 dumpcap은 특수 권한으로 실행된다.
ping 명령어는 raw socket을 사용하므로 CAP_NET_RAW capability가 설정되어 있다.
인증(Authentication)
사용자 신원을 확인하는 과정이다.
인증 방법의 세 가지 유형:
- 지식 기반: 비밀번호 (something you know)
- 소유 기반: ID 카드 (something you have)
- 생체 기반: 지문 (something you are/do)
**다중 인증(MFA)**은 이들을 조합한다.
패스워드 파일
/etc/passwd의 각 항목은 사용자 계정 정보를 담지만, 비밀번호 자체는 여기에 저장되지 않는다.
마지막 필드는 로그인 후 실행할 첫 번째 명령(쉘)을 지정한다.
Shadow 파일
비밀번호는 /etc/shadow에 별도 저장된다. /etc/passwd에서 분리한 이유는 보안 강화를 위해서이다.
해싱과 솔팅
비밀번호 저장 시 **솔트(salt)**를 추가하여 해시한다.
솔트의 목적은 **무차별 대입 공격(brute-force)**을 늦추는 것이다. Dictionary 공격, Rainbow Table 공격에 대응한다.
동일한 비밀번호를 가진 세 계정도 솔트가 다르면 해시 값이 달라진다.
계정 잠금
비밀번호 필드에 무효한 값을 넣으면 계정이 잠긴다. Ubuntu의 root 계정이 이 방식으로 잠겨 있다.
Set-UID 프로그램
비밀번호 딜레마
/etc/shadow 파일은 root만 읽을 수 있다.
그렇다면 일반 사용자는 어떻게 자신의 비밀번호를 변경할 수 있을까?
Two-Tier 접근법
세분화된 접근 제어를 OS에 직접 구현하면 시스템이 지나치게 복잡해진다. 대신 OS는 **특권 프로그램(Privileged Programs)**이라는 확장을 통해 세밀한 제어를 구현한다.
특권 프로그램의 두 가지 유형:
- Daemon: 백그라운드에서 실행되며 root 권한이 필요한 프로그램
- Set-UID 프로그램: 특수 비트가 설정된 실행 파일로, 파일 소유자의 권한으로 실행된다
Set-UID 개념
Set-UID는 사용자가 프로그램 소유자의 권한으로 프로그램을 실행할 수 있게 한다.
모든 프로세스는 두 가지 UID를 가진다:
- Real UID (RUID): 프로세스의 실제 소유자
- Effective UID (EUID): 프로세스의 권한 (접근 제어에 사용)
일반 프로그램: RUID == EUID (실행한 사용자의 ID)
Set-UID 프로그램: RUID != EUID (RUID는 사용자, EUID는 프로그램 소유자)
파일 소유자를 root로 변경:
Set-UID 비트 활성화 전:
Set-UID 비트 활성화 후:
Set-UID 프로그램은 일반 프로그램과 동일하지만, Set-UID 비트라는 특수 표시가 있다.
실행 예시:
Set-UID의 보안성
Set-UID는 일반 사용자가 권한을 상승할 수 있게 하지만, sudo와는 다르다. 제한된 동작만 수행하도록 설계된다. 아이언맨 슈트처럼 정해진 기능만 사용 가능한 것이다.
모든 프로그램을 Set-UID로 만들면 위험하다:
/bin/sh를 Set-UID로 만들면? 무제한 root 쉘vi를 Set-UID로 만들면? 모든 파일 수정 가능
Set-UID 프로그램의 공격 표면
사용자 입력을 통한 공격 (명시적 입력)
- Buffer Overflow: 버퍼를 넘쳐 악성 코드 실행
- Format String 취약점: 사용자 입력을 포맷 스트링으로 사용
시스템 입력을 통한 공격
- Race Condition: 심볼릭 링크로 권한 있는 파일을 가리키게 함
- 쓰기 가능한(world writable) 폴더 내 파일 조작
환경 변수를 통한 공격
환경 변수는 프로세스 동작에 영향을 주는 동적 값이다. 사용자가 프로그램 실행 전에 설정할 수 있다.
PATH 환경 변수 공격:
system()함수는/bin/sh를 먼저 호출한다system("ls")실행 시 쉘이 PATH를 사용해ls를 찾는다- 공격자가 PATH를 조작하면 다른 프로그램이 실행될 수 있다
Capability Leaking
특권 프로그램이 실행 중 권한을 낮추는(downgrade) 경우가 있다.
su 프로그램 예시:
- EUID=root, RUID=user1로 시작
- 비밀번호 확인 후 EUID와 RUID 모두 user2로 변경
문제는 권한 낮추기 전에 특권 capability를 정리하지 않으면 누출된다는 것이다.
OS X 사례 (2015, OS X 10.10):
Dynamic linker(dyld)에 DYLD_PRINT_TO_FILE 환경 변수가 추가되었다. root 소유 Set-UID 프로그램에서 이 변수로 보호된 파일에 쓸 수 있었다. dyld가 파일 디스크립터를 닫지 않아 capability가 누출되었다.
외부 프로그램 호출
Set-UID 프로그램에서 외부 명령을 호출할 때 주의가 필요하다.
위험한 방식: system()
system() 함수는 가장 쉬운 외부 명령 호출 방법이지만 위험하다.
위 프로그램은 /bin/cat을 실행하려 하지만, root 소유 Set-UID이므로 모든 파일을 볼 수 있다. 다른 명령도 실행할 수 있을까?
안전한 방식: execve()
execve()는 코드(명령 이름)와 데이터를 명확히 분리한다. 사용자 데이터가 코드가 될 수 없다.
주의:
execlp(),execvp(),execvpe()는 쉘 동작을 모방하므로 PATH 환경 변수 공격에 취약하다.
다른 언어에서의 위험
외부 명령 호출 위험은 C에만 국한되지 않는다:
- Perl:
open()함수가 쉘을 통해 명령 실행 - PHP:
system()함수
공격 예시:
http://localhost/list.php?dir=.;date
서버에서 실행되는 명령: /bin/ls .;date
분리의 원칙 (Principle of Isolation)
코드와 데이터를 섞지 말 것
이 원칙 위반으로 발생하는 공격:
- system() 코드 실행
- Cross Site Scripting (XSS)
- SQL Injection
- Buffer Overflow
최소 권한의 원칙 (Principle of Least Privilege)
특권 프로그램은 필요한 권한만 가져야 한다. 권한이 불필요할 때는 일시적/영구적으로 비활성화한다.
Linux에서 seteuid(), setuid()로 권한을 조절할 수 있다.
환경 변수
환경 변수는 프로세스 실행 환경의 일부로, 프로세스 동작에 영향을 준다. Unix에서 도입되어 Windows에서도 채택되었다.
PATH 변수 예시: 전체 경로 없이 프로그램을 실행하면 쉘이 PATH를 사용해 프로그램 위치를 찾는다.
환경 변수 접근
main 함수에서 접근:
전역 변수 사용 (더 안정적):
환경 변수 획득
두 가지 방법:
- fork(): 자식 프로세스가 부모의 환경 변수를 상속
- execve(): 메모리가 덮어씌워지므로 명시적으로 전달해야 함
execve()로 환경 변수 전달:
새 환경 변수 배열을 세 번째 인자로 전달한다.
환경 변수의 메모리 위치
envp와 environ은 초기에 같은 위치를 가리킨다. 하지만:
envp: main 함수 내에서만 접근 가능environ: 전역 변수
환경 변수가 추가되면 저장 위치가 힙으로 이동할 수 있어 environ은 변경되지만 envp는 변경되지 않는다.
쉘 변수
쉘 변수와 환경 변수는 다르다. 쉘 변수는 쉘 내부에서 사용하는 변수이다.
/proc 파일 시스템
Linux의 가상 파일 시스템으로, 각 프로세스 디렉토리에
environ파일이 있다.
/proc/932/environ: 프로세스 932의 환경 변수strings /proc/$$/environ: 현재 쉘의 환경 변수 출력
env명령어는 자식 프로세스에서 실행되므로 쉘 자체가 아닌 자식의 환경 변수를 출력한다.
쉘이 시작되면 환경 변수를 쉘 변수로 복사한다. 쉘 변수 변경은 환경 변수에 반영되지 않는다.
쉘 변수가 자식 프로세스의 환경 변수에 영향을 주는 과정:
쉘에서 env를 실행하면 자식 프로세스가 생성된다:
환경 변수의 공격 표면
사용자가 환경 변수를 설정할 수 있으므로, Set-UID 프로그램의 공격 표면이 된다. 숨겨진 환경 변수 사용이 특히 위험하다.
Dynamic Linker를 통한 공격
링킹은 프로그램이 참조하는 외부 라이브러리 코드를 찾는 과정이다:
- Static linking: 컴파일 시 링킹
- Dynamic linking: 런타임에 링킹 (환경 변수 사용)
Static linking:
링커가 프로그램 코드와 라이브러리 코드를 결합한다. 정적 컴파일된 프로그램은 동적 프로그램보다 약 100배 크다.
Dynamic linking:
런타임에 공유 라이브러리(Windows의 DLL)와 링킹된다.
ldd 명령어로 의존하는 공유 라이브러리를 확인할 수 있다:
위험성
Dynamic linking은 메모리를 절약하지만, 프로그램 코드 일부가 컴파일 시점에 결정되지 않는다. 사용자가 이 부분에 영향을 줄 수 있다면 프로그램 무결성이 훼손될 수 있다.
사례 1: 일반 프로그램
LD_PRELOAD는 링커가 먼저 검색할 공유 라이브러리 목록이다. 함수를 찾지 못하면 LD_LIBRARY_PATH에서 검색한다. 두 변수 모두 사용자가 설정 가능하다.
sleep 함수를 호출하는 프로그램:
직접 구현한 sleep() 함수:
공유 라이브러리 생성 및 LD_PRELOAD 설정:
사례 2: Set-UID 프로그램
위 기법이 Set-UID 프로그램에도 적용된다면 매우 위험하다.
직접 만든 sleep()이 호출되지 않았다. Dynamic linker의 방어 기능 때문이다. EUID와 RUID가 다르면 LD_PRELOAD와 LD_LIBRARY_PATH를 무시한다.
검증 실험:
사례 3: OS X Dynamic Linker
OS X 10.10에서 DYLD_PRINT_TO_FILE 환경 변수가 보안 분석 없이 추가되었다.
Set-UID 프로그램에서 사용자가 보호된 파일에 쓸 수 있었고, 파일 디스크립터가 닫히지 않아 capability leak이 발생했다.
공격 예시:
- DYLD_PRINT_TO_FILE을 /etc/sudoers로 설정
- Bob 계정으로 전환
- echo 명령으로 /etc/sudoers에 쓰기
외부 프로그램을 통한 공격
애플리케이션이 환경 변수를 직접 사용하지 않아도, 호출하는 외부 프로그램이 사용할 수 있다.
외부 프로그램 호출 방식:
- exec() 계열 → execve() 호출: 프로그램 직접 실행
- system() → execl() → execve() → /bin/sh: 쉘을 통해 실행
쉘 프로그램은 PATH 등 많은 환경 변수의 영향을 받는다.
공격 표면 비교:
system(): 쉘을 호출하므로 환경 변수 영향을 받음execve(): 쉘을 호출하지 않으므로 더 안전
특권 프로그램에서 외부 명령 호출 시 execve()를 사용해야 한다.
애플리케이션 코드를 통한 공격
프로그램이 직접 환경 변수를 사용하면 신뢰할 수 없는 입력이 된다.
위 프로그램은 getenv()로 PWD 환경 변수를 가져와 배열에 복사한다. 입력 길이를 검사하지 않아 버퍼 오버플로우가 발생할 수 있다.
대응책
Set-UID 프로그램에서 환경 변수 사용 시 **적절한 검증(sanitization)**이 필요하다.
secure_getenv() 함수를 사용할 수 있다:
getenv(): 환경 변수 목록에서 검색하여 포인터 반환secure_getenv(): 동일하지만 "secure execution"이 필요하면 NULL 반환
Secure execution 조건: 프로세스의 EUID와 RUID가 다를 때
Set-UID 방식 vs Service 방식
일반 사용자가 특권 작업을 수행하는 두 가지 접근법:
- Set-UID 방식: 특수 프로그램을 실행해 일시적으로 root 권한 획득
- Service 방식: 특권 서비스에 작업 요청
Set-UID의 공격 표면이 훨씬 넓다. 환경 변수가 주요 원인이다:
- Set-UID: 환경 변수를 신뢰할 수 없음
- Service: 환경 변수를 신뢰할 수 있음
Service 방식에도 다른 공격 표면이 존재하지만, Set-UID보다 안전하다고 평가된다.
이러한 이유로 Android OS는 Set-UID와 Set-GID 메커니즘을 완전히 제거했다.
HGU 전산전자공학부 고윤민 교수님의 24-2 컴퓨터 보안 수업을 듣고 작성한 포스트이며, 첨부한 모든 사진은 교수님 수업 PPT의 사진 원본에 필기를 한 수정본입니다.