Step 99. Bandit 16~20 — setuid 첫 만남
Level 2 — 보안 입문과 공격 스킬 기초 | 난이도 ★★★☆☆ | 예상 소요 시간 3시간
전제: Step 97의 find 조건과 SUID 개념, Step 98의 nc·openssl·키 접속을 마쳤다. Bandit 15→16 비밀번호를 가지고 있다.
- 준비물: SSH 접속 환경, 비밀번호 기록, 로컬 실험용 리눅스/WSL.
- ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다.
- 합법 연습장 안내: OverTheWire Bandit은 공식 공격 연습 플랫폼입니다. setuid 실험은 이 서버와 내 컴퓨터의 연습 폴더에서만 하며, 내가 소유한 파일에만 권한 실험을 적용합니다. 실험 파일은 끝나는 즉시 정리합니다.
지금까지의 Bandit은 "찾고 읽는" 게임이었습니다. 오늘부터 격이 다른 개념이 등장합니다 — 남의 권한으로 실행되는 프로그램. 질문 하나로 시작합니다. 내 계정으로는 절대 못 여는 파일이 있습니다. 그런데 이상한 프로그램이 하나 놓여 있습니다. 이 프로그램을 실행하면, 실행하는 순간만큼은 내가 그 파일의 주인이 됩니다. 그 프로그램에게 "저 파일을 읽어 줘"라고 시키면? 읽어 줍니다. 이것이 setuid — 리눅스 권한 상승의 알파이자 오메가입니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 포트 범위 스캔으로 열린 서비스를 찾고 TLS 여부를 확인한다
diff로 두 파일의 차이만 읽어 낸다ssh 주소 "명령"형태로 쉘 없이 명령만 직행한다ls -l의s표식으로 setuid 파일을 알아보고,id실험으로 권한 교체를 증명한다- nc 리스너와 클라이언트를 두 터미널로 연결해 "상대가 나에게 오게" 만든다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 리눅스 셸(Bandit 서버 + 로컬 WSL) |
| 오늘의 명령 | nmap -p 범위, diff, ssh ... "명령", id, chmod u+s, nc -l |
| 필요한 개념 | setuid 비트, uid/gid, 권한 상승, 리스너-클라이언트 구도 |
| 오늘의 산출물 | Bandit 16→21 비밀번호 체인 + setuid 개념 정리 문서 |
2-1. setuid의 원리 — 권한의 탈을 쓴 프로그램
ls -l에서 소유자 실행 자리가 x가 아니라 s인 파일을 봅시다.
-rwsr-xr-x 1 bandit20 bandit19 ... bandit20-do
이 s가 setuid 비트입니다. 의미는 이렇습니다 — "이 파일을 실행하면, 실행한 사람이 누구든 소유자의 권한으로 프로세스가 돈다."
왜 이런 장치가 존재할까요? 대표 예가 passwd입니다. 내 비밀번호를 바꾸려면 root만 쓸 수 있는 시스템 파일을 건드려야 합니다. 일반 사용자에게 root를 줄 수는 없으니 "passwd를 실행하는 순간만 root 권한"이라는 안전핀을 단 것입니다. 문제는 그 안전핀이 달린 프로그램에 구멍이 있을 때 — 구멍을 통해 임의의 명령을 실행시키면 그 명령이 주인의 권한으로 돕니다. Step 97에서 "목록을 뽑아 보는" 개념으로 만난 SUID가, 오늘은 실제 계급 사다리가 됩니다.
2-2. diff — 두 파일의 차이만 보기
diff 파일1 파일2는 두 파일을 줄 단위로 비교해 다른 부분만 보여 줍니다. 설정 파일이 업데이트 전후로 뭐가 바뀌었는지, 두 비밀번호 목록 중 어느 줄이 새 것인지 — "차이"가 곧 정보인 상황에서 씁니다.
2-3. ssh의 숨은 재주 — 접속과 동시에 명령 전달
ssh 사용자@주소 명령 형태로 접속 끝에 명령을 붙이면, 쉘에 들어가지 않고 그 명령만 실행하고 결과를 가져옵니다. 로그인하자마자 쫓겨나는 까다로운 계정을 우회하는 비상구이고, 자동화 스크립트의 기본 형태이기도 합니다.
2-4. 리스너와 클라이언트 — 내가 벌이는 만남
Step 98에서 nc의 두 얼굴을 봤습니다 — 접속하는 쪽과 기다리는 쪽(-l). 오늘은 이 둘을 같은 사람(나)이 동시에 합니다. 한 터미널에서 기다리고, 다른 터미널에서 프로그램이 나에게 접속하게 만드는 것. "내가 판을 깔고, 상대가 걸어오게 한다"는 구도는 리버스 쉘(공격 대상이 거꾸로 공격자에게 접속하는 기법)의 뼈대입니다.
3. 따라 하기
3-1. Level 16 → 17 — 열린 포트를 스스로 찾기
목표: "localhost의 31000~32000 사이에서 열린 포트를 찾아, SSL로 접속해 키를 제출하라."
입력 (서버 안에서, 출력 예시)
nmap -p 31000-32000 localhost
이 스캔을 내 컴퓨터에서 실측했습니다. 31500번에 nc 서버를 띄워 두고 31490~31510을 스캔한 결과입니다 (2026-09-09 WSL 실측, Nmap 7.94):
PORT STATE SERVICE
31490/tcp closed unknown
...
31500/tcp open unknown
31501/tcp closed unknown
...
Nmap done: 1 IP address (1 host up) scanned in 0.10 seconds
읽는 법: 21개 중 하나만 open입니다. Bandit에서는 열린 포트가 몇 개 나오고, 그중 어느 것이 SSL인지는 각각 openssl s_client로 시험해 봐야 합니다 (Step 98의 기술). 스캔 → 확인 → 대화의 3단 콤보입니다.
3-2. Level 17 → 18 — diff로 다른 줄 찾기
passwords.old와 passwords.new가 있고, 바뀐 한 줄이 답입니다.
입력 (서버 안에서, 출력 예시)
diff passwords.old passwords.new
로컬 재현 (2026-09-09 WSL 실측):
printf 'aaaa\nbbbb\noldpass_w0rd\ndddd\n' > passwords.old
printf 'aaaa\nbbbb\nNEWPASS_XYZ\ndddd\n' > passwords.new
diff passwords.old passwords.new
3c3
< oldpass_w0rd
---
> NEWPASS_XYZ
읽는 법: 3c3은 "3번째 줄이 바뀌었다(change)"는 뜻입니다. <는 왼쪽(옛) 파일, >는 오른쪽(새) 파일의 내용. 바뀐 그 한 줄이 정답입니다. diff는 설정 변경 추적, 코드 리뷰, 침해 흔적 비교("원본과 지금의 차이")까지 쓰임이 끝없는 도구입니다.
3-3. Level 18 → 19 — 로그인하자마자 쫓겨나는 계정
이번 계정은 접속하면 즉시 튕깁니다 — .bashrc(로그인 때 자동 실행되는 설정 스크립트)에 exit이 심어져 있습니다.
입력 (쉘에 들어가지 않는 방법, 출력 예시)
ssh bandit18@bandit.labs.overthewire.org -p 2220 "cat readme"
비밀번호를 묻고, 입력하면 쉘 프롬프트 없이 readme의 내용만 출력되고 연결이 끊깁니다.
읽는 법: 접속 끝에 붙인 명령이 로그인 쉘을 거치지 않고 직행했기에, .bashrc의 퇴장 명령을 피해 갔습니다. "자동 실행되는 것"이 흐름을 바꾼다는 감각 — 이것이 바로 다음 단원(cron)의 예고편입니다.
3-4. Level 19 → 20 — setuid 바이너리 실행
입력 (서버 안에서, 출력 예시)
ls -l
./bandit20-do
./bandit20-do id
./bandit20-do cat /etc/bandit_pass/bandit20
출력 예시 (핵심 장면):
$ id
uid=11019(bandit19) gid=11019(bandit19) groups=11019(bandit19)
$ ./bandit20-do id
uid=11020(bandit20) gid=11019(bandit19) groups=11019(bandit19)
읽는 법: 프로그램을 거치자 uid가 bandit20으로 바뀝니다. 내 권한으로는 못 읽는 파일을 이 프로그램이 대신 읽어 줍니다. 이 세 줄이 권한 상승(privilege escalation)의 축소 모형입니다.
3-5. setuid 비트 관찰 — 내 컴퓨터에서
시스템을 바꾸지 않고, 내 소유 파일에만 비트를 켜서 표식을 관찰합니다 (2026-09-09 WSL 실측):
cp /bin/echo myecho
ls -l myecho
chmod u+s myecho
ls -l myecho
find . -perm /4000 -type f
출력 (2026-09-09 실측):
-rwxr-xr-x 1 root root 35208 Sep 9 14:31 myecho
-rwsr-xr-x 1 root root 35208 Sep 9 14:31 myecho
./myecho
읽는 법: chmod u+s 한 번에 소유자 실행 자리의 x가 s로 바뀌었고, Step 97의 정찰 명령(find -perm /4000)이 이 파일을 정확히 집어냈습니다. 만드는 쪽 명령과 찾는 쪽 명령이 하나의 고리로 이어졌습니다. 실험이 끝나면 이 파일은 지우세요.
3-6. Level 20 → 21 — 내가 판을 깔고 기다리기
목표: suconnect라는 setuid 프로그램이 "지정한 포트로 접속해, 현재 비밀번호를 보내고, 맞으면 다음 비밀번호를 보내 준다."
입력 (서버 안, 터미널 1 — 내가 서버, 출력 예시)
nc -l -p 1234
입력 (터미널 2)
./suconnect 1234
터미널 1에 연결이 들어옵니다. 현재 비밀번호를 치면 검증 후 다음 비밀번호가 터미널 1에 도착합니다.
읽는 법: 접속하는 쪽(setuid 프로그램)과 기다리는 쪽(내 nc)이 만났습니다. Step 98의 로컬 nc 실험(2026-09-09 실측)과 같은 구조인데, 이번엔 접속하는 주체가 "높은 권한의 프로그램"이라는 점이 다릅니다. 통신의 두 끝을 내가 동시에 쥐는 경험입니다.
3-7. id와 권한의 해부 — 나는 누구인가
오늘의 핵심 명령 id를 뜯어 봅시다 (2026-09-09 WSL 실측 — 실험 계정이 root라 출력도 root입니다):
id
uid=0(root) gid=0(root) groups=0(root)
읽는 법: uid는 사용자 번호, gid는 주 그룹 번호, groups는 속한 모든 그룹입니다. 리눅스의 권한 판정은 이름이 아니라 이 숫자들로 일어납니다. root의 uid가 0인 것도 확인됩니다 — 시스템의 왕은 번호가 0번입니다.
왜: setuid 프로그램이 하는 일의 실체가 보입니다 — 그 프로그램을 거치면 프로세스의 uid가 파일 주인의 번호로 교체됩니다. "권한 상승"이라는 거창한 말의 실체가 숫자 하나의 교체입니다.
4. 미션과 연습문제
미션 — Bandit 16→21 체인과 setuid 정리 문서
- bandit20까지 체인을 완성하고 각 레벨을 write-up 형식으로 기록합니다
- setuid 확인 절차를 정리합니다 —
ls -l에서s찾기 →find / -perm /4000 -type f 2>/dev/null로 목록화 - 내 컴퓨터에서 3-5의 관찰 실험을 재현하고 실험 파일을 즉시 지웁니다
- 위키에
setuid.md를 씁니다 — 원리 / 왜 존재하는가(passwd 예시) / 왜 위험한가 / 수비자의 대책(목록 감시) - "권한 상승"을 자기 말로 세 문장으로 정의해 문서 맨 위에 적습니다
연습문제
문제 1. -rwsr-xr-x에서 s의 의미와, 이 비트가 켜진 프로그램이 실행될 때 프로세스에 일어나는 일을 설명해 보세요.
문제 2. diff 출력의 3c3, <, >가 각각 무엇을 뜻하나요?
문제 3. Level 18 계정에서 ssh ... "cat readme"가 .bashrc의 exit을 피해 가는 원리를 설명해 보세요.
문제 4. 어떤 서버에 cat 프로그램이 root 소유 + setuid로 설정되어 있습니다. 일반 사용자가 할 수 있는 일은 무엇이며, 이것이 위험한 이유는 무엇인가요?
5. 모범 답안과 완료 기준
미션 모범 답안
체인의 뼈대 (서버 안 명령은 출력 예시):
nmap -p 31000-32000 localhost # L16→17 (열린 포트 후보)
openssl s_client -connect localhost:<열린포트> # SSL 포트 확인 후 키 제출
diff passwords.old passwords.new # L17→18
ssh bandit18@bandit.labs.overthewire.org -p 2220 "cat readme" # L18→19
./bandit20-do cat /etc/bandit_pass/bandit20 # L19→20
nc -l -p 1234 (다른 터미널) ./suconnect 1234 # L20→21
검증하는 법: ① ./bandit20-do id의 출력에서 uid가 바뀐 것을 눈으로 확인했는가. ② setuid.md에 "존재 이유"와 "위험 이유"가 둘 다 적혀 있는가 — 하나만 적혀 있으면 반쪽짜리 이해입니다. ③ 로컬 실험 파일(myecho)을 지웠는가.
연습문제 해답
문제 1 해답. s는 setuid 비트로, 이 파일을 실행하면 실행자가 누구든 프로세스의 유효 사용자 번호(uid)가 파일 소유자의 번호로 교체됩니다. 즉 소유자의 권한으로 프로그램이 돕니다 (3-4절의 id 출력 대조 참조).
문제 2 해답. 3c3은 "왼쪽 3번째 줄이 오른쪽 3번째 줄로 바뀌었다(change)"는 뜻이고, <는 왼쪽 파일의 내용, >는 오른쪽 파일의 내용입니다 (2026-09-09 로컬 실측 출력 참조).
문제 3 해답. .bashrc는 대화형 로그인 쉘이 시작될 때 실행됩니다. ssh 주소 "명령"은 쉘에 들어가지 않고 명령만 직행하므로 대화형 쉘이 뜨지 않고, 따라서 .bashrc의 exit도 실행되지 않습니다.
문제 4 해답. 시스템의 모든 파일을 root 권한으로 읽을 수 있습니다 — /etc/shadow(전 사용자 비밀번호 해시)까지. cat은 "인자로 준 파일을 읽는" 도구이고, setuid는 그 읽기를 root 권한으로 일어나게 합니다. setuid의 위험은 프로그램의 기능이 아니라 "높은 권한 × 내가 조작 가능한 입력"에서 나옵니다.
완료 기준 체크리스트
- [ ]
nmap -p 범위 주소로 열린 포트를 찾을 수 있다 - [ ]
ls -l출력에서 setuid 비트(s)를 읽을 수 있다 - [ ]
id앞뒤 비교로 권한 교체를 증명할 수 있다 - [ ]
diff출력(줄번호,<,>)을 읽을 수 있다 - [ ]
ssh ... "명령"형태로 비대화형 실행을 할 수 있다 - [ ] nc 리스너와 클라이언트를 두 터미널로 연결할 수 있다
- [ ] 미션: 체인 완성과 setuid.md 작성을 끝냈다
6. 흔한 실수와 해결
벽 1. nmap에서 포트가 전부 closed로 나온다
증상 (2026-09-09 실측 형태):
PORT STATE SERVICE
31000/tcp closed unknown
... (전부 closed)
원인: 대상 호스트를 잘못 적었거나(localhost여야 함), 범위를 오타했거나, 내 컴퓨터에서 치고 있거나.
해결: nmap -p 31000-32000 localhost의 주소와 범위를 다시 확인하고, Bandit 서버 안에서 치는지 확인하세요. 서버 안에서 쳐야 서버의 포트가 보입니다.
벽 2. SSL 포트에 접속했는데 키를 내도 침묵한다
증상: s_client로 접속했는데 대화가 안 됩니다.
원인: SSL이 아닌 다른 포트에 접속했거나, 제출할 값(이전 레벨의 키)이 틀렸습니다.
해결: 열린 포트가 여러 개면 하나씩 시험하세요. 대화가 성립하는 포트가 정답입니다 — "스캔 → 확인 → 대화"의 확인 단계를 건너뛰면 생기는 벽입니다.
벽 3. bandit18에 접속하면 명령 칠 새도 없이 튕긴다
증상 (출력 예시):
Byebye !
Connection to bandit.labs.overthewire.org closed.
원인: .bashrc의 exit — 문제의 의도 그 자체입니다.
해결: 3-3의 "접속과 동시에 명령" 형태를 쓰세요. ssh ... "cat readme" — 따옴표로 명령을 감싸는 것을 잊지 마세요.
벽 4. suconnect를 실행했는데 nc 쪽에 아무것도 안 온다
증상: 양쪽 터미널이 서로를 모릅니다.
원인: 포트 번호가 양쪽에서 다르거나, nc -l을 먼저 안 띄웠거나(기다리는 쪽이 먼저!), 다른 레벨의 홈에서 실행 중일 수 있습니다.
해결: 순서 확인 — ① nc -l -p 포트 대기 시작 → ② 다른 터미널에서 ./suconnect 포트 → ③ 같은 포트 번호. localhost라 이 셋만 맞으면 됩니다.
벽 5. setuid 파일을 만들었는데 find가 못 찾는다
증상: chmod u+s를 했는데 find . -perm /4000에 안 나옵니다.
원인: 실행 파일이 아니거나(x 비트 부재), 다른 폴더에서 검색 중이거나.
해결: ls -l로 -rws 표식을 먼저 확인하세요 (3-5절 실측처럼 s가 보여야 합니다). 비트가 켜져 있는데도 안 나오면 검색 경로를 확인하세요.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| setuid | 실행 순간 소유자 권한으로 도는 비트 — ls -l의 s |
| uid / gid | 권한 판정에 쓰이는 사용자·그룹 번호 (root는 0번) |
| 권한 상승 | 내 권한이 아닌 것을 빌려 더 높은 권한으로 올라서기 |
| 비대화형 ssh | 쉘 없이 명령만 직행하는 ssh ... "명령" |
| 리스너 구도 | 내가 기다리고 상대가 오게 만드는 통신 설계 |
오늘의 명령어
| 명령 | 하는 일 |
|---|---|
nmap -p 시작-끝 주소 |
포트 범위 스캔 |
diff A B |
두 파일의 차이만 보기 |
ssh 주소 "명령" |
쉘 없이 명령 직행 |
id |
현재 uid/gid/그룹 확인 |
chmod u+s 파일 |
setuid 비트 켜기 (내 파일에만) |
find / -perm /4000 -type f 2>/dev/null |
setuid 파일 목록 |
nc -l -p 포트 |
기다리는 서버 되기 |
명령어보다 중요한 감각
앞으로 프로그램이나 스크립트를 볼 때마다 물을 질문을 하나 가져가세요 — "이건 누구의 권한으로 도는가?" 내가 실행하면 내 권한, setuid면 소유자 권한, 자동 작업이 돌리면 그 작업의 주인 권한입니다. 그리고 위험 평가 공식: 높은 권한 곱하기 조작 가능한 입력은 위험입니다. 오늘 여러분은 처음으로 "나의 것이 아닌 권한"을 손에 쥐었습니다. 이 사다리를 만드는 쪽 명령(chmod u+s)과 찾는 쪽 명령(find -perm /4000)을 둘 다 알게 됐으니, 수비자의 눈도 함께 길러 두세요.
전부 체크되면 Step 99 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.