파일 · 권한 · 사용자 관리 09 / 50 · Part 1. Linux 파일 관리 기초
실습 환경: Rocky Linux 9.8 · Ubuntu 24.04.5 (Docker 격리 컨테이너, 테스트 계정 analyst)
기초편 연계: 「리눅스 시스템 기초」 11. Linux 파일과 디렉터리 관리

1. 들어가며

cp는 파일을 복사한다. 그런데 "복사"의 정확한 의미는 새 파일(새 inode)을 만들고 내용을 옮겨 쓰는 것 이다. 내용은 같아도 소유자·권한·시간은 옵션에 따라 원본과 달라진다.

이 차이는 두 상황에서 중요하다.

  • 운영: root가 설정 파일을 복사했더니 서비스 계정이 읽지 못한다 (소유자가 root로 바뀜)
  • 침해 대응: 증거 파일을 cp로만 복사했더니 원본 시간·소유자 정보가 사라졌다

이번 글에서는 root 권한으로 cp, cp -p, cp -a를 비교하고, SUID 권한이 복사 과정에서 사라지는 것 까지 실제로 확인한다.


2. 핵심 개념

2-1. 주요 옵션

옵션의미
-r / -R디렉터리를 재귀적으로 복사
-p권한(ACL 포함), 소유자, 시간(mode, ownership, timestamps) 보존
-a아카이브 모드 = -dR --preserve=all (링크 유지, 재귀, 모든 속성)
-i덮어쓰기 전 확인
-n이미 있으면 덮어쓰지 않음
-v복사 과정 출력
--preserve=속성mode, ownership, timestamps, links, context, xattr, all 중 선택

2-2. 보존되지 않는 것

어떤 옵션을 써도 새 inode 번호, ctime, birth time 은 보존할 수 없다. 이 값들은 커널이 파일 생성·변경 시점에 기록하기 때문이다. 따라서 증거 보존에서는 복사 전에 원본 stat 결과를 따로 저장해야 한다.


3. 동작 원리

cp 는 "새 파일"을 만든다 — 옵션별로 보존되는 속성

옵션 없는 cp app.conf copy.conf는 내부적으로 다음과 같이 동작한다.

  1. 원본을 open()으로 읽기 위해 연다 → 원본 읽기 권한 필요
  2. 대상을 open(O_CREAT, 원본 모드)로 만든다 → 대상 디렉터리 w 권한 필요, umask 적용
  3. 내용을 읽어서 쓴다 (새 inode에 새 데이터 블록)
  4. 새 파일의 소유자는 cp를 실행한 사용자, 시간은 현재 시각

-p/-a는 여기에 fchown(), fchmod(), utimensat()을 추가로 호출해 원본 값을 덮어쓴다. 일반 사용자는 chown을 할 수 없으므로 cp -p를 해도 소유자는 보존되지 않는다. 소유자 보존은 root만 가능 하다.

SUID·SGID는 특별히 처리된다. GNU cp 매뉴얼에 따르면 --preserve=mode(-p, -a에 포함)가 없을 때 새 파일의 권한은 원본 권한 − umask − SUID/SGID 비트 다. 복사본이 누구 소유가 되든 SUID가 따라붙어 의도치 않은 권한 상승 경로가 생기는 것을 막기 위한 설계 다. 이번 실습은 root가 root 소유 파일을 복사해 소유자가 같았는데도 SUID가 제거된 것을 확인할 수 있다.


4. 명령어 실습

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

5. 실행 결과

실제 실행 결과 — Rocky Linux 9.8 · root@rocky9-lab — cp 옵션별 속성 보존

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

[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

6. 결과 해석

출력해석
원본 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-10root가 실행했으므로 소유자·시간 보존
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-xcp -a는 SUID까지 보존
두 sha256 값이 동일속성은 달라도 내용은 완전히 같음 을 증명
cp: -r not specified; omitting directory 'src'디렉터리는 -r 없이 복사되지 않는다

inode 번호가 모두 다르다는 점도 확인하자(12 / 14 / 15 / 16). 복사본은 원본과 완전히 별개의 파일 이다. 원본이 나중에 삭제·변조되어도 사본은 영향을 받지 않는다. 반대로 하드 링크(14편)는 같은 inode를 공유한다.


7. 보안 관점

주제위험권장
기본 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 쉘 복사본을 숨겨 둔다.


8. 보안관제 관점

증거 복사 절차 (라이브 시스템에서 파일 보존이 필요할 때)

# 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
관점내용
Detectioncp 자체는 정상 명령이다. 탐지 가치는 무엇을 어디로 복사했는가에 있다. 예: /bin/bash → /tmp, /etc/shadow → 웹 루트
auditdexecve 로그로 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 로그, 취약 서비스) 역추적

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

실수결과예방
증거를 cp로만 복사소유자·시간 정보 손실stat 기록 + cp -a + 해시
일반 사용자로 cp -p 후 소유자 보존 기대소유자는 실행자로 바뀜소유자 보존은 root 필요
cp -r로 심볼릭 링크 포함 디렉터리 복사링크 대상 내용까지 복사되거나 경로 오류cp -a 사용
설정 파일 백업을 같은 디렉터리에 .bak로웹에서 노출, 설정 로더가 읽기도 함별도 백업 디렉터리
대상이 이미 존재하는지 확인 안 함조용히 덮어씀-i 또는 -n

10. 실습 체크리스트

[ ] cp 가 새 inode 를 만든다는 것을 ls -li 로 확인했다
[ ] cp / cp -p / cp -a 의 소유자·시간 차이를 비교했다
[ ] 기본 cp 가 SUID 를 제거하는 것을 확인했다
[ ] cp -a 는 SUID 를 유지한다는 점을 확인했다
[ ] sha256sum 으로 원본과 사본의 내용 동일성을 확인했다
[ ] ctime·birth 는 어떤 옵션으로도 보존되지 않음을 이해했다

11. 핵심 정리

  • cp는 새 inode 를 만들어 내용을 복사한다. 사본과 원본은 별개의 파일이다.
  • 기본 cp는 소유자를 실행자로, 시간을 현재로 바꾸고, SUID·SGID를 제거 한다.
  • -p는 권한(ACL 포함)·소유자·시간, -a는 여기에 심볼릭 링크·xattr·SELinux 컨텍스트까지 보존한다.
  • 소유자 보존은 root만 가능하다.
  • ctime·birth·inode 번호는 어떤 옵션으로도 보존되지 않는다. 증거 복사 전 원본 stat을 기록한다.
  • 해시가 같으면 이름이 달라도 같은 파일이다. SUID 쉘 복사본 탐지에 활용한다.

12. 다음 편 예고

다음 글 「10. mv와 rm의 동작 원리」 에서는 mv가 같은 파일 시스템 안에서는 inode를 그대로 둔 채 이름만 바꾸고, 다른 파일 시스템으로는 복사 후 삭제 한다는 것을 확인한다. 그리고 rm이 실제로 지우는 것이 무엇인지 링크 수로 추적한다.


참고 자료


시리즈 이동

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

0개의 댓글