Step 119. netcat 완전 정복 — 네트워크의 스위스 아미 나이프
Level 2 — 보안 입문과 공격 스킬 기초 | 난이도 ★★★☆☆ | 예상 소요 시간 3시간
전제: Step 118을 마쳤다. 바인드 쉘과 리버스 쉘을 만들어 봤고, 리스너(listener)가 무엇인지 안다. 포트와 배너 그래빙(Step 79)을 이해한다.
- 준비물: 리눅스 랩 터미널 2개 (Kali 한 대면 충분합니다. 두 VM이 있으면 Kali ↔ MS2 사이에서도 해 보세요)
- 주의: ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다.
Step 118에서 우리는 이미 netcat(이하 nc)으로 쉘을 주고받았습니다. 그런데 nc는 쉘 도구가 아니라 TCP/UDP 파이프 그 자체입니다 — 키보드로 친 것을 네트워크로 흘려보내고, 네트워크로 온 것을 화면에 흘려보낼 뿐이지요. 이 단순함이 힘입니다. 침투 중 아무 도구도 없는 서버에 들어갔을 때, 파일을 옮기고 포트를 훑고 서비스와 직접 대화하는 최후의 병기가 nc입니다. 오늘은 쉘 없이 nc의 나머지 얼굴 다섯 가지 — 채팅, 파일 전송, 포트 스캔, 배너 그래빙, 수동 HTTP — 를 손에 익힙니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- nc의 두 가지 모드(리스너/클라이언트)를 상황에 맞게 고른다
- nc 두 대로 채팅하고, 리다이렉션으로 파일을 전송한다
-zv로 간이 포트 스캔을 하고 출력을 해석한다- 접속 즉시 오는 배너를 받아 서비스 이름과 버전을 읽는다
printf와 nc를 조합해 HTTP 요청을 손으로 만들어 보낸다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 리눅스 쉘 (Kali 권장, macOS/우분투도 동일) |
| 오늘의 명령 | nc -l -p, nc 대상 포트, nc -zv, nc -w, printf 파이프 |
| 필요한 개념 | 리스너와 클라이언트, 리다이렉션(>, <), 배너, HTTP 요청 형식 |
| 오늘의 산출물 | nc 활용 노트 — 전송한 파일, 스캔 결과, 수집한 배너 |
2-1. nc의 두 얼굴 — 기다리는 쪽과 두드리는 쪽
nc는 항상 둘 중 하나입니다. nc -l -p 5555는 리스너(listener) — 5555번 문을 열고 기다립니다. nc 대상IP 5555는 클라이언트 — 그 문을 두드립니다. 연결이 맺어진 순간부터는 구분이 사라집니다. 어느 쪽에서 쳐도 상대 화면에 글자가 흐릅니다.
Step 118의 바인드 쉘은 "피해자가 리스너", 리버스 쉘은 "공격자가 리스너"였지요. 오늘 실습에서는 쉘(-e) 대신 순수 데이터를 흘려보냅니다.
2-2. 오늘 쓸 플래그 정리
| 플래그 | 의미 |
|---|---|
-l |
리스너 모드 (listen) |
-p 포트 |
열 포트 번호 |
-v |
상세 출력 — 연결 성공/실패를 말해 줌 |
-z |
스캔 모드 — 데이터를 보내지 않고 연결만 시험 |
-w 초 |
대기 상한 — 이 시간이 지나면 끊음 |
-k |
손님이 끊겨도 리스너 유지 (계속 받기) |
여러분의 nc 종류를 확인해 두세요. nc -h 첫 줄에 OpenBSD netcat이라고 나오는 계열(Kali, 우분투 기본)에는 -e 옵션이 없습니다 — 쉘 붙이기가 안 되는 대신 안전한 버전입니다 (2026-09-09 실측: OpenBSD netcat (Debian patchlevel 1.226-1ubuntu2)). MS2의 전통 nc는 -e가 됩니다. 같은 nc라도 계열이 다르면 옵션이 다르다는 것, 이것이 오늘의 숨은 주제입니다.
2-3. 배너 그래빙과 수동 프로토콜
Step 79에서 파이썬으로 배너를 받아 봤습니다. nc로는 더 간단합니다 — 접속하면 배너가 그냥 화면에 흐릅니다. 그리고 nc는 "받기"만 하는 도구가 아닙니다. HTTP처럼 손님이 먼저 말해야 하는 규약에는, 우리가 직접 첫 마디를 타이핑해서 보낼 수 있습니다. 브라우저 없이 웹서버와 대화하는 경험은 HTTP의 날것을 보여 줍니다.
3. 따라 하기
3-1. 채팅 — 가장 단순한 통신
터미널을 두 개 여세요.
입력 (터미널 1 — 받는 쪽)
nc -l -p 5555
입력 (터미널 2 — 보내는 쪽)
nc 127.0.0.1 5555
이제 어느 쪽에서든 글자를 치고 엔터를 누르세요. 상대 화면에 그대로 나타납니다. 파이프로 한 줄을 바로내면 이렇게 됩니다 (2026-09-09 실측):
printf "안녕, 서버! 네트워크로 보내는 첫 메시지\n" | nc -w 1 127.0.0.1 5555
터미널 1 화면:
안녕, 서버! 네트워크로 보내는 첫 메시지
읽는 법: nc는 글자가 무엇인지 관심이 없습니다. 바이트를 그대로 옮길 뿐입니다. -w 1은 "1초 대기 후 끊기"로, 파이프로 보낼 때 연결이 안 끝나는 문제를 방지합니다. 채팅이 된다는 것은 곧 임의의 데이터를 양방향으로 흘릴 수 있다는 뜻이고, 그 데이터가 쉘 명령이면 그것이 쉘이었던 것입니다.
왜: 쉘, 파일 전송, 프록시 — nc의 모든 응용이 이 단순한 파이프의 변주입니다. 원리를 먼저 손에 넣으세요.
3-2. 파일 전송 — 리다이렉션의 마법
채팅에서 화면에 나오던 글자를 파일로 돌리면(>), 그것이 파일 받기입니다. 반대로 파일을 nc에 쏟아부으면(<), 그것이 파일 보내기입니다.
입력 (터미널 1 — 받는 쪽)
nc -l -p 5556 > got.txt
입력 (터미널 2 — 보내는 쪽)
printf "비밀 파일 내용입니다.\n두 번째 줄: netcat으로 옮겼습니다.\n" > secret.txt
nc -w 1 127.0.0.1 5556 < secret.txt
검증 (2026-09-09 실측):
md5sum secret.txt got.txt
be44d17924f67dd152d1eac8ee51fb6a secret.txt
be44d17924f67dd152d1eac8ee51fb6a got.txt
읽는 법: 해시가 완전히 같습니다 — 바이트 하나 틀리지 않고 77바이트가 그대로 건너갔습니다. 침투 후 수집한 파일을 빼내거나 공격 도구를 밀어 넣을 때, 도구가 없는 환경에서 쓰는 고전 기법 그대로입니다.
왜: 받는 쪽이 >로 저장하고 보내는 쪽이 <로 읽는다 — 방향만 외우면 됩니다. 단, 전송이 끝나도 nc는 "파일 끝"을 모릅니다. -w 1을 붙이거나 Ctrl+C로 끊어야 합니다 (벽 1).
3-3. 간이 포트 스캔 — -zv
Step 79의 스캐너를 nc 한 줄로 흉내 냅니다. 먼저 일부러 문을 하나 열어 두고(터미널 1: nc -l -p 5555), 터미널 2에서 스캔합니다.
입력
nc -zv -w 1 127.0.0.1 5550-5560
출력 (2026-09-09 실측):
nc: connect to 127.0.0.1 port 5550 (tcp) failed: Connection refused
nc: connect to 127.0.0.1 port 5551 (tcp) failed: Connection refused
nc: connect to 127.0.0.1 port 5552 (tcp) failed: Connection refused
nc: connect to 127.0.0.1 port 5553 (tcp) failed: Connection refused
nc: connect to 127.0.0.1 port 5554 (tcp) failed: Connection refused
Connection to 127.0.0.1 5555 port [tcp/*] succeeded!
nc: connect to 127.0.0.1 port 5556 (tcp) failed: Connection refused
(이하 생략)
읽는 법: -z는 데이터를 보내지 않고 연결만 시험하는 스캔 모드, -v는 결과를 말해 주는 상세 모드입니다. 5555번만 succeeded!이고 나머지는 Connection refused — Step 79에서 배운 "열림/닫힘" 판별 그대로입니다. 열린 것만 추리고 싶으면 nc -zv -w 1 127.0.0.1 5550-5560 2>&1 | grep succeeded처럼 걸러 보세요 (출력이 표준에러로 나오므로 2>&1이 필요합니다).
왜: nmap이 없는 환경에서 "포트 범위를 빠르게 훑는" 최소 기능입니다. 속도와 정보량은 nmap에 못 미치지만, 손에 익은 nc는 어디에나 있습니다.
3-4. 배너 그래빙 — 접속하자마자 나오는 자기 소개
먼저 실험용 배너 서버를 파이썬으로 만들어 직접 확인합니다.
입력 (banner_server.py)
import socket
srv = socket.socket()
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
srv.bind(("127.0.0.1", 2222))
srv.listen(1)
conn, addr = srv.accept()
conn.sendall(b"SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13\r\n")
conn.close(); srv.close()
입력
python3 banner_server.py & # 터미널 1
timeout 2 nc 127.0.0.1 2222 # 터미널 2
출력 (2026-09-09 실측):
SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13
읽는 법: 접속하자마자 한 줄이 떴습니다. 규약(SSH-2.0), 프로그램(OpenSSH), 버전(9.6p1), 배포판(Ubuntu)이 전부 들어 있습니다. 진짜 SSH 서버도 접속하면 똑같은 형식의 명함을 먼저 보냅니다.
실제 랩의 MS2를 향해 같은 일을 하면 (출력 예시 — 여러분의 랩에서 확인하세요):
nc MS2_IP 21
220 (vsFTPd 2.3.4)
nc MS2_IP 22
SSH-2.0-OpenSSH_4.7p1 Debian-8ubuntu1
읽는 법: vsFTPd 2.3.4 — Step 117에서 우리가 백도어를 심었던 바로 그 버전입니다. 배너 한 줄이 "어떤 취약점을 검색해야 하는지"를 알려 줍니다. 방어자 관점에서는, 버전을 그대로 알려 주는 배너가 곧 공격자의 길잡이라는 뜻이기도 합니다.
3-5. 수동 HTTP — 브라우저 없이 웹서버와 대화
파이썬 웹서버를 하나 열고, 브라우저 대신 nc로 요청을 보냅니다.
입력
mkdir -p /tmp/websrv && cd /tmp/websrv
echo "<h1>내 랩 웹서버</h1>" > index.html
python3 -m http.server 8080 --bind 127.0.0.1 & # 터미널 1
printf "GET / HTTP/1.0\r\n\r\n" | nc -w 2 127.0.0.1 8080 # 터미널 2
출력 (2026-09-09 실측):
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.12.3
Date: Wed, 09 Sep 2026 06:09:55 GMT
Content-type: text/html
Content-Length: 27
Last-Modified: Wed, 09 Sep 2026 06:09:55 GMT
<h1>내 랩 웹서버</h1>
읽는 법: 우리가 보낸 것은 GET / HTTP/1.0 한 줄과 빈 줄(\r\n\r\n)뿐입니다. 서버는 상태 줄(200 OK), 헤더들, 빈 줄, 본문 순서로 응답했습니다. 브라우저가 매번 하는 일의 날것입니다. 헤더의 Server: SimpleHTTP/0.6 Python/3.12.3도 일종의 배너 — 웹서버의 명함입니다.
왜: \r\n은 HTTP 규약의 줄바꿈 규칙(CRLF)이고, 요청의 끝은 빈 줄 하나로 알립니다. 이 형식을 손으로 써 본 사람은 나중에 웹 취약점 도구의 요청/응답 패널을 볼 때 겁먹지 않습니다.
4. 미션과 연습문제
미션 — nc 활용 노트 완성
Kali와 MS2(없으면 127.0.0.1 끼리)를 사용해 다음을 수행하고, 각 결과를 노트 한 장에 기록하세요:
- 두 터미널 사이 채팅으로 세 줄 이상 대화를 주고받는다
- 임의의 텍스트 파일을 만들어 nc로 전송하고, 양쪽의 md5sum이 같은지 확인한다
- MS2(또는 127.0.0.1)의 20~100번 포트를
nc -zv로 훑고 열린 포트만 grep으로 추린다 - 열린 포트 중 두 곳에 접속해 배너를 받아 서비스 이름과 버전을 노트에 적는다
- MS2의 80번 포트(없으면 3-5의 로컬 웹서버)에 수동으로
GET /요청을 보내고 응답의 상태 줄과 Server 헤더를 베낀다
연습문제
문제 1. nc의 리스너 모드와 클라이언트 모드를 만드는 옵션과 형식을 각각 말해 보세요.
문제 2. 파일 전송에서 받는 쪽 nc -l -p 5556 > got.txt와 보내는 쪽 nc 127.0.0.1 5556 < secret.txt의 >, <는 각각 무슨 일을 하나요?
문제 3. nc -zv 스캔에서 -z와 -v의 역할을 구분해 설명하고, 출력이 표준에러로 나온다는 것이 왜 grep과 함께 2>&1을 필요로 하는지 말해 보세요.
문제 4. 배너 그래빙으로 vsFTPd 2.3.4를 얻었다고 합시다. 공격자와 방어자가 이 한 줄로 각각 무엇을 하는지 설명해 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
노트의 예 (환경에 따라 값은 다릅니다):
[1] 채팅: 터미널1 nc -l -p 5555 / 터미널2 nc 127.0.0.1 5555 — 양방향 확인
[2] 파일 전송: secret.txt 77바이트 → got.txt, md5sum 일치 확인
[3] 스캔: nc -zv -w 1 MS2_IP 20-100 2>&1 | grep succeeded
→ 21, 22, 23, 25, 53, 80 열림 (예시 — 랩마다 다름)
[4] 배너: 21번 vsFTPd 2.3.4 / 22번 OpenSSH_4.7p1
[5] HTTP: "HTTP/1.1 200 OK" + Server 헤더 베껴 둠
검증하는 법: ① 파일 전송의 md5sum 두 줄이 일치하는가. ② 스캔 결과에서 열린 포트만 추렸는가. ③ 배너에서 버전 숫자까지 적었는가. ④ HTTP 응답의 상태 줄(200 OK 등)을 읽을 수 있는가.
연습문제 해답
문제 1 해답. 리스너는 nc -l -p 포트(문을 열고 기다림), 클라이언트는 nc 대상IP 포트(문을 두드림)입니다. 연결 후에는 어느 쪽에서내도 상대에게 흐릅니다.
문제 2 해답. >는 nc가 네트워크에서 받은 바이트를 화면 대신 파일에 쓰는 출력 리다이렉션, <는 파일의 내용을 nc의 입력으로 흘려보내는 입력 리다이렉션입니다. 셸의 리다이렉션이 nc와 만나 파일 전송이 됩니다.
문제 3 해답. -z는 데이터를 보내지 않고 연결 성립만 시험하는 스캔 모드이고, -v는 성공/실패를 문장으로 출력하게 하는 상세 모드입니다. nc는 결과 메시지를 표준출력이 아니라 표준에러로내므로, 파이프로 grep에 넘기려면 2>&1로 표준에러를 표준출력에 합쳐야 합니다.
문제 4 해답. 공격자는 "vsFTPd 2.3.4 취약점"을 검색해 Step 117의 백도어 익스플로잇을 찾아냅니다 — 배너가 공격의 목차입니다. 방어자는 같은 배너를 보고 "이 버전이 알려진 취약 버전이구나"를 인지해 업데이트하거나, 배너가 버전을 숨기도록 설정을 바꿉니다 — 배너가 업데이트 체크리스트입니다.
완료 기준 체크리스트
- [ ] 리스너/클라이언트 두 모드를 자유롭게 열고 접속할 수 있다
- [ ] nc로 파일을 전송하고 md5sum 일치를 확인했다
- [ ]
nc -zv로 범위 스캔을 하고 열린 포트만 추렸다 - [ ] 배너에서 서비스 이름과 버전을 읽었다
- [ ]
printf ... | nc로 수동 HTTP 요청을 성공했다 - [ ]
-w가 필요한 이유(연결이 자동으로 안 끊김)를 설명할 수 있다 - [ ] 내 nc의 계열(OpenBSD인지)과
-e유무를 확인했다
6. 흔한 실수와 해결
벽 1. 파일 전송이 끝났는데 nc가 안 끝난다
증상: 파일을 다 보냈는데 양쪽 터미널이 멍하니 기다립니다.
원인: nc는 "파일의 끝"을 모릅니다. 연결은 살아 있고, 닫으라는 신호를 받지 못했을 뿐입니다.
해결: 보내는 쪽에 -w 1을 붙이거나, 확인 후 Ctrl+C로 끊으세요. 파일 크기와 md5sum으로 전송 완료를 검증하는 습관을 들이면 안심됩니다.
벽 2. Connection refused만 뜬다
증상 (2026-09-09 실측 메시지 그대로): nc: connect to 127.0.0.1 port 5556 (tcp) failed: Connection refused
원인: 그 포트에 리스너가 없습니다. 리스너를 먼저 켰는지, 포트 번호가 양쪽에서 같은지 확인하세요.
해결: 받는 쪽(nc -l -p 포트)이 먼저 떠 있어야 합니다. 스캔에서는 이 메시지가 "닫힘"이라는 정상 판별 결과입니다.
벽 3. -e가 안 먹힌다
증상: nc -l -p 4444 -e /bin/bash가 옵션 오류로 실패합니다.
원인 (2026-09-09 실측): Kali/우분투 기본 nc는 OpenBSD 계열로 -e가 없습니다. nc -h의 옵션 목록에 e가 없는 것으로 확인할 수 있습니다.
해결: 쉘 붙이기가 필요하면 전통 nc(Traditional)나 ncat을 쓰거나, FIFO 파이프를 이용한 우회법을 씁니다. 오늘의 채팅·전송·스캔은 -e 없이 전부 가능합니다.
벽 4. 수동 HTTP 요청에 아무 응답도 없다
증상: printf "GET / HTTP/1.0\n\n" | nc ...를 보냈는데 침묵합니다.
원인: HTTP는 줄바꿈이 CRLF(\r\n)입니다. \n만내면 서버가 요청이 끝났다고 인식하지 못해 기다립니다.
해결: printf "GET / HTTP/1.0\r\n\r\n"처럼 \r\n을 쓰고, 끝에 빈 줄(두 번 연속)을 꼭 보내세요. 그래도 막히면 -w 2로 기다림의 상한을 정합니다.
벽 5. grep으로 걸렀는데 아무 줄도 안 나온다
증상: nc -zv 127.0.0.1 1-100 | grep succeeded가 텅 빕니다.
원인: nc의 결과 메시지는 표준에러(stderr)로 나갑니다. 파이프는 표준출력만 넘깁니다.
해결: nc -zv -w 1 127.0.0.1 1-100 2>&1 | grep succeeded처럼 2>&1로 합치세요. 그리고 정말 열린 포트가 하나도 없으면 빈 결과가 정상입니다 — 먼저 nc -l -p로 문을 하나 열어 두고 시험하세요.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 리스너 | nc -l -p 포트 — 문을 열고 기다리는 쪽 |
| 클라이언트 | nc 대상 포트 — 문을 두드리는 쪽 |
| 배너 | 접속 즉시 서비스가 보내는 자기 소개 (이름·버전) |
| 간이 스캔 | -zv로 연결 성립만 시험해 열림/닫힘 판별 |
| 수동 프로토콜 | 규약의 첫 마디를 손으로 타이핑해 서비스와 대화 |
| nc 계열 차이 | OpenBSD nc는 -e 없음 — 같은 이름, 다른 옵션 |
오늘의 명령어
| 명령 | 하는 일 |
|---|---|
nc -l -p 5555 |
5555번에서 리스너 열기 |
nc 127.0.0.1 5555 |
리스너에 접속 (채팅) |
nc -l -p 5556 > got.txt |
받는 파일로 저장 |
nc -w 1 대상 5556 < secret.txt |
파일 전송 (1초 대기 후 종료) |
nc -zv -w 1 대상 20-100 |
포트 범위 간이 스캔 |
printf "GET / HTTP/1.0\r\n\r\n" | nc 대상 80 |
수동 HTTP 요청 |
md5sum 파일1 파일2 |
전송 무결성 확인 |
명령어보다 중요한 감각
nc를 다루는 능력은 "네트워크가 결국 바이트의 흐름이다"를 몸으로 아는 것입니다. 채팅과 파일 전송과 쉘이 모두 같은 파이프라는 것을 오늘 확인했습니다 — 그래서 방어자는 내부 서버에 nc 같은 도구가 있는지, 비정상적인 리스너가 열려 있지 않은지(ss -tlnp)를 감시합니다.
그리고 배너 그래빙에서 보았듯, 서비스는 접속만으로 자기 버전을 알려 주는 경우가 많습니다. 공격자의 정찰이 왜 빠른지, 방어자가 왜 버전 노출을 줄이는지 — 같은 한 줄을 두 입장에서 읽는 훈련이 이 Level 전체의 눈입니다.
전부 체크되면 Step 119 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.