
서비스 · 프로세스 관리 39 / 50 · Part 4. 로그·스케줄링·운영
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
Part 3·4에서 systemd unit, timer, cron, anacron, at, socket unit을 차례로 다뤘다. 이들의 공통점은 사람이 명령하지 않아도 무언가를 실행한다 는 것이다. 여기에 로그인할 때 읽히는 셸 초기화 파일과 사용자 systemd unit까지 더하면, 한 서버에서 자동 실행이 일어나는 지점은 열 군데가 넘는다.
침해 대응에서 "공격자가 다시 들어올 수 있는 장치가 남아 있는가"를 확인하려면 이 지점을 빠짐없이 봐야 한다. 이번 글에서는 자동 실행 경로를 지도로 정리하고, 각 경로에 무해한 테스트 항목(logger 한 줄) 을 심어 둔 뒤 이를 모두 찾아내는 점검 스크립트를 만든다.
| 트리거 | 경로 | 실행 권한 | 관련 편 |
|---|---|---|---|
| 부팅 | /etc/systemd/system/*.wants/, /usr/lib/systemd/system | unit의 User= | 22·23 |
| 부팅 | /etc/rc.d/rc.local (Rocky), /etc/rc.local (Ubuntu) | root | — |
| 부팅 | crontab의 @reboot | 해당 사용자 | 33 |
| 시간 | /var/spool/cron/, /etc/crontab, /etc/cron.d/ | 사용자 / 지정 계정 | 33·34 |
| 시간 | /etc/cron.{hourly,daily,weekly,monthly}/, anacron | root | 34 |
| 시간 | systemd timer (시스템·사용자·transient) | 서비스의 User= | 35 |
| 시간 | at 스풀 | 예약한 사용자 | 36 |
| 로그인 | /etc/profile, /etc/profile.d/*.sh, /etc/bashrc | 로그인한 모든 사용자 | — |
| 로그인 | ~/.bashrc, ~/.bash_profile, ~/.profile | 해당 사용자 | — |
| 로그인 | ~/.config/systemd/user/ (+ linger 시 상시) | 해당 사용자 | 21 |
| 이벤트 | socket · path unit | 서비스의 User= | 37 |
| 원칙 | 이유 |
|---|---|
| 경로별로 섹션을 나눠 출력 | 어느 경로가 비어 있는지도 결과가 된다 |
| root로 실행 | 다른 사용자의 스풀·홈 디렉터리를 읽어야 한다 |
| 읽기 전용 | 점검 도구는 시스템을 바꾸지 않는다 |
| 결과를 파일로 저장 | 기준선과 diff로 비교 |

[1단계] 테스트 항목 심기 (모두 logger 로 로그 한 줄만 남기는 무해한 명령)
crontab @reboot · /etc/cron.d · /etc/profile.d · ~/.bashrc · 사용자 unit · at
[2단계] 점검 스크립트 실행 → 경로별 목록 출력
[3단계] 심은 항목이 모두 탐지되었는지 확인 → 정리 → 재점검 (탐지 결과가 사라지는가)
"점검 도구가 제대로 찾는가"를 검증하려면 정답을 알고 있는 테스트 데이터 가 필요하다. 탐지 규칙을 만들 때도 같은 방식(알려진 테스트 항목으로 검증)을 쓴다.
# 1) 테스트 항목 심기 (무해한 logger 명령)
echo '@reboot /usr/bin/logger lab-autostart-cron-reboot' | crontab -u analyst -
echo '*/30 * * * * root /usr/bin/logger lab-autostart-cron-d' > /etc/cron.d/lab-auto
echo 'logger lab-autostart-profile' > /etc/profile.d/lab-auto.sh
echo 'logger lab-autostart-bashrc # lab' >> /home/analyst/.bashrc
# 사용자 unit + enable 과 같은 링크
mkdir -p /home/analyst/.config/systemd/user && printf '...[Install]\nWantedBy=default.target\n' > .../lab-auto.service
su - analyst -c 'mkdir -p ~/.config/systemd/user/default.target.wants && ln -s ../lab-auto.service ~/.config/systemd/user/default.target.wants/'
echo 'logger lab-autostart-at' | at now + 3 hours
# 2) 점검 스크립트 (/root/autostart-audit.sh) — 경로별 섹션 출력
# systemd enabled · timers · 사용자 unit · 사용자 crontab · /etc/crontab·cron.d
# · 주기 디렉터리 · at · 로그인 셸 초기화 · rc.local
/root/autostart-audit.sh
# 3) 정리 후 재점검
/root/autostart-audit.sh | grep -A1 -E '사용자 unit|사용자 crontab|at 작업|로그인 셸'
스크립트 전체는 실행 결과 화면에 있다. 운영 서버에 적용할 때는 Ubuntu 경로(
/var/spool/cron/crontabs/,/var/spool/cron/atjobs/,/etc/rc.local,/etc/bash.bashrc)를 추가한다.



텍스트 원본(실제 출력):
[root@rocky9-lab ~]# echo '@reboot /usr/bin/logger lab-autostart-cron-reboot' | crontab -u analyst -
[root@rocky9-lab ~]# echo '*/30 * * * * root /usr/bin/logger lab-autostart-cron-d' > /etc/cron.d/lab-auto
[root@rocky9-lab ~]# echo 'logger lab-autostart-profile' > /etc/profile.d/lab-auto.sh
[root@rocky9-lab ~]# echo 'logger lab-autostart-bashrc # lab' >> /home/analyst/.bashrc
[root@rocky9-lab ~]# mkdir -p /home/analyst/.config/systemd/user && printf '[Unit]\nDescription=Lab user autostart\n[Service]\nExecStart=/usr/bin/logger lab-autostart-user-unit\n[Install]\nWantedBy=default.target\n' > /home/analyst/.config/systemd/user/lab-auto.service && chown -R analyst:analyst /home/analyst/.config
[root@rocky9-lab ~]# su - analyst -c 'mkdir -p ~/.config/systemd/user/default.target.wants && ln -s ../lab-auto.service ~/.config/systemd/user/default.target.wants/ && ls -l ~/.config/systemd/user/default.target.wants/'
total 0
lrwxrwxrwx 1 analyst analyst 19 Sep 24 12:33 lab-auto.service -> ../lab-auto.service
[root@rocky9-lab ~]# echo 'logger lab-autostart-at' | at now + 3 hours 2>&1 | tail -1
job 5 at Thu Sep 24 15:33:00 2026
[root@rocky9-lab ~]# cat > /root/autostart-audit.sh <<'EOF'
> #!/bin/bash
> sec(){ printf '\n== %s\n' "$1"; }
> sec "systemd: enabled units (관리자 영역)"; find /etc/systemd/system -name '*.wants' -type d -exec ls {} + 2>/dev/null | grep -v ':$' | sort -u | tr '\n' ' '; echo
> sec "systemd: timers"; systemctl list-timers --all --no-legend --no-pager | awk '{print $(NF-1)}'
> sec "systemd: 사용자 unit"; ls -d /home/*/.config/systemd/user/*.service /home/*/.config/systemd/user/*.wants/* /root/.config/systemd/user/* 2>/dev/null
> sec "cron: 사용자 crontab"; for f in /var/spool/cron/*; do echo "[$f]"; grep -v '^#' "$f"; done 2>/dev/null
> sec "cron: /etc/crontab · cron.d"; grep -hvE '^(#|$|SHELL|PATH|MAILTO)' /etc/crontab /etc/cron.d/* 2>/dev/null
> sec "cron: 주기 디렉터리"; ls /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly 2>/dev/null | tr '\n' ' '; echo
> sec "at 작업"; atq
> sec "로그인 셸 초기화"; grep -l 'logger\|curl\|wget\|nc ' /etc/profile.d/*.sh /etc/bashrc /home/*/.bashrc /home/*/.bash_profile /root/.bashrc 2>/dev/null
> sec "rc.local"; ls -l /etc/rc.d/rc.local 2>/dev/null
> EOF
[root@rocky9-lab ~]# chmod 700 /root/autostart-audit.sh; /root/autostart-audit.sh
== systemd: enabled units (관리자 영역)
atd.service crond.service dbus.socket dnf-makecache.timer getty@tty1.service labapp.service nis-domainname.service remote-fs.target sshd.service systemd-network-generator.service
== systemd: timers
dnf-makecache.timer
systemd-tmpfiles-clean.timer
== systemd: 사용자 unit
/home/analyst/.config/systemd/user/default.target.wants/lab-auto.service
/home/analyst/.config/systemd/user/lab-auto.service
== cron: 사용자 crontab
[/var/spool/cron/analyst]
@reboot /usr/bin/logger lab-autostart-cron-reboot
== cron: /etc/crontab · cron.d
01 * * * * root run-parts /etc/cron.hourly
*/30 * * * * root /usr/bin/logger lab-autostart-cron-d
== cron: 주기 디렉터리
/etc/cron.daily: /etc/cron.hourly: 0anacron /etc/cron.monthly: /etc/cron.weekly:
== at 작업
5 Thu Sep 24 15:33:00 2026 a root
== 로그인 셸 초기화
/etc/profile.d/lab-auto.sh
/home/analyst/.bashrc
== rc.local
-rw-r--r-- 1 root root 474 Sep 16 04:40 /etc/rc.d/rc.local
[root@rocky9-lab ~]# /root/autostart-audit.sh 2>&1 | grep -A1 -E '사용자 unit|사용자 crontab|at 작업|로그인 셸'
== systemd: 사용자 unit
== cron: 사용자 crontab
[/var/spool/cron/*]
--
== at 작업
== 로그인 셸 초기화
| 관찰 | 의미 |
|---|---|
사용자 unit 링크 lab-auto.service -> ../lab-auto.service | systemctl --user enable은 이 링크를 만드는 것과 같다. 실습 환경에서는 사용자 manager 버스가 없어 링크를 직접 만들었다 |
job 4 at ... 15:32 | at 대기 작업 등록 |
| enabled units: atd, crond, labapp, sshd, ... | 관리자 영역의 부팅 시 실행 목록. labapp은 25편에서 등록한 것이다 |
| timers: dnf-makecache, systemd-tmpfiles-clean | 시스템 timer 2개 (35편과 같음) |
사용자 unit: .../default.target.wants/lab-auto.service, .../lab-auto.service | 사용자 홈에 숨어 있는 자동 실행 항목을 찾았다 |
사용자 crontab [/var/spool/cron/analyst] @reboot ... | 부팅 시 실행 항목 |
cron.d: */30 * * * * root ... lab-autostart-cron-d | root 권한 주기 실행 항목 |
at 작업 4 ... a root | 대기 중인 일회성 작업 |
로그인 셸 초기화: /etc/profile.d/lab-auto.sh, /home/analyst/.bashrc | logger 패턴이 들어간 초기화 파일 두 곳 |
rc.local -rw-r--r-- | 실행 권한이 없어 부팅 시 실행되지 않는 상태다. 권한이 x로 바뀌면 점검 대상 |
| 정리 후 재점검: 해당 섹션이 모두 비어 있음 | 테스트 항목이 모두 제거되었고, 스크립트가 정상 동작함을 확인했다 |
실습에서 심은 6가지 항목(@reboot, cron.d, profile.d, .bashrc, 사용자 unit, at)이 모두 탐지 되었다. 탐지 도구를 만들 때는 이렇게 알려진 항목으로 먼저 검증한다.
| 주제 | 내용 |
|---|---|
| 지속성 점검의 범위 | 침해 대응에서 악성 프로세스를 제거해도 자동 실행 항목이 하나라도 남으면 재감염된다. 모든 경로 를 확인해야 "제거 완료"라고 할 수 있다 |
| 사용자 영역 | root 권한이 없어도 crontab, ~/.bashrc, 사용자 systemd unit으로 자동 실행을 만들 수 있다. 일반 계정 침해도 이 경로를 점검한다 |
| 로그인 트리거 | /etc/profile.d/는 모든 사용자의 로그인 에 실행된다. 관리자가 로그인할 때 실행되는 코드는 특히 위험하다 |
| rc.local | 기본은 비활성(실행 권한 없음)이다. 권한과 내용이 바뀌었는지 확인한다 |
| 기준선 | 이 스크립트 결과를 평소에 저장해 두면, 사고 시 diff 한 번으로 새로 생긴 항목 만 볼 수 있다 |
[평소] /root/autostart-audit.sh > /var/lib/audit/autostart_baseline_$(date +%F).txt
↓
[정기·사고] /root/autostart-audit.sh > /tmp/autostart_now.txt
diff /var/lib/audit/autostart_baseline_*.txt /tmp/autostart_now.txt
↓
[새 항목] 경로별 상세 확인
systemd : systemctl cat <unit> · rpm -qf <파일>
cron : 스풀 파일 · journalctl -t CROND
셸 초기화: stat (수정 시각) · 내용
↓
[판단] 변경 관리 기록과 대조 → 설명되지 않는 항목은 45·46편 절차로 분석
| 경로 | 수정 시각 확인 |
|---|---|
| unit·drop-in | find /etc/systemd /home/*/.config/systemd -newermt '-7 days' |
| cron | ls -la --time-style=full-iso /var/spool/cron /etc/cron.d |
| 셸 초기화 | stat -c '%y %n' /etc/profile.d/* /home/*/.bashrc |
| 실수 | 결과 | 예방 |
|---|---|---|
| crontab만 보고 점검 종료 | timer·at·셸 초기화·사용자 unit 누락 | 경로 목록 전체 점검 |
| 일반 계정으로 점검 | 다른 사용자 스풀·홈을 못 읽음 | root로 실행 |
| 점검 결과를 저장하지 않음 | 변화 비교 불가 | 날짜별 결과 파일 |
| Rocky 경로만 스크립트에 넣음 | Ubuntu 서버에서 누락 | 배포판별 경로 모두 포함 |
| 점검 스크립트가 시스템을 수정 | 증거 훼손 | 읽기 전용 명령만 사용 |
[ ] 자동 실행 경로를 트리거(부팅·시간·로그인·이벤트)별로 정리했다
[ ] 무해한 테스트 항목 6가지를 각 경로에 심었다
[ ] 점검 스크립트가 6가지를 모두 탐지하는 것을 확인했다
[ ] rc.local 의 실행 권한 여부를 확인했다
[ ] 정리 후 재점검으로 탐지 결과가 사라지는 것을 확인했다
[ ] 기준선 저장과 diff 비교 절차를 설명할 수 있다
.bashrc·사용자 unit으로 자동 실행을 만들 수 있다.diff로 새로 생긴 항목을 찾는다.Part 4의 마지막 글 「40. 서비스 장애 트러블슈팅 흐름」 에서는 지금까지 배운 도구를 하나의 절차로 묶는다. "서비스가 안 떠요"라는 신고를 받았을 때 증상 → 상태 → 로그 → 원인 → 조치 → 검증 순서로 포트 충돌과 권한 문제 두 가지 장애를 해결한다.