리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.

리눅스 시스템 기초 28 / 50 · Part 3. Linux 명령어 활용
실습 환경: Rocky Linux 9 (10.0.0.200) · 21편 실습용 샘플 로그
이전 글: 27. 파이프라인과 리다이렉션

1. 들어가며

21~27편에서 만든 명령어 조합은 매번 손으로 입력하기엔 길다. 같은 분석을 반복하거나 여러 서버에서 똑같이 실행하려면 스크립트 로 묶어야 한다. 이번 글은 Bash 스크립트의 기본 문법을 정리하고, Part 3의 grep·awk·sort를 모아 SSH 인증 로그 요약 리포트 스크립트를 만든다.

관제 업무에서 스크립트를 쓸 때 가장 중요한 것은 문법보다 안전성 이다.

  • 로그 경로가 틀렸는데 "로그인 실패 0건"이라고 보고하는 스크립트는 없는 것보다 위험 하다.
  • root로 실행되는 스크립트에 따옴표 하나가 빠지면 의도하지 않은 파일이 지워질 수 있다.

그래서 이 글은 "조용히 틀리지 않는 스크립트" 를 목표로 한다.


2. 핵심 개념

2-1. 기본 요소

요소문법예
shebang#!/bin/bash첫 줄, 인터프리터 지정
변수NAME=값 (공백 없이), 사용 "$NAME"LOG=/var/log/secure
인자$1, $2, $#(개수), $@(전체)LOG="${1:-/var/log/secure}"
명령 치환$(명령)N=$(wc -l < "$LOG")
산술$(( )), 비교 (( a >= b ))
조건if [[ 조건 ]]; then … elif … else … fi
반복for x in …; do … done, while read -r line; do … done
함수name() { … }, 인자는 함수 안의 $1
종료 코드exit 0 성공, exit 1 실패, 직전 결과 $?

2-2. 자주 쓰는 조건식

조건의미
-f 파일 / -d 디렉터리존재하는 일반 파일 / 디렉터리
-r / -w / -x읽기 / 쓰기 / 실행 가능
-z "$s" / -n "$s"빈 문자열 / 비어 있지 않음
"$a" == "$b"문자열 같음
"$s" =~ 정규식정규식 일치 ([[ ]] 안에서)
(( n > 10 ))숫자 비교

2-3. 안전 옵션

옵션효과
set -e명령이 실패하면 즉시 종료
set -u정의되지 않은 변수를 쓰면 종료 (오타 방지)
set -o pipefail파이프 중간 명령 실패도 실패로 처리 (27편)

set -e와 grep을 함께 쓸 때 주의할 점이 있다. grep은 일치하는 줄이 없으면 종료 코드 1 을 반환하므로, "0건"이 정상인 경우에도 스크립트가 멈춘다. 그래서 grep -c … || true처럼 결과 없음을 허용하는 처리를 넣는다.


3. 동작 원리

안전한 Bash 스크립트의 구조 — auth_report.sh 해부

스크립트를 실행하면(./script.sh) 커널이 첫 줄 #!/bin/bash를 보고 bash를 실행해 파일 내용을 넘긴다. 새 bash 프로세스(자식)에서 실행되므로, 스크립트 안의 cd나 변수는 실행한 쉘에 영향을 주지 않는다(9편 환경 상속).

실행 방법에 따른 차이:

방법동작
./script.sh실행 권한(x) 필요, shebang의 인터프리터로 새 프로세스
bash script.sh실행 권한 불필요, bash로 새 프로세스
source script.sh / . script.sh현재 쉘 에서 실행 — 변수·cd가 현재 쉘에 남음

4. 실습 — SSH 인증 로그 요약 스크립트

#!/bin/bash
# auth_report.sh — SSH 인증 로그 요약 리포트
# 사용법: auth_report.sh [로그파일] [실패 임계치]
#   예)  sudo ./auth_report.sh /var/log/secure 10
set -euo pipefail

LOG="${1:-/var/log/secure}"          # 인자가 없으면 기본값
THRESHOLD="${2:-10}"
OUT="report_$(date +%Y%m%d_%H%M%S).txt"

# ── 함수 ─────────────────────────────────────────
die()  { echo "[오류] $*" >&2; exit 1; }
line() { printf '%.0s-' {1..60}; echo; }

count() {   # count <패턴>  → 로그에서 패턴 줄 수 (없으면 0)
  grep -cE -- "$1" "$LOG" || true
}

# ── 입력 검증 ─────────────────────────────────────
[[ -r "$LOG" ]] || die "로그 파일을 읽을 수 없음: $LOG"
[[ "$THRESHOLD" =~ ^[0-9]+$ ]] || die "임계치는 숫자여야 함: $THRESHOLD"

{
  echo "SSH 인증 로그 요약 — $(hostname -s) — $(date '+%F %T %Z')"
  echo "대상: $LOG ($(wc -l < "$LOG") lines) / 임계치: ${THRESHOLD}회"
  line
  echo "로그인 실패     : $(count 'Failed password')"
  echo "없는 계정 시도  : $(count 'Invalid user')"
  echo "로그인 성공     : $(count 'Accepted')"
  echo "sudo 거부       : $(count 'NOT in sudoers')"
  echo "su 실패         : $(count 'FAILED SU')"
  line
  echo "[실패 임계치 초과 출발지]"
  grep 'Failed password' "$LOG" | grep -oE 'from ([0-9]{1,3}\.){3}[0-9]{1,3}' \
    | awk '{print $2}' | sort | uniq -c | sort -rn \
    | while read -r cnt ip; do
        if (( cnt >= THRESHOLD )); then
          printf '  %-16s %5d회\n' "$ip" "$cnt"
        fi
      done
  line
  echo "[실패 후 성공한 출발지 — 우선 확인 대상]"
  for ip in $(grep 'Accepted' "$LOG" | grep -oE 'from ([0-9]{1,3}\.){3}[0-9]{1,3}' | awk '{print $2}' | sort -u); do
    fails=$(grep 'Failed password' "$LOG" | grep -c "from $ip " || true)
    if (( fails > 0 )); then
      grep 'Accepted' "$LOG" | grep "from $ip " | awk -v f="$fails" '{printf "  %s %s  %s(%s) ← 실패 %d회 후 성공\n", $1" "$2, $3, $9, $7, f}'
    fi
  done
  line
} | tee "$OUT"

sha256sum "$OUT" | tee -a "$OUT.sha256" > /dev/null
echo "[저장] $OUT"
cd ~/lab
chmod 700 auth_report.sh
./auth_report.sh secure 10          # 샘플 로그, 임계치 10회
./auth_report.sh nofile             # 입력 검증 확인
./auth_report.sh secure abc
# 실제 서버: sudo ./auth_report.sh /var/log/secure 20

5. 결과 분석

샘플 로그에 실행한 실제 결과다 (호스트명만 가림).

SSH 인증 로그 요약 — (호스트명) — 2026-09-23 19:12:01 KST
대상: secure (176 lines) / 임계치: 10회
------------------------------------------------------------
로그인 실패     : 113
없는 계정 시도  : 45
로그인 성공     : 4
sudo 거부       : 1
su 실패         : 1
------------------------------------------------------------
[실패 임계치 초과 출발지]
  10.0.0.128          95회
------------------------------------------------------------
[실패 후 성공한 출발지 — 우선 확인 대상]
  Sep 23 02:19:21  devuser(password) ← 실패 95회 후 성공
------------------------------------------------------------
[저장] report_20260923_191201.txt
$ ./auth_report.sh nofile
[오류] 로그 파일을 읽을 수 없음: nofile        (exit 1)
$ ./auth_report.sh secure abc
[오류] 임계치는 숫자여야 함: abc               (exit 1)
결과 항목의미
임계치 초과 출발지인터넷 스캐너(203.0.113.x, 최대 9회)는 걸러지고 집중 공격 IP만 남음
실패 후 성공21편에서 손으로 찾던 핵심 질문을 자동으로 답함
인증 방식 (password)관리자는 키 인증(publickey), 침해 계정은 비밀번호 인증
입력 오류 시 종료잘못된 경로를 "0건"으로 보고하지 않음

6. 보안 관점

원칙이유적용
변수는 큰따옴표로공백·와일드카드가 든 값이 여러 인자로 쪼개지거나 확장됨"$LOG"
입력 검증사용자 입력(인자)이 명령에 그대로 들어감정규식으로 형식 확인
-- 사용패턴이 -로 시작하면 옵션으로 해석됨grep -- "$1"
절대경로·cd 실패 처리12편 상대경로 문제`cd /dir
임시 파일은 mktemp예측 가능한 /tmp/파일명은 링크 공격 대상 (12편)tmp=$(mktemp)
스크립트 권한다른 사용자가 수정 가능하면 권한 상승 경로 (15편)root 실행 스크립트는 700 root:root
비밀번호 하드코딩 금지스크립트 파일에서 노출별도 권한 파일, 환경변수

7. SOC / 보안관제 활용

7-1. 스크립트를 관제 업무에 넣는 방법

방식예주의
수동 Triage경보 발생 시 서버에서 실행 (10편)결과 해시 기록
정기 실행cron으로 매일 리포트 (38편)실행 계정·권한 최소화
SIEM 보조SIEM이 없는 서버, SIEM 결과 교차 검증결과를 중앙 서버로 전송

7-2. 스크립트 자체가 공격 대상이 되는 경우

관제용 스크립트는 보통 root로 실행된다. 그래서 공격자에게는 권한 상승 경로 가 될 수 있다.

# 점검: root cron·systemd가 실행하는 스크립트 중 다른 사용자가 쓸 수 있는 것
sudo find /usr/local/bin /opt /etc/cron.* -type f -perm /022 -ls 2>/dev/null

7-3. 분석 흐름

[경보]     SIEM: SSH 실패 급증
   ↓
[스크립트]  sudo ./auth_report.sh /var/log/secure 20
   ↓
[결과]     임계치 초과 IP, 실패 후 성공 계정·시각·인증 방식
   ↓
[판단]     "실패 후 성공"이 있으면 침해 의심 → 해당 계정의 이후 행위 조사(29편)
          없으면 차단·모니터링
   ↓
[기록]     리포트 파일 + sha256 → 티켓에 첨부

8. 핵심 정리

  • 스크립트 첫 줄은 #!/bin/bash, 안전 옵션은 set -euo pipefail 이다.
  • 인자는 ${1:-기본값}으로 받고, 변수는 항상 큰따옴표 로 감싼다.
  • 입력(파일 존재, 숫자 형식)을 검증하고 틀리면 stderr 메시지와 함께 exit 1.
  • grep의 "0건"은 종료 코드 1이므로 set -e 환경에서는 || true로 처리한다.
  • ./, bash는 새 프로세스, source는 현재 쉘에서 실행된다.
  • 관제 스크립트는 root로 실행되는 경우가 많으므로 권한 700, 절대경로, mktemp 를 지킨다.

9. 다음 글

다음 글 「29. 명령어 조합을 이용한 시스템 분석」 에서는 로그를 넘어 살아 있는 시스템 을 분석한다. 이번 스크립트가 찾아낸 "실패 후 성공한 계정"을 출발점으로 ps, ss, lsof, find, /proc를 조합해 그 계정이 서버에서 무엇을 실행하고 어디와 통신했는지 추적한다.


참고 자료

profile
코드에 숨겨진 위협을 읽고 AI로 보안의 미래를 설계합니다. 프론트엔드 개발 경험을 자산 삼아 더 견고하고 지능적인 보안 운영 시스템을 구축해 나가는 과정을 기록합니다

0개의 댓글