![[실전 보안 10] 외부 자원이 바뀌지 않았음을 증명하기](https://img.thenullpage.com/posts/5717/5717_1_2ecd9d.webp)
지난 편 끝에서 콘텐츠 보안 정책이 자원의 출처는 통제하지만, 그 출처가 침해되어 내용이 악성으로 바뀌면 무력하다고 했다. 이번 편은 그 빈틈을 메우는 방어, 외부 자원의 무결성 검증에 대한 이야기다. 짧게 무결성 검증이라 부르는 이 방식은, 외부에서 불러온 스크립트나 스타일이 우리가 기대한 바로 그 내용인지 지문을 대조해 확인한다. 조금이라도 다르면 브라우저가 실행을 거부한다. 개념은 간단하지만 그 안에 담긴 발상이 꽤 아름다워서, 우리 사이트에 적용하며 느낀 점을 정리해 본다.
1. 왜 외부 자원을 의심해야 하나
현대의 웹 사이트는 혼자 모든 걸 만들지 않는다. 자주 쓰는 기능은 외부의 공용 저장소에 올라온 코드를 불러다 쓴다. 이런 저장소는 여러 사이트가 같은 자원을 공유하니 효율적이고, 전 세계에 흩어진 서버에서 빠르게 내려받을 수 있어 속도에도 유리하다. 그래서 나도 초기에 몇몇 기능을 외부 저장소의 코드로 붙였다. 편리했지만, 동시에 내가 통제하지 못하는 무언가에 의존하게 된 것이기도 했다.
문제는 그 외부 저장소가 내 것이 아니라는 데 있다. 만약 그 저장소가 해킹당하거나, 운영자가 악의를 품거나, 중간 경로에서 내용이 바꿔치기당하면, 내 사이트는 나도 모르게 악성 코드를 불러와 실행하게 된다. 사용자 입장에서는 우리 사이트를 방문했는데 피해를 입는 것이니, 책임은 고스란히 우리에게 돌아온다. 남의 코드를 불러 쓴다는 건 그만큼 남을 신뢰한다는 뜻이고, 그 신뢰가 배신당할 가능성을 늘 안고 있었다.
실제로 널리 쓰이던 외부 자원이 침해되어, 그걸 불러 쓰던 수많은 사이트가 한꺼번에 오염된 사례들이 있었다. 공격자 입장에서는 인기 있는 공용 자원 하나만 오염시키면 그걸 쓰는 모든 사이트를 동시에 노릴 수 있으니, 아주 효율적인 공격 지점이다. 첫 편에서 봇이 인터넷 전체를 노린다고 했는데, 공용 자원 오염은 그런 광범위한 공격의 지렛대가 되는 셈이다. 편리한 공유가 곧 광범위한 위험의 통로이기도 했다.
2. 지문으로 내용을 확인한다
이 위험을 막는 발상이 무결성 검증이었다. 원리는 이렇다. 외부 자원을 불러오는 코드에, 그 자원이 마땅히 가져야 할 내용의 지문을 함께 적어 둔다. 이 지문은 앞서 비밀번호 편에서 다룬 해싱과 같은 원리로 만든 값이다. 어떤 내용이든 그것으로부터 고정된 길이의 고유한 지문을 계산할 수 있고, 내용이 조금만 달라져도 지문은 완전히 달라진다.
브라우저는 외부 자원을 내려받은 뒤, 그 내용으로부터 지문을 직접 계산한다. 그리고 우리가 코드에 적어 둔 기대 지문과 비교한다. 둘이 일치하면 내용이 우리가 기대한 그대로라는 뜻이니 실행한다. 조금이라도 다르면, 그러니까 누군가 자원을 바꿔치기했다면 지문이 어긋나므로 브라우저는 그 자원을 아예 실행하지 않고 버린다. 오염된 코드가 실행되기 전에 문 앞에서 걸러 내는 것이다.
이 방식이 우아한 이유는 우리가 외부 저장소를 신뢰할 필요가 없어진다는 데 있다. 저장소가 정직하든 침해됐든 상관없다. 내용이 우리가 처음 확인한 그대로면 실행하고, 아니면 거부한다. 신뢰를 사람이나 서버가 아니라 내용 그 자체에 대한 수학적 확인으로 바꾼 것이다. 지난 편의 콘텐츠 보안 정책이 어디서 왔는지를 본다면, 무결성 검증은 무엇이 왔는지를 본다. 이 둘을 함께 쓰면 출처와 내용을 동시에 지킬 수 있었다.
3. 적용하며 부딪힌 현실
막상 적용해 보니 몇 가지 현실적인 조건이 있었다. 첫째, 무결성 검증은 내용이 고정된 자원에만 쓸 수 있다. 지문은 특정 내용에 대응하므로, 자원이 수시로 바뀌면 지문도 매번 어긋나 버린다. 그래서 버전이 고정된, 내용이 변하지 않는 자원을 불러올 때만 이 방식이 통했다. 항상 최신으로 바뀌는 자원에는 지문을 박아 둘 수 없으니 다른 접근이 필요했다.
둘째, 외부 자원을 제공하는 쪽이 다른 출처에서의 사용을 허락하는 설정을 해 두어야 브라우저가 지문 검증에 필요한 방식으로 자원을 다룰 수 있었다. 이 조건이 갖춰지지 않은 자원은 검증을 적용하기 어려웠다. 그래서 외부 자원을 고를 때 이 검증을 지원하는지도 하나의 기준이 됐다. 지원하지 않는다면, 아예 그 자원을 우리 쪽으로 가져와 직접 제공하는 방법을 택하기도 했다.
사실 가장 근본적인 해법은 중요한 외부 자원을 우리 서버에서 직접 제공하는 것이었다. 남의 저장소에 의존하지 않고 우리가 검증한 사본을 우리 통제 아래 두면, 애초에 바꿔치기 위험이 크게 줄어든다. 속도나 편의를 조금 양보하더라도 통제권을 갖는 편이 안전한 경우가 많았다. 무결성 검증은 외부에 의존할 수밖에 없을 때의 방어이고, 가능하면 의존 자체를 줄이는 게 더 상위의 원칙이었다.
4. 의존성이라는 더 큰 그림
무결성 검증을 고민하다 보니 자연스럽게 더 큰 주제로 이어졌다. 우리가 쓰는 외부 코드 전체, 즉 의존성을 어떻게 관리하느냐의 문제였다. 무결성 검증은 화면에서 불러오는 자원을 지키지만, 서버 쪽에서 끌어다 쓰는 수많은 외부 코드 묶음에도 비슷한 위험이 있다. 그 묶음 중 하나에 악성 코드가 숨어들면 우리 서버가 오염된다.
그래서 나는 외부 코드를 새로 들일 때 신중해지는 습관을 들였다. 정말 필요한지, 관리가 잘 되는 것인지, 알려진 취약점은 없는지를 살폈다. 앞 편에서 낡은 편집 도구의 취약점을 정화 계층으로 막았다고 했는데, 그것도 결국 의존성 관리의 한 장면이었다. 쓰는 외부 코드가 많아질수록 우리가 감시해야 할 표면도 넓어지니, 꼭 필요한 것만 최소로 들이는 절제가 방어의 시작이었다.
또 들여온 외부 코드는 방치하지 않고 알려진 취약점이 발견되면 갱신하려 했다. 소프트웨어의 취약점은 시간이 지나며 계속 발견되니, 한 번 안전하다고 영원히 안전한 게 아니다. 물론 1인 운영자가 모든 걸 완벽히 좇을 수는 없어서, 위험도가 높은 것부터 우선순위를 두고 챙기는 현실적인 선에서 관리했다. 완벽하진 않아도 방치하지 않는 것, 그 차이가 컸다.
5. 신뢰의 관리라는 공통 주제
여기까지 콘텐츠 보안 정책과 무결성 검증을 다루며 관통한 주제는 신뢰였다. 무엇을 신뢰하고, 그 신뢰를 어떻게 검증하며, 신뢰가 배신당했을 때를 어떻게 대비하는가. 외부 자원을 신뢰하되 그 내용을 지문으로 검증하고, 출처를 정책으로 제한하는 이 모든 것이 결국 신뢰를 함부로 주지 않고 매번 확인한다는 원칙의 구체적 실천이었다.
이 원칙은 사실 시리즈 전체를 관통한다. 사용자를 신뢰하지 않고 인증으로 확인하고, 입력을 신뢰하지 않고 정화하고, 화면 검사를 신뢰하지 않고 서버에서 다시 확인했다. 신뢰하지 않음을 기본으로 두고 확인을 통해서만 신뢰를 부여하는 이 태도는, 보안을 배우며 얻은 가장 값진 사고방식이었다. 편리함은 종종 무언가를 무조건 신뢰하는 데서 오는데, 그 편리함에 숨은 위험을 늘 의심하는 습관이 사이트를 지켰다.
그런데 지금까지 이야기하며 계속 미뤄 둔 주제가 하나 있다. 인증의 서명 열쇠, 메일 발송 열쇠, 외부 서비스 접근 열쇠처럼 절대 노출되면 안 되는 비밀들을 대체 어디에 어떻게 보관하느냐다. 여러 편에서 코드에 박지 말고 따로 보관하라고 반복했는데, 그 따로가 구체적으로 무엇인지 이제 다룰 때가 됐다. 덧붙이자면, 무결성 검증을 공부하며 해싱이라는 하나의 도구가 얼마나 여러 자리에서 쓰이는지 새삼 감탄했다. 비밀번호를 되돌릴 수 없게 저장할 때도, 외부 자원이 바뀌지 않았음을 확인할 때도, 인증 증표에 서명할 때도 같은 원리가 모습을 바꿔 등장한다. 내용이 조금만 달라져도 결과가 완전히 달라지는 이 단방향 계산의 성질이, 보안의 여러 문제를 푸는 공통 열쇠였던 것이다. 기초 원리를 하나 제대로 이해해 두면 여러 방어를 관통해 이해할 수 있다는 걸 이 대목에서 실감했다.
물론 1인 운영자의 현실에서 모든 외부 자원에 무결성 검증을 빠짐없이 적용하기는 어려웠다. 그래서 위험도와 노출 범위를 기준으로 우선순위를 정했다. 많은 사용자가 보는 페이지에서 실행되는 핵심 스크립트일수록 먼저 챙기고, 영향이 작은 것은 나중으로 미뤘다. 한정된 시간과 노력을 어디에 먼저 쓸지 판단하는 이 우선순위 감각이, 완벽할 수 없는 현실에서 방어를 실질적으로 굴러가게 하는 힘이었다.
그래서 다음 편에서는 시크릿 관리, 즉 코드에 열쇠를 박지 않는 방법을 실제 사고 경험까지 곁들여 이야기하겠다.