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

1. 들어가며

Part 3·4에서 systemd unit, timer, cron, anacron, at, socket unit을 차례로 다뤘다. 이들의 공통점은 사람이 명령하지 않아도 무언가를 실행한다 는 것이다. 여기에 로그인할 때 읽히는 셸 초기화 파일과 사용자 systemd unit까지 더하면, 한 서버에서 자동 실행이 일어나는 지점은 열 군데가 넘는다.

침해 대응에서 "공격자가 다시 들어올 수 있는 장치가 남아 있는가"를 확인하려면 이 지점을 빠짐없이 봐야 한다. 이번 글에서는 자동 실행 경로를 지도로 정리하고, 각 경로에 무해한 테스트 항목(logger 한 줄) 을 심어 둔 뒤 이를 모두 찾아내는 점검 스크립트를 만든다.


2. 핵심 개념

2-1. 자동 실행 경로 정리

트리거경로실행 권한관련 편
부팅/etc/systemd/system/*.wants/, /usr/lib/systemd/systemunit의 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}/, anacronroot34
시간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

2-2. 점검 스크립트의 원칙

원칙이유
경로별로 섹션을 나눠 출력어느 경로가 비어 있는지도 결과가 된다
root로 실행다른 사용자의 스풀·홈 디렉터리를 읽어야 한다
읽기 전용점검 도구는 시스템을 바꾸지 않는다
결과를 파일로 저장기준선과 diff로 비교

3. 동작 원리

Linux 자동 실행 지점 지도

[1단계] 테스트 항목 심기 (모두 logger 로 로그 한 줄만 남기는 무해한 명령)
        crontab @reboot · /etc/cron.d · /etc/profile.d · ~/.bashrc · 사용자 unit · at
[2단계] 점검 스크립트 실행 → 경로별 목록 출력
[3단계] 심은 항목이 모두 탐지되었는지 확인 → 정리 → 재점검 (탐지 결과가 사라지는가)

"점검 도구가 제대로 찾는가"를 검증하려면 정답을 알고 있는 테스트 데이터 가 필요하다. 탐지 규칙을 만들 때도 같은 방식(알려진 테스트 항목으로 검증)을 쓴다.


4. 명령어 실습

# 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)를 추가한다.


5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — 실습용 자동 실행 항목 심기 (무해한 명령)

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — 자동 실행 경로 전수 점검

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — 정리

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

[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 작업

== 로그인 셸 초기화

6. 결과 해석

관찰의미
사용자 unit 링크 lab-auto.service -> ../lab-auto.servicesystemctl --user enable은 이 링크를 만드는 것과 같다. 실습 환경에서는 사용자 manager 버스가 없어 링크를 직접 만들었다
job 4 at ... 15:32at 대기 작업 등록
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-droot 권한 주기 실행 항목
at 작업 4 ... a root대기 중인 일회성 작업
로그인 셸 초기화: /etc/profile.d/lab-auto.sh, /home/analyst/.bashrclogger 패턴이 들어간 초기화 파일 두 곳
rc.local -rw-r--r--실행 권한이 없어 부팅 시 실행되지 않는 상태다. 권한이 x로 바뀌면 점검 대상
정리 후 재점검: 해당 섹션이 모두 비어 있음테스트 항목이 모두 제거되었고, 스크립트가 정상 동작함을 확인했다

실습에서 심은 6가지 항목(@reboot, cron.d, profile.d, .bashrc, 사용자 unit, at)이 모두 탐지 되었다. 탐지 도구를 만들 때는 이렇게 알려진 항목으로 먼저 검증한다.


7. 보안 관점

주제내용
지속성 점검의 범위침해 대응에서 악성 프로세스를 제거해도 자동 실행 항목이 하나라도 남으면 재감염된다. 모든 경로 를 확인해야 "제거 완료"라고 할 수 있다
사용자 영역root 권한이 없어도 crontab, ~/.bashrc, 사용자 systemd unit으로 자동 실행을 만들 수 있다. 일반 계정 침해도 이 경로를 점검한다
로그인 트리거/etc/profile.d/는 모든 사용자의 로그인 에 실행된다. 관리자가 로그인할 때 실행되는 코드는 특히 위험하다
rc.local기본은 비활성(실행 권한 없음)이다. 권한과 내용이 바뀌었는지 확인한다
기준선이 스크립트 결과를 평소에 저장해 두면, 사고 시 diff 한 번으로 새로 생긴 항목 만 볼 수 있다

8. 보안관제 관점

[평소]      /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-infind /etc/systemd /home/*/.config/systemd -newermt '-7 days'
cronls -la --time-style=full-iso /var/spool/cron /etc/cron.d
셸 초기화stat -c '%y %n' /etc/profile.d/* /home/*/.bashrc

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

실수결과예방
crontab만 보고 점검 종료timer·at·셸 초기화·사용자 unit 누락경로 목록 전체 점검
일반 계정으로 점검다른 사용자 스풀·홈을 못 읽음root로 실행
점검 결과를 저장하지 않음변화 비교 불가날짜별 결과 파일
Rocky 경로만 스크립트에 넣음Ubuntu 서버에서 누락배포판별 경로 모두 포함
점검 스크립트가 시스템을 수정증거 훼손읽기 전용 명령만 사용

10. 실습 체크리스트

[ ] 자동 실행 경로를 트리거(부팅·시간·로그인·이벤트)별로 정리했다
[ ] 무해한 테스트 항목 6가지를 각 경로에 심었다
[ ] 점검 스크립트가 6가지를 모두 탐지하는 것을 확인했다
[ ] rc.local 의 실행 권한 여부를 확인했다
[ ] 정리 후 재점검으로 탐지 결과가 사라지는 것을 확인했다
[ ] 기준선 저장과 diff 비교 절차를 설명할 수 있다

11. 핵심 정리

  • 자동 실행 지점은 systemd(시스템·사용자), cron, anacron, timer, at, 셸 초기화 파일, rc.local, socket/path unit까지 여러 곳이다.
  • 사용자 권한만으로도 crontab·.bashrc·사용자 unit으로 자동 실행을 만들 수 있다.
  • 점검 스크립트는 경로별 섹션·root 실행·읽기 전용·결과 저장을 원칙으로 한다.
  • 알려진 테스트 항목으로 점검 도구가 제대로 찾는지 먼저 검증 한다.
  • 결과를 기준선으로 저장하고 diff로 새로 생긴 항목을 찾는다.

12. 다음 편 예고

Part 4의 마지막 글 「40. 서비스 장애 트러블슈팅 흐름」 에서는 지금까지 배운 도구를 하나의 절차로 묶는다. "서비스가 안 떠요"라는 신고를 받았을 때 증상 → 상태 → 로그 → 원인 → 조치 → 검증 순서로 포트 충돌과 권한 문제 두 가지 장애를 해결한다.


참고 자료


시리즈 이동

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

0개의 댓글