리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.

리눅스 시스템 기초 9 / 50 · Part 1. Linux 기본 구조
실습 환경: Rocky Linux 9 (10.0.0.200)
이전 글: 8. Linux 부팅 과정

1. 들어가며

3편에서 로그인할 때 /etc/profile, ~/.bashrc 같은 파일이 실행된다고 정리했다. 그 파일들이 주로 하는 일이 환경변수 설정 이다. 이번 글은 환경변수가 무엇이고, 프로세스 사이에서 어떻게 전달되며, 공격자가 이를 어떻게 악용하는지 다룬다.

환경변수는 단순한 설정값처럼 보이지만, 어떤 프로그램이 실행될지(PATH), 어떤 라이브러리가 먼저 로드될지(LD_PRELOAD) 를 결정한다. 그래서 환경변수 조작은 권한 상승과 사용자 영역 루트킷의 대표 기법이다.


2. 핵심 개념

2-1. 셸 변수와 환경변수

구분설정 방법범위확인 명령
셸 변수NAME=value현재 셸에서만set
환경변수export NAME=value현재 셸 + 이후 실행하는 자식 프로세스env, printenv

2-2. 주요 환경변수

변수의미보안 관련성
PATH명령어를 찾는 디렉터리 목록 (: 구분, 앞에서부터 검색)PATH 하이재킹
HOME홈 디렉터리
USER, LOGNAME사용자 이름로그 기록
SHELL로그인 쉘
LANG, LC_*언어·문자 인코딩로그 시각·문자 형식
TZ시간대로그 시각 해석
PS1프롬프트 모양
HISTFILE, HISTSIZE명령 이력 (3편)흔적 회피
LD_PRELOAD먼저 로드할 공유 라이브러리함수 가로채기, 루트킷
LD_LIBRARY_PATH라이브러리 검색 경로 추가라이브러리 바꿔치기
http_proxy, https_proxy프록시 서버트래픽 우회·도청

2-3. 환경변수를 설정하는 파일

파일적용 범위비고
/etc/environment전체 사용자 (PAM이 읽음)KEY=value 형식만 가능
/etc/profile, /etc/profile.d/*.sh전체 사용자 로그인 셸전사 정책 (예: HISTTIMEFORMAT)
~/.bash_profile, ~/.bashrc개별 사용자
systemd 유닛 Environment=해당 서비스서비스는 로그인 셸 설정을 읽지 않음

3. 동작 원리

환경변수 상속 구조와 악용 지점

3-1. 상속은 "복사"다

프로세스가 fork()로 자식을 만들면 환경변수 목록이 복사 되고, execve()로 새 프로그램을 실행할 때 그 목록이 전달된다. 따라서:

  • 자식이 환경변수를 바꿔도 부모에게는 반영되지 않는다. (스크립트 안에서 export 해도 스크립트가 끝나면 원래 셸에는 없는 이유)
  • 실행 시점의 환경은 /proc/<PID>/environ에 남는다. 해당 프로세스의 소유자와 root만 읽을 수 있다.

3-2. PATH 검색

ls를 입력하면 셸은 PATH에 적힌 디렉터리를 앞에서부터 확인해 처음 발견한 ls를 실행한다. 만약 PATH가 /tmp:/usr/bin이고 /tmp/ls가 있다면 /tmp/ls가 실행된다. 이것이 PATH 하이재킹이다.

3-3. sudo와 환경변수

sudo는 기본적으로 환경변수를 초기화한다(/etc/sudoers의 Defaults env_reset). 또 Defaults secure_path로 PATH를 안전한 경로로 강제한다. 이 설정 덕분에 일반 사용자가 조작한 PATH나 LD_PRELOAD가 root 권한 명령에 그대로 전달되지 않는다. 반대로 sudoers에 env_keep += "LD_PRELOAD" 같은 설정이 추가되어 있다면 위험 하다.

3-4. 동적 링커와 LD_PRELOAD

대부분의 프로그램은 실행 시 동적 링커(ld.so)가 필요한 공유 라이브러리를 불러온다. LD_PRELOAD나 /etc/ld.so.preload에 지정된 라이브러리는 다른 라이브러리보다 먼저 로드되기 때문에, 같은 이름의 함수(readdir, open 등)를 가로챌 수 있다. 정상적인 용도(디버깅, 성능 측정)도 있지만, 파일·프로세스를 숨기는 사용자 영역 루트킷 이 이 방식을 쓴다.

SUID 프로그램 실행 시에는 보안 모드(secure-execution)가 적용되어 LD_PRELOAD가 크게 제한된다. 반면 /etc/ld.so.preload는 root만 쓸 수 있는 대신 모든 프로세스에 적용 된다.


4. 실습

# 1) 셸 변수와 환경변수 차이
MYVAR=hello
bash -c 'echo "child: $MYVAR"'     # 비어 있음
export MYVAR
bash -c 'echo "child: $MYVAR"'     # hello

# 2) 자식의 변경은 부모에 반영되지 않음
bash -c 'export MYVAR=changed'
echo "parent: $MYVAR"              # hello

# 3) 환경변수 목록
env | sort | head -20
printenv PATH
echo $PATH | tr ':' '\n'

# 4) 특정 프로세스의 환경변수 (실행 시점)
sudo tr '\0' '\n' < /proc/$(pgrep -o sshd)/environ

# 5) sudo 환경 정책
sudo grep -E 'env_reset|env_keep|secure_path' /etc/sudoers /etc/sudoers.d/* 2>/dev/null
sudo env | grep PATH

# 6) 프리로드 설정 점검
ls -l /etc/ld.so.preload 2>/dev/null || echo "없음 (정상)"
env | grep -E '^LD_'

5. 결과 분석

아래 출력은 형식 설명용 예시다.

① echo $PATH | tr ':' '\n'

/home/roror/.local/bin
/home/roror/bin
/usr/local/bin
/usr/bin
/usr/local/sbin
/usr/sbin
확인판단
/tmp, /var/tmp, /dev/shm 포함위험 — PATH 하이재킹 가능
. 또는 빈 항목(::) 포함위험 — 현재 디렉터리의 동일 이름 파일 실행
사용자 디렉터리가 시스템 디렉터리보다 앞일반 계정에선 흔함. root 계정이면 점검

② sudo grep ... /etc/sudoers

Defaults    env_reset
Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin

env_reset과 secure_path가 있어야 정상이다. env_keep에 LD_PRELOAD, LD_LIBRARY_PATH, PATH가 들어 있다면 권한 상승 위험 설정이다.

③ /etc/ld.so.preload — 일반적인 서버에는 존재하지 않는 것이 보통 이다. 파일이 있다면 내용(라이브러리 경로)과 해당 라이브러리 파일의 출처·해시를 확인한다.


6. 보안 관점

기법원리흔적대응
PATH 하이재킹쓰기 가능한 경로를 PATH 앞에 두고 같은 이름의 가짜 명령 배치PATH 값, 해당 경로의 실행 파일root PATH 점검, secure_path
cron·스크립트의 상대 경로root cron이 tar처럼 전체 경로 없이 명령 실행 → PATH 조작 시 가짜 명령 실행cron 스크립트 내용스크립트에 절대 경로 사용
LD_PRELOAD 주입특정 프로세스 실행 시 악성 라이브러리 선로드/proc/PID/environ, /proc/PID/maps의 낯선 .so환경 점검
전역 프리로드/etc/ld.so.preload로 모든 프로세스에 적용 → 파일·프로세스 은닉파일 존재, 도구 간 결과 불일치파일 무결성 감시
sudo 환경 유지env_keep에 위험 변수 추가sudoers 변경sudoers 감사 규칙

7. SOC / 보안관제 활용

7-1. 점검 스크립트

echo "== root PATH =="; sudo -i bash -c 'echo $PATH' | tr ':' '\n'
echo "== ld.so.preload =="; ls -l /etc/ld.so.preload 2>/dev/null || echo none
echo "== sudo env policy =="; sudo grep -hE 'env_keep|env_reset|secure_path' /etc/sudoers /etc/sudoers.d/* 2>/dev/null

echo "== LD_PRELOAD가 설정된 프로세스 =="
for p in /proc/[0-9]*; do
  if sudo grep -qa 'LD_PRELOAD=' $p/environ 2>/dev/null; then
    echo "PID ${p#/proc/}: $(tr '\0' ' ' < $p/cmdline 2>/dev/null)"
  fi
done

7-2. 감사 규칙

# /etc/audit/rules.d/preload.rules
-w /etc/ld.so.preload -p wa -k ld_preload
-w /etc/environment -p wa -k env_change
-w /etc/profile.d/ -p wa -k env_change

7-3. 분석 흐름

[Event]   ps로는 없는데 ss에 알 수 없는 외부 연결이 보임 (도구 간 불일치)
   ↓
[가설]    프로세스 은닉 → 사용자 영역 루트킷 가능성
   ↓
[IOC]     /etc/ld.so.preload 존재 → /usr/lib64/libxxx.so 경로
          sha256sum, rpm -qf 로 패키지 소속 확인 (소속 없음)
   ↓
[Root Cause] ausearch -k ld_preload -i → 생성 시각, auid
   ↓
[Response]  증적 확보 → 프리로드 제거 → 은닉되던 프로세스 확인·종료 → 재설치 검토

8. 핵심 정리

  • 셸 변수는 현재 셸에만, export한 환경변수는 자식 프로세스에 복사 된다. 자식의 변경은 부모에 반영되지 않는다.
  • 실행 시점의 환경은 /proc/<PID>/environ에 남는다.
  • PATH는 앞에서부터 검색한다. 쓰기 가능한 경로나 .이 앞에 있으면 하이재킹 위험이 있다.
  • sudo는 env_reset, secure_path로 환경을 초기화한다. env_keep에 위험 변수가 있는지 점검한다.
  • LD_PRELOAD와 /etc/ld.so.preload는 라이브러리를 먼저 로드해 함수를 가로챌 수 있다. /etc/ld.so.preload는 보통 존재하지 않는다.
  • 도구 간 결과 불일치(ps와 ss, ls와 find)는 은닉을 의심하는 신호다.

9. 다음 글

다음 글 「10. Linux 시스템 정보 확인」 은 Part 1의 마무리다. 1~9편에서 다룬 커널·쉘·메모리·파일 시스템·부팅·환경변수 확인 명령을 하나로 묶어, 서버에 처음 접속했을 때 실행하는 시스템 정보 수집(Triage) 스크립트 를 만든다.


참고 자료

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글