웹 인프라

하위 리소스 무결성(SRI), integrity 속성이 하는 일

CDN에서 복사해 온 script 태그의 integrity 값은 그 파일의 해시다.
브라우저가 받은 파일을 이 값과 대조해 다르면 실행하지 않는 원리와, 정상 업데이트까지 막히는 이유를 설명한다.

이 글의 목차남의 서버에서 받아 오는 코드7
  1. 남의 서버에서 받아 오는 코드
  2. integrity 값은 파일의 해시다
  3. crossorigin이 같이 붙는 이유
  4. 결제 화면의 스크립트가 바뀌었던 사건
  5. 주인이 바뀐 스크립트 주소
  6. 정상 업데이트도 똑같이 막힌다
  7. 주소와 지문을 한 쌍으로

라이브러리 문서에서 script 태그를 복사해 오면 주소 말고도 낯선 속성이 두 개 붙어 있는 경우가 많다.
sha384- 같은 접두어로 시작하는 긴 문자열이 든 integrity와 crossorigin이다.

지워도 페이지는 잘 동작하니 그냥 넘어가기 쉽다.
그런데 이 문자열은 브라우저에게 받은 파일이 맞는지 확인하라고 시키는 값이다.

남의 서버에서 받아 오는 코드

웹페이지는 다른 곳에 있는 코드를 많이 가져다 쓴다.
지도를 그리거나 소셜 로그인 창을 여는 코드처럼, 다른 회사가 만든 기능 묶음을 SDK라고 부른다.

이런 파일은 CDN⁠에서 받아 오는 경우가 많다.
CDN은 여러 지역에 파일 사본을 두고 방문자와 가까운 곳에서 보내 주는 서버망이다.
빠르고 편하지만, 내 페이지가 그 서버에 있는 파일의 내용까지 믿는다는 뜻이기도 하다.

공격자가 이런 외부 서버를 장악하면 파일에 악성 코드를 넣거나 파일을 통째로 바꿀 수 있다.
직접 노리기 어려운 서비스 대신 그 서비스가 가져다 쓰는 코드를 노리는 이런 공격을 공급망 공격이라고 한다.

CDN 파일 변조 여부에 따른 실행 코드 HTML이 그대로여도 CDN의 파일이 바뀌면, 방문자의 브라우저는 바뀐 코드를 내 페이지 안에서 그대로 실행한다. 개념 도해 CDN 파일 변조 여부에 따른 실행 코드 페이지가 코드를 받아 오는 경로 내 서버 HTML · script 태그 방문자 브라우저 ④ 두 파일을 한 페이지에서 실행 CDN 제공자가 올린 sdk.js ① HTML ② sdk.js 요청 ③ sdk.js 파일 CDN의 파일이 바뀌었을 때 내 서버의 HTML CDN의 sdk.js 브라우저가 실행하는 코드 평소 제공자가 올린 그대로 그대로 원래 파일 SDK 코드 CDN 장악 뒤 공격자가 파일 교체 그대로 내 서버 기록 변화 없음 바뀐 파일 SDK 코드 · 공격자 코드 HTML이 그대로여도 CDN의 파일이 바뀌면, 방문자의 브라우저는 바뀐 코드를 내 페이지 안에서 그대로 실행한다. SOSHIN · DEV NOTES
내 서버의 HTML은 두 줄에서 같아서, 서버 쪽 기록만 봐서는 아래 줄의 변조를 알아챌 수 없다.

integrity 값은 파일의 해시다

해시는 파일 전체를 계산해 정해진 길이의 문자열로 만든 값이다.
내용이 한 글자만 달라져도 결과가 완전히 달라지기 때문에, 파일이 그대로인지 확인하는 지문처럼 쓴다.

integrity 속성에는 기대하는 파일의 해시를 적는다.
브라우저는 받은 파일의 해시를 직접 계산해 적힌 값과 비교하고, 같으면 불러오고 다르면 불러오기를 거부한다.
앞에 붙은 sha384-⁠는 어떤 계산 방식을 썼는지 알려 주는 표시다.

이 장치를 SRI(Subresource Integrity), 우리말로 하위 리소스 무결성이라고 한다.
페이지가 불러오는 스크립트와 스타일시트 같은 하위 자원이 기대한 내용 그대로인지 확인한다는 뜻이다.

해시 일치 여부에 따른 스크립트 실행 브라우저는 받은 파일의 해시가 HTML의 약속과 다를 때 실행 전에 차단한다. 개념 도해 해시 일치 여부에 따른 스크립트 실행 무결성 확인 경로 ① HTML 기대 해시 ② 외부 파일 수신 ③ 파일 해시 계산 ④ 기대값과 계산값 비교 해시 비교 결과 HTML 기대값 파일 계산값 스크립트 상태 실행 경계 해시 일치 기대 해시 같은 값 계산 해시 실행 해시 불일치 기대 해시 다른 값 계산 해시 차단 비교 판정 경로 브라우저는 받은 파일의 해시가 HTML의 약속과 다를 때 실행 전에 차단한다. SOSHIN · DEV NOTES
브라우저는 받은 외부 파일의 해시를 다시 계산하고, HTML에 기록된 값과 다르면 실행을 막는다.

여기서 HTTPS를 쓰면 이미 안전하지 않느냐는 의문이 생길 수 있다.
HTTPS는 통신하는 서버가 맞는지는 확인해 주지만, 그 서버가 보내는 내용까지 확인하지는 않는다.

서버를 손에 넣은 사람은 정상 주소에서 다른 파일을 내보낼 수 있다.
SRI는 그 내용을 페이지 쪽에서 고정해 두는 장치다.

crossorigin이 같이 붙는 이유

integrity는 남기고 crossorigin만 지우면, 다른 도메인에서 받는 스크립트는 아예 불러와지지 않는다.
처음 쓸 때 자주 막히는 지점이다.

다른 출처의 파일에 SRI를 쓰려면 CORS 규칙을 따라야 한다.
CORS는 다른 사이트가 이 파일의 내용을 읽어도 되는지를, 파일을 보내는 서버가 응답 헤더로 허락하는 규칙이다.

페이지 쪽에서는 crossorigin 속성으로 이 방식의 요청을 보내야 한다.
파일을 보내는 서버가 허락하지 않으면 해시가 맞아도 검사를 통과하지 못한다.

파일과 기대하는 지문을 함께 본다

버전만 바꾼 경우와 검토한 파일·지문을 함께 맞춘 경우는 검사 결과가 다르다.

받은 파일

버전 A

sha384-R6tVqR+eV/5q…

기대하는 지문

버전 A의 해시

sha384-R6tVqR+eV/5q…

SRI는 받은 파일과 기대한 해시가 다르면 실행을 막는다. 정상 업데이트도 지문을 함께 맞추지 않으면 막힐 수 있다.

SHA-384라는 해시 함수로 계산한 실제 지문을 짧게 표시한다. HTML의 신뢰와 파일 접근 조건은 갖췄다고 가정한다. 지문 일치가 파일의 안전성을 보증하지는 않는다.

결제 화면의 스크립트가 바뀌었던 사건

스크립트 하나가 사용자의 입력에 얼마나 가까이 닿는지 보여 준 사건이 있다.
2018년 6월, 공격자가 협력업체 직원의 탈취된 계정으로 British Airways 내부망에 들어왔다.

공격자는 결국 웹사이트의 자바스크립트 파일을 고쳤다.
그 코드는 2018년 8월 21일부터 9월 5일까지 15일 동안, 고객이 결제 화면에 입력한 카드 정보를 공격자의 도메인으로 한 벌 더 보냈다.

예약과 결제 절차는 평소대로 진행되어 겉으로는 달라진 것이 없었다.
유출은 9월 5일 외부에서 알려 준 뒤에야 드러났다.

영국 개인정보 감독기관 ICO는 약 42만 9천 명의 개인정보에 공격자가 접근했을 수 있다고 보고, 2020년 2천만 파운드의 과징금을 매겼다.

스크립트 분기에 따른 입력 행선지 조작된 결제 스크립트는 정상 전송을 유지한 채 같은 입력의 외부 사본을 하나 더 만든다. 개념 도해 스크립트 분기에 따른 입력 행선지 입력 처리 경로 ① 카드 입력 ② 결제 스크립트 분기점 · 브라우저 내부 정상 전송 ③ 정상 결제 서버 결제 완료 숨은 복사 ③ 외부 수집 주소 외부 사본 관찰 위치별 경로 화면 표시 결제 완료 추가 표시 없음 네트워크 전송 정상 결제 서버 외부 수집 주소 같은 입력의 도달 경로 조작된 결제 스크립트는 정상 전송을 유지한 채 같은 입력의 외부 사본을 하나 더 만든다. SOSHIN · DEV NOTES
조작된 스크립트는 결제를 방해하지 않은 채, 입력 정보를 공격자의 주소로 한 벌 더 보냈다.

이 파일은 외부 CDN에 있지 않았다.
항공사 자신의 웹사이트 파일이었다.
HTML에 적힌 지문까지 함께 고칠 수 있는 공격자라면 SRI만으로는 막지 못한다.

주인이 바뀐 스크립트 주소

2024년에는 외부 주소 쪽에서 일이 벌어졌다.
폴리필은 오래된 브라우저에 없는 기능을 채워 주는 코드인데, 많은 사이트가 polyfill.io라는 주소에서 이 코드를 바로 받아 썼다.

이 일을 공개한 보안 업체 Sansec은 10만 곳이 넘는 사이트가 이 주소를 쓰고 있다고 밝혔다.
그해 2월 한 회사가 도메인과 GitHub 계정을 사들였고, 그 뒤 이 주소는 특정 모바일 기기의 방문자를 스포츠 도박 사이트로 보내는 코드를 내보냈다.

Cloudflare도 자체 관측 자료에서 악성 코드 삽입을 확인하고, 자기 네트워크를 거치는 사이트의 polyfill.io 링크를 자기 쪽 사본으로 바꿔 주는 기능을 내놓았다.

SRI가 있었다면 막을 수 있었을까.
이 경우에는 애초에 SRI를 붙이기 어려운 구조였다.

이 주소의 코드는 요청 헤더에 따라 그때그때 다르게 만들어졌다.
방문자의 브라우저마다 내용이 달라지는 파일에는 고정된 지문 하나를 붙일 수 없다.

변조 지점에 따른 SRI 보호 범위 SRI는 신뢰할 HTML과 고정 해시가 있는 범위에서 파일만 바뀐 상황을 가려낸다. 개념 도해 변조 지점에 따른 SRI 보호 범위 SRI 비교 경로 HTML 원본 외부 리소스 신뢰 경계 ① HTML · integrity ② 외부 파일 해시 ③ 브라우저 비교 변조 위치별 판정 파일만 변조 불일치 · 차단 매번 다른 응답 적용 어려움 HTML·해시·파일 변조 일치 · 통과 HTML 신뢰 경계 변조 지점 SRI는 신뢰할 HTML과 고정 해시가 있는 범위에서 파일만 바뀐 상황을 가려낸다. SOSHIN · DEV NOTES
SRI는 고정 파일의 변조에는 강하지만, 매번 내용이 달라지거나 HTML까지 함께 바뀌는 상황에는 한계가 있다.

정상 업데이트도 똑같이 막힌다

SRI는 파일이 왜 바뀌었는지 묻지 않는다.
공격으로 바뀌었든 제공자가 새 버전을 올렸든, 지문이 다르면 똑같이 막는다.

로그인 SDK 주소의 버전 번호만 올리고 integrity 값은 예전 그대로 두면, 브라우저는 새 파일의 실행을 거부한다.
사용자에게는 로그인 버튼이 갑자기 고장 난 것처럼 보인다.

이때 서버 기록에는 흔적이 없다.
파일은 정상으로 내려갔고, 실행을 거부한 것은 방문자의 브라우저이기 때문이다.
단서는 브라우저 콘솔에 남는 무결성 검사 오류다.

그래서 SRI 오류가 보이면 공격인지 업데이트 실수인지부터 가려야 한다.
원인을 보지 않고 integrity 속성을 지워 버리면 약속하지 않은 파일도 함께 통과한다.

상황SRI가 하는 일남는 위험
고정된 파일이 변조됐다지문이 달라 실행을 막는다그 기능이 멈춘다
파일 내용이 요청마다 다르다고정 지문을 붙일 수 없다확인 없이 실행된다
HTML과 지문이 함께 바뀌었다바뀐 지문과 맞으면 통과시킨다페이지 자체를 따로 지켜야 한다
버전만 바뀌고 지문은 옛 값이다정상 업데이트도 막는다사용자에게는 기능 장애로 보인다

주소와 지문을 한 쌍으로

SRI는 버전이 고정된 주소에 붙인다.
항상 최신 파일을 주는 주소에 붙이면 제공자가 파일을 고치는 순간 막힌다.

해시는 직접 받아 검토한 그 파일로 계산한다.
지문은 파일이 그대로인지 확인할 뿐, 처음 믿은 파일이 안전한지까지 보증하지는 않기 때문이다.
계산하는 명령은 MDN의 SRI 문서에 예시로 나와 있다.

SDK 버전을 올릴 때는 주소의 버전과 integrity 값을 같은 변경에서 함께 바꾼다.
둘이 어긋나면 실패하는 검사를 빌드에 두면, 이 실수가 사용자 화면에서 처음 드러나는 일을 막을 수 있다.

소신을 상징하는 까마귀 목판화 일러스트
작성자소신 · Soshin

웹과 서버를 만들고, 개발하며 배운 것들을 글로 남깁니다.

좌우로 움직여 보세요