생성된 nonce를 사용하는 방법
CSP nonce 프리셋이 선택되어 있습니다. 랜덤 16바이트를 Base64로 나타내
==로 끝나는 24자 값을 만듭니다.바로 붙여넣기에 헤더와 짝이 맞는 태그가 나옵니다. 예를 들면 다음과 같습니다.
Content-Security-Policy: script-src 'nonce-SQu4/GjzG3plY6CUwsx8gA==' <script nonce="SQu4/GjzG3plY6CUwsx8gA==" src="/app.js"></script>로컬 테스트 페이지나 스테이징 설정에서 정책을 시험해 볼 때 쓰세요. 다른 값이 필요하면 새로 생성을 누르세요.
이 페이지에서 복사한 nonce는 모든 요청에서 똑같으므로 운영 환경에서는 아무것도 보호하지 못합니다. 실제 배포에서는 아래처럼 서버 코드에서 생성합니다.
nonce 기반 정책의 작동 방식
script-src 'nonce-…'가 있으면 브라우저는 nonce 속성이 바로 그 응답의 헤더에 있는 값과 일치하는 스크립트만 실행합니다. 직접 작성한 <script> 태그는 템플릿에서 이 속성을 받습니다. 공격자가 XSS 버그로 삽입한 마크업은 페이지를 불러올 때마다 바뀌는 값을 알 수 없으므로 브라우저가 실행을 거부합니다.
실무에서는 세 가지가 중요합니다.
- 값은 텍스트로 비교됩니다.
=까지 정확히 복사하고, 헤더에서는'nonce-…'를 작은따옴표로 감싸되 속성에서는 감싸지 마세요. - 브라우저는 속성을 숨깁니다. 페이지가 로드되면
getAttribute("nonce")는 빈 문자열을 돌려주고 CSS 선택자도 값을 볼 수 없어서, 일부 유출 기법이 막힙니다. 값이 필요한 스크립트는 대신nonce프로퍼티를 읽습니다. 'strict-dynamic': nonce가 붙은 신뢰할 수 있는 스크립트가 추가 스크립트를 불러올 때 각각에 nonce가 없어도 되게 합니다. 최신 정책에서 nonce와 함께 쓰는 것이 일반적이며, 이것이 있으면 브라우저는https:같은 호스트 허용 목록을 무시합니다.
정책은 <meta http-equiv="Content-Security-Policy"> 태그로도 전달할 수 있습니다. 여기서도 nonce는 작동하지만 frame-ancestors 같은 일부 지시문은 작동하지 않습니다.
응답마다 nonce 생성하기
서버는 요청마다 랜덤 16바이트를 만들어 헤더에 넣고 같은 값을 템플릿에 넘깁니다. 프레임워크 없는 Node.js에서는 다음과 같습니다.
const crypto = require("crypto");
const http = require("http");
http.createServer((req, res) => {
const nonce = crypto.randomBytes(16).toString("base64");
res.setHeader("Content-Security-Policy",
`script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none'`);
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(`<script nonce="${nonce}" src="/app.js"></script>`);
}).listen(8080);
Python에서는 base64.b64encode(secrets.token_bytes(16)).decode()가 같은 종류의 값을 만들며, CSP 미들웨어가 있는 프레임워크는 보통 이를 템플릿 변수로 제공합니다. 무엇을 쓰든 서버 앞의 캐시가 nonce 하나가 박힌 HTML을 저장해 모든 방문자에게 내주지 않도록 하세요. 헤더는 바뀌는데 페이지 본문만 캐시되면 모든 스크립트가 깨지고, 둘 다 캐시되면 모든 사람에게 같은 nonce가 반복됩니다.
nonce, 해시, SRI
nonce는 한 응답에 대해 스크립트 태그를 승인합니다. CSP 해시는 특정 인라인 스크립트를 영구히 승인합니다. 'sha256-…'는 <script>와 </script> 사이의 정확한 텍스트에 대한 SHA-256을 Base64로 나타낸 값입니다. <script>alert('Hello');</script>라면 다음과 같습니다.
script-src 'sha256-gKHd+pSZOJ3MwBsFalomyNobAcinjJ44ArqbIKlcniQ='
해시는 정적 사이트와 절대 바뀌지 않는 인라인 코드에 적합합니다. SHA-256을 Base64로 도구로 계산하되, 공백 하나만 더 있어도 해시가 바뀌므로 공백에 주의하세요. 하위 리소스 무결성(SRI)은 같은 Base64 다이제스트를 쓰는 세 번째 방식입니다. <script src>에 integrity="sha384-…"를 붙이면 브라우저가 내려받은 파일 자체를 검사합니다. SRI 해시 생성기가 이런 태그를 만들어 줍니다.
자주 묻는 질문
CSP nonce는 얼마나 길어야 하나요?
CSP 명세는 인코딩 전 기준으로 최소 128비트의 무작위성을 요구합니다. 16바이트, Base64로 24자입니다. 더 길어도 되지만 더 짧으면 보호 효과가 약해집니다.
모든 페이지에 같은 nonce를 다시 써도 되나요?
안 됩니다. 고정 nonce는 HTML에 그대로 보이므로, 마크업을 삽입할 수 있는 공격자가 자신의 <script> 태그에 복사해 넣을 수 있습니다. HTTP 응답마다 새로 생성하세요.
정적 사이트에서도 CSP nonce를 쓸 수 있나요?
앞단에서 새 nonce를 넣어 주는 엣지 함수처럼 모든 응답을 다시 쓰는 무언가가 있을 때만 됩니다. 순수한 정적 호스팅은 모든 사람에게 같은 바이트를 보내므로, 대신 스크립트 해시('sha256-…')나 'self'의 외부 파일을 쓰세요.
nonce에 URL-safe 문자를 써도 되나요?
됩니다. CSP 문법은 두 Base64 알파벳을 모두 받습니다. 영문자, 숫자, +, /, -, _와 끝의 = 최대 두 개입니다. 일반적으로는 표준 Base64를 씁니다.