브라우저가 로그인을 막은 날: SRI와 공급망 공격 이야기
브라우저가 정상 파일도 실행하지 않는 이유는 무엇일까요? 파일의 지문을 비교하는 SRI와, 그 검사가 보호하는 범위를 살펴봐요.
이 글의 목차웹페이지는 혼자 만들어지지 않는다6
경비원이 장부의 봉인 번호와 배송 상자의 번호를 대조하더니 문을 열어 주지 않아요. 보낸 곳도 같고 겉모습도 익숙하지만 번호 하나가 달라요. 주민에게는 기다리던 물건이 오지 않은 사고이고, 경비원에게는 맡은 일을 제대로 한 하루예요. 웹에서 로그인이 갑자기 멈추는 사고 가운데 일부는 정확히 이 구조예요.
로그인 버튼이 고장 난 것도, 서버가 뚫린 것도 아니에요. 브라우저가 외부에서 배달된 파일의 지문을 확인하고 실행을 거부한 것이에요. 이 검사가 무엇을 막고 무엇은 막지 못하는지가 이 글의 주제예요.
웹페이지는 혼자 만들어지지 않는다
웹사이트 한 곳에는 여러 회사가 만든 부품이 함께 들어와요. 소셜 로그인 창을 여는 코드, 지도를 그리는 코드, 결제를 돕는 코드가 대표적이에요. 이런 기능 묶음을 SDK(Software Development Kit)라고 불러요. 서비스를 만드는 사람이 모든 기능을 처음부터 만들지 않고, 이미 준비된 도구를 빌려 쓰게 해 주는 상자예요.
SDK 파일은 종종 CDN(Content Delivery Network)에서 와요. CDN은 여러 지역에 파일을 나눠 두고 사용자와 가까운 곳에서 빠르게 배달하는 서버망이에요. 서비스는 속도와 관리 편의를 얻지만, 그 파일을 보관하고 전달하는 바깥의 길목까지 믿게 돼요.
문제는 믿고 가져오는 경로 중 한 곳이 공격당했을 때 생겨요. 공격자가 널리 쓰이는 파일에 악성 코드를 섞으면, 그 파일을 불러오는 수많은 웹사이트가 자기 손으로 위험한 코드를 방문자의 브라우저에 들여놓아요. 완성품을 직접 공격하는 대신 부품이나 유통 경로를 노리는 이런 방식을 공급망 공격이라고 해요.
브라우저가 확인하는 파일의 지문
브라우저에는 배송 상자의 봉인을 확인하는 장치가 있어요. 이름은 SRI(Subresource Integrity), 우리말로는 하위 리소스 무결성이에요. 웹페이지가 외부 스크립트나 스타일 파일을 받을 때, 미리 약속한 파일과 정확히 같은지 확인하는 기능이에요. 스크립트는 로그인 버튼이나 결제창처럼 브라우저의 기능을 움직이는 작은 프로그램이에요.
확인에는 해시(hash)가 쓰여요. 해시는 파일 전체를 계산해 일정한 길이의 문자열로 만든 결과예요. 내용이 한 글자만 달라져도 결과가 크게 달라지므로 파일의 지문처럼 쓸 수 있어요. 여기서 지문은 사람이나 기기를 추적하는 브라우저 지문이 아니라, 파일이 바뀌지 않았는지 가리는 표식이에요.
웹페이지는 기대하는 지문을 integrity라는 칸에 적어 둬요. 브라우저는 받은 파일의 지문을 새로 계산해 둘을 대조해요. 같으면 실행하고, 다르면 파일을 이미 내려받았더라도 실행하지 않아요. 암호를 풀어 내용을 복구하는 과정이 아니라 두 결과가 같은지 비교하는 검사예요.
웹 기술의 공통 규칙을 만드는 월드 와이드 웹 컨소시엄(W3C)은 2016년 SRI를 웹 표준 권고로 발표했어요. 당시 설명에는 이 장치가 필요한 이유도 담겼어요. 주소 앞에 자물쇠가 붙는 HTTPS는 올바른 서버와 통신하고 있는지 확인해 주지만, 그 서버 안의 파일이 믿었던 내용 그대로인지는 보장하지 못해요. 파일을 보관한 CDN이 뚫리거나 운영 권한이 잘못 쓰이면, 정상 주소에서도 다른 내용이 올 수 있어요.
버전만 바꾼 경우와 검토한 파일·지문을 함께 맞춘 경우를 비교해 보세요.
받은 파일
버전 Asha384-R6tVqR+eV/5q…
기대하는 지문
버전 A의 해시sha384-R6tVqR+eV/5q…
받은 파일
버전 Bsha384-y96Y5jaZ5M3D…
기대하는 지문
버전 A의 해시sha384-R6tVqR+eV/5q…
받은 파일
버전 Bsha384-y96Y5jaZ5M3D…
기대하는 지문
버전 B의 해시sha384-y96Y5jaZ5M3D…
SRI는 받은 파일과 기대한 해시가 다르면 실행을 막아요. 정상 업데이트도 지문을 함께 맞추지 않으면 막힐 수 있어요.
받은 A 파일과 기대한 A 지문이 같아요. 무결성 검사를 통과해요.
SHA-384라는 해시 함수로 계산한 실제 지문을 짧게 표시해요. HTML의 신뢰와 파일 접근 조건은 갖췄다고 가정해요. 지문 일치가 파일의 안전성을 보증하지는 않아요.
결제창 뒤에 숨어 있던 복사기
2018년 여름, British Airways에서 항공권을 사던 사람들은 평소처럼 카드 정보를 입력했어요. 결제도 겉보기에는 정상적으로 끝났어요. 화면 뒤에서는 입력한 내용을 공격자의 주소로 한 벌 더 보내는 스크립트가 움직이고 있었어요.
영국 개인정보 감독기관인 ICO의 공식 조사 결정문에 따르면 공격자는 회사 시스템에 원격으로 들어갈 때 쓰는 탈취된 계정 정보로 6월 22일 내부에 접근했어요. 이후 여러 시스템을 거쳐 웹사이트의 자바스크립트 파일을 수정했어요. 카드 정보를 복사해 공격자가 관리하는 주소로 보내는 코드는 8월 21일부터 9월 5일까지 15일 동안 작동했어요. 정상 결제를 방해하지 않았기 때문에 고객이 알아차리기도 어려웠어요.
전체 침해로 40만 명이 넘는 고객의 개인·금융 정보가 영향을 받았고, ICO는 2020년 British Airways에 2천만 파운드의 과징금을 부과했어요. 이 규모와 처분은 ICO의 2020~2021년 연차보고서에도 기록돼 있어요.
이 사건은 외부 CDN의 부품이 바뀐 전형적인 공급망 공격과는 달라요. 항공사 내부망에 들어온 공격자가 항공사 웹사이트의 파일을 직접 고쳤어요. 원본 웹페이지와 그 페이지에 적힌 지문까지 함께 바꿀 수 있다면 SRI 하나로 막을 수 없어요. 다만 브라우저에서 실행되는 작은 스크립트 하나가 사용자가 화면에 입력하는 정보 가까이에 얼마나 깊이 닿는지는 선명하게 보여 줬어요.
익숙한 배달 주소의 주인이 바뀌었다
그 위험은 2024년 polyfill.io 사건에서 공급망의 모습으로 나타났어요. 폴리필(polyfill)은 오래된 브라우저가 새 웹 기능을 이해하도록 빈틈을 메워 주는 코드예요. 많은 웹사이트가 polyfill.io라는 주소에서 이 코드를 바로 받아 썼어요.
2024년 2월, polyfill.io라는 인터넷 주소와 프로젝트 관리 계정은 Funnull이라는 새 소유자에게 넘어갔어요. 같은 주소가 계속 열렸기 때문에 이를 불러오던 웹사이트에서는 겉으로 달라진 것이 없어 보였어요. 하지만 보안업체 Sansec은 그해 6월 25일 악성 코드 삽입을 공개했어요. 당시 10만 곳이 넘는 웹사이트가 그 주소를 사용하고 있었고, 발견된 코드는 특정 모바일 기기와 시간대를 골라 사용자를 스포츠 도박 사이트로 돌려보냈어요.
CDN 사업자 Cloudflare도 자체 관측 자료로 악성 동작을 확인했어요. Cloudflare는 고객 웹사이트의 polyfill.io 주소를 안전한 복사본으로 자동 교체하는 조치까지 취했어요. 여러 사이트를 하나씩 침입하지 않아도, 모두가 믿던 배달 주소 하나를 장악하면 방문자의 브라우저까지 코드가 흘러갈 수 있었기 때문이에요.
이 사건은 SRI가 겨냥한 신뢰 문제를 그대로 드러내요. 다만 당시 서비스는 방문자의 브라우저 정보에 따라 서로 다른 코드를 만들어 보냈어요. 내용이 계속 달라지는 파일에는 하나의 고정 지문을 붙이기 어려워요. SRI는 모든 외부 코드를 안전하게 만드는 우산이 아니라, 내용과 버전을 고정할 수 있는 파일에 봉인을 붙이는 장치예요.
멈춘 화면이 남긴 역설
파일이 정상적으로 업데이트됐는데 웹페이지의 지문만 예전 값이라면 브라우저는 새 파일도 막아요. 버전은 바꿨지만 지문을 함께 바꾸지 않은 경우도 같아요. 사용자 눈에는 로그인이나 결제가 갑자기 고장 난 것으로 보여요.
그렇다고 검사를 없애면 화면은 다시 움직일 수 있지만, 약속하지 않은 파일도 함께 통과해요. SRI 오류는 공격의 증거가 아니에요. 정상적인 업데이트 실수와 실제 변조 모두 같은 불일치를 만들 수 있으므로 원인을 확인해야 해요. 중요한 점은 브라우저가 모르는 파일을 조용히 실행하는 대신 안전한 쪽으로 멈췄다는 데 있어요.
고정된 외부 SDK를 쓴다면 파일 주소의 버전과 그 버전에서 계산한 지문을 한 쌍으로 다뤄야 해요. 둘 중 하나만 바뀌는 상태를 배포 전에 잡아내는 검사가 없으면, 이 불일치는 반드시 운영에서 발견돼요.
차단을 알아채는 경로도 함께 필요해요. 브라우저 콘솔의 오류와 스크립트 로드 실패, 실제 로그인 동작을 각각 살펴볼 수 있어요. MDN은 무결성 정책의 보고 기능도 설명하지만, 일반 CSP 보고를 켜면 모든 SRI 불일치가 자동 수집된다는 뜻은 아니에요. 어떤 브라우저와 어떤 보고 기능을 쓰는지 구별해야 해요.
| 상황 | SRI가 하는 일 | 남는 위험 |
|---|---|---|
| 고정 파일이 변조됨 | 지문 불일치로 실행 차단 | 기능 중단, 처음 믿은 파일의 안전성 |
| 요청마다 다른 파일 | 하나의 고정 지문으로 확인하기 어려움 | 버전과 응답을 고정할 방법 필요 |
| HTML과 지문이 함께 변조됨 | 바뀐 지문과 파일이 맞으면 통과 | 원본 페이지도 별도로 보호해야 함 |
| 지문만 옛 값으로 남음 | 정상 업데이트도 차단 | 사용자에게는 기능 장애로 보임 |
제가 이 문지기를 만난 날
제가 일하던 서비스에서도 웹 로그인이 갑자기 동작하지 않은 적이 있어요. 페이지가 불러오던 외부 소셜 로그인 SDK의 버전과 integrity 값이 서로 맞지 않았고, 브라우저는 지문이 다른 스크립트의 실행을 거부했어요. 로그인 기능 뒤쪽의 문제가 아니라, 필요한 코드가 문턱을 넘지 못한 상태였어요.
침해 사고는 아니었어요. 브라우저의 보안 장치가 정해진 규칙대로 움직인 결과였어요. 외부 SDK의 버전과 무결성 값을 다시 일치시키자 로그인도 정상으로 돌아왔어요.
그 일을 겪고 나서 바꾼 것은 인식이 아니라 절차였어요. SDK 주소와 지문을 같은 파일에서 함께 관리하고 둘이 어긋나면 빌드가 멈추게 했어요. 사람이 한쪽만 고치는 실수는 반드시 다시 나올 것이고, 그때 그 실수가 운영이 아니라 배포 전에 드러나야 하기 때문이에요.
브라우저가 막았을 때 사용자에게는 그냥 고장으로 보인다는 점도 남았어요. 보안 장치가 제대로 동작했다는 사실은 그것을 아는 사람에게만 위안이 돼요. 그래서 이런 차단은 막는 것만큼 알아채는 쪽을 함께 설계해야 했어요.