Step 119. netcat 완전 정복 — 네트워크의 스위스 아미 나이프

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 끼리)를 사용해 다음을 수행하고, 각 결과를 노트 한 장에 기록하세요:

  1. 두 터미널 사이 채팅으로 세 줄 이상 대화를 주고받는다
  2. 임의의 텍스트 파일을 만들어 nc로 전송하고, 양쪽의 md5sum이 같은지 확인한다
  3. MS2(또는 127.0.0.1)의 20~100번 포트를 nc -zv로 훑고 열린 포트만 grep으로 추린다
  4. 열린 포트 중 두 곳에 접속해 배너를 받아 서비스 이름과 버전을 노트에 적는다
  5. 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 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.