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

인가와 입력 처리를 코드로 지키기

남의 주문과 관리자 기능을 서버의 사실로 막는다

TT Lab 에서 이어서 보기

목표

주문 서비스의 객체 소유 검사와 관리자 기능 권한을 요구사항 시험으로 적고, 판단을 한 함수에 모아 고칩니다.

왜 중요한가

토큰이 유효하다는 것과 그 주문을 읽어도 된다는 것은 다른 질문입니다. 한 경로만 고치면 옆 경로가 열려 있고, 헤더를 믿으면 헤더를 붙이는 누구나 관리자가 됩니다. 판단을 시험으로 고정해 두면 다음 사람이 경로를 더할 때 빠뜨린 검사를 시험이 먼저 알려 줍니다.

단계

  1. /opt/app/appsec/az-orders-lab/ 의 파일을 /root/appsec/az-orders-lab/ 로 복사하고 python3 -m pytest -q test_functional.py 로 기존 기능 시험이 통과하는지 봅니다. REQUIREMENTS.md 를 끝까지 읽습니다.
  2. /root/appsec/az-orders-lab/test_security.py 에 요구 A(고객은 자기 주문만 읽고, 남의 주문은 없는 주문과 같은 404)를 확인하는 시험을 test_a_ 로 시작하는 이름으로 씁니다. 지금 코드에서 실패하고, 올바르게 고친 코드에서 통과해야 합니다.
  3. service.py 를 고쳐 요구 A 를 지킵니다. 채점기는 숨은 요구 A 시험과 여러분의 test_a_ 시험을 함께 돌립니다.
  4. 요구 B(환불과 전체 목록은 저장된 역할이 admin 일 때만)를 확인하는 시험을 test_b_ 로 시작하는 이름으로 더합니다. 판정은 2단계와 같습니다.
  5. service.py 를 고쳐 요구 B 도 지킵니다.
  6. test_functional.py 가 그대로 통과하는지 봅니다. 채점기는 숨은 기능 시험도 함께 돌립니다.
  7. REQUIREMENTS.md 의 '감사 로그' 표대로 audit(...) 을 부릅니다(거절은 authz.denied, 환불은 order.refunded).
  8. 채점기가 그럴듯한 오답을 하나씩 끼워 여러분의 test_security.py 를 돌립니다. 오답마다 시험이 하나 이상 실패해야 합니다.

참고

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

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

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

요구 A 를 시험으로 쓴다

/root/appsec/az-orders-lab/test_security.py 에 요구 A(고객은 자기 주문만 읽고, 남의 주문은 없는 주문과 같은 404)를 확인하는 시험을 test_a_ 로 시작하는 이름으로 쓴다. 이 시험은 지금 코드에서 실패하고, 올바르게 고친 코드에서 통과해야 한다.

bob 의 토큰으로 alice 의 주문(o-100)을 상세와 영수증 두 경로로 읽어 보세요. 요구 A 가 말하는 결과는 상태 코드와 본문 둘 다 '없는 주문과 같다' 입니다. 주인과 운영자는 읽을 수 있다는 것도 함께 확인하면 지나치게 막은 수정도 걸러집니다.

요구 A 를 지키도록 고친다

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

판단은 can_read(user, order) 같은 함수 하나에 모으고, 상세와 영수증이 모두 load_order 를 지나게 하세요. 주문이 없을 때와 읽을 수 없을 때 같은 예외를 냅니다.

요구 B 를 시험으로 쓴다

/root/appsec/az-orders-lab/test_security.py 에 요구 B(환불과 전체 목록은 저장된 역할이 admin 일 때만)를 확인하는 시험을 test_b_ 로 시작하는 이름으로 더한다. 02 단계처럼 지금 코드에서 실패하고 올바른 코드에서 통과해야 한다.

고객 토큰에 X-Role: admin 헤더를 붙여 환불과 /admin/orders 를 불러 보세요. 거절된 환불이 주문 상태를 바꾸지 않았는지, 운영자는 헤더 없이도 되는지도 시험할 거리입니다.

요구 B 를 지키도록 고친다

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

require_admin(user, action) 이 저장된 역할(user['role'])만 보게 하고, 환불과 전체 목록 둘 다 그 함수를 먼저 부르게 하세요. x_role 인자는 받기만 하고 쓰지 않습니다.

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

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

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

감사 로그를 남긴다

REQUIREMENTS.md 의 '감사 로그' 표대로 audit(...) 을 부른다(거절은 authz.denied, 환불은 order.refunded). 남기지 말라고 한 값은 어떤 항목에도 넣지 않는다.

거절하는 자리에서 audit('authz.denied', actor=..., action=..., resource=..., status=...) 를 부릅니다. 없는 주문은 권한 판단이 아니므로 기록하지 않습니다. 응답은 같게, 기록은 다르게.

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

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

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