리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.
리눅스 시스템 기초 49 / 50 · Part 5. 네트워크·보안·SOC
실습 환경: 21편 샘플 로그(secure, RHEL 형식) · 모든 집계 결과는 샘플 로그에 명령을 실행한 실제 출력
이전 글: 48. Linux 로그와 journalctl
지금까지 배운 것을 하나의 사건에 적용할 차례다. 이번 글은 SSH 무차별 대입(Brute Force) 을 로그만으로 분석한다.
분석 대상은 21편에서 만든 샘플 /var/log/secure다(RHEL 형식, 176줄). 이 로그에는 정상 관리자의 접속, 인터넷에서 들어오는 산발적인 시도, 그리고 하나의 실제 공격 흐름 이 섞여 있다. 목표는 다음 질문에 근거를 가지고 답하는 것이다.
사용하는 도구는 21~28편의 grep, awk, sort, uniq, 쉘 스크립트가 전부다. Ubuntu라면 파일만 /var/log/auth.log로 바꾸면 같은 방법이 통한다(메시지 형식이 같다).
| 유형 | 특징 | 로그 모양 |
|---|---|---|
| 무차별 대입 (Brute Force) | 한 계정에 비밀번호를 대량으로 시도 | 같은 계정 Failed password 반복 |
| 사전 대입 (Dictionary) | 흔한 계정명 × 흔한 비밀번호 | Invalid user admin/test/oracle… |
| 패스워드 스프레이 (Spraying) | 많은 계정에 비밀번호 1~2개씩, 느리게 | 계정은 많고 계정당 실패는 적음 |
| 크리덴셜 스터핑 | 유출된 계정·비밀번호 쌍 재사용 | 실패 적고 첫 시도에 성공 하기도 |
MITRE ATT&CK에서는 T1110(Brute Force)과 그 하위 기법(.001 Password Guessing, .003 Password Spraying, .004 Credential Stuffing)으로 분류된다. 성공한 뒤 그 계정을 쓰는 것은 T1078(Valid Accounts)이다.
| 지표 | 계산 방법 | 의미 |
|---|---|---|
| 출발지별 실패 수 | Failed password의 IP 집계 | 누가 가장 많이 시도했나 |
| 시도 기간·속도 | 첫 시각 ~ 마지막 시각, 분당 횟수 | 자동화 도구 여부 |
| 시도 계정 수·종류 | 계정명 집계, Invalid user 비율 | 사전 대입인가, 표적 계정인가 |
| 실패 후 성공 | 같은 IP의 Accepted | 침해 여부의 핵심 |
| 성공 이후 행위 | sudo, su, 세션 시간 | 공격 목적·진행 단계 |

분석은 넓게 → 좁게 → 시간순 으로 진행한다.
[1. 전체 규모] 실패 총계, 출발지별 집계
↓
[2. 대상 선정] 가장 많은 출발지 · 짧은 기간 · 많은 계정
↓
[3. 성공 여부] 그 출발지의 Accepted
↓
[4. 사후 행위] 성공 세션의 PID·계정으로 이후 로그 추적
↓
[5. 판단·대응] 침해 여부 결론, 차단·계정 조치, 탐지 규칙
$ grep -c 'Failed password' secure
113
$ grep 'Failed password' secure | grep -oE 'from [0-9.]+' | awk '{print $2}' | sort | uniq -c | sort -rn
95 10.0.0.128
9 203.0.113.7
4 203.0.113.88
4 203.0.113.150
1 203.0.113.45
113회 중 95회(84%)가 10.0.0.128 한 곳 이다.
한 번에 보기 위해 분석 스크립트(bf_analyze.sh, 7-1절)의 두 번째 항목을 실행한 실제 결과다.
== 2. 출발지별 시작·끝·계정 수 ==
10.0.0.128 95회 02:10:09 ~ 02:19:15 계정 8개
203.0.113.7 9회 03:50:16 ~ 23:07:10 계정 4개
203.0.113.88 4회 03:06:43 ~ 21:03:48 계정 3개
203.0.113.150 4회 01:47:45 ~ 21:59:16 계정 3개
203.0.113.45 1회 21:14:49 ~ 21:14:49 계정 1개
| 출발지 | 판단 |
|---|---|
| 10.0.0.128 | 9분 동안 95회 (분당 약 10회), 계정 8개 → 자동화된 집중 공격 |
| 203.0.113.x | 하루 종일 몇 번씩 → 인터넷에 노출된 SSH가 늘 받는 배경 소음 |
배경 소음을 모두 사건으로 다루면 관제가 마비된다. 양·속도·결과 로 우선순위를 나눈다.
$ grep 'Failed password' secure | grep 10.0.0.128 | awk '{print substr($3,1,5)}' | uniq -c
10 02:10
8 02:11
10 02:12
8 02:13
10 02:14
10 02:15
11 02:16
14 02:17
10 02:18
4 02:19
$ grep 'Failed password' secure | grep 10.0.0.128 \
| sed -E 's/.*for (invalid user )?([^ ]+) from.*/\2/' | sort | uniq -c | sort -rn
40 root
25 devuser
5 ubuntu
5 test
5 postgres
5 oracle
5 guest
5 admin
root 40회: PermitRootLogin no라면 성공할 수 없지만 공격자는 일단 시도한다.admin, test, oracle 등 6개 계정은 각 5회 → 이 서버에 없는 계정(Invalid user 30회). 흔한 계정명 목록으로 사전 대입한 것이다.devuser 25회 → 실제로 존재하는 계정이다. 공격자가 계정명을 알고 있었거나, 사전 목록에 있었다.== 4. 성공 로그인과 직전 실패 수 ==
Sep 23 02:19:21 password devuser 10.0.0.128 | 이전 실패 95회
Sep 23 09:03:20 publickey roror 10.0.0.10 | 이전 실패 0회
Sep 23 13:03:56 publickey roror 10.0.0.10 | 이전 실패 0회
Sep 23 18:17:26 publickey roror 10.0.0.10 | 이전 실패 0회
02:19:15 마지막 실패, 6초 뒤 02:19:21 Accepted password for devuser. 무차별 대입이 성공 했다. 정상 관리자 roror는 업무 시간에 공개키로, 관리 PC(10.0.0.10)에서만 접속하므로 뚜렷이 구분된다.
$ grep -E 'sshd\[5224\]|devuser' secure | grep -v 'Failed password'
Sep 23 02:19:21 rocky sshd[5224]: Accepted password for devuser from 10.0.0.128 port 51514 ssh2
Sep 23 02:19:21 rocky sshd[5224]: pam_unix(sshd:session): session opened for user devuser(uid=1001) by (uid=0)
Sep 23 02:21:21 rocky sudo[5233]: devuser : user NOT in sudoers ; TTY=pts/1 ; PWD=/home/devuser ; USER=root ; COMMAND=/bin/bash
Sep 23 02:22:21 rocky su[5270]: pam_unix(su-l:auth): authentication failure; logname=devuser uid=1001 euid=0 tty=pts/1 ruser=devuser rhost= user=root
Sep 23 02:22:21 rocky su[5270]: FAILED SU (to root) devuser on pts/1
Sep 23 02:31:21 rocky sshd[5224]: pam_unix(sshd:session): session closed for user devuser
| 시각 | 행위 | 의미 |
|---|---|---|
| 02:19:21 | 로그인 성공 | 초기 접근 (T1078) |
| 02:21:21 | sudo /bin/bash → NOT in sudoers | root 쉘 획득 시도 실패 (19·20편) |
| 02:22:21 | su → FAILED SU (to root) | root 비밀번호 추측 실패 |
| 02:31:21 | 세션 종료 (12분) |
권한 상승은 로그상 실패했다. 하지만 "실패했으니 끝"이 아니다. 12분 동안 devuser 권한으로 읽을 수 있는 모든 것(홈 디렉터리, 설정 파일, /tmp)에 접근했을 수 있고, 같은 IP가 02:30부터 웹 서버를 스캔한 기록도 있다(50편).
샘플 로그로 직접 따라 해 본다(21편 make_sample_logs.py로 생성).
LOG=secure # 실제 서버: /var/log/secure · Ubuntu: /var/log/auth.log
# 1) 규모
grep -c 'Failed password' $LOG
grep 'Failed password' $LOG | grep -oE 'from [0-9.]+' | awk '{print $2}' | sort | uniq -c | sort -rn
# 2) 특정 IP의 분당 속도
grep 'Failed password' $LOG | grep '10.0.0.128' | awk '{print substr($3,1,5)}' | uniq -c
# 3) 시도 계정
grep 'Failed password' $LOG | grep '10.0.0.128' | sed -E 's/.*for (invalid user )?([^ ]+) from.*/\2/' | sort | uniq -c | sort -rn
# 4) 없는 계정 시도
grep -oE 'Invalid user [^ ]+ from [0-9.]+' $LOG | awk '{print $5, $3}' | sort | uniq -c | sort -rn
# 5) 성공
grep 'Accepted' $LOG
# 6) 성공 세션의 이후 행위 (PID·계정 기준)
grep -E 'sshd\[5224\]|devuser' $LOG | grep -v 'Failed password'
# 7) 로그인 기록과 교차 확인 (실제 서버)
last -F devuser | head
sudo lastb -F | awk '{print $3}' | sort | uniq -c | sort -rn | head # btmp 실패 기록
분석 결과를 한 장으로 정리한다.
| 항목 | 결과 | 근거 |
|---|---|---|
| 공격 유형 | 사전 + 무차별 대입 (T1110.001) | 9분 95회, 분당 약 10회, 계정 8개(없는 계정 6개) |
| 출발지 | 10.0.0.128 (내부 사설 IP) | 외부가 아니라 내부 호스트 → 그 호스트도 침해 의심 |
| 성공 여부 | 성공 — 02:19:21 devuser | 마지막 실패 6초 후 Accepted password |
| 사후 행위 | sudo 거부, su 실패 | user NOT in sudoers, FAILED SU |
| 권한 상승 | 로그상 실패 | 추가 확인 필요 (SUID·취약점 경유 가능성, 16·30편) |
| 영향 범위 | devuser 권한 12분 | 홈·/tmp·읽기 가능한 설정 파일, 이후 웹 스캔 |
| 배경 소음 | 203.0.113.x 4곳, 18회 | 산발적, 성공 없음 |
오탐 가능성 점검. "관리자가 비밀번호를 잊어서 여러 번 틀린 것"과 구분해야 한다. 사람은 몇 번 틀리고 멈추거나 같은 계정만 시도한다. 여기서는 분당 10회가 9분간 일정하게, 없는 계정 6개를 각 5회씩 시도했으므로 자동화 도구가 확실하다.
| 규칙 | 조건 | 등급 |
|---|---|---|
| R1 무차별 대입 시도 | 같은 IP, 5분 내 Failed password ≥ 10 | 중 |
| R2 사전 대입 | 같은 IP, 10분 내 서로 다른 계정 ≥ 5 | 중 |
| R3 대입 성공 | R1/R2 발생 IP에서 1시간 내 Accepted | 긴급 |
| R4 성공 후 권한 상승 시도 | R3 계정의 NOT in sudoers / FAILED SU | 높음 |
| R5 비정상 로그인 | 평소와 다른 시간대·출발지·인증 방식 | 중 |
임계값(10회, 5분)은 환경에 맞게 조정한다. 너무 낮으면 오탐이, 너무 높으면 느린 스프레이를 놓친다. 스프레이는 계정 수 기준(R2) 과 긴 시간 창(24시간) 으로 따로 본다.
| 계층 | 조치 | 효과 |
|---|---|---|
| 즉시 | 출발 IP 차단 (46편 rich rule), 세션 종료 | 추가 시도·세션 차단 |
| 계정 | devuser 잠금(usermod -L)·비밀번호 변경, authorized_keys 점검 | 탈취 계정 무력화, 지속성 제거 |
| 설정 | PasswordAuthentication no, PermitRootLogin no, AllowUsers, MaxAuthTries 3 (45편) | 대입 자체를 불가능하게 |
| 자동 차단 | fail2ban (실패 N회 시 방화벽 자동 등록) | 반복 공격 자동 대응 |
| 네트워크 | SSH는 관리망·VPN·배스천 호스트로만 | 노출 면적 축소 |
| 조사 | 10.0.0.128 호스트 침해 조사 | 근본 원인 — 내부 거점 |
위 과정을 하나로 묶은 스크립트다(shellcheck 경고 없음).
#!/usr/bin/env bash
# bf_analyze.sh — SSH 무차별 대입 분석
# 사용법: ./bf_analyze.sh /var/log/secure (Ubuntu: /var/log/auth.log)
LOG=${1:-/var/log/secure}
F=$(mktemp); grep -E 'Failed password' "$LOG" > "$F"
echo "== 1. 실패 총계 / 출발지별 =="
wc -l < "$F"
grep -oE 'from [0-9.]+' "$F" | awk '{print $2}' | sort | uniq -c | sort -rn
echo "== 2. 출발지별 시작·끝·계정 수 =="
awk '{for(i=1;i<=NF;i++) if($i=="from") ip=$(i+1);
u=($9=="invalid")?$11:$9;
if(!(ip in first)) first[ip]=$3; last[ip]=$3; n[ip]++;
if(!((ip,u) in seen)){seen[ip,u]=1; nu[ip]++}}
END{for(ip in n) printf "%-15s %4d회 %s ~ %s 계정 %d개\n", ip, n[ip], first[ip], last[ip], nu[ip]}' "$F" | sort -k2 -rn
echo "== 3. 없는 계정(Invalid user) 시도 =="
grep -oE 'Invalid user [^ ]+ from [0-9.]+' "$LOG" | awk '{print $5, $3}' | sort | uniq -c | sort -rn | head
echo "== 4. 성공 로그인과 직전 실패 수 =="
grep -E 'Accepted' "$LOG" | while read -r l; do
ip=$(grep -oE 'from [0-9.]+' <<<"$l" | awk '{print $2}')
ts=$(awk '{print $3}' <<<"$l")
fails=$(awk -v ip="$ip" -v ts="$ts" '$0 ~ "from "ip" " && $3<=ts' "$F" | wc -l)
printf '%s | 이전 실패 %d회\n' "$(awk '{print $1,$2,$3,$7,$9,$11}' <<<"$l")" "$fails"
done
rm -f "$F"
한계: 시각을 문자열(
HH:MM:SS)로 비교하므로 하루 안의 로그 를 가정한다. 여러 날에 걸친 로그는 날짜를 포함해 비교하거나journalctl -o short-iso출력을 쓴다.
# /etc/fail2ban/jail.local
[sshd]
enabled = true
backend = systemd # journal 에서 읽기 (RHEL 9 / Ubuntu 22.04)
maxretry = 5
findtime = 10m
bantime = 1h
sudo fail2ban-client status sshd # 차단 중인 IP
fail2ban은 대응 자동화 이지 탐지를 대신하지 않는다. 차단된 IP 목록도 SIEM으로 보내 "어떤 IP가 언제 차단됐는지"를 남긴다.
[사건] SSH 무차별 대입 성공 — 10.0.0.200 (rocky)
[일시] 2026-09-23 02:10:09 ~ 02:31:21 (KST)
[출발지] 10.0.0.128 (내부)
[경과] 02:10~02:19 실패 95회(계정 8개) → 02:19:21 devuser 로그인 성공
→ 02:21 sudo 거부 → 02:22 su 실패 → 02:31 세션 종료
[영향] devuser 계정 탈취. root 권한 획득 흔적 없음(로그 기준)
[조치] 10.0.0.128 SSH 차단, devuser 잠금·비밀번호 변경, authorized_keys 점검
[권고] PasswordAuthentication no, SSH 관리망 한정, fail2ban, 10.0.0.128 침해 조사
Invalid user, 실패 후 성공.마지막 글 「50. 보안관제 실습 — 로그에서 공격 흔적 찾기」 에서는 이번 글의 SSH 로그에 웹 로그(access_log) 를 더해, 한 공격자의 행위를 처음부터 끝까지 타임라인으로 재구성한다. 그리고 시리즈 전체에서 배운 명령어를 초동 대응 체크리스트 로 정리한다.