서비스 · 프로세스 관리 31 / 50 · Part 4. 로그·스케줄링·운영
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정 analyst)

1. 들어가며

Part 4 「로그·스케줄링·운영」을 시작한다. 지금까지 거의 모든 편에서 journalctl -u 서비스로 로그를 확인했다. systemd 환경에서 서비스 출력, 인증 로그, sudo 기록, 커널 메시지는 모두 journald 에 모인다. 보안관제 요원에게 journalctl은 grep만큼 기본적인 도구다.

이번 글에서는 unit·시간·우선순위·패턴·필드 로 로그를 거르는 방법을 체계적으로 정리하고, 실제로 SSH 로그인 실패와 sudo 사용 이벤트를 만들어 journal에서 보안 이벤트를 뽑아내는 실습을 한다.

「리눅스 시스템 기초 48편」에서 journalctl 기본을, 「49편」에서 SSH Brute Force 로그 분석을 다뤘다. 이번 글은 journald의 구조화 필드 활용에 집중한다.


2. 핵심 개념

2-1. journald가 텍스트 로그와 다른 점

항목전통 syslog 파일journald
저장텍스트 한 줄바이너리 + 필드(key=value)
메타데이터시간·호스트·프로그램 이름 정도PID, UID, 실행 파일, 명령줄, unit, 부팅 ID 등
신뢰성프로그램이 쓴 그대로 (위조 가능)_로 시작하는 필드는 journald가 커널 정보로 기록
검색grep필드 인덱스 검색 (_UID=1000) + 정규식(-g)

2-2. 필터 옵션

옵션예의미
-u-u sshd서비스(unit)
-t-t CRONDSYSLOG_IDENTIFIER (프로그램 태그)
--since / --until--since "-2min", "2026-09-24 12:00"시간 범위
-b-b, -b -1이번/이전 부팅
-p-p warning, -p err우선순위 이상
-g-g 'Invalid user'MESSAGE 정규식
-k커널 로그
필드 매칭_COMM=sudo, _UID=0특정 필드 값
-F 필드-F _SYSTEMD_UNIT필드에 기록된 값 목록
-n / -f-n 50, -f마지막 N줄 / 실시간 추적

2-3. 출력 형식 (-o)

형식용도
short (기본) / short-iso / short-precise사람이 읽기, ISO 시간, 마이크로초
cat메시지만 (파싱·집계용)
verbose모든 필드
json / json-prettySIEM 전송·스크립트 처리

3. 동작 원리

journald 로그 수집 구조와 journalctl 필터

sudo 가 syslog("... COMMAND=/bin/cat /etc/shadow") 호출
  → /dev/log 소켓 → journald
  → journald: 보낸 프로세스의 PID·UID·실행 파일·cgroup 을 커널에서 확인해 _ 필드로 부착
  → 저장 (MESSAGE + PRIORITY + SYSLOG_IDENTIFIER + _PID + _UID + _EXE + _SYSTEMD_UNIT ...)
journalctl _COMM=sudo → 인덱스로 즉시 검색

메시지 내용은 프로그램이 마음대로 쓸 수 있지만, _UID, _EXE, _SYSTEMD_UNIT 같은 밑줄 필드는 journald가 커널에서 얻은 값 이다. "누가 이 로그를 남겼나"를 신뢰할 수 있는 근거가 된다.


4. 명령어 실습

# 1) 보안 이벤트 만들기: 존재하지 않는 계정으로 SSH 3회, analyst 의 sudo 2회
for u in admin oracle test; do ssh -o BatchMode=yes $u@localhost true 2>/dev/null; done
su - analyst -c 'sudo -n cat /etc/shadow > /dev/null; sudo -n id -u'

# 2) unit · 시간 · 패턴 · 우선순위
journalctl -u sshd --since "-2min" --no-pager -o short-iso | tail -6
journalctl -u sshd --since "-2min" --no-pager -g 'Invalid user' -o cat
journalctl _COMM=sudo --since "-2min" --no-pager -o cat
journalctl -p warning --since "-10min" --no-pager -o short-iso | tail -4

# 3) 필드 보기와 활용
journalctl _COMM=sudo -n 1 -o verbose --no-pager | grep -E '^\s+(_PID|_UID|_COMM|_EXE|_CMDLINE|_SYSTEMD_UNIT|SYSLOG_IDENTIFIER|PRIORITY|MESSAGE)='
journalctl -u sshd -g 'Invalid user' -o json --no-pager | tail -1 | python3 -c '...필드 추출...'
journalctl -F _SYSTEMD_UNIT --no-pager | sort | head -8
journalctl -u sshd --since "-2min" -g 'Invalid user' -o cat | awk '{print $3}' | sort | uniq -c

실습 계정 analyst는 lab 환경에서 비밀번호 없는 sudo가 허용되어 있다. 그래서 /etc/shadow 읽기가 성공 했고, 그 기록이 남았다. 실제 서버라면 이런 권한 자체가 점검 대상이다.


5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — 보안 이벤트 만들기 (SSH 실패 · sudo)

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — 필터: unit · 시간 · 우선순위 · 패턴

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — 필드로 보기 (-o verbose · json · 필드 필터)

텍스트 원본(실제 출력):

[root@rocky9-lab ~]# for u in admin oracle test; do ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o LogLevel=ERROR $u@localhost true 2>/dev/null; done; echo "잘못된 계정 SSH 시도 3회"
잘못된 계정 SSH 시도 3회
[root@rocky9-lab ~]# su - analyst -c 'sudo -n cat /etc/shadow > /dev/null; sudo -n id -u'
0
[root@rocky9-lab ~]# journalctl -u sshd --since "-2min" --no-pager -o short-iso | tail -6
2026-09-24T12:22:57+0000 rocky9-lab sshd-session[4567]: Connection closed by invalid user oracle 127.0.0.1 port 59346 [preauth]
2026-09-24T12:22:57+0000 rocky9-lab sshd-session[4570]: Invalid user test from 127.0.0.1 port 59360
2026-09-24T12:22:57+0000 rocky9-lab sshd-session[4570]: Connection closed by invalid user test 127.0.0.1 port 59360 [preauth]
2026-09-24T12:22:57+0000 rocky9-lab sshd-session[4525]: pam_unix(sshd:session): session closed for user root
2026-09-24T12:22:57+0000 rocky9-lab sshd-session[4610]: Accepted publickey for root from 172.17.0.1 port 44308 ssh2: ED25519 SHA256:Svd4EDEkiIH4de8x0M9DZ6BinOc0V/ysEmvVqU0Niug
2026-09-24T12:22:57+0000 rocky9-lab sshd-session[4610]: pam_unix(sshd:session): session opened for user root(uid=0) by root(uid=0)
[root@rocky9-lab ~]# journalctl -u sshd --since "-2min" --no-pager -g 'Invalid user' -o cat
Invalid user admin from 127.0.0.1 port 42946
Invalid user oracle from 127.0.0.1 port 42958
Invalid user test from 127.0.0.1 port 42968
Invalid user admin from 127.0.0.1 port 59330
Invalid user oracle from 127.0.0.1 port 59346
Invalid user test from 127.0.0.1 port 59360
[root@rocky9-lab ~]# journalctl _COMM=sudo --since "-2min" --no-pager -o cat
 analyst : PWD=/home/analyst ; USER=root ; COMMAND=/bin/cat /etc/shadow
pam_unix(sudo:session): session opened for user root(uid=0) by root(uid=1000)
pam_unix(sudo:session): session closed for user root
 analyst : PWD=/home/analyst ; USER=root ; COMMAND=/bin/id -u
pam_unix(sudo:session): session opened for user root(uid=0) by root(uid=1000)
pam_unix(sudo:session): session closed for user root
 analyst : PWD=/home/analyst ; USER=root ; COMMAND=/bin/cat /etc/shadow
pam_unix(sudo:session): session opened for user root(uid=0) by root(uid=1000)
pam_unix(sudo:session): session closed for user root
 analyst : PWD=/home/analyst ; USER=root ; COMMAND=/bin/id -u
pam_unix(sudo:session): session opened for user root(uid=0) by root(uid=1000)
pam_unix(sudo:session): session closed for user root
[root@rocky9-lab ~]# journalctl -p warning --since "-10min" --no-pager -o short-iso | tail -4
2026-09-24T12:19:30+0000 rocky9-lab systemd[1]: Failed to start Lab crashloop.
2026-09-24T12:19:35+0000 rocky9-lab systemd[1]: lab-crash.service: Start request repeated too quickly.
2026-09-24T12:19:35+0000 rocky9-lab systemd[1]: lab-crash.service: Failed with result 'exit-code'.
2026-09-24T12:19:35+0000 rocky9-lab systemd[1]: Failed to start Lab crashloop.
[root@rocky9-lab ~]# journalctl _COMM=sudo -n 1 -o verbose --no-pager | grep -E '^\s+(_PID|_UID|_COMM|_EXE|_CMDLINE|_SYSTEMD_UNIT|SYSLOG_IDENTIFIER|PRIORITY|MESSAGE)='
    SYSLOG_IDENTIFIER=sudo
    _UID=1000
    _COMM=sudo
    _EXE=/usr/bin/sudo
    _SYSTEMD_UNIT=session-145.scope
    PRIORITY=6
    MESSAGE=pam_unix(sudo:session): session closed for user root
    _PID=4573
    _CMDLINE=sudo -n id -u
[root@rocky9-lab ~]# journalctl -u sshd -g 'Invalid user' -o json --no-pager | tail -1 | python3 -c 'import json,sys; d=json.loads(sys.stdin.read()); print({k:d[k] for k in ("_PID","_SYSTEMD_UNIT","PRIORITY","MESSAGE") if k in d})'
{'_PID': '4570', '_SYSTEMD_UNIT': 'sshd.service', 'PRIORITY': '6', 'MESSAGE': 'Invalid user test from 127.0.0.1 port 59360'}
[root@rocky9-lab ~]# journalctl -F _SYSTEMD_UNIT --no-pager | sort | head -8
auditd.service
broken.service
crond.service
dbus-broker.service
dnf-makecache.service
exit2.service
init.scope
lab-app1.service
[root@rocky9-lab ~]# journalctl -u sshd --since "-2min" -g 'Invalid user' -o cat --no-pager | awk '{print $3}' | sort | uniq -c
      2 admin
      2 oracle
      2 test

6. 결과 해석

관찰의미
Invalid user oracle from 127.0.0.1 ... → Connection closed by invalid user oracle ... [preauth]존재하지 않는 계정 접속 시도. 인증 전 단계(preauth) 에서 끊겼다
Accepted publickey for root from 172.17.0.1 ... ED25519 SHA256:...성공한 로그인. 출발지 IP와 사용된 키의 지문 까지 남는다
-g 'Invalid user' -o cat → 3줄패턴과 출력 형식을 조합하면 필요한 줄만 깔끔하게 뽑힌다
sudo: analyst : PWD=/home/analyst ; USER=root ; COMMAND=/bin/cat /etc/shadow누가, 어디서, 누구 권한으로, 무엇을 실행했는지 한 줄에 남는다
session opened for user root(uid=0) by root(uid=1000)sudo 세션 시작. by ...(uid=1000)이 실제 요청자다
-p warning → lab-crash 관련 경고들우선순위로 거르면 장애·보안 이벤트만 빠르게 볼 수 있다 (29편의 흔적)
verbose: _UID=1000, _EXE=/usr/bin/sudo, _CMDLINE=sudo -n id -u, _SYSTEMD_UNIT=session-145.scope로그를 남긴 프로세스의 실제 계정·실행 파일·명령줄·로그인 세션
json 추출 {'_PID': '4570', '_SYSTEMD_UNIT': 'sshd.service', ...}SIEM으로 보낼 때 이 형식을 쓴다
-F _SYSTEMD_UNIT → auditd, broken, exit2, lab-app1 ...이 서버에서 로그를 남긴 적 있는 모든 unit. 지금은 없는 unit(broken, exit2)도 흔적이 남는다
계정별 집계 admin 2, oracle 2, test 2실습을 두 번 실행해 계정마다 2회씩 기록되었다. awk·uniq -c로 Brute Force 대상 계정 분포를 만든다

7. 보안 관점

주제내용
신뢰할 수 있는 필드공격자가 logger "Accepted password for admin"으로 가짜 로그를 넣어도 _COMM=logger, _UID가 남아 위조를 구분 할 수 있다
흔적의 지속성-F _SYSTEMD_UNIT에는 삭제된 unit 이름도 남는다. 공격자가 지운 악성 서비스의 이름을 찾을 수 있다
로그 삭제root는 journal 파일을 지울 수 있다(--vacuum, 파일 삭제). 원격 전송 이 없으면 증거가 사라진다 (32편)
접근 통제일반 사용자는 자신의 로그만 본다. adm, systemd-journal, wheel 그룹은 전체를 본다 → 그룹 구성원 점검 필요 (33편 실측)

8. 보안관제 관점

[Detection]  SIEM 경보: sshd "Invalid user" 5분간 200회 (단일 IP)
     ↓
[집계]       journalctl -u sshd --since "-10min" -g 'Invalid user' -o cat | awk '{print $5}' | sort | uniq -c | sort -rn
             journalctl -u sshd --since "-10min" -g 'Invalid user' -o cat | awk '{print $3}' | sort | uniq -c | sort -rn | head
     ↓
[성공 여부]  journalctl -u sshd --since "-10min" -g 'Accepted' -o short-iso    ← 같은 IP 의 성공이 있는가
     ↓
[후속 행위]  성공했다면: journalctl _COMM=sudo --since <로그인 시각>
                         journalctl _SYSTEMD_UNIT=session-N.scope           ← 그 세션의 모든 기록
     ↓
[Response]   출발지 차단 · 계정 잠금 · 세션 종료 · 키/비밀번호 교체
자주 쓰는 한 줄목적
journalctl -u sshd -g 'Failed password\|Invalid user' --since today오늘의 인증 실패
journalctl -u sshd -g 'Accepted' --since today -o short-iso오늘의 로그인 성공
journalctl _COMM=sudo -g 'COMMAND=' --since today -o cat오늘의 sudo 명령
journalctl -p err -b이번 부팅의 오류
journalctl _UID=48 --since "-1h"특정 계정(예: 웹 서버)이 남긴 로그

9. 실무에서 자주 발생하는 실수

실수결과예방
journalctl \| grep만 사용느리고 필드 활용 불가-u, _COMM=, -g 조합
일반 계정으로 조회시스템 로그가 안 보여 "로그 없음"으로 오판root 또는 adm/systemd-journal 그룹
시간대를 확인하지 않음로그(UTC)와 보고서 시간 불일치-o short-iso, --utc 명시
-u sshd로 Ubuntu 조회Ubuntu의 unit 이름은 ssh-u ssh 또는 -t sshd
긴 출력을 pager로 봄스크립트가 멈춤--no-pager

10. 실습 체크리스트

[ ] -u, --since, -p, -g 로 로그를 걸렀다
[ ] SSH Invalid user 와 Accepted 로그의 형식을 확인했다
[ ] sudo 로그에서 요청자·명령·대상 계정을 읽었다
[ ] -o verbose 로 _UID, _EXE, _CMDLINE, _SYSTEMD_UNIT 필드를 확인했다
[ ] -o json 으로 필드를 추출했다
[ ] 계정별 로그인 실패 횟수를 집계했다

11. 핵심 정리

  • journald는 로그를 메시지 + 구조화 필드 로 저장한다. _ 필드는 journald가 붙인 신뢰 가능한 값이다.
  • -u(unit), --since(시간), -p(우선순위), -g(패턴), _필드=값(필드)으로 필요한 로그만 뽑는다.
  • -o cat은 집계용, -o verbose는 분석용, -o json은 SIEM 연동용이다.
  • SSH 인증 실패·성공, sudo 명령은 journal에서 바로 추출할 수 있다.
  • 로그가 서버 안에만 있으면 root 권한 공격자가 지울 수 있다 → 보존·전송 설정(32편).

12. 다음 편 예고

다음 글 「32. journald 저장 설정과 로그 보존」 에서는 journal이 재부팅하면 사라질 수 있다 는 사실부터 확인한다. Storage=persistent로 영구 저장을 켜고, 용량·보존 기간 제한과 무결성 검증(--verify), 원격 전송 구성의 기준을 정리한다.


참고 자료


시리즈 이동

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

0개의 댓글