Computer >> 컴퓨터 >  >> 소프트웨어 >> 브라우저

브라우저는 웹 콘텐츠를 어떻게 처리할까? 웹 애플리케이션 보안의 기초

웹 애플리케이션 보안 시리즈: 브라우저의 기본 원리

브라우저는 웹 콘텐츠를 어떻게 처리할까? 웹 애플리케이션 보안의 기초

웹 애플리케이션 보안 시리즈의 첫 번째 글에서는 브라우저가 무엇을 하는지, 그리고 어떻게 동작하는지 살펴봅니다. 대부분의 사용자는 브라우저를 통해 여러분의 웹 애플리케이션과 상호작용하기 때문에, 이 놀라운 프로그램의 기본 원리를 이해하는 것이 매우 중요합니다.

브라우저는 일종의 렌더링 엔진입니다. 브라우저의 역할은 웹 페이지를 다운로드하고, 사람이 이해할 수 있는 형태로 화면에 그려주는 것입니다.

물론 이는 지나치게 단순화한 설명이지만, 지금 단계에서는 이 정도 지식만으로 충분합니다.

  • 사용자가 주소창에 주소를 입력합니다.
  • 브라우저는 해당 URL의 '문서'를 내려받아 화면에 렌더링합니다.

Chrome, Firefox, Edge, Safari 같은 인기 브라우저에 익숙하겠지만, 세상에는 이 외에도 다양한 브라우저가 존재합니다.

예를 들어 lynx는 명령줄에서 동작하는 경량 텍스트 기반 브라우저입니다. 흥미로운 점은 lynx의 핵심 원리가 어느 '메인스트림' 브라우저와 완전히 동일하다는 사실입니다. 사용자가 URL을 입력하면 브라우저가 문서를 가져와 렌더링하는 것이죠. 유일한 차이점은 lynx가 시각적 렌더링 엔진 대신 텍스트 기반 인터페이스를 사용한다는 것뿐입니다. 그래서 Google 같은 사이트도 lynx로 열면 텍스트만 덩그러니 보이게 됩니다.

브라우저가 대략적으로 무슨 일을 하는지 이해했으니, 이제 이 영리한 프로그램이 우리를 위해 수행하는 단계들을 좀 더 자세히 들여다보겠습니다.

브라우저는 구체적으로 무엇을 할까?

간단히 요약하면 브라우저의 역할은 크게 다음과 같습니다.

  • DNS 조회(DNS Resolution)
  • HTTP 통신(HTTP Exchange)
  • 렌더링(Rendering)
  • 그리고 이 과정의 반복

DNS 조회

이 과정은 사용자가 URL을 입력했을 때 브라우저가 어느 서버에 연결해야 하는지 알아내는 단계입니다. 브라우저는 DNS 서버에 문의하여 google.com216.58.207.110이라는 IP 주소로 변환된다는 사실을 확인하고, 해당 주소로 연결합니다.

HTTP 통신

요청을 처리할 서버를 파악하면 브라우저는 해당 서버와 TCP 연결을 맺고 HTTP 통신을 시작합니다. HTTP 통신이란 브라우저가 서버에게 필요한 것을 전달하고, 서버가 그에 대한 답변을 돌려주는 방식일 뿐입니다.

HTTP는 웹에서 가장 널리 쓰이는 통신 프로토콜의 이름이며, 브라우저는 서버와 대화할 때 주로 HTTP를 사용합니다. HTTP 통신은 클라이언트(브라우저)가 요청(request)을 보내면 서버가 응답(response)으로 답하는 구조입니다.

예를 들어 브라우저가 google.com 뒤에 있는 서버에 성공적으로 연결되면, 다음과 같은 요청을 보냅니다.

GET / HTTP/1.1Host: google.comAccept: */*

이 요청을 한 줄씩 분해해 보겠습니다.

  • GET / HTTP/1.1: 첫 줄에서 브라우저는 서버에게 위치 /에 있는 문서를 달라고 요청하며, 이후 요청이 HTTP/1.1 프로토콜을 따른다고 알립니다(1.0이나 2를 사용할 수도 있습니다).
  • Host: google.com: 이것은 HTTP/1.1에서 유일하게 필수인 헤더입니다. 하나의 서버가 여러 도메인(google.com, google.co.uk 등)을 서비스할 수 있으므로, 클라이언트는 요청이 어느 호스트를 향한 것인지 명시해야 합니다.
  • Accept: */*: 선택 헤더로, 브라우저가 어떤 형식의 응답이든 받아들이겠다는 의미입니다. 서버에 JSON, XML, HTML 등 여러 포맷의 리소스가 있다면 서버가 선호하는 형식을 골라 응답할 수 있습니다.

클라이언트 역할을 하는 브라우저가 요청을 마치면, 이번에는 서버가 답변할 차례입니다. 응답은 대략 다음과 같은 모습입니다.

HTTP/1.1 200 OKCache-Control: private, max-age=0Content-Type: text/html; charset=ISO-8859-1Server: gwsX-XSS-Protection: 1; mode=blockX-Frame-Options: SAMEORIGINSet-Cookie: NID=1234; expires=Fri, 18-Jan-2019 18:25:04 GMT; path=/; domain=.google.com; HttpOnly
<!doctype html><html>......</html>

정보가 꽤 많네요. 서버는 요청이 성공했다고 알려주고(200 OK), 응답에 여러 헤더를 추가합니다. 예컨대 어떤 서버가 요청을 처리했는지(Server: gws)나, 이 응답의 X-XSS-Protection 정책이 무엇인지 등을 알려줍니다.

지금 당장 응답의 모든 줄을 이해할 필요는 없습니다. HTTP 프로토콜과 헤더들은 이 시리즈 후반부에서 자세히 다룰 예정입니다.

일단 기억해야 할 핵심은 클라이언트와 서버가 HTTP를 통해 정보를 주고받는다는 사실입니다.

렌더링

마지막이자 가장 중요한 단계가 바로 렌더링입니다. 브라우저가 사용자에게 보여주는 것이 아래처럼 알 수 없는 문자열들뿐이라면 얼마나 불편할까요?

<!doctype html><html>......</html>

응답의 바디(body)에는 Content-Type 헤더에 명시된 형식에 맞춰 응답 본문이 담깁니다. 위 예시에서 Content-Type이 text/html로 설정되어 있으므로, 응답 본문에는 HTML 마크업이 들어 있는 것이 정상이며 실제로도 그렇습니다.

여기서 브라우저가 진가를 발휘합니다. 브라우저는 HTML을 해석하고, 마크업에 포함된 추가 리소스(JavaScript 파일이나 CSS 문서 등)를 불러온 뒤, 가능한 한 빨리 사용자에게 화면을 보여줍니다.

결국 그 결과물은 누구나 이해할 수 있는 형태가 되는 것이죠.

브라우저는 웹 콘텐츠를 어떻게 처리할까? 웹 애플리케이션 보안의 기초

주소창에서 Enter 키를 눌렀을 때 실제로 벌어지는 일에 대해 더 깊이 알고 싶다면, 이 과정의 메커니즘을 아주 정교하게 설명한 "What happens when..."이라는 자료를 읽어보시길 권합니다.

이 시리즈는 보안에 초점을 맞추고 있으므로, 방금 배운 내용에서 한 가지 힌트를 드리자면 공격자들은 HTTP 통신과 렌더링 단계의 취약점을 통해 손쉽게 공격을 시도합니다. 취약점과 악의적인 사용자는 다른 영역에도 도사리고 있지만, 이 두 계층에서 보안을 더 단단히 다지는 것만으로도 보안 태세를 크게 개선할 수 있습니다.

브라우저 벤더와 웹 표준

가장 인기 있는 4대 브라우저는 각기 다른 회사에서 만듭니다.

  • Google의 Chrome
  • Mozilla의 Firefox
  • Apple의 Safari
  • Microsoft의 Edge

벤더들은 시장 점유율을 높이기 위해 경쟁하는 동시에, 브라우저의 '최소 요건'이라 할 수 있는 웹 표준(web standards)을 함께 발전시키기 위해서도 협력합니다.

표준 개발을 주도하는 기관은 W3C이지만, 브라우저가 자체 기능을 먼저 개발하고 그것이 나중에 웹 표준으로 편입되는 경우도 드물지 않습니다. 보안 분야 역시 예외가 아닙니다.

예를 들어 Chrome 51은 CSRF(Cross-Site Request Forgery, 자세한 내용은 뒤에서 다룹니다)라는 특정 유형의 취약점을 없애는 데 도움이 되는 SameSite 쿠키를 도입했습니다. 다른 벤더들도 좋은 아이디어라고 판단해 뒤따랐고, 결국 SameSite는 웹 표준이 되었습니다. 현재 Safari만이 SameSite 쿠키를 지원하지 않는 주요 브라우저입니다.

브라우저는 웹 콘텐츠를 어떻게 처리할까? 웹 애플리케이션 보안의 기초

이 사례에서 두 가지를 배울 수 있습니다.

  • Safari는 사용자 보안에 관심이 없어 보인다(농담입니다! SameSite 쿠키는 Safari 12에서 지원될 예정이며, 이 글을 읽는 시점에는 이미 출시되었을 수도 있습니다).
  • 한 브라우저의 취약점을 패치했다고 해서 모든 사용자가 안전해지는 것은 아니다.

첫 번째는 Safari를 향한 농담성 지적이지만, 두 번째 포인트는 정말 중요합니다. 웹 애플리케이션을 개발할 때는 여러 브라우저에서 화면이 똑같이 보이도록 만드는 것뿐 아니라, 플랫폼과 무관하게 사용자가 동일한 수준의 보호를 받도록 보장해야 합니다.

웹 보안 전략은 브라우저 벤더가 허용하는 기능의 범위에 따라 조정해야 합니다. 요즘 대부분의 브라우저는 비슷한 기능 세트를 지원하며 로드맵에서 크게 벗어나는 일이 드물지만, 위와 같은 사례는 여전히 발생하며 보안 전략을 수립할 때 반드시 고려해야 합니다.

예를 들어 CSRF 공격을 SameSite 쿠키로만 방어하겠다고 결정했다면, 그 순간 Safari 사용자를 위험에 노출시키게 된다는 점을 인지해야 합니다. 그리고 사용자 역시 이 사실을 알 권리가 있습니다.

마지막으로, 어떤 브라우저 버전을 지원할지는 여러분이 직접 결정할 수 있다는 점을 기억하세요. 모든 브라우저 버전을 지원하는 것은 비현실적입니다(Internet Explorer 6를 생각해 보세요). 다만 주요 브라우저의 최신 몇 개 버전을 지원하는 것은 대체로 좋은 선택입니다. 특정 플랫폼에 대한 보호를 제공하지 않을 계획이라면 사용자에게 미리 알려주는 것이 바람직합니다.

프로 팁: 오래된 브라우저를 사용하도록 유도하거나 적극적으로 지원해서는 안 됩니다. 여러분이 필요한 모든 조치를 취했더라도, 다른 웹 개발자들은 그렇지 않았을 수 있습니다. 사용자에게는 주요 브라우저의 최신 지원 버전 사용을 권장하세요.

벤더 버그인가, 표준 버그인가?

평균적인 사용자가 제3의 클라이언트(브라우저)를 통해 우리 애플리케이션에 접속한다는 사실은, 안전한 브라우징 환경이라는 목표 앞에 또 하나의 변수를 더합니다. 바로 브라우저 자체에 보안 취약점이 존재할 수 있다는 점입니다.

벤더들은 브라우저 자체의 취약점을 발견한 보안 연구자들에게 일반적으로 보상(일명 버그 바운티, Bug Bounty)을 제공합니다. 이런 버그는 여러분의 구현 방식과 무관하며, 브라우저가 스스로 보안을 처리하는 방식에 관련된 것입니다.

예를 들어 Chrome의 리워드 프로그램은 보안 엔지니어가 발견한 취약점을 Chrome 보안팀에 보고할 수 있는 창구를 제공합니다. 취약점이 확인되면 패치가 배포되고, 대개 보안 권고문이 공개되며, 연구자는 프로그램으로부터 (보통 금전적인) 보상을 받습니다.

Google 같은 기업이 버그 바운티 프로그램에 상당한 자본을 투자하는 이유는, 애플리케이션의 문제를 발견하면 금전적 보상을 약속함으로써 연구자들을 끌어모을 수 있기 때문입니다.

버그 바운티 프로그램에서는 모두가 윈윈입니다. 벤더는 소프트웨어의 보안을 개선하고, 연구자는 발견한 문제에 대해 보상을 받습니다. 이러한 프로그램은 보안 생태계에서 독립적인 섹션을 차지할 만큼 가치가 있다고 생각하므로, 시리즈 후반부에서 자세히 다루겠습니다.

Jake Archibald는 Google의 디벨로퍼 어드보킷으로, 최근 여러 브라우저에 영향을 미치는 취약점을 발견했습니다. 그는 자신의 노력, 각 벤더에 접근한 방식, 그리고 벤더들의 반응을 흥미로운 블로그 글로 정리했는데, 읽어볼 가격이 충분합니다.

개발자를 위한 브라우저 이해

지금까지 우리는 아주 단순하지만 중요한 개념을 배웠습니다. 브라우저란 평범한 인터넷 사용자를 위해 만들어진 HTTP 클라이언트일 뿐이다는 것입니다.

브라우저는 플랫폼의 기본 HTTP 클라이언트(NodeJS의 require('http') 등)보다 확실히 강력하지만, 결국 더 단순한 HTTP 클라이언트의 자연스러운 진화형에 불과합니다.

개발자 입장에서 선택하는 HTTP 클라이언트는 아마 Daniel Stenberg가 만든 cURL일 것입니다. 웹 개발자들이 매일 사용하는 가장 인기 있는 소프트웨어 중 하나로, 명령줄에서 즉석으로 HTTP 요청을 보내 HTTP 통신을 테스트할 수 있습니다.

$ curl -I localhost:8080
HTTP/1.1 200 OKserver: ecstatic-2.2.1Content-Type: text/htmletag: "23724049-4096-"2018-07-20T11:20:35.526Z""last-modified: Fri, 20 Jul 2018 11:20:35 GMTcache-control: max-age=3600Date: Fri, 20 Jul 2018 11:21:02 GMTConnection: keep-alive

위 예제에서는 localhost:8080/의 문서를 요청했고, 로컬 서버가 성공적으로 응답했습니다.

응답 본문을 명령줄에 출력하는 대신 -I 플래그를 사용해 응답 헤더만 확인했습니다. 한 걸음 더 나아가 -v(verbose) 옵션을 사용하면 cURL이 실제로 수행하는 요청까지 포함한 더 많은 정보를 출력하도록 지시할 수 있어, HTTP 통신 전체를 더 잘 들여다볼 수 있습니다.

$ curl -I -v localhost:8080* Rebuilt URL to: localhost:8080/* Trying 127.0.0.1...* Connected to localhost (127.0.0.1) port 8080 (#0)> HEAD / HTTP/1.1> Host: localhost:8080> User-Agent: curl/7.47.0> Accept: */*>< HTTP/1.1 200 OK< server: ecstatic-2.2.1< Content-Type: text/html< etag: "23724049-4096-"2018-07-20T11:20:35.526Z""< last-modified: Fri, 20 Jul 2018 11:20:35 GMT< cache-control: max-age=3600< Date: Fri, 20 Jul 2018 11:25:55 GMT< Connection: keep-alive
* Connection #0 to host localhost left intact

사실 이와 거의 동일한 정보는 메인스트림 브라우저의 개발자 도구(DevTools)에서도 확인할 수 있습니다.

앞서 살펴본 것처럼 브라우저는 정교하게 만들어진 HTTP 클라이언트일 뿐입니다. 물론 방대한 기능(자격 증명 관리, 북마크, 방문 기록 등)을 추가로 제공하지만, 본질적으로 브라우저는 '사람을 위한 HTTP 클라이언트'로 탄생했습니다. 이 점이 중요한 이유는, 대부분의 경우 웹 애플리케이션의 보안을 테스트하는 데 굳이 브라우저가 필요 없고 단순히 curl로 요청을 날린 뒤 응답을 살펴보면 되기 때문입니다.

마지막으로 강조하고 싶은 점은 무엇이든 브라우저가 될 수 있다는 사실입니다. HTTP 프로토콜을 통해 API를 호출하는 모바일 앱이 있다면, 그 앱이 곧 여러분의 브라우저입니다. 다만 자신이 직접 만든, 오직 특정 유형의 HTTP 응답(자신의 API 응답)만 이해하는 고도로 커스터마이즈된 브라우저일 뿐입니다.

다음 글 예고: HTTP 프로토콜 깊이 들여다보기

앞서 언급했듯이, HTTP 통신렌더링 단계는 악의적인 사용자에게 가장 많은 공격 벡터(attack vector)를 제공하기 때문에 이 시리즈에서 집중적으로 다룰 예정입니다.

다음 글에서는 HTTP 프로토콜을 더 깊이 살펴보고, HTTP 통신을 안전하게 지키기 위해 어떤 조치를 취해야 하는지 알아보겠습니다.


원문: Alex Nadalin, odino.org (2018년 7월 29일 게시). freeCodeCamp 오픈소스 커리큘럼은 4만 명 이상의 사람들이 개발자로 취업하는 데 도움을 주었습니다.