파일 · 권한 · 사용자 관리 09 / 50 · Part 1. Linux 파일 관리 기초
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (Docker 격리 컨테이너, 테스트 계정analyst)
기초편 연계: 「리눅스 시스템 기초」 11. Linux 파일과 디렉터리 관리
cp는 파일을 복사한다. 그런데 "복사"의 정확한 의미는 새 파일(새 inode)을 만들고 내용을 옮겨 쓰는 것 이다. 내용은 같아도 소유자·권한·시간은 옵션에 따라 원본과 달라진다.
이 차이는 두 상황에서 중요하다.
cp로만 복사했더니 원본 시간·소유자 정보가 사라졌다이번 글에서는 root 권한으로 cp, cp -p, cp -a를 비교하고, SUID 권한이 복사 과정에서 사라지는 것 까지 실제로 확인한다.
| 옵션 | 의미 |
|---|---|
-r / -R | 디렉터리를 재귀적으로 복사 |
-p | 권한(ACL 포함), 소유자, 시간(mode, ownership, timestamps) 보존 |
-a | 아카이브 모드 = -dR --preserve=all (링크 유지, 재귀, 모든 속성) |
-i | 덮어쓰기 전 확인 |
-n | 이미 있으면 덮어쓰지 않음 |
-v | 복사 과정 출력 |
--preserve=속성 | mode, ownership, timestamps, links, context, xattr, all 중 선택 |
어떤 옵션을 써도 새 inode 번호, ctime, birth time 은 보존할 수 없다. 이 값들은 커널이 파일 생성·변경 시점에 기록하기 때문이다. 따라서 증거 보존에서는 복사 전에 원본 stat 결과를 따로 저장해야 한다.

옵션 없는 cp app.conf copy.conf는 내부적으로 다음과 같이 동작한다.
open()으로 읽기 위해 연다 → 원본 읽기 권한 필요open(O_CREAT, 원본 모드)로 만든다 → 대상 디렉터리 w 권한 필요, umask 적용-p/-a는 여기에 fchown(), fchmod(), utimensat()을 추가로 호출해 원본 값을 덮어쓴다. 일반 사용자는 chown을 할 수 없으므로 cp -p를 해도 소유자는 보존되지 않는다. 소유자 보존은 root만 가능 하다.
SUID·SGID는 특별히 처리된다. GNU cp 매뉴얼에 따르면 --preserve=mode(-p, -a에 포함)가 없을 때 새 파일의 권한은 원본 권한 − umask − SUID/SGID 비트 다. 복사본이 누구 소유가 되든 SUID가 따라붙어 의도치 않은 권한 상승 경로가 생기는 것을 막기 위한 설계 다. 이번 실습은 root가 root 소유 파일을 복사해 소유자가 같았는데도 SUID가 제거된 것을 확인할 수 있다.
root 권한 실습은 격리된 테스트 컨테이너에서만 진행한다.
tool은 실습용으로/bin/true를 복사해 SUID를 설정한 파일이다.
# 사전 준비 (root): app.conf 는 analyst 소유, 640, mtime 2026-01-10
cd /data/lab09
ls -li app.conf
# 1) 옵션별 복사 비교
cp app.conf copy-default.conf
cp -p app.conf copy-p.conf
cp -a app.conf copy-a.conf
ls -li --time-style=long-iso app.conf copy-*.conf
# 2) SUID 파일 복사
ls -l tool && cp tool tool-copy && cp -a tool tool-a && ls -l tool*
# 3) 내용 동일성 확인
sha256sum app.conf copy-a.conf
# 4) 디렉터리 복사
mkdir -p src/sub && touch src/sub/f1 && cp src dst
cp -r src dst && ls -R dst

텍스트 원본(실제 출력):
[root@rocky9-lab ~]# cd /data/lab09
[root@rocky9-lab lab09]# ls -li app.conf
12 -rw-r----- 1 analyst analyst 10 Jan 10 2026 app.conf
[root@rocky9-lab lab09]# cp app.conf copy-default.conf
[root@rocky9-lab lab09]# cp -p app.conf copy-p.conf
[root@rocky9-lab lab09]# cp -a app.conf copy-a.conf
[root@rocky9-lab lab09]# ls -li --time-style=long-iso app.conf copy-*.conf
12 -rw-r----- 1 analyst analyst 10 2026-01-10 10:00 app.conf
16 -rw-r----- 1 analyst analyst 10 2026-01-10 10:00 copy-a.conf
14 -rw-r----- 1 root root 10 2026-09-24 10:15 copy-default.conf
15 -rw-r----- 1 analyst analyst 10 2026-01-10 10:00 copy-p.conf
[root@rocky9-lab lab09]# ls -l tool && cp tool tool-copy && cp -a tool tool-a && ls -l tool*
-rwsr-xr-x 1 root root 51 Sep 24 10:15 tool
-rwsr-xr-x 1 root root 51 Sep 24 10:15 tool
-rwsr-xr-x 1 root root 51 Sep 24 10:15 tool-a
-rwxr-xr-x 1 root root 51 Sep 24 10:15 tool-copy
[root@rocky9-lab lab09]# sha256sum app.conf copy-a.conf
d84f7d984648af610d97057f374fb21edf72aa11e9d00c261c4ec94767245afa app.conf
d84f7d984648af610d97057f374fb21edf72aa11e9d00c261c4ec94767245afa copy-a.conf
[root@rocky9-lab lab09]# mkdir -p src/sub && touch src/sub/f1 && cp src dst
cp: -r not specified; omitting directory 'src'
[root@rocky9-lab lab09]# cp -r src dst && ls -R dst
dst:
sub
dst/sub:
f1
| 출력 | 해석 |
|---|---|
원본 12 -rw-r----- analyst analyst 2026-01-10 | 원본 inode 12, 640, analyst 소유 |
copy-default.conf → 14 ... root root 2026-09-24 | 새 inode, 소유자 root, 현재 시각. 원본 속성이 사라졌다 |
copy-p.conf → 15 ... analyst analyst 2026-01-10 | root가 실행했으므로 소유자·시간 보존 |
copy-a.conf → 16 ... analyst analyst 2026-01-10 | -p와 같고, 추가로 심볼릭 링크·xattr·SELinux 컨텍스트까지 보존 |
권한 -rw-r-----는 셋 다 동일 | 원본 640에 umask 022를 적용해도 결과가 같다. 원본이 666이었다면 기본 cp는 644가 됐을 것 |
tool = -rwsr-xr-x | 원본은 SUID(s) 설정 |
tool-copy = -rwxr-xr-x | 기본 cp는 SUID를 제거 |
tool-a = -rwsr-xr-x | cp -a는 SUID까지 보존 |
| 두 sha256 값이 동일 | 속성은 달라도 내용은 완전히 같음 을 증명 |
cp: -r not specified; omitting directory 'src' | 디렉터리는 -r 없이 복사되지 않는다 |
inode 번호가 모두 다르다는 점도 확인하자(12 / 14 / 15 / 16). 복사본은 원본과 완전히 별개의 파일 이다. 원본이 나중에 삭제·변조되어도 사본은 영향을 받지 않는다. 반대로 하드 링크(14편)는 같은 inode를 공유한다.
| 주제 | 위험 | 권장 |
|---|---|---|
기본 cp로 SUID 파일 복사 | SUID는 제거되지만, cp -a/cp -p는 SUID를 유지 한다 | root가 SUID 바이너리를 /tmp 등으로 -a 복사하지 않도록 주의 |
| root가 설정 파일 복사 | 소유자가 root로 바뀌어 서비스 장애, 또는 반대로 권한이 넓어짐 | cp -p 또는 install -m -o -g |
| 백업 파일 권한 | cp app.conf app.conf.bak 후 웹 루트에 방치 → 다운로드 가능 | 백업은 웹 루트 밖, 600 |
| 민감 파일 복사 | /etc/shadow를 복사한 사본이 644로 남음 | 복사 후 권한 확인 |
| 공격자 관점 | cp /bin/bash /tmp/.b; chmod u+s /tmp/.b (root 권한 획득 후 백도어) | SUID 파일 기준값 비교 (47편) |
마지막 항목은 권한 상승 후 지속성(persistence) 을 만들기 위한 대표적인 기법이다. root 권한을 이미 얻은 공격자가 다음에도 쉽게 root가 되기 위해 SUID 쉘 복사본을 숨겨 둔다.
증거 복사 절차 (라이브 시스템에서 파일 보존이 필요할 때)
# 1) 원본 메타데이터 먼저 기록 (복사로 보존되지 않는 ctime·birth 포함)
stat /var/www/html/upload/x.php > /evidence/x.php.stat.txt
# 2) 해시
sha256sum /var/www/html/upload/x.php > /evidence/x.php.sha256
# 3) 속성 보존 복사
cp -a /var/www/html/upload/x.php /evidence/
# 4) 사본 해시 비교
sha256sum /evidence/x.php
| 관점 | 내용 |
|---|---|
| Detection | cp 자체는 정상 명령이다. 탐지 가치는 무엇을 어디로 복사했는가에 있다. 예: /bin/bash → /tmp, /etc/shadow → 웹 루트 |
| auditd | execve 로그로 cp 인자(원본·대상 경로)를 확인할 수 있다 |
| IOC | 사본의 해시는 원본과 같다. 따라서 /tmp/.b의 해시가 /usr/bin/bash와 같다면 "쉘 복사본"임을 즉시 확인할 수 있다 |
| Evidence | 증거 복사 후 원본·사본 해시 일치 여부를 보고서에 기록한다 (Chain of Custody) |
[Detection] SUID 기준값 비교: /var/tmp/.cache/dbus (root, -rwsr-xr-x) 신규
↓
[IOC 확인] sha256sum → /usr/bin/bash 와 동일 해시
↓
[판단] root 권한 획득 후 만든 SUID 쉘 백도어 (지속성)
↓
[Response] 파일 보존 → 제거 → root 권한을 얻은 경로(sudo 로그, 취약 서비스) 역추적
| 실수 | 결과 | 예방 |
|---|---|---|
증거를 cp로만 복사 | 소유자·시간 정보 손실 | stat 기록 + cp -a + 해시 |
일반 사용자로 cp -p 후 소유자 보존 기대 | 소유자는 실행자로 바뀜 | 소유자 보존은 root 필요 |
cp -r로 심볼릭 링크 포함 디렉터리 복사 | 링크 대상 내용까지 복사되거나 경로 오류 | cp -a 사용 |
설정 파일 백업을 같은 디렉터리에 .bak로 | 웹에서 노출, 설정 로더가 읽기도 함 | 별도 백업 디렉터리 |
| 대상이 이미 존재하는지 확인 안 함 | 조용히 덮어씀 | -i 또는 -n |
[ ] cp 가 새 inode 를 만든다는 것을 ls -li 로 확인했다
[ ] cp / cp -p / cp -a 의 소유자·시간 차이를 비교했다
[ ] 기본 cp 가 SUID 를 제거하는 것을 확인했다
[ ] cp -a 는 SUID 를 유지한다는 점을 확인했다
[ ] sha256sum 으로 원본과 사본의 내용 동일성을 확인했다
[ ] ctime·birth 는 어떤 옵션으로도 보존되지 않음을 이해했다
cp는 새 inode 를 만들어 내용을 복사한다. 사본과 원본은 별개의 파일이다.cp는 소유자를 실행자로, 시간을 현재로 바꾸고, SUID·SGID를 제거 한다.-p는 권한(ACL 포함)·소유자·시간, -a는 여기에 심볼릭 링크·xattr·SELinux 컨텍스트까지 보존한다.stat을 기록한다.다음 글 「10. mv와 rm의 동작 원리」 에서는 mv가 같은 파일 시스템 안에서는 inode를 그대로 둔 채 이름만 바꾸고, 다른 파일 시스템으로는 복사 후 삭제 한다는 것을 확인한다. 그리고 rm이 실제로 지우는 것이 무엇인지 링크 수로 추적한다.