CORS, HTTP Only

CORS, HTTP Only

HTTP, CORS 에 대한 이해 부족으로 발생한 문제


스스로 HTTPCORS에 대해 어느정도 이해하고 있다고 생각하고 있었으나, 이번 사이드 프로젝트를 진행 중, 서버를 A to Z로 구축하며 여러번 몇번의 삽질을 하게 되어 삽질을 하게 만든 두 가지 문제와 이 문제의 원인 및 해결방안에 대해 기고합니다.


SOP(Same-Origin Policy)


웹(Web)에는 다른 출처로의 리소스 요청을 제한하는 SOP(Same-Origin Policy)라는 보안정책이 있습니다.

SOP는 2011년에 RFC-6454 에 제안되었으며, 말 그대로 같은 출처에 대해서만 리소스를 요청할 수 있어야 한다는 보안정책을 의미합니다.

이 정책이 나온 이유는 XSS, CSRF 등의 보안공격이 주 원인이며, XSS, CSRF는 매번 OWASP Top 10에 들 정도로 빈번하게 사용되는 위험한 공격방법입니다.

흔히 특정 사이트를 똑같이 베낀 페이크 사이트를 통해 민감정보를 탈취하거나 악의적인 스크립트를 내장한 이메일을 무차별로 살포해서 민감정보를 탈취하는 등의 수법들이 있으며, 출처를 모르는 이메일의 링크는 함부로 클릭하지 말라는 말을 다들 어디선가 한번쯤은 들어보셨을 정도로 유명한 공격 방법입니다.


🔔 RFC: 간단하게 모든 인터넷 기술의 표준을 정의한 문서라고 생각해도 무방합니다.


이러한 SOP 정책으로 인해 기본적으로 다른 출처로의 요청은 금지돼있으나, 이렇게 FM으로 다 막아버리면 효율성이 너무 떨어집니다.


비유가 좀 이상하긴 하지만, 원칙적으로 도로를 건널때는 횡단보도, 육교, 지하도를 통해 건너야 하는데 골목길이나 이면도로같은 곳에서는 현실적으로 무단횡단이 빈번하게 일어나고, 경찰이 이를 보더라도 제지하지는 않습니다.

도로가 좁거나 차가 많이 지나다니지 않는곳에선 사고가 발생할 가능성이 현저히 낮기 때문에, 횡단보도가 1km 앞에 있다면 대부분의 사람들은 횡단보도로 건너기 위해 1km를 걸어가느니 그냥 무단횡단하는것을 선택할 가능성이 높습니다.

이편이 훨씬 더 시간을 절약할 수 있으니까요. (물론 법적으로는 불법이긴 합니다. 무단횡단은 가급적 하지말고, 하더라도 조심조심 합시다…)


비슷하게 웹 개발을 하다 보면 타 출처로의 리소스 요청은 빈번하게 있는 일이기 때문에 항상 동일 출처로만 리소스 요청을 하도록 강제하는 것은 위와 비슷한 비효율적인 상황을 빈번하게 조성할 수 있습니다.

따라서 타 출처로의 리소스 요청은 SOP를 근거로 원칙적으로 금지하되, 특정한 조건을 만족하면 타 출처로의 리소스 요청을 허용하는 CORS라는 이름의 일종의 위법성 조각 사유를 만들어 놓은 것입니다.

만약 CORS 정책마저 만족하지 못한다면 SOP를 근거로 타 출처로의 리소스 요청이 금지되고 웹 브라우저 콘솔창에는 CORS 관련 에러메시지가 표시되게 됩니다.


Access to fetch at ‘http://localhost:3000’ from origin ‘https://www.google.com/’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.


CORS(Cross-Origin Resource Sharing)


물론 완벽하게 정확하진 않겠지만, CORS를 아주 간단하게 요약해보자면 현재 보고있는 페이지로 현재 페이지를 준 서버와는 다른 서버의 리소스를 가져오는 것을 의미합니다.

우리가 웹 브라우저에서 https://www.google.com/에 접속한다면 다음과 같은 화면을 볼 수 있습니다.


image


위 페이지는 html, css, javascript로 이루어져 있는 리소스이며, 이러한 데이터들은 모두 https://www.google.com/라는 도메인을 사용하고있는 모종의 서버에서 나온 것입니다.

이때 위 리소스출처(Origin)https://www.google.com/가 되는 것이죠.


그리고 이렇게 https://www.google.com/에서 받아온 리소스에서 내부적으로 자바스크립트를 통해 https://www.naver.com/로 임의의 데이터를 요청하는 상황을 CORS라고 부르게 됩니다.

https://www.google.com/https://www.naver.com/는 서로 다른 출처(Origin)이기 때문이죠.


여기서 출처(Origin)에 대해 조금 더 자세히 설명해보자면, URL과 관련이 있습니다.

URL에는 정말 많은 요소가 존재하지만, 기본적으로 아래의 요소가 모두 같은 경우 출처가 같다(Same-Origin)고 말합니다.


image


위 이미지에서 ProtocolScheme이라고 부르기도 합니다.


한가지 짚고 넘어가자면 우리는 보통 브라우저에서 https://www.google.com/ 라고 입력하여 구글에 접속하지, https://www.google.com:443/ 이라고 입력해서 접속하진 않죠?

이렇게 생략할 수 있는 특별한 포트(Port)들이 존재하는데 이러한 포트들을 Well Known Ports라고 부르며, 이 특별한 포트들을 관리하는 범세계적인 기구가 IANA입니다.

위 포트들에 대한 내용은 https://www.iana.org/Service Name and Transport Protocol Port Number Registry문서에서 확인하실 수 있습니다.

위 URL에서 스킴(Scheme)https이므로 자연스레 Well Known Port443을 사용하게 되고, 이는 생략할 수 있게 되는것입니다.


이에 대한 추가적인 레퍼런스는 HTTP/1.1이 정의된 RFC-2616을 참고하시면 됩니다.


3.3.2 http URL

If the port is empty or not given, port 80 is assumed. The semantics are that the identified resource is located at the server listening for TCP connections on that port of that host, and the Request-URI for the resource is abs_path (section 5.1.2).


혹은 영문서라 부담스럽다 싶으시면 흔히 다람쥐책이라고 불리우는 HTTP 완벽 가이드를 읽어보시는 것도 좋을 것 같습니다.


CORS는 세가지 방식을 통해 허용됩니다.

일단 명심해야 할 것은 아래의 모든 시나리오는 결국 서버에서 내려주는 응답 헤더에 Access-Control-Allow-Origin가 포함돼있는지를 알기 위함임을 잊지 말아야 하며, 아래의 모든 시나리오를 잘 이해해야 결과적으로 CORS로 인한 문제를 회피하거나 해결할 수 있습니다.


Preflight


사전요청(Preflight)은 타 출처로의 리소스 요청시 요청을 받는 서버에서 CORS 요청에 대한 메서드와 헤더에 대해 인식하고 있는지를 먼저 체크하는 것으로, 가장 일반적인 시나리오입니다.


image


사전요청(Preflight)Access-Control-Request-Method, Access-Control-Request-Headers, Origin 총 3가지의 HTTP 요청 헤더를 사용하는 OPTIONS 요청입니다.


OPTIONSGET, POST와 같이 자주사용되는 표준 HTTP Methods의 일종으로 서버가 해당 API에 대해 어떤 옵션들을 허용하고 있는지를 서버에 질의(Query)하는 요청입니다.


우리가 자바스크립트의 fetch API 같은 것을 사용하여 브라우저에게 타 서버의 특정 API를 통해 리소스를 받아오라는 명령을 내리면 브라우저는 해당 API로 사전요청을 먼저 보내고, 서버는 사전요청에 대한 응답으로 현재 자신의 특정 API에서 어떤 것들을 허용하고, 어떤 것들을 허용하지 않는지에 대한 정보를 응답 헤더에 담아서 브라우저에게 다시 보내주게 됩니다.


OPTIONS /resource/foo
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://foo.bar.org


HTTP/1.1 204 No Content
Connection: keep-alive
Access-Control-Allow-Origin: https://foo.bar.org
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 86400


이후 브라우저는 자신이 타 서버로 보낸 사전요청과 서버가 응답에 담아준 내용을 비교한 후, 이후 보낼 요청을 서버에서 허용하고 있음을 확인하면 다시 같은 API로 본래 보내려던 진짜 요청을 보내게 됩니다.

이후 서버가 본 요청에 대한 응답을 하면 브라우저는 최종적으로 이 응답 데이터를 자바스크립트에게 넘겨줍니다.

간단하게 vue cli를 통해 프로젝트를 하나 만들고, axios를 통해 사전요청 시나리오를 재현해봤습니다.


login() {
      let uri = 'http://www.ark-inflearn.shop/api/v1/login';
      let data = {
        email: "test12551@email.com",
        password: "AASHFKHQWFQYW#qwhfgqwf123!",
      }
      axios.post(uri, data)
          .then(() => {
            alert("Successes login!");
          })
          .catch(err => {
            alert(err.response.data.responseBody);
          });
    }


image


Login API에 대해 사전요청이 먼저 발생 후 이에 200 응답을 받고, Login API로 본 요청이 전송됐음을 확인할 수 있었습니다.


만약 서버에서 사전요청에 대한 알맞은 처리가 돼있지 않다면 사전요청 시 아래와 같은 에러 메시지가 발생하게 됩니다.


OPTIONS http://www.ark-inflearn.shop/api/v1/login 405 (Method Not Allowed) Failed to load http://www.ark-inflearn.shop/api/v1/login: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:3000’ is therefore not allowed access. The response had HTTP status code 405. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.


Simple Request


image


MDN 문서를 보면 특정한 조건을 만족할때 사전요청이 배제된 단순요청(Simple Request)이 발생한다고 나와있습니다.


image


첫번째 조건은 다른 Http Methods를 쓰면 되지만, 두번째와 세번째 조건을 보니 사실상 단순요청이 발생하는 상황을 만들기가 어려울 것 같다는 생각이 듭니다.

대체로 API 서버를 개발할때 Content-Typeapplication/json이나 text/xml을 사용하고, 기본적인 HTTP 헤더를 제외한 다른 헤더를 사용하면 안된다는 것은 매우 지키기 어렵기 때문입니다.

이 시나리오로 진행할 수 있다면 사전 요청을 생략할 수 있으므로 하는게 좋을 것 같지만, 현실적으로 어려워보이니 그냥 이런것도 있구나 정도로 이해하고 넘기면 될만한 내용인 것 같습니다.


Credentialed Request


일반적인 HTTP 요청과 다르게 HTTP 요청에 자격증명(Credential)이 포함된 경우를 의미합니다.

여기서 자격증명(Credential) 이라는 것은, 인증과 관련된 헤더(JWT, 커스텀 인증키 등)와 쿠키같은 것들을 의미하는데, 기본적으로 브라우저가 제공하는 XMLHttpRequestFetch API를 사용하게 되면 요청에 이러한 자격증명을 담지 않습니다.

이렇게 되면 만약 서버에서 인증정보를 요구하는 경우, 예를 들자면 세션-쿠키 방식의 인증체계를 사용하는데, 인증이 필요한 경우에 브라우저에서 절대 쿠키를 보내지 않게 되어 결과적으로 성공적으로 로그인이 됐음에도 서버는 계속해서 인증정보를 요구하는 상황이 발생할 수 있습니다.

사용자 입장에서 보면 분명 로그인을 했는데 뭐만하면 계속 로그인 페이지로 이동되는 상황이니 “무슨 이딴 웹페이지가 다 있냐? 안 써! 😡” 같은 대참사가 발생할 수도 있는것이죠.

따라서 브라우저는 서버에 임의의 요청을 보낼 경우 HTTP 헤더에 인증 정보를 담아야 하는데, 이때 사용할 수 있는 옵션이 credentials 옵션입니다.

이 부분은 코드로 보시는게 더 이해하기 쉬울 것 같습니다.


login() {
      let uri = 'http://www.ark-inflearn.shop/api/v1/login';
      let data = {
        email: "test12551@email.com",
        password: "AASHFKHQWFQYW#qwhfgqwf123!",
      }
      axios.post(uri, data, { withCredentials: true })
          .then(() => {
            alert("Successes login!");
          })
          .catch(err => {
            alert(err.response.data.responseBody);
          });
    }


axios 헤더에 { withCredentials: true }라는 코드를 작성했습니다.

http://www.ark-inflearn.shop/api/v1/login로 요청을 보낼 때 쿠키 등의 인증정보를 함께 보내겠다는 의미입니다.


이렇게 보면 매우 간단하지만 이 경우 정말 생각지도 못한 골때리는 문제가 발생합니다. (제가 이번에 삽질을 하게 된 문제 중 하나가 이 문제였습니다. 😭)


image

바로 Access-Control-Allow-Origin와일드카드(*, asterisk)를 사용할 수 없다는 것입니다.


image


와일드카드를 통해 어떤 경우에라도 CORS 헤더를 응답하는것을 허용하겠다고 했는데 되려 와일드카드를 사용했기 때문에 안된다니? 처음에 이 부분이 정말 이해가 안되서 삽질을 했던거죠.

바로 CORS 시나리오 중 HTTP 요청에 자격증명(Credential)이 포함됐을 경우에 대한 내용을 이해하지 못했기 때문이었습니다.

이 시나리오에서는 두가지 규칙을 반드시 지켜야만 합니다.


  • Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 반드시 명시적인 URL을 작성해야 합니다.
  • 응답 헤더에는 반드시 Access-Control-Allow-Credentials: true가 존재해야 합니다.


이 내용을 이해했음에도 약간 더 삽질을 하게 됐는데, 우리가 진행하는 사이드 프로젝트의 현 상황때문이었습니다.

백엔드 서버는 구축이 되어 특정한 도메인을 갖고 24시간 가동중인데, 프론트 서버가 아직 구축되지 않은 상태라 프론트 팀에서는 각자의 로컬에 프론트 서버를 띄우고 작업하고 있었기 때문에 명시할 URL이 마땅찮았습니다. (보안상 권장되지 않는 방법임에도 불구하고 와일드카드를 통해 CORS를 허용한 이유이기도 합니다)

로컬에서 리액트 프로젝트를 npm run dev등의 명령어를 통해 띄울 경우 http://localhost:3000으로 서버가 뜨니 생각나는 URL이래봐야 http://localhost:3000/이라던가 http://127.0.0.1:3000 따위의 것들인데, 해봤자 안될게 뻔했기 때문이죠. (내부적으로 hosts 파일을 통해 라우팅되는 도메인이므로…😫)

결국 생각나는 유효한 방법은 프론트 개발자들이 사용하는 로컬 서버의 공인 IP를 전달받아 모두 등록하는 방법뿐이었습니다.

그리고 이 방법은 일단 사용하고 싶지 않았습니다.


CORS 해결


여기부턴 자바, 스프링 부트와 스프링 시큐리티의 내용을 다룹니다.


CORS관련 문제의 원인은 알았는데, 마땅한 해결 방법을 찾지 못하던 중 다행히 스프링 시큐리티(Spring Security)의 문서에서 이러한 상황을 해결할 수 있는 방법을 찾아냈습니다.


image


스프링 부트에서는 스프링 MVC를 통해 CORS를 허용하는 방법과 스프링 시큐리티를 통해 CORS를 허용하는 두가지 방법이 있습니다.

스프링 MVC 공식 문서의 CORS 섹션 에서는 CORS를 허용하려면 다음과 같은 코드를 작성하라고 소개해주고 있습니다.


@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {

        registry.addMapping("/api/**")
            .allowedOrigins("https://domain2.com")
            .allowedMethods("PUT", "DELETE")
            .allowedHeaders("header1", "header2", "header3")
            .exposedHeaders("header1", "header2")
            .allowCredentials(true).maxAge(3600);

        // Add more mappings...
    }
}


반대로 스프링 시큐리티 공식문서의 CORS 섹션에서는 이렇게 서술합니다.

Spring Framework provides first class support for CORS. CORS must be processed before Spring Security because the pre-flight request will not contain any cookies (i.e. the JSESSIONID). If the request does not contain any cookies and Spring Security is first, the request will determine the user is not authenticated (since there are no cookies in the request) and reject it.

The easiest way to ensure that CORS is handled first is to use the CorsFilter. Users can integrate the CorsFilter with Spring Security by providing a CorsConfigurationSource using the following:


🤔 JSESSIONID ? :

자바 서블릿이 정의한 쿠키 이름 표준 스펙입니다.

자바 서블릿을 구현한 구현체로 네티, 언더토우, 톰캣, 아파치 등등이 있는데, 이처럼 자바 서블릿을 구현한 웹 서버라면 기본적으로 자바 서블릿 표준 스펙을 따르기 때문에 쿠키 이름이 JSESSIONID으로 응답됩니다.

이는 서버 개발자가 임의로 변경할수도 있습니다.

편의상 자바 진영에서 톰캣을 가장 많이 사용하기 때문에 톰캣이 발급하는 쿠키 이름이라고도 많이 표현되는 편입니다.

📜 관련 레퍼런스: https://javaee.github.io/servlet-spec/downloads/servlet-4.0/servlet-4_0_FINAL.pdf

7.1.1 Cookies

...

The container sends a cookie to the client. The client will then return the cookie on
each subsequent request to the server, unambiguously associating the request with a
session. The standard name of the session tracking cookie must be JSESSIONID.
Containers may allow the name of the session tracking cookie to be customized
through container specific configuration.

...


위 내용에 대해 설명드리자면, 사전요청(Preflight)에는 쿠키가 없으므로 CORS는 반드시 스프링 시큐리티보다 먼저 체크되고 처리되어야 한다는 것입니다.

왜냐하면 쿠키가 없는 요청에 대해 스프링 시큐리티가 검증하려들면, 쿠키가 없기 때문에 인증되지 않은 사용자로 구분될 것이 분명하고 결과적으로 요청이 항상 튕겨져 나가게 되기 때문입니다.

이를 스프링 시큐리티에서 가장 간단하게 해결할 수 있는 방법으로 CorsFilter를 사용하는 방법을 소개하고 있습니다.


이러한 이유로 CorsFilterSpring Security Filter Chain의 선두에 위치하고 있습니다.


저는 스프링 시큐리티를 사용하므로 모든 인증 관련 처리를 스프링 시큐리티에서 통합해 관리하고 싶어 스프링 MVC를 사용한 방식이 아닌 스프링 시큐리티의 CorsFilter를 설정했고, 아래와 같은 코드를 추가했습니다.


@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
        .cors() // Bean으로 등록된 CorsFilter를 사용. Bean이 없다면 스프링 시큐리티에서 제공하는 기본 CorsFilter 사용.
    ...
}

@Bean // Bean으로 등록 필요
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();

    configuration.setAllowedOrigins(Collections.singletonList("*")); // 모든 Origin에서의 요청을 허용
    configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); // 해당 Http Methods를 사용하는 요청을 허용
    configuration.setAllowedHeaders(List.of("authorization", "content-type", "x-auth-token")); // 해당 헤더를 사용하는 요청을 허용
    configuration.setExposedHeaders(Collections.singletonList("x-auth-token")); // 헤더에 CSRF 토큰이 있는 요청에 대해 모든 응답 헤더를 노출
    configuration.setAllowCredentials(true); // 사용자 자격 증명(쿠키, 인증키) 사용을 허용할 것

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration); // 모든 URL에 대해 위의 설정을 사용해 CORS 처리를 할 것
    return source;
}


configuration.setAllowedOrigins(Collections.singletonList("*"));


위 코드가 문제였는데, 명시해야 할 URL이 없었기 때문입니다.

다행히 스프링 시큐리티 팀에서 해당 문제를 회피할 수 있는 setAllowedOriginPatterns라는 API를 하나 제공하고 있었습니다.


configuration.setAllowedOriginPatterns(Collections.singletonList("*"));


이 API에 대한 문서를 찾아보니 다음과 같은 내용이 있습니다.


public CorsConfiguration setAllowedOriginPatterns(@Nullable List<String> allowedOriginPatterns)

Alternative to setAllowedOrigins(java.util.List<java.lang.String>) that supports more flexible origins patterns with "*" anywhere in the host name in addition to port lists. Examples:

https://*.domain1.com -- domains ending with domain1.com
https://*.domain1.com:[8080,8081] -- domains ending with domain1.com on port 8080 or port 8081
https://*.domain1.com:[*] -- domains ending with domain1.com on any port, including the default port

In contrast to allowedOrigins which only supports "*" and cannot be used with allowCredentials, when an allowedOriginPattern is matched, the Access-Control-Allow-Origin response header is set to the matched origin and not to "*" nor to the pattern. 
Therefore allowedOriginPatterns can be used in combination with setAllowCredentials(java.lang.Boolean) set to true.

By default this is not set.

Since:
5.3


즉, Origin을 명시해야 하는데, 명시해야 할 Origin이 마땅치 않으니 이를 패턴을 통해 보다 더 유연한 방법으로 요청을 구분하고 허용하는 것입니다.

간단히 말해 위와 아래의 차이는 서버로 오는 모든 요청에 대해 허용하느냐, 서버로 오는 모든 요청 중 특정한 패턴을 만족하는 요청만 허용하느냐의 차이인 것이죠…

저는 근데 여기다가 와일카드를 사용했고, 이는 서버로 오는 모든 요청 중 모든 패턴의 요청을 허용이 되므로 결과적으로 말장난 같긴 합니다 ㅎㅎㅎ 🤣

아마 내부적으로는 모종의 복잡한 과정을 거쳐 결과적으로 configuration.setAllowedOrigins(Collections.singletonList("*"));를 사용한 것과 같은 효과를 내게 되겠죠?

아무튼 이렇게 CORS 문제를 해결할 수 있었습니다.

하지만 이게 끝이 아니었습니다… 😣


HttpOnly


이건 뭔지 몰라서 실제로 문제가 없는데 문제가 있는 걸로 착각했던 내용이었습니다.

CORS 문제를 해결했음에도 불구하고 크롬 개발자도구에서 서버에서 응답한 쿠키를 찾을 수 없어서, 서버에서 인증요청에 대한 쿠키가 제대로 응답되지 않은 줄 알았습니다.

그래서 패킷을 까봤는데, HTTP 응답 헤더에는 쿠키가 제대로 들어있었죠. 🤔


image


근데 위 이미지에서 보시다시피 맨 끝에 HttpOnly라는 알수없는 태그가 노란 느낌표와 함께 달려있었고, 이게 원인인것 같아 찾아보다가 결국 OWASP 에서 관련 내용을 찾을 수 있었습니다.


If the HttpOnly flag (optional) is included in the HTTP response header, the cookie cannot be accessed through client side script (again if the browser supports this flag). As a result, even if a cross-site scripting (XSS) flaw exists, and a user accidentally accesses a link that exploits this flaw, the browser (primarily Internet Explorer) will not reveal the cookie to a third party.


원칙적으로 SOP에 의해 출처(Origin)가 다른 서버에서 리소스를 얻어올 수 없는데, 이를 CORS 정책을 통해 임의의 리소스를 얻어오게 되면 자연스럽게 XSS, CSRF 등의 공격에 취약해지게 됩니다.

따라서 이 문제를 보완하기 위한 추가적인 보안 기법이 적용됬는데, 이게 바로 HttpOnly라는 이름의 세션 탈취 방어 기법입니다.

HttpOnly는 HttpOnly가 설정된 쿠키는 HTTP 통신 상에서만 사용되어야 한다는 것으로, HTTP 통신에서만 사용되는 데이터이기 때문에 자바스크립트 같은 외부 프로세스가 접근할 수 없게 됩니다.


쉽게 말하자면 “이거 원래 안되는건데 니가 하도 달라고해서 준거니까, 여기다 이상한짓 하지말고 준대로 쓰기만 해 !” 라고 강제하는 것입니다.


실제로 자바스크립트를 통해 해당 쿠키에 접근하려고 하니 HttpOnly 설정이 되어 있어 액세스할 수 없다는 에러 메시지가 발생하였습니다. (이미지 캡처를 깜빡했습니다…)


하지만 HttpOnly는 세션 관련 공격들에 대해 최소한의 방어는 보장하지만, XST(Cross-site Tracing) 공격의 여지가 조금이나마 남아있어 결국 완벽한 방어는 보장하지 못하기 때문에, 고수준의 보안 정책을 고려한다면 결국 XSS Filter, CSRF Filter등의 추가적인 보안 시스템을 구축해야 합니다.


정리하자면 CORS 문제를 해결했고 이후 정상적인 인증과정을 거쳐 서버에서는 인증 쿠키를 응답하였으나 해당 쿠키는 HttpOnly가 적용된 보안 쿠키였기 때문에, 클라이언트 환경에서 해당 쿠키가 노출되지 않는 상태였습니다.

저는 쿠키가 넘어오지 않는 (실제론 넘어왔으나 보이지만 않은 😅) 이 상황이 이해되지 않아 HTTP 패킷을 뜯어 HTTP 헤더를 분석했고, 거기서 숨겨져있는 쿠키와 함께 HttpOnly라는 키워드를 얻어 이 문제의 전말을 깨닫게 된 것이죠. 🤣


원래 정상인걸 문제있다고 오판한 것이니 해결방법이랄건 딱히 없었고 좋은 것 하나 배웠다고 생각합니다.



© 2022. All rights reserved.