최근 웹 분야에 핵폭탄 수준의 거대 취약점이 하나 터졌다. 'React2Shell'이라는, React의 SSR(Server Side Rendering, 서버측 렌더링) 기능을 담당하는 React Server Component(이하 RSC)가 클라이언트와 데이터를 주고받는 과정 중 직렬화 단계에서 클라이언트로부터 주입된 코드를 무분별하게 실행하게 되는 치명적인 버그이며, 이미 수많은 서비스가 피해를 입은 것으로 알려졌다. 소프트웨어 취약도 점수인 CVSS는 무려 10점 만점에 10.0점을 받았다.
이에 대한 기술적 설명은 생략하고, 이 사건과 더불어 웹 개발이라는 분야에 대한 내 생각을 조금 적어보고자 한다.
먼저 이러한 사건이 발생하게 된 원인에 결국 리액트의 SSR 기능이 핵심이었던 것은 부정할 수 없는 사실이다. 그것도 클라이언트와의 통신 과정에서 발생한 것이니 말이다. 하지만 리액트는 분명 몇년 전까지 '프론트 라이브러리'였다. 그러나 어느 순간부터 당당하게 서버 속으로 들어가 SSR을 담당하고 있다. 어디서부터 시작된 일인지 거슬러 올라가보자.
최초 웹의 태동기 시절에는 오로지 HTML만 있을 뿐, CSS도 JS도 없었다. 이 당시 웹 페이지는 '상태'라는 것을 가질 수 없었으며, 클라이언트가 웹 페이지에 데이터를 전송하기 위한 방법은 오로지 Form 밖에 없었다. 이를 Submit받은 서버는 새로운 웹페이지 안에 정보를 넣어 응답하는 것만이 가능했기에 PHP, JSP와 같이 템플릿 문법을 활용하여 HTML 내에 서버측 데이터를 주입하는 방법을 사용하였다.
그러나 1995년 JS의 등장 후 웹은 폭발적인 가능성을 품게 된다. 웹 페이지 하나가 곧 브라우저라는 작은 운영체제 위에서 돌아가는 작은 응용 프로그램이 될 수 있었고, 이러한 개념은 플래시나 Sliverlight같은 시행착오를 거쳐 2014년 HTML5까지 와서야 비로소 정립된다. HTML5 도입 당시 여기저기서 이를 가지고 홍보를 했던 것은 다들 기억할 것이다. 그 시점부터 완성형의 DOM API를 자유자재로 활용하여 외부 플러그인 없이도 다양한 멀티미디어 기능을 구현할 수 있었다.
1999년 MS의 인터넷 익스플로러에서 처음으로 HTTP를 통해 XML을 주고받는 기능이 도입된 이후 다른 브라우저에서도 몇년의 간격을 두고 XMLHttpRequest라는 메서드 하에 해당 기능들이 도입되기 시작했다. 구글이 이를 활용하여 Gmail이나 구글 맵을 구현하고 AJAX(Asyncronous Javascript And XML)라 명명하였는데, 이는 웹페이지가 외부 플러그인을 배제하고도 새로고침 없이 서버와 HTTP를 이용해 동적으로 정보를 주고받으며 상태를 변경할 수 있게 되었음을 의미했다.
그리고 이때부터 등장한 개념이 RESTful API였다. HTTP를 통해 데이터를 주고받는 원칙을 정립한 개념이었고, 수많은 서비스들이 REST API를 채용해 동적인 웹을 개발하기 시작했다. 그리고 이름으로부터 알 수 있듯이 XML을 주고받기 위해 만들어졌던 AJAX는 어느새부터 XML보다 더욱 직관적이고 JS에 활용하기 편한 JSON을 주고받게 되었다.
문제는 여기서부터 시작되었다. AJAX를 통해 동적으로 서버와 데이터를 주고받게 된 것은 좋은데, 결국에 다른 페이지로 넘어가려면 새로고침은 피할 수 없고 이 과정에서 모든 상태가 초기화될 뿐 아니라 이용자는 그 사이의 지연으로 불편함을 느끼게 되었다. 이에 HTML5의 등장에 힘입어 웹 서비스를 아예 새로고침 없이 하나의 연속적인 경험으로 통합하자는 아이디어가 나타났고, 이를 구현한 것이 리액트를 필두로 한 각종 프론트엔드 라이브러리였다. 비로소 '웹 애플리케이션'의 등장을 알린 것이다.
프론트 라이브러리의 등장은 가히 혁명과 같았다. 웹 이용자들은 불편한 새로고침 시간을 기다릴 필요 없이 연속적인 웹 경험을 할 수 있었고, Virtual DOM을 활용해 무거운 DOM 조작을 최소화하여 최적화도 이루어냈기에 체감 성능 향상은 더더욱 컸다. 프론트엔드 개발자들은 거대한 웹 서비스를 하나의 상태를 통해 통합적으로 관리할 수 있게 되어 웹 개발은 앱 개발에 보다 가까워졌다(그리고 재밌게도 나중에는 Electron이나 React Native등을 통해 웹이 역으로 앱과 응용 프로그램에 수출된다). 백엔드 개발자들은 프론트에 전혀 신경 쓸 필요 없이 그저 명세 대로의 API 작성만 해주면 되었다. 또한 서버측과의 모든 통신을 REST API로 일원화하여 매번 HTML을 전송하며 발생한 오버헤드를 줄여 트래픽 절약 효과도 이뤄낼 수 있게 되었다.
다만 이러한 CSR(Client Side Rendering, 클라이언트측 렌더링)에는 치명적인 약점이 있었다. 정적으로 배포된 웹 앱의 HTML은 JS로 내용물을 로딩하기 위한 껍데기에 불과했기 때문에 SEO(Search Engine Optimization, 검색엔진 최적화)에 쥐약이었고, 구글봇과 같은 유능한 검색엔진은 직접 JS를 로딩시키는 방식으로라도 내용을 수집하기는 했으나 각종 SEO 기법을 사용하는 것이 거의 불가능에 가까웠다. 또한 최초 접속 시 DOM 렌더링과 서버로부터 데이터를 로딩하는 동안의 지연으로 사용자 경험을 저해한다는 지적도 있었다.
위 문제를 해결하기 위해 최종적으로 등장한 것이 지금 이 사태의 원인이기도 한 프론트엔드 프레임워크의 SSR(Server Side Rendering, 서버측 렌더링)이다. 프론트엔드 프레임워크의 SSR은 앞선 시대의 정적 웹과는 다소 다른데, Hydration이라는 기법을 통해 최초로 클라이언트에 전달될 페이지만 미리 HTML을 완성하여 전달하고, 그 이후로는 CSR을 통해 웹 앱으로써 동작한다. 이는 두가지 이점이 있었는데 첫번째는 미리 완성된 페이지를 제공함으로써 사용자 입장에서 최초 로딩 시간으로 인한 지연을 줄일 수 있다는 점, 두번째는 SEO가 가능하다는 점이었다.
그러나 내가 직접 개발을 해보면서도 느꼈던 가장 큰 문제는 '프론트엔드 프레임워크'의 서버 영역 침범이었다. 내가 앞서 제시한 프론트 라이브러리의 장점 중 하나였던 '프론트와 백의 완벽한 분업'이 다시 무너진 것이다. 서버에는 SSR을 제공하는 프론트 프레임워크의 서버와 REST API를 제공하는 서버 두개가 공존하게 되었고, 둘 사이의 교통정리가 상당히 머리 아픈 상황이 되었다. 물론 SSR 프레임워크에게 REST API를 맡기는 것도 불가능한 것은 아니지만, 본격적인 백엔드 프레임워크에 비해 신뢰하기가 어려운 것이 사실이다.
그리고 결국에는 사고가 터졌다. 리액트의 Next.js 등 각종 SSR 라이브러리들은 프론트와 서버의 SSR 담당 영역 사이에 최대한 연속적인 개발이 가능하도록 부단한 노력을 했다. 그 과정에서 클라이언트와 서버의 경계가 희미해졌고, 클라이언트가 보낸 코드를 서버가 그대로 실행해 버리는 초유의 사태가 벌어진 것이다.
난 물론 이 사태의 발생에 JS의 근본적인 불안정성도 한몫 크게 했다고 생각하고 있다. '남의 코드'를 쉽게 실행할 수 있는 것은 JS뿐만 아니라 파이썬 등 스크립트 언어의 공통적인 특징이다. 하지만 JS는 그러한 스크립트 언어 중에서 독보적으로 구조가 불안정하고 취약하다. 객체 멤버 접근에 '.'을 통한 접근자와 딕셔너리 문법을 통한 접근까지 다양한 방법을 제공하는 등 자유도 면에서는 단연 최고인 언어이지만 그만한 자유에는 책임이 따르게 마련이다.

최근에는 개발자의 자유도를 제한하는 것이 트렌드인 만큼 JS를 마개조한 Typescript가 절찬리 사용되고 있으며 그외에는 Rust같은 언어의 등장도 트렌드를 보여준다. 다만 TS도 타입 면에서의 불안정성을 보완해줄 뿐 JS로 최종적으로 변환되어 돌아간다는 면에서는 변함 없으며, JS 특유의 '객체' 시스템의 불안정성은 여전하다. 모두가 알면서도 애써 무시하던 JS의 아픈 손가락이 한방 먹은 셈이다.
내가 항상 웹개발 분야에서 느끼는 것은 "변화가 정말 빠르다"는 것이다. 트렌드가 월, 주 단위로 계속 바뀌고 신기술이 등장하고 있으니 따라가기가 쉽지가 않다. 누군가는 웹개발자들이 겉멋을 낸다고 비웃기도 하지만 나는 다양한 신기술이 나오는 것이 나쁘다고 생각하지는 않는다. 하지만 그만큼 안정화되기 전 기술이 비교적 빨리빨리 도입된다고 느껴지는 면이 있다. 물론 Next.js가 나온지 하루이틀 된 프레임워크는 아니다. 하지만 백엔드는 아직도 Spring을 쓰는 곳이 태반이다. 웹도 보통 백엔드보다는 프론트엔드의 트렌드가 빨리 변하는 특징이 있는데, 보통은 각종 버그나 취약점으로 인한 피해가 데이터를 쥐고 CRUD를 담당하는 백엔드에서 더 치명적이기 때문일 것이다. 이번 사건도 어쩌면 왜 백엔드 개발자들이 신기술을 한발짝 뒤에서 따라가는지 보여주는 단면이 아닐까 싶다.
'프로그래밍' 카테고리의 다른 글
| IoT BLE 전등 스위치 해킹하기 (아이클리오) - 1 (0) | 2024.08.13 |
|---|---|
| 티스토리에 GitHub Gist 코드 삽입하기 (0) | 2021.12.20 |
| 인터파크 티켓팅 매크로 회차 관련 안내 (5) | 2020.09.24 |
댓글