리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.
리눅스 시스템 기초 9 / 50 · Part 1. Linux 기본 구조
실습 환경: Rocky Linux 9 (10.0.0.200)
이전 글: 8. Linux 부팅 과정
3편에서 로그인할 때 /etc/profile, ~/.bashrc 같은 파일이 실행된다고 정리했다. 그 파일들이 주로 하는 일이 환경변수 설정 이다. 이번 글은 환경변수가 무엇이고, 프로세스 사이에서 어떻게 전달되며, 공격자가 이를 어떻게 악용하는지 다룬다.
환경변수는 단순한 설정값처럼 보이지만, 어떤 프로그램이 실행될지(PATH), 어떤 라이브러리가 먼저 로드될지(LD_PRELOAD) 를 결정한다. 그래서 환경변수 조작은 권한 상승과 사용자 영역 루트킷의 대표 기법이다.
| 구분 | 설정 방법 | 범위 | 확인 명령 |
|---|---|---|---|
| 셸 변수 | NAME=value | 현재 셸에서만 | set |
| 환경변수 | export NAME=value | 현재 셸 + 이후 실행하는 자식 프로세스 | env, printenv |
| 변수 | 의미 | 보안 관련성 |
|---|---|---|
PATH | 명령어를 찾는 디렉터리 목록 (: 구분, 앞에서부터 검색) | PATH 하이재킹 |
HOME | 홈 디렉터리 | |
USER, LOGNAME | 사용자 이름 | 로그 기록 |
SHELL | 로그인 쉘 | |
LANG, LC_* | 언어·문자 인코딩 | 로그 시각·문자 형식 |
TZ | 시간대 | 로그 시각 해석 |
PS1 | 프롬프트 모양 | |
HISTFILE, HISTSIZE | 명령 이력 (3편) | 흔적 회피 |
LD_PRELOAD | 먼저 로드할 공유 라이브러리 | 함수 가로채기, 루트킷 |
LD_LIBRARY_PATH | 라이브러리 검색 경로 추가 | 라이브러리 바꿔치기 |
http_proxy, https_proxy | 프록시 서버 | 트래픽 우회·도청 |
| 파일 | 적용 범위 | 비고 |
|---|---|---|
/etc/environment | 전체 사용자 (PAM이 읽음) | KEY=value 형식만 가능 |
/etc/profile, /etc/profile.d/*.sh | 전체 사용자 로그인 셸 | 전사 정책 (예: HISTTIMEFORMAT) |
~/.bash_profile, ~/.bashrc | 개별 사용자 | |
systemd 유닛 Environment= | 해당 서비스 | 서비스는 로그인 셸 설정을 읽지 않음 |

프로세스가 fork()로 자식을 만들면 환경변수 목록이 복사 되고, execve()로 새 프로그램을 실행할 때 그 목록이 전달된다. 따라서:
export 해도 스크립트가 끝나면 원래 셸에는 없는 이유)/proc/<PID>/environ에 남는다. 해당 프로세스의 소유자와 root만 읽을 수 있다.ls를 입력하면 셸은 PATH에 적힌 디렉터리를 앞에서부터 확인해 처음 발견한 ls를 실행한다. 만약 PATH가 /tmp:/usr/bin이고 /tmp/ls가 있다면 /tmp/ls가 실행된다. 이것이 PATH 하이재킹이다.
sudo는 기본적으로 환경변수를 초기화한다(/etc/sudoers의 Defaults env_reset). 또 Defaults secure_path로 PATH를 안전한 경로로 강제한다. 이 설정 덕분에 일반 사용자가 조작한 PATH나 LD_PRELOAD가 root 권한 명령에 그대로 전달되지 않는다. 반대로 sudoers에 env_keep += "LD_PRELOAD" 같은 설정이 추가되어 있다면 위험 하다.
대부분의 프로그램은 실행 시 동적 링커(ld.so)가 필요한 공유 라이브러리를 불러온다. LD_PRELOAD나 /etc/ld.so.preload에 지정된 라이브러리는 다른 라이브러리보다 먼저 로드되기 때문에, 같은 이름의 함수(readdir, open 등)를 가로챌 수 있다. 정상적인 용도(디버깅, 성능 측정)도 있지만, 파일·프로세스를 숨기는 사용자 영역 루트킷 이 이 방식을 쓴다.
SUID 프로그램 실행 시에는 보안 모드(secure-execution)가 적용되어
LD_PRELOAD가 크게 제한된다. 반면/etc/ld.so.preload는 root만 쓸 수 있는 대신 모든 프로세스에 적용 된다.
# 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_'
아래 출력은 형식 설명용 예시다.
① 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 — 일반적인 서버에는 존재하지 않는 것이 보통 이다. 파일이 있다면 내용(라이브러리 경로)과 해당 라이브러리 파일의 출처·해시를 확인한다.
| 기법 | 원리 | 흔적 | 대응 |
|---|---|---|---|
| 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 감사 규칙 |
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
# /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
[Event] ps로는 없는데 ss에 알 수 없는 외부 연결이 보임 (도구 간 불일치)
↓
[가설] 프로세스 은닉 → 사용자 영역 루트킷 가능성
↓
[IOC] /etc/ld.so.preload 존재 → /usr/lib64/libxxx.so 경로
sha256sum, rpm -qf 로 패키지 소속 확인 (소속 없음)
↓
[Root Cause] ausearch -k ld_preload -i → 생성 시각, auid
↓
[Response] 증적 확보 → 프리로드 제거 → 은닉되던 프로세스 확인·종료 → 재설치 검토
export한 환경변수는 자식 프로세스에 복사 된다. 자식의 변경은 부모에 반영되지 않는다./proc/<PID>/environ에 남는다..이 앞에 있으면 하이재킹 위험이 있다.env_reset, secure_path로 환경을 초기화한다. env_keep에 위험 변수가 있는지 점검한다.LD_PRELOAD와 /etc/ld.so.preload는 라이브러리를 먼저 로드해 함수를 가로챌 수 있다. /etc/ld.so.preload는 보통 존재하지 않는다.다음 글 「10. Linux 시스템 정보 확인」 은 Part 1의 마무리다. 1~9편에서 다룬 커널·쉘·메모리·파일 시스템·부팅·환경변수 확인 명령을 하나로 묶어, 서버에 처음 접속했을 때 실행하는 시스템 정보 수집(Triage) 스크립트 를 만든다.