세션 식별자를 실어 나르는 수단으로 쿠키를 짚었지만, 쿠키 자체는 세션보다 넓은 쓰임을 가진 도구다. 무상태 요청 흐름에서 서버가 브라우저에 작은 값을 심어 두고 이후 요청마다 되돌려 받는 이 장치는, 로그인 상태를 유지하는 일부터 사용자 설정을 기억하는 일까지 두루 쓰인다. 그런데 이 값이 인증의 핵심을 담게 되는 순간, 쿠키를 어떻게 설정하느냐가 곧 보안의 성패를 가른다.


이번 편은 쿠키가 무엇이고 어떻게 오가는지, 쿠키를 보호하는 속성들이 각각 어떤 위협을 막는지, 쿠키의 범위와 수명을 어떻게 좁혀야 하는지, 그리고 흔히 놓치는 함정이 무엇인지를 다룬다. 쿠키의 설정 한 줄에 붙는 속성들은 사소해 보이지만, 그 하나하나가 특정한 공격을 겨냥한 방어선이다. 방어의 관점에서 쿠키를 다시 읽으면 왜 이 속성들이 기본값처럼 붙어야 하는지가 드러난다.

쿠키가 오가는 방식

쿠키는 서버가 응답에 실어 브라우저에 저장하도록 지시하는 작은 값이다. 서버가 응답 헤더에 쿠키 설정을 담아 보내면, 브라우저는 그 이름과 값을 저장해 둔다. 이후 같은 대상으로 요청을 보낼 때마다 브라우저는 저장한 쿠키를 요청 헤더에 자동으로 실어 보낸다. 서버는 이 되돌아온 값을 읽어 이전의 맥락을 이어 간다.


이 자동 전송이 쿠키의 편리함이자 위험의 근원이다. 사용자가 매번 값을 첨부하지 않아도 브라우저가 알아서 실어 주므로 로그인 유지가 자연스럽게 이루어진다. 그러나 브라우저는 요청이 사용자의 의도로 시작되었는지 아닌지를 따지지 않고 조건만 맞으면 쿠키를 싣는다. 이 성질이 뒤에 다룰 사이트 간 요청 위조의 바탕이 된다.


쿠키에는 이름과 값 외에 여러 속성을 함께 붙일 수 있다. 언제까지 유효한지, 어느 경로와 어느 대상에만 실릴지, 어떤 조건에서만 전송될지를 정하는 속성들이다. 이 속성들이 쿠키의 행동 범위를 정한다. 값만 정하고 속성을 방치하면 쿠키는 필요 이상으로 넓게 퍼지고 오래 남는다.


인증에 쓰이는 쿠키는 특히 이 속성들을 촘촘히 채워야 한다. 세션 식별자나 토큰을 담은 쿠키가 새어 나가거나 엉뚱한 요청에 실리면 그대로 계정 탈취로 이어지기 때문이다. 그래서 인증 쿠키를 다룰 때는 값의 강도만큼이나 속성의 설정에 공을 들여야 한다.

훔침과 엿봄을 막는 속성

가장 먼저 붙여야 할 것은 스크립트가 쿠키를 읽지 못하게 하는 속성이다. 이 속성이 붙은 쿠키는 브라우저가 서버로 자동 전송하는 데만 쓰이고, 페이지 안에서 도는 스크립트의 손에는 닿지 않는다. 만약 페이지에 악성 스크립트가 끼어드는 일이 벌어지더라도, 이 속성이 있으면 그 스크립트가 인증 쿠키를 읽어 내가는 것을 막을 수 있다.


이 속성은 스크립트 주입을 통한 쿠키 탈취에 대한 직접적인 방어다. 페이지에 주입된 스크립트가 할 수 있는 위험한 일 가운데 하나가 저장된 인증 쿠키를 읽어 외부로 보내는 것인데, 애초에 스크립트가 그 값에 접근하지 못하면 이 경로가 닫힌다. 그래서 인증 쿠키에는 이 속성이 사실상 필수다.


다음으로 암호화된 연결에서만 쿠키가 전송되게 하는 속성이 있다. 이 속성이 붙은 쿠키는 안전한 연결을 통할 때만 실려 나가고, 암호화되지 않은 연결에는 실리지 않는다. 이로써 전송 과정에서 쿠키가 중간에 엿보여 새어 나가는 위험을 막는다. 인증 쿠키가 평문으로 오가는 순간 손쉽게 가로채일 수 있으므로 이 속성도 함께 붙여야 한다.


두 속성은 서로 다른 위협을 겨냥한다. 하나는 페이지 안의 스크립트가 값을 읽어 가는 것을 막고, 다른 하나는 전송 도중에 값이 엿보이는 것을 막는다. 훔침과 엿봄이라는 두 경로를 각각 차단하는 것이므로, 어느 하나만으로는 충분하지 않고 둘을 함께 두어야 인증 쿠키가 온전히 보호된다.

다른 사이트의 요청을 통제하는 속성

쿠키가 자동으로 실린다는 성질은 다른 사이트에서 시작된 요청에도 적용된다. 사용자가 어느 사이트에 로그인한 상태로 다른 사이트를 방문했을 때, 그 다른 사이트가 원래 사이트로 요청을 보내게 만들면 브라우저는 로그인 쿠키를 함께 실어 보낸다. 이 틈을 막기 위해 쿠키가 어떤 출처의 요청에 실릴지를 제한하는 속성이 있다.


이 속성을 엄격하게 설정하면 쿠키는 오직 그 쿠키를 심은 사이트 자체에서 시작된 요청에만 실린다. 다른 사이트에서 건너온 요청에는 쿠키가 붙지 않으므로, 남의 사이트가 사용자의 로그인 상태를 몰래 빌려 쓰는 길이 막힌다. 이것이 사이트 간 요청 위조에 대한 근본적인 방어 가운데 하나다.


다만 이 속성을 지나치게 엄격하게 두면 정상적인 외부 진입까지 막힐 수 있다. 다른 사이트의 링크를 눌러 들어오는 경우처럼 사용자가 의도한 이동에서도 쿠키가 실리지 않아 로그인이 풀린 것처럼 보일 수 있다. 그래서 이 속성에는 완전히 막는 단계와 안전한 이동은 허용하는 중간 단계가 있으며, 쿠키의 쓰임에 맞춰 적절한 단계를 골라야 한다.


인증 쿠키에는 대개 엄격에 가까운 설정이 권장된다. 요청 위조의 위험이 큰 값일수록 다른 출처의 요청에 실리지 않게 막는 편이 안전하기 때문이다. 이 속성만으로 요청 위조가 완전히 막히는 것은 아니지만, 다른 방어와 겹쳐 두면 방어가 한층 두꺼워진다.

범위와 수명을 좁히기

쿠키가 실릴 대상과 경로를 정하는 속성은 쿠키의 노출 범위를 정한다. 이 범위를 넓게 잡으면 쿠키가 여러 하위 대상과 여러 경로에 두루 실려, 필요 없는 곳까지 값이 흘러 다닌다. 반대로 좁게 잡으면 쿠키가 꼭 필요한 곳에만 실린다. 인증 쿠키는 그것이 쓰이는 최소한의 범위로 좁히는 것이 원칙이다.


범위를 좁히면 쿠키가 새어 나갈 표면이 줄어든다. 넓은 범위의 쿠키는 그 범위 안의 어느 한 곳에 약점이 있어도 함께 위험에 노출되지만, 좁은 범위의 쿠키는 그 영향이 국한된다. 특히 여러 하위 서비스가 한 상위 대상 아래 묶여 있을 때, 인증 쿠키를 상위 전체에 퍼뜨리기보다 필요한 하위에만 두는 편이 안전하다.


수명을 정하는 속성도 방어의 일부다. 수명을 정하지 않으면 브라우저가 닫힐 때 사라지는 쿠키가 되고, 만료 시점을 지정하면 그때까지 남는 쿠키가 된다. 인증 쿠키의 수명은 세션의 수명과 맞물려 짧게 잡는 것이 안전하다. 오래 남는 쿠키일수록 그 사이에 새어 나갈 기회가 늘어나기 때문이다.


수명과 범위를 좁히는 것은 결국 쿠키가 살아 있는 시간과 퍼지는 공간을 모두 최소로 줄이는 일이다. 값의 강도로 훔침을 막고, 속성으로 엿봄과 위조를 막았다면, 범위와 수명으로는 새어 나갈 시간과 공간 자체를 줄이는 것이다. 이 세 겹이 함께 작동해야 인증 쿠키가 촘촘히 보호된다.

쿠키를 서로 구별해 지키기

쿠키에는 그것이 어떤 조건으로 설정되었는지를 이름 자체에 표시하도록 하는 규칙이 있다. 특정한 접두 규칙을 따르는 이름을 쓰면, 브라우저는 그 쿠키가 안전한 연결에서 설정되었는지, 범위가 적절히 좁혀졌는지를 확인한 뒤에만 받아들인다. 이름의 형태가 곧 그 쿠키가 지켜야 할 조건을 강제하는 장치가 되는 것이다.


이 규칙은 쿠키가 엉뚱한 조건으로 덮어 쓰이는 것을 막는 데 쓸모가 있다. 안전하지 않은 경로에서 같은 이름의 쿠키를 심어 원래의 인증 쿠키를 밀어내려는 시도가 있을 수 있는데, 접두 규칙을 따르는 쿠키는 그런 조건에서 설정되지 않으므로 이 밀어내기가 통하지 않는다. 이름 하나에 방어 조건을 얹는 셈이다.


인증에 쓰이는 쿠키와 그렇지 않은 쿠키를 구별해 다루는 것도 중요하다. 사용자 설정처럼 새어 나가도 큰 문제가 없는 값과, 세션 식별자처럼 새어 나가면 계정이 넘어가는 값을 같은 강도로 다룰 필요는 없다. 위험이 큰 쿠키에 보호 속성을 집중하고, 그렇지 않은 쿠키는 가볍게 두면 관리가 명료해진다.


쿠키에 담는 값은 되도록 가리키는 표에 그치게 하는 것이 안전하다. 사용자에 관한 실제 정보를 쿠키에 직접 담으면 그 값이 새어 나갈 때 정보도 함께 새어 나간다. 세션 방식처럼 쿠키에는 서버 저장소를 가리키는 식별자만 두고 정보는 서버에 남기면, 쿠키가 노출되어도 드러나는 것이 없다. 무엇을 쿠키에 담고 무엇을 서버에 남길지의 판단이 방어의 밑그림이 된다.

흔히 놓치는 함정들

가장 흔한 함정은 보호 속성을 아예 붙이지 않는 것이다. 개발 단계에서 편의를 위해 속성을 비워 두었다가 그대로 운영에 넘어가면, 스크립트가 읽을 수 있고 평문으로도 오가는 인증 쿠키가 방치된다. 그래서 인증 쿠키의 보호 속성은 선택이 아니라 기본값으로 두고, 붙이지 않은 경우를 예외로 다루어야 한다.


범위를 너무 넓게 잡는 것도 흔한 실수다. 편의를 위해 상위 대상 전체에 쿠키를 퍼뜨리면, 그 아래의 모든 하위 서비스가 인증 쿠키를 함께 보게 된다. 하위 가운데 어느 하나에 약점이 있으면 인증 쿠키가 그곳으로 새어 나갈 수 있다. 범위는 처음부터 좁게 잡고 필요할 때만 넓히는 방향이 안전하다.


수명을 지나치게 길게 잡는 것도 위험을 키운다. 사용자의 편의를 위해 로그인 유지 기간을 길게 두면 그만큼 쿠키가 새어 나갈 창이 넓어진다. 오래 유지가 필요한 경우에도, 짧은 수명의 인증 값과 그것을 갱신하는 별도의 값을 나누어 쓰는 편이 하나의 값을 오래 살려 두는 것보다 안전하다.


마지막으로, 쿠키를 지웠다고 여기지만 실제로는 남아 있는 경우를 조심해야 한다. 로그아웃 시 쿠키를 지우려면 설정할 때와 같은 범위와 조건으로 지워야 하는데, 이 조건이 어긋나면 쿠키가 그대로 남는다. 그래서 쿠키를 지우는 처리도 설정과 같은 세심함으로 다루어야 하며, 서버 쪽 세션까지 함께 무효화해야 로그아웃이 온전히 이루어진다.


쿠키와 다른 저장 수단의 경계

브라우저에는 쿠키 말고도 값을 저장하는 여러 공간이 있다. 스크립트가 자유롭게 읽고 쓰는 저장 공간이 대표적이며, 다루기 편해 자주 쓰인다. 그러나 인증에 쓰이는 값을 어디에 둘지는 편의가 아니라 위협을 기준으로 판단해야 한다. 저장 위치마다 노출되는 위험이 다르기 때문이다.


스크립트가 접근하는 저장 공간에 인증 값을 두면, 페이지에 악성 스크립트가 끼어들 경우 그 값이 그대로 읽혀 나간다. 반면 스크립트가 읽지 못하게 막은 쿠키에 두면 이 경로는 닫히지만, 쿠키의 자동 전송 성질 때문에 다른 출처의 요청에 실려 나가는 위험을 따로 막아야 한다. 어느 쪽도 그 자체로 안전하지 않으며, 각자의 위험에 맞는 방어를 함께 세워야 한다.


인증 값처럼 새어 나가면 계정이 넘어가는 값은 스크립트의 손이 닿지 않는 쿠키에 두고 보호 속성을 채우는 편이 대체로 안전하다. 스크립트가 굳이 그 값을 읽을 필요가 없다면, 읽지 못하게 막아 두는 것이 노출 표면을 줄이는 길이다. 반대로 스크립트가 다루어야 하는 값이라면 그 값이 새어 나가도 피해가 크지 않도록 범위와 수명을 좁혀야 한다.


결국 선택의 기준은 그 값이 새어 나갔을 때의 피해와, 그 값에 스크립트가 접근할 필요가 있는지다. 피해가 크고 스크립트의 접근이 필요 없는 값일수록 쿠키에 두고 접근을 막으며, 피해가 작고 다루어야 하는 값일수록 접근을 허용하되 노출을 줄인다. 저장 위치를 정하는 일도 이처럼 위협을 저울질하는 방어의 판단이다.

정리하면 쿠키는 서버가 브라우저에 값을 심어 요청마다 되돌려 받는 장치이며, 자동 전송이라는 편의가 곧 위험의 근원이 된다. 스크립트 접근 차단과 암호화 전송, 다른 출처 요청 제한, 좁은 범위와 짧은 수명, 접두 규칙이라는 방어들이 각각 훔침과 엿봄과 위조와 노출을 겨냥해 겹겹이 작동한다. 다음 편에서는 서버가 상태를 쥐는 세션과 대비되는, 상태를 서버에 두지 않는 토큰 기반 인증을 들여다본다.