044. Linux 서버 보안 — 자체 점검 스크립트 설계와 결과 표준화

changseop lee·5일 전

시스템 보안 · 취약점 › A. Linux 서버 보안 설정 · 44/50편 (전체 044/450)
학습 단계: 4단계 · 자동 점검·로그 분석
실습 표기: 이 글의 명령어·출력·로그는 로컬 VMware 테스트 VM(Rocky Linux 9 / Ubuntu 22.04) 기준의 「실습 예시」이며, IP·계정·호스트명은 가상의 값입니다.

선행 학습

1. 개념

OpenSCAP·Lynis가 다루지 않는 조직 고유 항목(예: 관제 에이전트 상태, 사내 NTP 서버, 역할별 허용 포트)은 자체 스크립트로 점검합니다. 이때 중요한 것은 기능보다 설계 원칙입니다.

설계 원칙
① 읽기 전용   : 점검만 하고 절대 설정을 바꾸지 않음 (조치는 별도 절차)
② 멱등성      : 몇 번 실행해도 같은 상태면 같은 결과
③ 표준 출력   : item= expected= actual= result= 형식 (key=value)
④ 종료 코드   : 0 = 모두 PASS, 1 = 실행 오류, 2 = FAIL 존재
⑤ 자기 보호   : 스크립트 파일 권한(700 root) + FIM 감시

실행 흐름
systemd timer → check.sh → logger -t hardening-check → journald/rsyslog → SIEM

2. 왜 중요한가

  • 결과 형식이 사람마다 다르면 SIEM에서 파싱할 수 없고, 서버 간 비교도 불가능합니다.
  • 점검 스크립트가 설정을 바꾸면 장애 원인이 되고, 점검 결과의 신뢰성도 사라집니다.
  • 공격자가 점검 스크립트를 수정해 항상 PASS를 출력하게 만들 수 있으므로, 스크립트 자체의 무결성도 감시 대상입니다.

3. 핵심 명령어 / 설정

요소선택이유
실행 주기systemd timer실행 이력·실패가 journal에 남고 systemctl list-timers로 확인 가능
출력logger -t hardening-check별도 파일 없이 기존 로그 경로·원격 전송 재사용
형식key=valueWazuh·Logstash에서 kv 필터로 바로 추출
위치/usr/local/sbin/hardening-check.sh (700 root)일반 사용자 수정 불가

4. 실습 (실습 예시)

sudo tee /usr/local/sbin/hardening-check.sh >/dev/null <<'EOF'
#!/bin/bash
# 읽기 전용 점검 스크립트 (설정 변경 없음)
FAIL=0
chk() {  # chk 항목 기대값 실제값
  local r=PASS; [ "$2" = "$3" ] || { r=FAIL; FAIL=1; }
  logger -t hardening-check "item=$1 expected=$2 actual=$3 result=$r"
}
chk ssh_permitrootlogin no  "$(sshd -T 2>/dev/null | awk '/^permitrootlogin /{print $2}')"
chk selinux             Enforcing "$(getenforce 2>/dev/null)"
chk audit_enabled       2   "$(auditctl -s | awk '/^enabled/{print $2}')"
chk uid0_count          1   "$(awk -F: '$3==0' /etc/passwd | wc -l)"
chk ip_forward          0   "$(sysctl -n net.ipv4.ip_forward)"
chk agent_active        active "$(systemctl is-active wazuh-agent 2>/dev/null)"
logger -t hardening-check "summary result=$([ $FAIL -eq 0 ] && echo PASS || echo FAIL)"
exit $(( FAIL * 2 ))
EOF
sudo chmod 700 /usr/local/sbin/hardening-check.sh

# systemd service + timer (매일 05:00)
sudo tee /etc/systemd/system/hardening-check.service >/dev/null <<'EOF'
[Unit]
Description=Read-only hardening check
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/hardening-check.sh
SuccessExitStatus=2
EOF
sudo tee /etc/systemd/system/hardening-check.timer >/dev/null <<'EOF'
[Timer]
OnCalendar=*-*-* 05:00:00
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable --now hardening-check.timer
sudo systemctl start hardening-check.service && journalctl -t hardening-check -n 8 --no-pager

SuccessExitStatus=2는 "FAIL 존재(2)"를 서비스 실행 실패로 취급하지 않게 합니다. 실행 오류(1)와 점검 결과(2)를 구분하기 위한 설정입니다.

5. 정상 상태

Oct  1 05:00:01 rocky9-web01 hardening-check[10101]: item=ssh_permitrootlogin expected=no actual=no result=PASS
Oct  1 05:00:01 rocky9-web01 hardening-check[10103]: item=selinux expected=Enforcing actual=Enforcing result=PASS
Oct  1 05:00:01 rocky9-web01 hardening-check[10107]: item=audit_enabled expected=2 actual=2 result=PASS
Oct  1 05:00:02 rocky9-web01 hardening-check[10115]: summary result=PASS
$ systemctl list-timers hardening-check.timer --no-pager | head -2
NEXT                        LEFT     LAST                        PASSED  UNIT
Fri 2026-10-02 05:00:00 KST 23h left Thu 2026-10-01 05:00:00 KST 1min ago hardening-check.timer

6. 이상 상태

$ systemctl list-timers hardening-check.timer --no-pager
0 timers listed.
$ sudo sha256sum -c /root/baseline/hardening-check.sha256
/usr/local/sbin/hardening-check.sh: FAILED
$ sudo grep -n 'logger' /usr/local/sbin/hardening-check.sh | head -2
6:  logger -t hardening-check "item=$1 expected=$2 actual=$2 result=PASS"
  • 타이머가 비활성화되어 정기 점검이 멈춤
  • 스크립트가 수정되어 실제값 대신 기대값을 출력하고 항상 PASS → 점검 결과 위조
  • SIEM 입장에서는 "PASS가 계속 들어오는 것"과 "아무것도 안 들어오는 것" 모두 확인이 필요합니다.

7. 로그 분석 (분석 방법)

점검 결과와 점검 체계 자체의 이상을 함께 봅니다(가상의 예시 로그).

Oct  1 05:00:01 rocky9-web01 hardening-check[10101]: item=ssh_permitrootlogin expected=no actual=yes result=FAIL
Oct  1 05:00:01 rocky9-web01 hardening-check[10107]: item=uid0_count expected=1 actual=2 result=FAIL
Oct  1 05:00:02 rocky9-web01 hardening-check[10115]: summary result=FAIL
type=PATH msg=audit(1759742400.200:3101): item=0 name="/usr/local/sbin/hardening-check.sh" nametype=NORMAL key="check_script"
관찰해석
result=FAIL 항목해당 하드닝 항목 변화 → 037편 원인 분류
스크립트 파일 쓰기 이벤트점검 체계 변조 시도
다음 날 hardening-check 로그 없음타이머 중지 또는 서비스 삭제

8. SOC 관제 포인트

  • summary result=FAIL을 1차 Alert, 개별 item을 상세 근거로 사용합니다.
  • 호스트별 마지막 점검 로그 수신 시각을 감시해 점검 중단을 탐지합니다.
  • 점검 스크립트·유닛 파일은 FIM과 auditd(check_script 키)로 보호합니다.

9. 탐지 규칙

<group name="local,hardening_check,">
  <rule id="100490" level="9">
    <program_name>hardening-check</program_name>
    <match>summary result=FAIL</match>
    <description>자체 하드닝 점검 FAIL 발생</description>
  </rule>
  <rule id="100491" level="12">
    <if_sid>550</if_sid>
    <field name="file">^/usr/local/sbin/hardening-check.sh$|^/etc/systemd/system/hardening-check</field>
    <description>하드닝 점검 스크립트/유닛 변경</description>
  </rule>
</group>

Logstash를 쓴다면 kv { source => "message" } 필터로 item, expected, actual, result 필드를 자동 추출할 수 있습니다.

10. 대응 방법

  1. 초기 확인 — FAIL 항목과 점검 체계(타이머·스크립트 해시) 상태를 함께 확인합니다.
  2. 범위 확인 — 같은 FAIL 항목이 다른 서버에도 있는지, 점검 로그가 끊긴 서버가 있는지 확인합니다.
  3. 증거 확보 — 점검 로그 원본, 스크립트·유닛 파일 사본과 해시를 보존합니다.
  4. 차단/조치 — FAIL 항목은 원인별로 조치하고, 변조된 스크립트는 배포 원본으로 교체합니다.
  5. 재발 방지 — 스크립트 FIM, 점검 중단 감시, 결과 형식 표준을 운영 기준으로 둡니다.

11. 핵심 정리

구분핵심 내용
설계 원칙읽기 전용 · 멱등 · key=value · 종료 코드(0/1/2) · 자기 보호
실행systemd timer (SuccessExitStatus=2)
출력 경로logger -t → journald/rsyslog → SIEM
변조 대응스크립트 FIM, 점검 로그 수신 공백 감시
면접 포인트"점검 결과뿐 아니라 점검 체계 자체도 감시한다"

12. 다음 편 예고

다음 편 045. Linux 서버 보안 — Wazuh SCA로 설정 준수 모니터링 에서는 5단계로 넘어가 에이전트 기반 상시 점검인 Wazuh SCA로 설정 준수 모니터링을 다룹니다.


이전 편: 043. Linux 서버 보안 — Lynis 하드닝 점검 결과 해석
📚 시리즈 전체 보기: 시스템 보안 · 취약점

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

0개의 댓글