본문 바로가기

회고와 이슈/이슈와 정보

여러분의 사이트는 안전한가요? ② HTTP Strict Transport Security 와 ③ X-Content-Type-Options

반응형

 

 


 

이전 포스트 | 여러분의 사이트는 안전한가요? ① | CSP 와 X-frame-Options

 

사이트 보안 향상 시키기 CSP 와 X-frame-Options

들어가는 말이전 포스트에서 제가 만들었던 명언 웹 사이트( https://wise-sayings.com/ ) 의 웹 보안 수준을 MDN 에서 제공하는 무료 도구를 사용해서 측정했었고, 30/100 으로 낮은 점수를 받았습니다. 그

duklook.tistory.com

 

들어가는 말

이번 시간은 저번 포스트에 이어서 HTTP Strict Transport Security 에 대해 알아보고 실제 사이트에 적용해보는 시간을 가져볼까 합니다.

2024.07.14 기준으로 X-Content-type-Options 부분의 내용도 추가하였습니다. 부분부분 내용이 매끄럽지 못한 부분이 있을 수도 있습니다.

 

우선 저의 경우 NextJS 13 ~ 을 사용하여 개발된 명언 웹 사이트를 기준으로 합니다. 물론 이론적인 설명이나 옵션은 스택과는 아무런 관련이 없습니다.

 

현재 상황

현재 도메인 사이트의 보안 수준은 55점으로 보통인 상태입니다. 처음 30점에 비해서 25점이 늘었지만 현재도 취약한 상태이죠.

 

 

특히 오늘은 HSTS 부분을 보완할 것인데, -20 점으로 책정되어 있습니다. 이를 오늘 0 으로 만드는게 목표입니다.

 

HTTP Strict Transport Security

보통 HTTP 프로토콜만을 사용하는 경우 MITM 이라는 중간자 공격에 취약하다고 합니다. 즉, 악의적인 해커가 A 호스트와 B 호스트 사이에 HTTP 를 통해 패킷을 주고받을 때, 중간의 해커가 이 패킷을 탈취해서 해당 패킷에 저장된 데이터를 읽을 수 있게되고, 정말정말 악의적인 경우 패킷의 내용을 조작해서 호스트 B 에게 전달하고 호스트 B는 그 데이터를 A로 부터 받은 것이라 판단하고 사용하는 순간... 말을 안 해도 알겠죠

 

암튼, HSTS 는 이를 막는 해결책으로 사용되는 헤더 입니다. 이 헤더는 원래 지정된 스키마가 HTTP였더라도 HTTPS를 통해서만 주어진 사이트에 연결하도록 브라우저에 알리는 HTTP 헤더로서, 주어진 사이트에 HSTS가 설정된 브라우저는 해당 사이트의 모든 요청을 자동으로 HTTPS로 업그레이드합니다. HSTS는 또한 인증서 오류 페이지를 우회하는 기능을 비활성화하여 브라우저에 TLS 및 인증서 관련 오류를 더 엄격하게 처리하도록 지시하는 역할을 합니다.

 

HTTP 요청이 이루어지는 TCP/IP 연결을 위한 3-handshake 와 HTTPS

앞서 HTTPS 는 HTTP 에 보안 계층을 한 개더 올린거라 생각하면 됩니다.

 

보통 HTTP 를 통해 서버와 클라이언트 간에 요청과 응답을 주고 받기전에 TCP/IP 연결 과정을 거칩니다. 이를 3-handshake 라고 합니다. 이 과정을 통해 서버와 클라이언트 간에 정상적으로 연결되었음을 HTTP 요청 이전에 확인하게 됩니다. 다만, TCP를 연결하는 과정에서 연결과 관련한 보안상 처리가 존재하긴 하지만, 클라이언트의 요청 자체는 암호화되지 않습니다.

 

여기서 HTTPS 가 중요하게 됩니다. HTTPS 는 HTTP 에 안전한 계층을 추가하게 되는데, 이를 TLS 라고 합니다.  TCP/IP 를 통한 3-handshake 가 이루어지고 다시 한번 TLS 연결(보안계층 연결)을 위한 3-handshake 가 이루어지게 되는데, 이 때, 클라이언트의 요청을 암호화 하는 작업이 이루어지게 됩니다.

 

이렇게 되면 악의적인 해커가 중간에서 패킷을 탈취한다고 해도, 암호화되어 있기 때문에 뜯어보기가 매우어렵게 되어 MITM 을 방지할 수 있게 됩니다.

 

HSTS 가 중요한 이유

앞서 중간자 공격이 문제가 되고, HSTS 는 이를 방지하는데 중요한 역할을 합니다. 

 

웹사이트가 HTTP를 통한 연결을 수락하고 HTTPS로 리디렉션하는 경우, 방문자는 리디렉션되기 전에 먼저 사이트의 비암호화 버전과 통신할 것입니다. 예를 들어 방문자가 http://www.foo.com/을 입력하거나 심지어 그냥 foo.com을 입력하는 경우입니다. 이는 중간자 공격의 기회를 만듭니다. 리디렉션을 악용하여 방문자를 원래 사이트의 보안 버전이 아닌 악의적인 사이트로 안내할 수 있습니다.

HTTP Strict Transport Security 헤더는 브라우저가 HTTP를 사용하여 사이트를 불러와서는 안 되며 대신 HTTP를 사용하여 사이트에 접근하려는 모든 시도를 HTTPS 요청으로 자동 변환해야 함을 알립니다
.

 

자세한 내용은 아래 링크를 참고해주세요

 

Transport Layer Security (TLS) configuration - Security on the web | MDN

Transport Layer Security (TLS) provides assurances about the confidentiality, authenticity, and integrity of all communications, and as such, should be used for all inbound and outbound website communications.

developer.mozilla.org

 

공용 와이파이가 무서운 점 | 공짜가 좋은 것만은 아닙니다. 

해당 예시도 MDN 에서 나와 있는 부분입니다. 공공 와이파이를 무턱대고 사용하면 어떤 일을 초래하는지 알려주는 무서운 사례입니다.

공항에서 무료 Wi-Fi 액세스 포인트에 로그인하고 웹 서핑을 시작하고 온라인 뱅킹 서비스를 방문하여 잔액을 확인하고 몇 가지 청구서를 지불합니다. 불행하게도 여러분이 사용하고 있는 액세스 포인트는 실제로 해커의 노트북이며 해커는 여러분의 원래 HTTP 요청을 가로채 실제 사이트 대신 은행 사이트의 복제본으로 리디렉션하고 있습니다. 이제 여러분의 개인 데이터가 해커에게 노출됩니다.


이 문제도 결국 Strict Transport Security는 이 문제를 해결합니다. HTTPS를 사용하여 은행 웹사이트에 한 번 접근했고 은행 웹사이트에서 Strict Transport Security를 사용하는 한, 브라우저는 자동으로 HTTPS만 사용한다는 것을 알게 되어 해커가 이러한 종류의 중간자 공격을 수행하지 못하도록 방지합니다

 

[실습] 적용해보기

그럼 이론적인 부분은 알아보았으니, NextJS13 ~ 에서 이를 어떻게 적용할 수 있는지를 알아보고 실제 적용해본 결과를 확인해보는 시간을 가져보겠습니다.

 

next.config.js

next.config.js 파일로 오셔서 headers() 메소드가 반환하는 배열 요소의 하나로 아래와 같이 추가해주기만 하면 됩니다. 

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=31536000; includeSubDomains; preload'
          }
        ]
      }
    ];
  }
};

 

위 옵션은 아래 내용을 참고해서 설정했습니다. 대략 최소 6달, 권장은 2년 이상은 https 연결을 권장하도록 max-age 를 지정해라, 하위 도메인은 모두 hsts 가 적용되도록 해라, rksmdgkaus HSTS 사전로드 목록에 너의 도메인 사이트를 포함시켜라 가 되겠습니다. (참고로 전 max-age 를 31536000 으로 1년을 지정했습니다)

https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/TLS#solution_4

 

 

적용 전 value 에 적용된 각 옵션에 대해 언급하고 가겠습니다.

max-age

브라우저가 HTTPS로 리디렉션되는 기간을 초 단위로 설정합니다.

 

includeSubDomains (선택)

브라우저가 모든 하위 도메인에 대한 요청을 HTTPS로 업그레이드해야 하는지 여부를 지정합니다. 예를 들어 domain.example.com에 includeSubDomains를 설정하면 host1.domain.example.com 및 host2.domain.example.com에 대한 요청도 추가로 업그레이드됩니다.

 

preload (선택)

사이트를 사전 로드해야 하는지 여부를 지정합니다. 이를 포함하면 사이트가 HSTS 사전 로드 목록 에 포함될 수 있습니다 .

 

검사해보기

이제 적용 후 사이트를 재배포 하고 검사를 시도해보겠습니다. TMI) 자동화 배포를 구축한 뒤에는 반영 후 push 만 하면 되어서 편하네요~

 

오.. 이번에는 75 점으로 55점에서 20 점이나 올랐습니다. test 를 통과한 케이스가 8개 로 원래는 6개만 통과했었는데 이제 2개만 더 보완하면 될 것 같습니다. 

 

보안은 눈에 보이지 않지만, 보이지 않기에 중요하다는 사실

이후  사이트에 접속해보면 눈에 드러나는 것은 당연히 없습니다. 보안이라는 것은 거의 눈에 보이지는 않지만, 보이지 않는다고 해서 중요하지 않은 것은 아니겠죠. 그래서 그 만큼 백엔드에서 처리해야 하는 역할이 막중하다는 생각이 듭니다. 요새 생성형 인공지능을 사용해서 프롬프트 한 줄로 간단하게 게임, 앱이나 웹 사이트를 만들 수 있는 시대가 왔는데요. 그 만큼 쉽게 정보를 취급하는 사이트가 양산되는 만큼 보안적인 맹점을 신경쓰지 못한다면 어떤 위험이 다분할지 모른다는 생각도 듭니다. 이번 시간은 이러한 점에서 경각심을 생각해보는 시간이 되기도 한 것 같아요.

 

MDN 에서 해당 스캔 도구를 제공하면서 사이트의 거진 80프로 이상이 최하점(F)을 받았다고 통계가 나와 있습니다. 완전히 해커들의 디저트가 가득 있는 것이죠

저는 개선 전에는 D 에 있었습니다.


 

X-Content-Type-Options 의 개념

이 부분은 다음 포스트로 만들까 고민하다가 현재 포스트에 이어서 작성하겠습니다. 옵션 자체만 다룬다면 그저 이전과 같이 추가만하면 되는 부분이라서 어려울 것은 없습니다. 무엇이든 적용 이전에 해당 옵션이 어떤 역할을 하는 지 알아보는 시간이 중요하다고 생각하므로 이에대해서 정리하는 시간을 가져보겠습니다.

 

X-Content-Type-Options 

이는 서버에서 Content-type 으로 알린 MIME 유형을 따라야 하며 이를 변경해서는 안 된다는 것을 나타내는 데 사용하는 마커입니다.  이 헤더를 사용하면 MIME 유형이 의도적으로 구성되었다고 말함으로 MIME 유형의 스니핑을 사전에 방지할 수 있습니다.

 

MIME(Multipurpose Internet Mail Extensions) 유형의 타입 지정이 중요한 이유

브라우저는 파일 확장자가 아닌 MIME 유형을 사용하여 URL을 처리하는 방법을 결정하므로 웹 서버가 응답 Content-Type헤더에 올바른 MIME 유형을 보내는 것이 중요합니다. 이것이 올바르게 구성되지 않으면 브라우저가 파일 내용을 잘못 해석할 가능성이 높고 사이트가 제대로 작동하지 않으며 다운로드한 파일이 잘못 처리될 수 있습니다.

 

흔히 클라이언트 측에서 데이터를 POST 의 body 객체에 담아 보낼 때 heaer 에 application/json 이라는 MIME 를 지정하여 보내는 이유도 데이터의 처리 방식을 명확히하고 불일치 등의 예기치 못한 문제들을 사전에 방지하기 위해서입니다. 

 

무엇 보다 웹 브라우저가 특정 파일을 읽을 때, 파일의 실제 내용과 Content-Type에 설정된 내용이 서로 다르면, 파일의 내용으로부터 파일의 형식을 추측하여 실행하는 MIME 스니핑를 사전에 방지할 수 있습니다.

 

[실습] 적용해보기

적용할 옵션은 한 가지 밖에 없습니다. X-Content-Type-Options: nosniff  만 추가해주면 됩니다. nosniff 로 지정하게 되면, MIME 타입이 아닌 엉뚱한 타입으로 인식되는 것이 있으면, 요청을 차단하는 옵션입니다.

 

적용하는 방법도 앞서 살펴본 것과 동일합니다. next.config.js 파일로 오셔서, 아래와 같이 추가해주면 끝입니다. 이렇게 되면 이제 브라우저와 서버 간에 데이터를 주고받을 때,  지정한 MIME 타입이 서로 다르다면 요청을 거절하게 됩니다.

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key:'X-Content-Type-Options',
            value: 'nosniff',
          }
        ]
      }
    ];
  }
};

  

적용 후 재검사 결과

적용 후에 5점이 더 추가되어서 80(B+) 까지 보안 수준이 향상되었습니다.  처음 35 부터 80 점까지 많은 개선을 한 것 같습니다.

 

 

나가는 말

이번에 총 3편에 걸쳐서 CSP 를 올리기 위한 과정을 지나왔습니다. 아직 20 점 정도가 부족한데, 이는 현재로서는 건드리기 힘든 옵션입니다. 제가 처음부터 끝까지 구축한 프레임워크라면 모르겠지만, NextJS 내부적으로 CSP 를 위반하면서도 불가피하게 사용되는 옵션들이 있기 때문에, 이를 건드리는 것은 위험부담이 많이 따르기 때문입니다. 다만, 이에 대한 공부를 추가로 해서 건드릴 수 있는 부분은 보완해 나가며 CSP 를 개선하는 노력을 해야 함을 분명 합니다.

 

 

끝으로, 현재 까지 제가할 수 있는 부분에서는 많은 개선을 할 수 있어서, 투자한 시간이 아깝지 않은 기회가 되었다고 생각합니다. 여러분도 이 글을 참고하셔서, 사이트의 보안을 조금이라도 더 높이기 위한 시간을 투자해보는 것이 어떨까요? 라며 권장드리며 글을 줄입니다.

 

 

참고 자료

MDN TLS | https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/TLS#http_strict_transport_security_implementation

MDN HSTS | https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Strict-Transport-Security

반응형