사용자 에이전트 문자열
고친 사람 github-actions[bot]
사용자 에이전트 문자열은 요청을 보내는 프로그램이 서버에게 자기를 소개하는 한 줄입니다. 브라우저는 요청을 보낼 때마다 이 줄을 함께 싣습니다. 서버는 이 줄을 읽고 어떤 프로그램이 보낸 요청인지 짐작합니다. 보내는 쪽이 마음대로 적는 값이라 참고로만 씁니다.
쉽고 빠른 이해
요청을 보낸 프로그램이 스스로 적어 보내는 자기소개입니다. 크롬 브라우저라면 운영체제와 브라우저 이름, 버전을 한 줄에 적어 보냅니다.
서버는 요청만 보고는 누가 보냈는지 모릅니다. 이 줄이 없으면 특정 브라우저에서만 나는 오류를 골라 피할 수 없습니다. 방문자가 무엇으로 들어오는지 셀 수도 없습니다. 검색 로봇이 왔는지 가리기도 어렵습니다.
이렇게 돕니다.
- 브라우저가 요청마다 이 줄을 헤더에 실어 보낸다
- 줄은 「이름/버전」 조각과 괄호 속 설명으로 이루어진다
- 서버는 줄을 읽어 로그에 남기거나 응답을 조금 바꾼다
대가는 믿을 수 없다는 점입니다. 보내는 쪽이 무엇이든 적을 수 있어서 권한을 가르는 데는 못 씁니다. 줄이 자세할수록 사용자를 알아보는 단서가 됩니다. 그래서 요즘 브라우저는 내용을 일부러 줄여 보냅니다.
상세
이 절은 사용자 에이전트 문자열이 어디에 실려 오고 무엇으로 이루어지는지부터 봅니다. 그다음 브라우저가 보내는 한 줄을 조각내 읽고, 서버가 이 값을 어디까지 믿어도 되는지 짚습니다.
사용자 에이전트는 사용자를 대신해 요청을 내는 프로그램입니다. 브라우저가 가장 흔합니다. 명령줄 도구, 검색 엔진의 크롤러, 모바일 앱도 여기에 듭니다. 사용자 에이전트 문자열은 이 프로그램이 자기 이름과 버전을 적은 텍스트입니다.
User-Agent 헤더
이 문자열은 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 규약) 요청의 User-Agent 헤더에 실려 옵니다. HTTP 헤더는 요청 앞머리에 붙는 「이름: 값」 꼴의 줄들입니다. 요청이 무엇을 원하고 누가 보냈는지 같은 부가 정보를 담습니다.
GET /index.html HTTP/1.1
Host: example.com
User-Agent: curl/8.5.0
마지막 줄의 콜론 뒤가 사용자 에이전트 문자열입니다. 명령줄 도구 curl 이 보낸 요청이라 이름과 버전 한 조각으로 끝납니다.
헤더는 값을 나르는 그릇입니다. 문자열은 그 안에 든 값입니다.
이 값은 헤더 밖에서도 읽힙니다. 브라우저에서 도는 JavaScript 프런트엔드 코드는 같은 값을
navigator.userAgent 로 읽습니다. 서버 코드와 프런트엔드 코드가 같은 문자열을 보고 따로
판단하는 일이 생기는 까닭입니다.
문자열을 이루는 조각
문자열은 두 가지 조각으로 이루어집니다. 하나는 제품 토큰이고 다른 하나는 주석입니다. 조각과 조각 사이는 공백으로 띄웁니다.
제품 토큰은 이름/버전 꼴입니다. 슬래시 앞이 제품 이름이고 뒤가 버전입니다. 버전은 빼도
됩니다. 이름과 버전 안에는 공백을 쓸 수 없습니다. 공백이 조각을 가르는 구분자이기 때문입니다.
주석은 괄호로 감싼 설명입니다. 괄호 안에서는 공백을 자유롭게 씁니다. 운영체제 이름처럼 제품 토큰 꼴에 안 맞는 정보를 여기에 적습니다. 괄호 안을 세미콜론으로 나누는 것은 문법이 아니라 브라우저들이 따르는 관례입니다.
조각을 늘어놓는 순서에도 원래 관례가 있습니다. 앞에 올수록 중요한 제품을 적습니다. 첫 조각이 그 프로그램 자신입니다. 뒤로 가며 그 안에 든 부품을 적습니다.
브라우저가 보내는 한 줄
윈도우 데스크톱의 크롬 브라우저가 보내는 문자열입니다. 조각마다 줄을 바꿔 적었습니다. 보낼 때는 공백으로 이어진 한 줄입니다.
Mozilla/5.0
(Windows NT 10.0; Win64; x64)
AppleWebKit/537.36
(KHTML, like Gecko)
Chrome/143.0.0.0
Safari/537.36
여섯 조각 가운데 제품 토큰이 넷, 주석이 둘입니다. 조각마다 뜻을 표로 풀면 이렇습니다.
| 조각 | 종류 | 뜻 |
|---|---|---|
Mozilla/5.0 |
제품 토큰 | 거의 모든 브라우저가 붙이는 호환 표시 |
(Windows NT 10.0; Win64; x64) |
주석 | 운영체제와 프로세서 종류 |
AppleWebKit/537.36 |
제품 토큰 | 화면을 그리는 엔진 |
(KHTML, like Gecko) |
주석 | 두 엔진의 이름을 빌린 호환 표시 |
Chrome/143.0.0.0 |
제품 토큰 | 브라우저 자신과 버전 |
Safari/537.36 |
제품 토큰 | 사파리와 비슷하다는 호환 표시 |
브라우저 자신을 가리키는 조각은 다섯째의 Chrome 하나뿐입니다. 앞에서 본 관례대로라면
첫 조각이 브라우저 자신이어야 합니다. 브라우저는 이 관례를 지키지 않습니다. 첫 조각이 호환
표시라서 자신은 뒤쪽에 있습니다.
표에 나온 엔진은 웹 페이지를 화면에 그리는 부품입니다. 이 부품을 렌더링 엔진이라고 부릅니다. Gecko 는 파이어폭스의 엔진입니다. KHTML(Konqueror 라는 리눅스 브라우저의 엔진)은 WebKit 이 갈라져 나온 옛 엔진입니다.
크롬의 지금 엔진은 WebKit 에서 갈라져 나온 Blink 입니다. 그런데도 문자열에는 AppleWebKit
이라는 옛 이름이 남아 있습니다. 까닭은 다음 소절에서 볼 Mozilla 와 같습니다.
모든 브라우저가 Mozilla 로 시작하는 까닭
초기 웹에서 Mozilla 는 넷스케이프 브라우저가 쓰던 이름이었습니다. 당시 서버들은 문자열에서 이 이름을 찾아 기능이 많은 페이지를 보냈습니다. 이름이 없는 브라우저는 기능을 뺀 페이지를 받았습니다.
뒤에 나온 브라우저들은 같은 페이지를 받으려고 자기 문자열 앞에 Mozilla 를 적었습니다. 새
브라우저가 나올 때마다 같은 일이 되풀이되었습니다. like Gecko 와 Safari 조각도 그렇게 붙은
호환 표시입니다.
그 결과 이 문자열은 남의 이름이 여럿 섞인 자기소개가 되었습니다. 첫 조각이 그 프로그램
자신이라는 관례도 이렇게 깨졌습니다. 문자열에 Safari 가 있다고 사파리 브라우저로 판단하면
틀립니다. 크롬도 이 조각을 보내기 때문입니다.
서버가 이 문자열로 하는 일
서버가 이 값을 쓰는 곳은 크게 셋입니다. 아래 표는 쓰임마다 예를 하나씩 보입니다.
| 쓰임 | 예 |
|---|---|
| 기록과 통계 | 접근 로그에 요청마다 남겨 방문자의 브라우저·기기 비율을 센다 |
| 호환성 우회 | 문자열에 Chrome/143 이 들어 있을 때만 그 버전에서 깨지는 기능을 끄고 보낸다 |
| 보낸 쪽 구분 | 크롤러나 웹뷰에서 온 요청을 가려 낸다 |
크롤러는 자기 이름을 제품 토큰으로 적어 보냅니다. robots.txt 는 사이트가 크롤러에게 들어오지 말 경로를 적어 두는 파일입니다. 이 파일은 크롤러를 그 제품 토큰 이름으로 가리킵니다.
웹뷰는 앱 화면 안에 박아 넣은 브라우저입니다. 안드로이드 웹뷰는 괄호 주석 안에 wv 를
붙여 보냅니다. 서버는 이 표시를 보고 앱 안에서 연 화면인지 가립니다.
사용자 에이전트 탐지
문자열을 읽어 브라우저를 가리고 응답을 바꾸는 일을 사용자 에이전트 탐지라고 합니다. 앞에서 본 것처럼 문자열에 남의 이름이 섞여 있어 판단이 쉽게 틀립니다. 새 브라우저나 새 버전이 나오면 탐지 규칙도 따라 고쳐야 합니다.
그래서 프런트엔드 코드는 브라우저 이름 대신 필요한 기능이 있는지를 직접 확인합니다. 이 방식이
기능 감지입니다. 예를 들어 navigator.clipboard 가 있는지 먼저 봅니다. 있으면 그것으로
복사합니다. 없으면 다른 방법으로 복사합니다.
문자열에 따라 응답을 바꾸면 캐시도 챙겨야 합니다. 캐시는 받은 응답을 저장해 두었다가 같은 요청에 다시 내주는 장치입니다. 캐시가 이 사정을 모르면 한 브라우저용 응답이 다른 브라우저에 갑니다.
이것을 막으려고 응답의 Vary 헤더에 User-Agent 를 적습니다. 요청의 User-Agent 값이
다르면 다른 응답으로 치라는 뜻입니다. 대가로 캐시에 같은 페이지가 문자열 종류만큼 따로 쌓입니다.
믿을 수 없는 값
사용자 에이전트 문자열은 보내는 쪽이 적는 값입니다. 서버는 그것이 참인지 확인할 방법이 없습니다. 명령줄 도구도 옵션 하나로 브라우저를 흉내 냅니다.
curl -A "Mozilla/5.0" https://example.com/
이 요청은 서버 로그에 브라우저가 보낸 것처럼 남습니다. 데이터를 긁어 가는 봇이 브라우저
문자열을 흉내 내는 일도 흔합니다. 구글 검색 로봇의 이름인 Googlebot 을 빌려 쓰는 요청도 있습니다.
그래서 이 값은 권한이나 보안 판단의 근거로 쓰지 않습니다. 「우리 앱에서 온 요청만 허용」을 이 문자열로 가르면 누구나 그 문자열을 적어 들어옵니다.
누가 보냈는지 확인해야 하면 인증을 씁니다. 인증은 요청을 보낸 쪽이 스스로 밝힌 그 사람이 맞는지 확인하는 일입니다. 비밀번호나 서명된 토큰처럼 흉내 낼 수 없는 증거를 받습니다.
줄어드는 문자열
문자열이 자세할수록 사용자를 알아보기 쉬워집니다. 운영체제 세부 버전, 기기 모델명, 브라우저의 잔 버전을 합치면 같은 값을 보내는 사람이 몇 안 됩니다.
이렇게 작은 정보를 모아 사용자를 알아보는 일을 브라우저 핑거프린팅이라고 합니다. 사용자 에이전트 문자열은 그 정보 가운데 하나입니다. 요청마다 실려 오니 모으기도 쉽습니다.
사람을 알아보는 흔한 수단은 쿠키입니다. 쿠키는 서버가 브라우저에 맡겨 두는 작은 값이라 사용자가 지울 수 있습니다. 핑거프린팅은 브라우저에 아무것도 맡기지 않아 지울 것도 없습니다. 쿠키 없이도 여러 사이트에 걸쳐 같은 사람을 따라갈 수 있어 개인정보 문제가 됩니다.
브라우저는 필요 이상으로 자세한 정보를 문자열에 넣지 않는 편이 좋습니다. 개인정보를 지키려고 헤더를 아예 빼도 됩니다.
요즘 브라우저는 문자열 일부를 고정값으로 바꿔 보냅니다. 이것을 사용자 에이전트 축소(user-agent reduction)라고 부릅니다. 안드로이드 폰의 크롬이 보내는 값을 줄이기 전과 뒤로 견주면 이렇습니다.
| 정보 | 줄이기 전 | 줄인 뒤 |
|---|---|---|
| 운영체제 버전 | Android 16 |
Android 10 |
| 기기 모델 | Pixel 9 |
K |
| 브라우저 버전 | 143.0.12.45 |
143.0.0.0 |
줄인 뒤의 값은 기기와 상관없이 늘 같습니다. 브라우저 버전은 맨 앞 번호만 참값입니다. 뒤는 0 으로
채웁니다. 앞에서 본 Chrome/143.0.0.0 이 0 으로 끝난 까닭입니다.
필요한 만큼만 묻는 Client Hints
줄여서 빠진 정보가 서버에 꼭 필요할 때가 있습니다. 그때는 따로 요청해서 받습니다. 이 방식을 Client Hints라고 합니다. 여기에 쓰는 헤더 이름의 CH 는 Client Hints 의 줄임입니다.
주고받는 헤더는 둘입니다. 서버는 응답의 Accept-CH 헤더에 더 알고 싶은 항목을 적습니다. 브라우저는 그 항목을 Sec-CH-UA 로 시작하는 헤더에 담아 보냅니다. UA 는 User Agent(사용자 에이전트)의 줄임입니다.
sequenceDiagram
participant 브라우저
participant 서버
브라우저->>서버: 첫 요청 · 줄인 문자열만 보낸다
서버-->>브라우저: 응답 · Accept-CH 에 원하는 항목을 적는다
브라우저->>서버: 다음 요청 · Sec-CH-UA 헤더를 더 붙인다
예를 들어 Sec-CH-UA-Platform-Version 헤더는 운영체제 세부 버전을 담습니다. 서버가 묻지 않은
항목은 보내지 않습니다. 사이트마다 필요한 만큼만 알려지는 셈입니다.
이 헤더들도 문자열처럼 보내는 쪽이 적는 값입니다. 그래서 믿을 수 없습니다.
응답에 붙는 Server 헤더
응답에는 같은 꼴의 Server 헤더가 있습니다. 응답을 만든 서버 소프트웨어가 제품 토큰과 주석으로 자기를 적습니다. 문법이 사용자 에이전트 문자열과 같습니다.
버전이 드러나면 공격자가 그 버전의 알려진 약점을 고르기 쉬워집니다. 그래서 운영 서버는 이 헤더에서 버전을 빼거나 헤더를 아예 안 보내기도 합니다.
관련 항목
이 문자열을 싣는 헤더
User-Agent 헤더 · HTTP 헤더 · Server 헤더 · Vary 헤더 · HTTP
이 문자열을 보내는 프로그램
사용자 에이전트 · 브라우저 · 크롤러 · 웹뷰 · 헤드리스 브라우저 · 명령줄 HTTP 클라이언트 · curl
이 문자열을 이루는 구성 요소
product token · 렌더링 엔진 · WebKit · Blink · Gecko
이 문자열을 읽어 응답을 가르는 방식
사용자 에이전트 탐지 · 콘텐츠 협상 · robots.txt · 접근 로그 · 캐시
이 문자열을 대신할 수 있는 다른 수단
Client Hints · Sec-CH-UA · Accept-CH · 기능 감지 · 인증
이 문자열이 낳는 개인정보 문제
브라우저 핑거프린팅 · 사용자 에이전트 축소 · 쿠키 · 웹 추적
다른 이름: user agent string · user-agent string · UA 문자열 · UA string · 유저 에이전트 문자열