Step 98. Bandit 11~15 — 인코딩과 네트워크 접속

Step 98. Bandit 11~15 — 인코딩과 네트워크 접속

Level 2 — 보안 입문과 공격 스킬 기초 | 난이도 ★★☆☆☆ | 예상 소요 시간 3시간

전제: Step 96~97의 풀이 사이클과 find 기술, Bandit 10→11 비밀번호를 마쳤다. 포트의 개념을 안다.

  • 준비물: SSH 접속 환경, 비밀번호 기록, 로컬 실험용 리눅스/WSL(tr, nc, openssl 포함).
  • ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다.
  • 합법 연습장 안내: OverTheWire Bandit은 공식적으로 공격 연습이 허가된 학습 플랫폼입니다. ncopenssl s_client로 접속하는 대상은 Bandit 서버와 내 컴퓨터에서 직접 띄운 서비스뿐입니다.

Bandit 중반부부터 문제의 결이 바뀝니다. 초반이 "파일 찾기"였다면 이제는 변환과 대화입니다. 꼬인 문자열을 풀고(인코딩), 네트워크 포트에 직접 말을 겁니다(소켓). 이 구간이 중요한 이유는 단순합니다 — 여기서 쓰는 도구들이 실제 현장의 맨손 무기이기 때문입니다. nc는 "해커의 스위스 아미 나이프"라 불립니다. 아무 포트에나 접속해 텍스트를 주고받는 이 단순함이, 웹 취약점 수동 테스트 전부의 출발점입니다.


1. 학습 목표

이 챕터를 끝내면 다음을 할 수 있습니다:

  • tr로 ROT13 등 문자 치환을 수행하고 원리를 설명한다
  • 겹겹으로 포장된 파일을 "판정 → 해제" 루프로 끝까지 깐다
  • nc로 임의의 포트에 접속해 텍스트를 주고받는다
  • openssl s_client로 TLS 포트와 대화하고, 평문 포트와 구분한다
  • SSH 개인키로 접속하고, 키 파일 권한 600의 이유를 설명한다

2. 배경 지식 — 오늘의 도구와 개념

오늘의 도구 한눈에 보기

구분 내용
언어·환경 리눅스 셸(Bandit 서버 + 로컬 WSL)
오늘의 명령 tr, xxd -r, gzip -d, bzip2 -d, tar -xf, nc, openssl s_client, ssh -i, chmod 600
필요한 개념 치환 암호(ROT13), 매직 넘버와 압축 포맷, 소켓과 포트, TLS 핸드셰이크, 개인키 인증
오늘의 산출물 Bandit 11→16 비밀번호 체인 + 도구 카드 3장(tr, nc, s_client)

2-1. ROT13 — 알파벳을 13칸 미는 치환

ROT13은 알파벳을 13글자씩 밀어 바꾸는 치환 암호입니다. A→N, B→O, …, N→A. 알파벳이 26글자라 두 번 적용하면 원래대로 돌아옵니다 — 변환과 되돌리기가 같은 동작입니다.

진짜 암호는 아닙니다. 규칙이 공개되어 열쇠 없이 누구나 되돌리니까요. 하지만 "치환"이라는 부류의 입구이고, 리눅스의 tr은 이 변환을 한 줄로 수행합니다. Base64(Step 97)와 마찬가지로 "꼬여 있다"가 "안전하다"를 뜻하지 않는다는 것을 다시 확인하는 구간입니다.

2-2. nc — 네트워크의 cat

nc(netcat)은 "네트워크의 cat"입니다. cat이 파일을 읽어 화면에 뿌리듯, nc는 포트에 접속해 보내고 받습니다.

nc 주소 포트      # 그 포트에 접속해 대화 시작
nc -l -p 포트     # 그 포트에서 기다림 (내가 서버가 됨)

Bandit의 "localhost의 N번 포트에 현재 비밀번호를 보내라"는 문제는 전부 이 도구로 풉니다. "내가 서비스에 직접 말을 건다"는 이 구조가 나중에 웹 요청을 손으로 만드는 수동 테스트의 기본 자세가 됩니다.

2-3. openssl s_client — 암호화 통로로 말 걸기

어떤 서비스는 평문이 아니라 TLS로만 대화를 받습니다. 그런 포트에 nc로 접속하면 대화가 성립하지 않습니다. 그때 쓰는 것이:

openssl s_client -connect 주소:포트

"암호화 통로를 깔아 주는 nc"라고 생각하면 됩니다. TLS 핸드셰이크(암호 방식 협상과 키 교환)를 대신 처리해 주고, 그 이후의 대화는 여러분이 치는 그대로 전달됩니다. 암호화는 통로를 지킬 뿐, 통로 안의 내용을 바꾸지 않습니다.

2-4. SSH 개인키 — 비밀번호 대신 신분증

지금까지는 비밀번호로 접속했지만, SSH는 개인키(private key) 파일로도 접속합니다. 비밀번호가 "아는 것"이라면 키 파일은 "가진 것"입니다. -i 옵션으로 키를 지정합니다.

ssh -i 키파일 사용자@주소 -p 포트

단, 키 파일 권한이 열려 있으면(남이 읽을 수 있으면) SSH가 스스로 사용을 거부합니다 — "이미 새어 나갔을 수 있는 키"로 보기 때문입니다. 처방은 chmod 600, "주인만 읽기·쓰기"입니다.


3. 따라 하기

3-1. Level 11 → 12 — ROT13을 tr로 풀기

이번 data.txt는 알파벳이 13칸 밀린 문자열입니다.

입력 (서버 안에서, 출력 예시)

cat data.txt | tr 'A-Za-z' 'N-ZA-Mn-za-m'

로컬 재현 (2026-09-09 WSL 실측):

echo 'Gur cnffjbeq vf jbxrrq' | tr 'A-Za-z' 'N-ZA-Mn-za-m'
The password is wokeed

읽는 법: tr '원래글자들' '바꿀글자들'은 문자를 일대일로 치환합니다. 대문자 A~Z를 N~ZA~M으로 이은 배열이 13칸 밀기의 수식입니다. 같은 명령을 한 번 더 돌리면 다시 꼬입니다 — 두 번 하면 원래대로라는 성질을 직접 확인해 보세요.

: tr은 ROT13 전용이 아니라 범용 치환 도구입니다. 예를 들어 echo 'hello world' | tr 'a-z' 'A-Z'HELLO WORLD를 냅니다 (2026-09-09 실측). 텍스트 가공의 단골 도구로 기억해 두세요.

3-2. Level 12 → 13 — 겹겹의 포장 까기

이번 파일은 16진수 덤프이고, 되돌리면 압축 파일, 그걸 풀면 또 다른 압축 파일인 양파 구조입니다.

반복 레시피file의 판정에 따라 맞는 도구로 한 겹씩 (서버 안, 출력 예시):

mkdir /tmp/mywork123 && cd /tmp/mywork123
cp ~/data.txt .
xxd -r data.txt > out1       # 16진수 덤프 → 바이너리
file out1                    # 이번 껍질의 정체는?
# gzip이면:  mv out1 out2.gz && gzip -d out2.gz
# bzip2이면: mv out2 out3.bz && bzip2 -d out3.bz
# tar이면:   mv out3 out4.tar && tar -xf out4.tar

이 루프를 내 컴퓨터에서 통째로 재현했습니다. bzip2 → gzip → tar 세 겹으로 포장한 파일입니다 (2026-09-09 WSL 실측):

echo 'The final password is onion_core!' > plain.txt
tar -cf stage1.tar plain.txt
gzip -c stage1.tar > stage2.gz
bzip2 -c stage2.gz > stage3.bz
file stage3.bz

출력 (2026-09-09 실측, 한 겹씩 까는 과정):

stage3.bz: bzip2 compressed data, block size = 900k
(→ bzip2 -d 후)  step: gzip compressed data, was "stage1.tar", ...
(→ gzip -d 후)   step: POSIX tar archive (GNU)
(→ tar -xf 후)   plain.txt 등장 → cat하면 "The final password is onion_core!"

읽는 법: 매 단계 file이 "이번 껍질의 정체"를 말해 줍니다. 판정하고 → 맞는 도구로 까고 → 다시 판정하는 이 루프가 파일 포렌식의 기본형입니다. gzip -d 등은 확장자(.gz)를 요구하므로 mv로 이름을 먼저 붙이는 것이 포인트입니다.

3-3. Level 13 → 14 — 개인키로 접속하기

이번 홈 폴더에는 비밀번호 대신 sshkey.private라는 키 파일이 있습니다.

입력 (서버 안에서, 출력 예시)

chmod 600 sshkey.private
ssh -i sshkey.private bandit14@localhost -p 2220

권한이 열려 있으면 SSH가 이렇게 거부합니다 (출력 예시):

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0664 for 'sshkey.private' are too open.

읽는 법: 키는 신분증입니다. 남이 복사할 수 있는 신분증은 시스템이 신뢰하지 않습니다. chmod 600으로 "주인만"으로 좁히면 접속됩니다 — 비밀번호를 한 번도 치지 않고.

: 실무 서버는 비밀번호 대신 키 접속을 강제하는 경우가 많고, 침해 사고에서 키 파일 유출은 비밀번호 유출과 동급의 재앙입니다.

3-4. Level 14 → 15 — nc로 서비스와 대화

목표: "localhost의 30000번 포트에 현재 비밀번호를 보내라."

입력 (서버 안에서, 출력 예시)

cat /etc/bandit_pass/bandit14    # bandit14로 접속해 있으면 읽을 수 있음
nc localhost 30000

접속되면 조용히 입력을 기다립니다. 비밀번호를 붙여 넣고 Enter — Correct!와 함께 다음 비밀번호가 돌아옵니다.

이 대화를 내 컴퓨터에서 통째로 재현했습니다. 한쪽은 기다리는 nc(서버), 한쪽은 접속하는 nc입니다 (2026-09-09 WSL 실측):

# 터미널 1 (기다리는 쪽)
echo 'Correct! next_password_is_abc123' | nc -l -p 34567
# 터미널 2 (접속하는 쪽)
echo 'current_password_xyz' | nc 127.0.0.1 34567

출력 (2026-09-09 실측, 터미널 2 화면):

current_password_xyz
Correct! next_password_is_abc123

읽는 법: 브라우저도 전용 클라이언트도 없이, 소켓 위의 맨텍스트로 서비스와 사람이 직접 대화했습니다. 이것이 "프로토콜을 직접 말하는" 경험의 시작입니다.

3-5. Level 15 → 16 — TLS 포트에 말 걸기

같은 방식이지만 이번 포트(30001)는 SSL/TLS 암호화 필수입니다.

입력 (서버 안에서, 출력 예시)

openssl s_client -connect localhost:30001

핸드셰이크 로그가 지나간 뒤 조용해지면 비밀번호를 입력합니다. 이 과정을 로컬 TLS 서버를 띄워 실측했습니다 (2026-09-09 WSL 실측 — 자체 서명 인증서를 만들고 openssl s_server로 시험용 서버를 열었습니다):

출력 (2026-09-09 실측, 핸드셰이크 로그 일부):

CONNECTED(00000003)
---
Certificate chain
 0 s:CN = localhost
   i:CN = localhost
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIUe/J10N2ZHt4zwerNUs5cAdwQ3gwwDQYJKoZIhvcNAQEL
...

통로가 깔린 뒤 보낸 문자열은 그대로 전달됩니다. 실측에서 my_password_123을 보내자 서버(에코 모드)가 받은 내용을 뒤집어 돌려주며 연결이 성립했음을 보여 줬습니다.

읽는 법: 출력 위쪽의 Certificate, Cipher 정보가 TLS 핸드셰이크의 흔적입니다. 통로가 깔린 뒤의 대화는 nc와 똑같이 평문입니다 — 암호화는 통로를 지키지, 내용을 바꾸지 않습니다.

3-6. nc로 웹 서버와 말하기 — HTTP를 손으로 치다

nc의 진가는 웹에서 드러납니다. 내 컴퓨터에 간이 웹 서버를 띄우고 손으로 요청을 칩니다.

입력 (터미널 1)

cd /tmp && python3 -m http.server 8000

입력 (터미널 2 — GET 줄을 치고 Enter를 두 번)

nc 127.0.0.1 8000
GET / HTTP/1.0

읽는 법: 여러분이 방금 브라우저의 일을 손으로 했습니다. GET / HTTP/1.0 한 줄과 빈 줄이 HTTP 요청의 최소형입니다. GET /없는파일 HTTP/1.0을 내면 404 응답이 직접 눈에 들어옵니다 — 상태 코드가 교과서가 아니라 대화의 답장임을 체감하는 순간입니다.

: 웹 취약점 테스트는 결국 "이 요청을 이상하게 바꾸면 서버가 어떻게 반응할까"의 반복입니다. 오늘 손으로 친 세 줄이 그 모든 실험의 원형입니다.


4. 미션과 연습문제

미션 — Bandit 11→16 체인과 도구 카드

  1. bandit15까지 비밀번호 체인을 완성하고 각 레벨을 write-up 형식으로 기록합니다
  2. Level 12의 양파 까기를 단계별로 전부 기록합니다 — 어떤 껍질(포맷)이 몇 겹이었는지
  3. 위키에 도구 카드 3장을 씁니다 — tr, nc, openssl s_client. 각각 "무엇을 하는가 / 대표 예시 / 언제 꺼내는가"
  4. 로컬에서 nc 두 터미널 대화를 재현하고 스크린샷이나 로그를 남깁니다

연습문제

문제 1. ROT13을 두 번 적용하면 원래 문자열로 돌아오는 이유를 알파벳 개수와 연결해 설명해 보세요.

문제 2. gzip -d out1이 "unknown suffix" 오류를 내는 이유와 해결 절차를 말해 보세요.

문제 3. nc로 TLS 전용 포트에 접속하면 어떻게 되며, 그 이유는 무엇인가요?

문제 4. SSH가 권한 644인 개인키를 거부하는 이유를 "신분증" 비유로 설명해 보세요.


5. 모범 답안과 완료 기준

미션 모범 답안

체인의 뼈대 (서버 안 명령은 출력 예시):

cat data.txt | tr 'A-Za-z' 'N-ZA-Mn-za-m'              # L11→12
# L12→13: xxd -r → file → (gzip|bzip2|tar) 해제 루프 반복
chmod 600 sshkey.private && ssh -i sshkey.private bandit14@localhost -p 2220   # L13→14
cat /etc/bandit_pass/bandit14 && nc localhost 30000    # L14→15
openssl s_client -connect localhost:30001              # L15→16

검증하는 법: ① 도구 카드 3장에 "직접 실행한 예시"가 붙어 있는가. ② 양파 까기 기록에 껍질 순서(gzip/bzip2/tar의 등장 순서)가 적혀 있는가. ③ 로컬 nc 대화에서 양쪽 터미널의 송수신이 교차 확인되는가.

연습문제 해답

문제 1 해답. 알파벳은 26글자이고 13은 그 정확히 절반입니다. 13칸을 두 번 밀면 26칸, 즉 한 바퀴 돌아 제자리입니다. 그래서 변환과 되돌리기가 같은 명령입니다 (3-1절 실측의 tr 배열이 대칭인 이유).

문제 2 해답. gzip -d는 파일 끝표(.gz)로 포맷을 확인하는데, 이름에 그 표시가 없기 때문입니다. mv out1 out1.gz로 확장자를 붙인 뒤 해제하면 됩니다. 매 단계 file로 정체를 먼저 확인하는 습관이 이 사고를 예방합니다.

문제 3 해답. 서버는 TLS 핸드셰이크(암호 협상 메시지)를 기대하는데 nc는 그냥 텍스트를 내므로 대화가 성립하지 않습니다. 이상한 바이트가 오거나 침묵 뒤 연결이 끊깁니다. "이 포트가 평문인가 TLS인가"를 구분하는 감각이 실전 정찰의 기본입니다.

문제 4 해답. 권한 644는 "다른 사용자도 읽을 수 있는" 상태입니다. 남이 복사할 수 있는 신분증은 이미 위조됐을 수 있으니, 시스템이 스스로 사용을 거부하는 것입니다. chmod 600(주인만)으로 좁혀야 신뢰가 회복됩니다.

완료 기준 체크리스트

  • [ ] tr로 ROT13을 해독하고 치환의 원리를 설명할 수 있다
  • [ ] 겹겹의 압축 파일을 file + 해제 도구 루프로 끝까지 깔 수 있다
  • [ ] nc로 포트에 접속해 텍스트를 주고받을 수 있다
  • [ ] openssl s_client로 TLS 포트와 대화할 수 있다
  • [ ] 평문 포트와 TLS 포트를 두 도구로 구분해 시험할 수 있다
  • [ ] SSH 개인키 접속과 권한 600의 이유를 설명할 수 있다
  • [ ] 미션: 체인 완성과 도구 카드 3장을 완성했다

6. 흔한 실수와 해결

벽 1. ssh -i가 키를 거부한다

증상 (출력 예시):

WARNING: UNPROTECTED PRIVATE KEY FILE!
Permissions 0664 for 'sshkey.private' are too open.

원인: 키 파일을 남도 읽을 수 있습니다. SSH는 새었을 수 있는 키를 신뢰하지 않습니다.
해결: chmod 600 키파일. 서버 안에서 권한 변경이 막혀 있으면 키를 내 /tmp로 복사해 거기서 고치세요.

벽 2. 압축 해제가 "unknown suffix"로 실패한다

증상 (실측 계열 메시지):

gzip: out1: unknown suffix -- ignored

원인: gzip -d.gz 확장자를 요구합니다.
해결: mv out1 out1.gz && gzip -d out1.gz. 그리고 매 단계 file 확인을 생략하지 마세요 — 어느 겹에서 꼬였는지 모르게 됩니다.

벽 3. nc에 접속했는데 깜깜무소식이다

증상: 접속 후 아무 출력이 없습니다.
원인: 그게 정상인 경우가 많습니다 — 서비스가 먼저 말을 안 거는 종류라 여러분이 먼저 입력해야 합니다.
해결: 비밀번호를 입력하고 Enter. 그래도 침묵이면 포트 번호와 "현재 레벨의 비밀번호를 썼는지"를 확인하세요.

벽 4. openssl s_client가 엄청난 출력을 쏟는다

증상: 인증서와 암호 목록이 화면을 채웁니다.
원인: 정상입니다 — 핸드셰이크 전 과정을 보여 주는 것입니다 (3-5절 실측 출력 참조).
해결: 출력이 멈추고 조용해지면 그때 입력하세요. 출력을 줄이고 싶으면 -quiet 옵션을 붙입니다.

벽 5. 기다리는 nc와 접속하는 nc가 서로를 모른다

증상: 양쪽 터미널에 아무 일도 안 일어납니다.
원인: 포트 번호가 다르거나, 기다리는 쪽(nc -l)을 나중에 띄웠거나.
해결: 순서는 ① nc -l -p 포트로 대기 시작 → ② 다른 터미널에서 접속 → ③ 같은 포트 번호. localhost라 방화벽 변수가 없으니 이 셋만 맞으면 됩니다 (3-4절 실측 절차 그대로).


7. 정리

오늘의 개념

개념 한 줄 설명
ROT13 알파벳 13칸 치환 — 두 번 하면 원래대로
치환 암호 글자를 일대일로 바꾸는 변환 — 열쇠 없이 되돌릴 수 있으면 암호가 아님
소켓 대화 포트에 직접 말을 거는 통신의 최소 단위
TLS 핸드셰이크 암호 방식 협상과 키 교환 — 통로만 지키고 내용은 그대로
개인키 인증 "가진 것"으로 증명하는 로그인 — 권한 600이 신뢰의 조건

오늘의 명령어

명령 하는 일
tr 'A-Za-z' 'N-ZA-Mn-za-m' ROT13 치환/복원
xxd -r 파일 > 출력 16진수 덤프를 바이너리로
gzip -d / bzip2 -d / tar -xf 압축 한 겹씩 해제
nc 주소 포트 포트에 접속해 대화
nc -l -p 포트 포트에서 기다리는 서버 되기
openssl s_client -connect 주소:포트 TLS 통로로 대화
ssh -i 키파일 사용자@주소 개인키로 접속
chmod 600 파일 주인만 읽기·쓰기

명령어보다 중요한 감각

오늘의 도구 셋은 하나의 질문으로 묶입니다 — "데이터의 겉모습이 바뀌었을 때, 어떻게 원래와 대화할 것인가?" 치환이면 tr, 포장이면 해제 도구, 암호화 통로면 s_client. 겉모습을 겁내지 않는 것이 이 구간의 수확입니다. 그리고 기억할 문장: "꼬여 있다"는 "안전하다"가 아닙니다. 되돌리는 명령이 있는 것은 전부 변환이고, 변환은 보호가 아닙니다. 파일을 넘어 네트워크로, 읽기를 넘어 대화로 — 오늘부터 여러분의 손은 서비스에 직접 닿습니다.


전부 체크되면 Step 98 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.