Cross-Site Scripting
XSS(Cross-Site Scripting)는 공격자가 주입한 스크립트가 피해자의 브라우저에서 해당 사이트의 정상 코드처럼 실행되는 웹 취약점입니다. 원인은 대개 사용자 입력이나 외부 데이터를 HTML·DOM에 안전하지 않게 삽입하는 데 있고, 한 번 실행되면 브라우저 안의 정보 읽기, 화면 변조, 사용자 권한으로의 요청 전송까지 이어질 수 있습니다.
▶아키텍처 다이어그램
🔄 프로세스 다이어그램점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다
웹 애플리케이션은 거의 항상 사용자 입력을 화면에 다시 보여 줍니다. 댓글, 프로필 소개, 검색어, 공유 링크 미리보기처럼 사용자가 넣은 값이 HTML 안으로 다시 들어가는 순간이 많습니다. 이 입력을 안전하게 처리하지 않으면 공격자는 텍스트 대신 스크립트를 심을 수 있고, 브라우저는 그것이 악성 코드인지 정상 코드인지 구분하지 못합니다. 문제는 한 군데만 놓쳐도 된다는 점입니다. 수십 개 입력 경로 중 하나가 출력 인코딩을 빼먹거나, DOM에 innerHTML로 값을 밀어 넣으면 그 지점이 실행 통로가 됩니다. 한번 실행된 스크립트는 사용자의 세션으로 페이지를 조작하고, 민감 정보를 읽고, 원치 않는 요청까지 보낼 수 있습니다.
초기의 웹 취약점이 주로 서버 측 입력 검증 실패에 몰려 있었다면, 웹 애플리케이션이 풍부해질수록 브라우저 안에서 코드가 조립되고 실행되는 지점이 더 많이 생겼습니다. 사용자 생성 콘텐츠, 리치 텍스트 편집기, 서드파티 태그, SPA의 직접적인 DOM 조작이 늘어나면서 XSS는 오랫동안 가장 흔한 웹 취약점 중 하나로 남았습니다. 프레임워크들은 자동 이스케이프와 템플릿 안전장치를 넣어 위험을 줄였지만, 모든 코드를 그 보호막 안에서만 쓰는 것은 아닙니다. HTML을 직접 삽입하는 escape hatch와 브라우저 API는 여전히 남아 있고, 이 때문에 XSS는 '예전 방식의 구식 취약점'이 아니라 현대 프론트엔드에서도 반복해서 나타나는 보안 문제입니다.
XSS는 공격자가 스크립트를 주입하고, 애플리케이션이 그것을 페이지 안에 넣고, 브라우저가 그 코드를 실행하는 순서로 진행됩니다. Stored XSS는 악성 입력이 서버나 DB에 저장됐다가 나중에 다른 사용자가 페이지를 열 때 실행되는 경우입니다. 댓글이나 프로필 소개가 대표적입니다. Reflected XSS는 검색어, 에러 메시지처럼 요청 값이 응답에 즉시 반사될 때 생깁니다. DOM-based XSS는 서버 응답이 아니라 클라이언트 JavaScript가 URL 조각이나 외부 데이터를 읽어 DOM에 위험하게 집어넣을 때 발생합니다. 실행이 시작되면 스크립트는 같은 출처 권한을 얻습니다. 이 말은 해당 사이트의 정상 코드와 거의 같은 위치에 선다는 뜻입니다. HttpOnly가 없는 쿠키를 읽을 수 있고, DOM 안의 개인정보를 훑을 수 있고, 사용자의 계정으로 요청을 보내 화면과 데이터를 바꿀 수 있습니다. 그래서 XSS는 '알림창 하나 뜨는 장난'이 아니라 브라우저 측 권한 탈취로 봐야 합니다.
XSS와 CSRF는 둘 다 사용자의 브라우저를 발판으로 삼지만, 메커니즘이 다릅니다. XSS는 신뢰된 페이지 안에 악성 코드를 심어 브라우저가 그 코드를 실행하게 만드는 공격입니다. 그래서 응답을 읽고, DOM을 바꾸고, 토큰을 훔치는 것까지 가능합니다. 반면 CSRF는 다른 사이트가 사용자의 인증 상태를 빌려 요청만 보내게 만드는 공격이라, 보통은 응답 내용을 읽지 못합니다. 이 차이는 방어도 갈라놓습니다. CSRF 토큰은 요청의 진위를 검사하지만, 이미 XSS가 있는 페이지에서는 그 토큰 자체를 읽어 갈 수 있습니다. 반대로 CSP는 악성 코드 실행을 막는 데 강하지만, 서버가 요청 진위를 별도로 검사하지 않으면 CSRF를 끝까지 막지는 못합니다.
실무에서 XSS는 사용자 입력이 HTML로 다시 나오는 지점을 중심으로 점검합니다. 댓글, 마크다운 미리보기, 관리자용 커스텀 템플릿, 쿼리 문자열을 읽어 화면을 조립하는 코드가 대표적인 위험 구간입니다. 방어는 한 가지로 끝나지 않습니다. 기본은 출력 인코딩과 HTML sanitize입니다. 여기에 innerHTML, dangerouslySetInnerHTML 같은 위험한 DOM 주입 경로를 최소화하고, 정말 필요할 때만 제한된 입력을 허용해야 합니다. 마지막으로 CSP를 두어 실수로 스크립트가 들어가더라도 실행까지 이어지지 않게 피해 범위를 줄입니다. 토큰 저장 위치도 XSS와 직접 연결됩니다. localStorage나 IndexedDB에 민감한 인증 정보를 두면 XSS가 났을 때 그대로 노출됩니다. HttpOnly 쿠키는 스크립트 읽기를 막지만, 그것만으로 모든 공격이 끝나는 것은 아니므로 DOM 조작 경로와 요청 권한까지 함께 봐야 합니다.