시스템 보안 · 취약점 › I. 지속성 · 재침투 징후 · 28/50편 (전체 428/450)
학습 단계: 스케줄·서비스·부팅 지속성 탐지
실습 표기: 이 글의 명령어·출력·로그는 로컬 VMware 테스트 VM(Rocky Linux 9 / Ubuntu 22.04) 기준의 「실습 예시」이며, IP·계정·호스트명은 가상의 값입니다.
스케줄·서비스·부팅 지속성은 "언제 다시 실행되는가"의 문제입니다. 아홉 가지 기법을 트리거별로 묶으면 점검 순서와 제거 순서가 명확해집니다.
트리거별 지속성 지도
[시간] cron(I19) · at(I20) · systemd timer(I22)
[부팅] systemd service(I21) · drop-in/generator(I23) · rc.local/init(I24)
[로그인] 셸 초기화 파일(I25)
[이벤트] udev 규칙(I26)
[모든 실행] ld.so.preload(I27)
↓
재생성 관계: 시간 트리거가 부팅·로그인 지속성을 다시 설치하는 구조가 흔함
핵심: 제거 순서는 "재생성 장치(시간 트리거) → 나머지"
실제 사고에서는 cron이 systemd 유닛을 다시 만들고, 유닛이 ld.so.preload를 복구하는 식의 상호 재생성 구조가 자주 보입니다.
| 트리거 | 기법 | 감사 키 | 룰 ID |
|---|---|---|---|
| 시간 | cron·at | persist_cron | 108180·108190 |
| 시간·부팅 | systemd service·timer | persist_svc | 108200·108210 |
| 부팅 | drop-in·rc.local | persist_svc·persist_boot | 108220·108230 |
| 로그인 | 셸 초기화 | persist_shell | 108240 |
| 이벤트 | udev | persist_boot | 108250 |
| 모든 실행 | ld.so.preload | persist_preload | 108260 |
# 트리거별 지속성 일괄 점검 (분석 방법)
echo "[시간]"; sudo ls -la /etc/cron.d /var/spool/cron 2>/dev/null; sudo atq; systemctl list-timers --all --no-legend | wc -l
echo "[부팅]"; systemd-delta --type=extended --no-pager | wc -l; ls -l /etc/rc.d/rc.local 2>/dev/null
echo "[로그인]"; sudo grep -lE 'nohup|alias sudo|/dev/tcp' /etc/profile.d/* /root/.bashrc /home/*/.bashrc 2>/dev/null
echo "[이벤트]"; sudo grep -rl 'RUN+=' /etc/udev/rules.d/ 2>/dev/null
echo "[실행]"; ls /etc/ld.so.preload 2>/dev/null
echo "[변경 이벤트 집계]"; sudo ausearch -k persist_cron -k persist_svc -k persist_boot -k persist_shell -k persist_preload -ts this-week 2>/dev/null | grep -c 'type=SYSCALL'
[시간] 패키지 cron만, atq 비어 있음, timer 9개(패키지)
[부팅] EXTENDED 1(승인), rc.local 실행 권한 없음
[로그인]/[이벤트]/[실행] 해당 없음
모든 트리거 위치가 기준선과 일치하고, 이번 주 변경 이벤트가 승인된 작업으로 설명되는 상태가 정상입니다.
[시간] apache crontab(curl|bash), update-check.timer(미소유)
[부팅] dbus-helper.service, sshd.service.d/override.conf
[로그인] /etc/profile.d/sys-locale.sh
[실행] /etc/ld.so.preload 존재
트리거별 종합 분석·근절 흐름입니다(가상의 예시).
[전수 점검] 시간 → 부팅 → 로그인 → 이벤트 → 실행
↓
[재생성 관계] 각 항목이 무엇을 다시 설치하는지(스크립트 내용) 확인
↓
[근절 순서]
① 시간 트리거(cron·at·timer) 중지
② 부팅·이벤트 트리거(유닛·drop-in·rc.local·udev) 비활성화
③ 로그인·실행 트리거(셸 파일·preload) 제거
④ 공통 바이너리 격리 → 재부팅 후 재점검
| 단계 | 이유 |
|---|---|
| 시간 먼저 | 재설치 차단 |
| 재부팅 후 재점검 | 부팅 트리거 잔존 확인 |
| 공통 바이너리 | 여러 트리거가 같은 파일 실행 |
여러 트리거가 같은 바이너리(/usr/lib/.x/agent)를 실행하는 경우가 많으므로, 바이너리 해시로 다른 위치를 역검색하면 놓친 트리거를 찾을 수 있습니다.
persist_ 접두어로 통일해 한 번에 조회합니다.<!-- 실습 예시 룰: 적용 전 wazuh-logtest 및 테스트 환경 검증 필요 -->
<group name="local,syssec_i,persistence,">
<rule id="108270" level="14">
<if_matched_group>persistence</if_matched_group>
<same_field>agent.id</same_field>
<different_field>audit.key</different_field>
<timeframe>3600</timeframe>
<description>동일 호스트 1시간 내 서로 다른 트리거의 지속성 이벤트(다중 지속성)</description>
<mitre><id>TA0003</id></mitre>
</rule>
</group>
different_field로 감사 키가 서로 다른 지속성 이벤트가 같은 호스트에서 이어지면 다중 지속성으로 판단합니다. different_field 지원 여부는 Wazuh 버전별로 확인합니다.
| 트리거 | 기법 |
|---|---|
| 시간 | cron·at·timer |
| 부팅 | service·drop-in·rc.local |
| 로그인 | 셸 초기화 |
| 이벤트·실행 | udev·preload |
| 면접 포인트 | "지속성은 서로를 되살린다 — 시간 트리거부터 끊고 재부팅 후 다시 본다" |
다음 편 429. 지속성·재침투 — 리버스 셸 재접속 시도 탐지 에서는 지속성이 주기적으로 띄우는 리버스 셸 재접속 시도를 connect 감사·방화벽 차단 로그·프로세스 계보로 탐지하는 방법을 다룹니다.
이전 편: 427. 지속성·재침투 — ld.so.preload·동적 라이브러리 지속성 탐지
📚 시리즈 전체 보기: 시스템 보안 · 취약점