ps 한 줄에 비밀번호가 보였다
한 줄 요약
명령행 인자와 환경 변수는 비밀값을 넘기는 가장 쉬운 길이지만, 같은 서버의 다른 계정에게도 보이는 길입니다. systemd 의 LoadCredential= 은 비밀값을 root 만 읽는 파일에 두고 그 서비스만 읽게 넘겨 줍니다.
왜 이게 문제인가
리눅스에서 /proc/<pid>/cmdline 은 기본으로 누구나 읽습니다. 그래서 --db-password 로 넘긴 값은 ps 한 줄에 보입니다. 환경 변수는 프로세스 소유자만 읽지만, systemd 유닛의 Environment= 는 systemctl show 로 일반 계정에게도 보이고 유닛 파일 자체도 대개 누구나 읽는 권한입니다.
서버에 사람 계정이 하나만 더 있어도, 웹 애플리케이션 하나가 뚫려 그 계정으로 명령을 칠 수 있게 돼도, 공격자는 권한 상승 없이 DB 비밀번호를 줍습니다. 장애를 보다가 켜 둔 디버그 로그가 같은 값을 적고 있는 경우도 흔합니다.
어떻게 동작하나
systemd 자격 증명은 세 부분으로 이루어집니다.
| 자리 | 하는 일 |
|---|---|
/etc/credstore/<이름> |
root 만 읽는(디렉터리 700, 파일 600) 원본 |
LoadCredential=<이름>:<경로> |
서비스가 뜰 때 원본을 서비스 전용 자리로 복사하라는 지시 |
$CREDENTIALS_DIRECTORY |
서비스 안에서 보이는 자리(/run/credentials/<유닛>/), 그 서비스만 읽는다 |
ExecStart 에서는 %d/<이름> 으로 파일 경로를 넘길 수 있습니다. 명령행에는 경로만 보이고 값은 보이지 않습니다. 환경 변수도 쓰지 않으므로 systemctl show 에도 나타나지 않습니다.
바꾼 뒤 확인할 자리가 셋입니다. systemd 가 새 정의를 읽었는지(daemon-reload 뒤 NeedDaemonReload=no), 돌고 있는 프로세스가 새 정의로 다시 떴는지(/proc/<pid>/cmdline 과 environ), 그리고 서비스가 여전히 정상인지입니다. 정의를 고친 것과 프로세스가 바뀐 것은 다른 사실이라, 셋 다 확인하지 않으면 "고쳤다" 고 말할 수 없습니다.
현장에서 만나는 모습
자격 증명으로 옮기는 일보다 이미 노출된 값을 정리하는 일이 더 오래 걸립니다. 노출됐던 값은 회전하고, 디버그 로그·백업한 유닛 파일·셸 기록처럼 일반 계정이 읽을 수 있는 자리에 남은 사본을 찾아 치워야 합니다. 이때 root 로 찾으면 모든 파일이 다 보이니 의미가 없습니다. 일반 계정의 권한으로 찾아야 그 계정이 실제로 볼 수 있는 것만 나옵니다.
또 하나는 감사입니다. 원본 파일을 감사할 때 파일만 감시하면, 상위 디렉터리에서 권한이 막힌 시도는 그 파일까지 닿지 않아 기록되지 않습니다. 누가 열어 보려 했는지가 궁금하다면 디렉터리를 감시해야 합니다.
고친 뒤 무엇으로 확인하나
서버 쪽 비밀값 정리는 확인할 자리가 많아서 순서를 정해 두면 빠뜨리지 않습니다.
- systemd 가 새 정의를 들고 있는가.
systemctl show -p NeedDaemonReload가 no 인지 봅니다. - 돌고 있는 프로세스가 새 정의로 떴는가. 메인 PID 의
/proc/<pid>/cmdline과/proc/<pid>/environ을 봅니다. - 권한 없는 계정이 무엇을 보는가.
runuser -u <계정> --로 프로세스 목록과systemctl show를 다시 봅니다. - 서비스가 정상인가. 상태 확인 주소나 실제 요청으로 봅니다.
- 노출됐던 값이 회전됐고, 그 사본이 남아 있지 않은가.
1번과 2번이 다른 질문이라는 것이 이 모듈의 핵심입니다. 정의를 고치는 일과 프로세스를 바꾸는 일 사이에는 항상 다시 띄우는 단계가 있고, 그 단계를 빠뜨린 채 "고쳤다" 고 보고하는 일이 가장 흔합니다.
English corner
- credential: 자격 증명. 비밀번호·토큰·키를 통틀어 부르는 말입니다.
- world-readable: 누구나 읽을 수 있는 권한. 권한 검토 문서에 자주 나옵니다.
- process listing: 프로세스 목록. "secrets may be exposed in process listings" 같은 경고 문장으로 만납니다.
- reload the unit: 유닛 정의를 다시 읽게 한다. systemd 에서는 daemon-reload 를 뜻하는 경우가 많습니다.
다음 실습에서 할 것
일반 계정의 눈으로 정산 서비스의 비밀번호가 보이는 세 자리를 확인합니다. 비밀번호를 자격 증명 파일로 옮기고, 유닛을 고치고, 다시 띄워 확인합니다. 노출됐던 값을 회전하고, 열어 보려는 시도를 감사로 남기고, 남은 사본을 치웁니다.
한 걸음 더 — 자격 증명의 수명
systemd 자격 증명은 값을 안전한 자리에 두는 것까지입니다. 그 값이 언제 바뀌는가 는 여전히 따로 설계해야 합니다. 파일을 바꾸고 서비스를 다시 띄우는 회전을 사람이 기억에 맡기면 결국 몇 달째 그대로가 됩니다. 그래서 자격 증명 파일을 비밀 관리자(Vault·클라우드 시크릿)에서 주기적으로 내려받아 갱신하는 사이드카나 타이머를 함께 두는 경우가 많습니다. 이 코스의 마지막 모듈(동적 비밀값)이 바로 그 자동 회전을 다루며, 여기서 한 '파일로 안전하게 넘기기' 가 그 토대가 됩니다.