주문 처리를 추적하려고 로그를 자세히 남기게 했다. 며칠 뒤 로그를 보다가 이런 줄을 봤다.
[주문] 접수 name=홍길동 phone=010-1234-5678 addr=서울시 ... card=1234-56**-****-7890
이름과 연락처와 주소와 카드 번호 일부가 다 있다.
Table of contents
Open Table of contents
증상 — 통째로 찍고 있었다
문제가 된 코드다.
log_message('info', '[주문] 접수 ' . json_encode($_POST, JSON_UNESCAPED_UNICODE));
json_encode($_POST) 라 무엇이 들어왔는지 다 보여 편했다. 그게 문제였고 안 남겨야 할 것도 다 남는다.
로그는 개발자가 보고 백업에 들어가고 여러 곳으로 복사된다. 원본 표는 접근 권한을 좁혀 뒀는데 로그 파일에는 그런 것이 없었다.
추적을 위한 편의가 다른 위험을 만들고 있었다.
남길 것을 골랐다
전부 찍는 대신 필요한 것만 골랐다.
log_message('info', sprintf(
'[주문] 접수 order=%s items=%d amount=%d pay=%s',
$orderNo, count($items), $amount, $payType
));
order 와 amount 와 pay 만 있으면 추적에 충분했다. 이름이나 연락처가 필요하면 주문번호로 조회해서 보므로 로그에 있을 이유가 없다.
식별이 필요한 경우도 있었다. 그때는 일부만 남겼다.
function maskPhone($p) {
$p = preg_replace('/[^0-9]/', '', $p);
if (strlen($p) < 8) return '***';
return substr($p, 0, 3) . '****' . substr($p, -4);
}
phone=010****5678
substr 로 앞뒤만 남기니 대조는 되고 전체는 안 남는다. 이름은 성만 남겼다.
function maskName($n) {
return mb_substr($n, 0, 1) . str_repeat('*', max(1, mb_strlen($n) - 1));
}
이미 쌓인 것을 봤다
앞으로를 고치는 것과 별개로 이미 있는 것을 확인했다.
$ grep -rlE "010-[0-9]{4}-[0-9]{4}" /var/log/app/
grep -rlE 로 석 달치가 나왔고 파일이 90개쯤이었다. 보관 기간을 확인하니 로그를 1년 두고 있었는데 개인정보가 든 채로 1년은 길다.
1. 개인정보가 든 옛 로그를 정리한다
2. 보관 기간을 줄인다 (1년 → 3개월)
3. 앞으로는 안 남긴다
1번은 지우는 것과 가리는 것 중에 골라야 했다. 문제 추적에 필요한 부분이 있어서 가리기로 했다.
sed -i -E 's/(01[0-9])-[0-9]{4}-([0-9]{4})/\1****\2/g' /var/log/app/*.log
sed -i 는 되돌릴 수 없으니 사본을 먼저 뜨고 시험 파일로 확인한 뒤에 돌렸다. logrotate 의 보관 개수도 줄이고 백업에 들어간 것도 확인했다.
앞을 막는 것만 하고 끝냈으면 이미 나간 것은 그대로 남았을 것이다.
기준과 검사와 권한
무엇을 어디까지 남길지 기준을 적었다.
남긴다
식별자 (주문번호, 회원번호, 요청 식별자)
분류 값 (상태, 유형, 결제수단)
수치 (금액, 개수, 소요 시간)
오류 내용
가리고 남긴다
연락처 (앞3 + 뒤4)
이름 (성만)
안 남긴다
주민번호·카드번호·계좌번호 (일부라도)
비밀번호·토큰·키
주소 전체
메일 주소 전체
식별자만 있으면 필요할 때 조회하므로 로그에 값을 둘 이유가 없다.
기준을 정해도 새로 짜는 코드에서 또 나올 수 있어서 배포 전에 훑게 했다.
PATTERNS='json_encode\(\$_POST|json_encode\(\$_REQUEST|print_r\(\$_POST'
if grep -rnE "$PATTERNS" --include=*.php application/; then
echo "요청 전체를 로그에 남기는 코드가 있습니다"
exit 1
fi
json_encode($_POST) 나 print_r($_POST) 같은 패턴을 잡는다. 완벽하지 않지만 흔한 경우는 걸린다.
내용을 정리해도 파일을 아무나 읽으면 소용없어서 권한도 봤다.
$ ls -l /var/log/app/
-rw-r--r-- 1 www-data www-data ... app.log
0644 라 같은 서버의 누구나 읽을 수 있었다. umask 기본값 때문에 그렇게 만들어진 것이었다.
$ chmod 640 /var/log/app/*.log
$ chown www-data:adm /var/log/app/*.log
읽을 사람을 그룹으로 묶고 0640 으로 좁혔다. 새로 생기는 파일도 그렇게 되게 설정을 바꿨다.
정리
- 요청을 통째로 찍으면 안 남겨야 할 것도 남는다
- 원본 표는 권한을 좁혀도 로그 파일에는 그것이 없다
- 추적에 필요한 것만 고른다. 식별자만 있으면 필요할 때 조회한다
- 식별이 필요하면
substr로 앞뒤만 남기고 덮는다 - 앞으로를 고치는 것과 이미 쌓인 것은 별개다
grep -rlE로 이미 쌓인 파일을 찾는다- 개인정보가 든 로그는 보관 기간도 다시 본다
- 무엇을 남기고 가리고 안 남길지 기준을 적는다
- 배포 전에 흔한 패턴을 검사한다
umask때문에0644로 만들어지니0640으로 좁힌다