TT Lab
블로그 시작하기
배우기 러닝패스 코스

비밀번호·키·비밀값 다루기

로그에는 값을 가려서, 사용자에게는 오류 번호만

TT Lab 에서 이어서 보기

목표

접근 로그·로그인 로그·500 응답에서 비밀값이 새지 않게 하는 요구를 caplog 시험으로 적고, 가리는 함수와 오류 번호로 고칩니다.

왜 중요한가

로그는 여러 사람이 오래 보고 여러 시스템으로 복사됩니다. 헤더와 쿼리와 본문을 통째로 남기면 토큰과 비밀번호가 쌓이고, 예외 메시지는 접속 문자열을 응답과 트레이스백에 싣습니다. 남아야 할 값까지 함께 시험해 로그를 꺼 버린 수정도 걸러 냅니다.

단계

  1. /opt/app/appsec/sk-redact-lab/ 의 파일을 /root/appsec/sk-redact-lab/ 로 복사하고 python3 -m pytest -q test_functional.py 로 기존 기능 시험이 통과하는지 봅니다. REQUIREMENTS.md 를 끝까지 읽습니다.
  2. /root/appsec/sk-redact-lab/test_security.py 에 요구 A(헤더·쿼리·본문의 민감한 값은 *** 로 가리고 경로·상태·사용자는 그대로 남긴다)를 확인하는 시험을 test_a_ 로 시작하는 이름으로 씁니다. 지금 코드에서 실패하고, 올바르게 고친 코드에서 통과해야 합니다.
  3. service.py 를 고쳐 요구 A 를 지킵니다. 채점기는 숨은 요구 A 시험과 여러분의 test_a_ 시험을 함께 돌립니다.
  4. 요구 B(500 응답은 error 와 error_id 만, 로그에는 같은 번호와 가린 메시지·트레이스백)를 확인하는 시험을 test_b_ 로 시작하는 이름으로 더합니다. 판정은 2단계와 같습니다.
  5. service.py 를 고쳐 요구 B 도 지킵니다.
  6. test_functional.py 가 그대로 통과하는지 봅니다. 채점기는 숨은 기능 시험도 함께 돌립니다.
  7. REQUIREMENTS.md 의 '감사 로그' 표대로 audit(...) 을 부릅니다(500 은 server.error(error_id·path·exc_type), 메시지는 싣지 않음).
  8. 채점기가 그럴듯한 오답을 하나씩 끼워 여러분의 test_security.py 를 돌립니다. 오답마다 시험이 하나 이상 실패해야 합니다.

참고

재료를 복사하고 기능 시험을 돌린다

/opt/app/appsec/sk-redact-lab/ 의 파일을 모두 /root/appsec/sk-redact-lab/ 로 복사하고, 그 디렉터리에서 python3 -m pytest -q test_functional.py 로 기존 기능 시험이 통과하는 것을 본다. REQUIREMENTS.md 를 끝까지 읽는다.

cp 로 디렉터리의 파일을 통째로 옮깁니다. 고치기 전에 지금 무엇이 되는지부터 확인해 두면, 나중에 무엇을 깨뜨렸는지 가릴 수 있습니다.

요구 A 를 시험으로 쓴다

/root/appsec/sk-redact-lab/test_security.py 에 요구 A(헤더·쿼리·본문의 민감한 값은 *** 로 가리고 경로·상태·사용자는 그대로 남긴다)를 확인하는 시험을 test_a_ 로 시작하는 이름으로 쓴다. 이 시험은 지금 코드에서 실패하고, 올바르게 고친 코드에서 통과해야 한다.

Authorization 헤더, 쿼리의 code, 로그인 본문의 Password(대문자로 시작)와 안쪽 사전의 token 에 눈에 띄는 값을 넣고 caplog.text 에 없는지 보세요. 경로와 사용자와 이메일은 남아 있어야 합니다.

요구 A 를 지키도록 고친다

/root/appsec/sk-redact-lab/service.py 를 고쳐 요구 A 를 지킨다. 채점기는 숨은 요구 A 시험과 여러분의 test_a_ 시험을 고친 코드에 돌린다.

SENSITIVE 집합과 redact·redact_query·redact_text 를 만들고 describe_request 와 log_login 이 그것을 지나게 하세요. 열쇠는 lower() 로 비교하고, 사전과 목록은 안으로 들어갑니다. 쿼리는 parse_qsl 로 나눠 urlencode(..., safe="*") 로 다시 묶습니다.

요구 B 를 시험으로 쓴다

/root/appsec/sk-redact-lab/test_security.py 에 요구 B(500 응답은 error 와 error_id 만, 로그에는 같은 번호와 가린 메시지·트레이스백)를 확인하는 시험을 test_b_ 로 시작하는 이름으로 더한다. 02 단계처럼 지금 코드에서 실패하고 올바른 코드에서 통과해야 한다.

/report 의 500 응답 본문이 error 와 error_id 두 열쇠뿐인지, 접속 비밀번호가 응답에도 caplog.text 에도 없는지, error_id 가 로그에 있는지 확인하세요.

요구 B 를 지키도록 고친다

/root/appsec/sk-redact-lab/service.py 를 고쳐 요구 B 도 지킨다. 채점기는 숨은 요구 B 시험과 여러분의 test_b_ 시험을 고친 코드에 돌린다.

uuid.uuid4().hex 로 번호를 만들고, traceback.format_exception(exc) 로 만든 글을 redact_text 에 통과시켜 logger.error 로 남깁니다. logger.exception 은 쓰지 않습니다.

기존 기능이 그대로인지 본다

고친 service.py 로 python3 -m pytest -q test_functional.py 가 그대로 통과하는지 본다. 채점기는 재료의 원본 기능 시험에 숨은 기능 시험을 더해 돌린다.

보안 수정이 정상 사용자를 막으면 그 수정은 되돌려집니다. 실패한 기능 시험의 이름이 어떤 정상 동작을 막았는지 알려 줍니다. 막을 것만 막았는지 요구사항의 '그대로 된다' 문장과 견주어 보세요.

감사 로그를 남긴다

REQUIREMENTS.md 의 '감사 로그' 표대로 audit(...) 을 부른다(500 은 server.error(error_id·path·exc_type), 메시지는 싣지 않음). 남기지 말라고 한 값은 어떤 항목에도 넣지 않는다.

error_response 에서 audit('server.error', error_id=..., path=..., exc_type=type(exc).name) 를 부릅니다. 예외 메시지는 넣지 않습니다.

그럴듯한 오답을 시험이 붙잡는지 본다

채점기가 기준 코드에 그럴듯한 오답을 하나씩 끼워 /root/appsec/sk-redact-lab/test_security.py 를 돌린다. 오답마다 시험이 하나 이상 실패하도록 필요한 시험을 더한다.

채점 메시지가 어떤 오답이 빠져나갔는지 알려 줍니다. 한 경로만 고친 수정이면 다른 경로를, 비교 방법이 틀린 수정이면 그 비교를 확인하는 시험을 더하세요. 요구사항의 문장 하나하나가 시험 하나가 될 수 있습니다.