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

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

사용자 글자는 글자로, 스크립트는 정한 곳에서만

TT Lab 에서 이어서 보기

목표

게시판 화면의 출력 인코딩(jinja2 자동 이스케이프)과 CSP 헤더를 요구사항 시험으로 적고 고칩니다.

왜 중요한가

사용자가 정한 글자가 HTML 문법으로 해석되면 다른 사용자의 브라우저에서 우리 사이트의 이름으로 스크립트가 돕니다. 자리마다 이스케이프를 기억하는 방식은 한 곳을 빠뜨리므로 엔진의 자동 이스케이프에 맡기고, CSP 로 둘째 방어선을 둡니다.

단계

  1. /opt/app/appsec/az-html-lab/ 의 파일을 /root/appsec/az-html-lab/ 로 복사하고 python3 -m pytest -q test_functional.py 로 기존 기능 시험이 통과하는지 봅니다. REQUIREMENTS.md 를 끝까지 읽습니다.
  2. /root/appsec/az-html-lab/test_security.py 에 요구 A(제목·작성자·본문·이름은 화면에 글자로만 나오고, 줄바꿈만 이 된다)를 확인하는 시험을 test_a_ 로 시작하는 이름으로 씁니다. 지금 코드에서 실패하고, 올바르게 고친 코드에서 통과해야 합니다.
  3. service.py 를 고쳐 요구 A 를 지킵니다. 채점기는 숨은 요구 A 시험과 여러분의 test_a_ 시험을 함께 돌립니다.
  4. 요구 B(모든 HTML 응답에 CSP·nosniff 를 붙이고, 인라인 스크립트를 없앤다)를 확인하는 시험을 test_b_ 로 시작하는 이름으로 더합니다. 판정은 2단계와 같습니다.
  5. service.py 를 고쳐 요구 B 도 지킵니다.
  6. test_functional.py 가 그대로 통과하는지 봅니다. 채점기는 숨은 기능 시험도 함께 돌립니다.
  7. REQUIREMENTS.md 의 '감사 로그' 표대로 audit(...) 을 부릅니다(CSP 위반 보고는 csp.violation, 주소의 쿼리는 남기지 않음).
  8. 채점기가 그럴듯한 오답을 하나씩 끼워 여러분의 test_security.py 를 돌립니다. 오답마다 시험이 하나 이상 실패해야 합니다.

참고

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

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

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

요구 A 를 시험으로 쓴다

/root/appsec/az-html-lab/test_security.py 에 요구 A(제목·작성자·본문·이름은 화면에 글자로만 나오고, 줄바꿈만 이 된다)를 확인하는 시험을 test_a_ 로 시작하는 이름으로 쓴다. 이 시험은 지금 코드에서 실패하고, 올바르게 고친 코드에서 통과해야 한다.

댓글 본문과 작성자에 · 같은 표시를 넣은 화면을 파서로 읽어, 그 태그가 생겼는지 세어 보세요. 줄바꿈이 로 남는지와, 프로필 이름에 큰따옴표가 있을 때 title 속성이 이름과 같은지도 확인합니다.

요구 A 를 지키도록 고친다

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

Environment 의 autoescape 를 켜고, 본문의 |replace(...)|safe 를 먼저 이스케이프한 뒤 만 Markup 으로 끼우는 필터로 바꾸세요.

요구 B 를 시험으로 쓴다

/root/appsec/az-html-lab/test_security.py 에 요구 B(모든 HTML 응답에 CSP·nosniff 를 붙이고, 인라인 스크립트를 없앤다)를 확인하는 시험을 test_b_ 로 시작하는 이름으로 더한다. 02 단계처럼 지금 코드에서 실패하고 올바른 코드에서 통과해야 한다.

글 화면과 프로필 화면의 Content-Security-Policy 를 ; 로 나눠 지시어별로 비교하고, X-Content-Type-Options 도 보세요. 글 화면의

요구 B 를 지키도록 고친다

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

security_headers 에서 content-type 이 text/html 인 응답에만 헤더를 붙입니다. 인라인 스크립트는 지우고 글 번호는 로 넘긴 뒤 POST_JS 가 dataset.postId 로 읽게 하세요.

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

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

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

감사 로그를 남긴다

REQUIREMENTS.md 의 '감사 로그' 표대로 audit(...) 을 부른다(CSP 위반 보고는 csp.violation, 주소의 쿼리는 남기지 않음). 남기지 말라고 한 값은 어떤 항목에도 넣지 않는다.

handle_csp_report 에서 csp-report 사전을 꺼내 urlsplit 으로 document-uri 의 path, blocked-uri 의 scheme 과 host 만 남깁니다. 모양이 다른 보고는 기록하지 않습니다.

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

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

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