최근 다양한 기업의 보안 사고를 접하면서 프론트엔드 개발자로서도 보안에 대한 고민이 필요하다고 느꼈습니다. 사용자 입력 처리, 인증 등에서 취약점이 발생할 수 있는 만큼, 주요 공격 유형과 대응 전략을 정리해보았습니다.
공격자가 웹사이트에 악성 스크립트 삽입하여 사용자의 브라우저에서 악성 스크립트가 실행되도록 하는 공격 기법입니다.
예를 들어 아래와 같은 입력값은 사용자 브라우저에서 의도치 않은 스크립트를 실행시킬 수 있습니다.
<script>alert("XSS");</script>
<img src = '#' onerror='alert('XSS');"/>
<a href = 'javascript:alert('XSS')'>XSS</a>
최신 브라우저 환경에서는 대부분 막힌 공격 기법이며 리액트의 경우 jsx에서 모든 값을 자동으로 이스케이프하여 기본적인 XSS를 방지합니다.
const userInput = '<script>alert('XSS');</script>
function MyComponent() {
return <div>{userInput}</div>
}
해당 코드는 입력값을 HTML로 해석하지 않고 단순 텍스트로 처리합니다. 따라서 브라우저에서는 문자 그대로 표시되어 스크립트가 실행되지 않습니다.
직접 HTML을 삽입하고자 하는 경우에는 dangerouslySetInnerHTML
프로퍼티를 사용합니다.
이름부터 사용에 주의해야하는 것만큼 해당 프로퍼티를 사용하는 경우 dompurify와 같은 라이브러리를 적용하여 보안 문제에 대비하는 것이 좋습니다.
function Component() {
const htmlContent = {__html:"<p>의도한 삽입</p>"};
return <div dangerouslySetInnerHTML = {htmlContent} />;
}
브라우저 단에서 악성 스크립트 실행을 방지하기 위해 CSP 헤더 설정도 고려할 수 있습니다.
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com;
사용자가 인증된 상태임을 악용하여 사용자의 의지와 무관하게 서버에 요청을 보내는 공격 기법입니다.
-> 로그인된 상태에서 사용자가 악성 웹사이트를 방문하여 악성 코드가 실행되면 사용자의 브라우저를 통해 사용자의 쿠키 등의 세션 정보를 사용하여 웹사이트에 요청을 보냅니다.
-> 서버는 사용자가 요청을 보낸 것으로 생각하고 처리합니다.
백엔드에서 발급한 CSRF 토큰을 HTML의 태그로 전달하고, 프론트엔드에서 이를 요청 시 헤더에 포함시켜 방어할 수 있습니다.
프론트엔드에서는 토큰을 쿠키나 로컬스토리지에서 꺼내 헤더에 넣는 작업을 합니다.
<!-- index.html -->
<meta name="csrf-token" content="random-token-value">
// API 요청 시 CSRF 토큰 포함
// 서버는 이 토큰을 검증함으로 요청의 출처가 올바른지 확인할 수 있습니다.
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch('/api/transfer-money', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ amount: 1000, to: 'account123' })
});
브라우저가 쿠키를 타 도메인 요청에 자동으로 전송하지 않도록 하는 방식입니다.
SameSite = Strict
쿠키가 현재 사이트와 동일한 사이트에서 시작한 요청에만 전송됩니다.
SameSite = Lax
Strict보다는 약간 완화된 설정입니다. 링크 클릭, 메서드 폼 제출 등 안전하다고 판단되는 일부 최상위 레벨 탐색 시에는 다른 사이트에서 요청되었더라도 쿠키를 전송합니다.
SameSite=None;Secure
모든 요청에 쿠키를 전송합니다. Secure 속성을 함께 사용하여 https에서만 쿠키를 전송하도록 제한합니다.
프론트엔드에서 .env 파일은 빌드 시점에 클라이언트 번들에 포함됩니다. 민감한 정보는 절대 포함하면 안됩니다.
서비스의 기본 URL, 구글 맵과 같은 서비스의 제한된 프론트용 키 등의 값만 포함하여 사용하도록 합니다.
프론트엔드는 사용자와 직접 닿아있는 영역인 만큼 보안에 대한 작은 실수 하나가 전체 시스템을 위협할 수 있습니다. 이 외에도 https 강제화, 패키지 취약점 점검, x-frame-options 등도 함께 고려해볼 수 있습니다.
혹시 추가하고 싶은 보안 전략이나 실무 경험이 있으면 댓글로 같이 공유해주시면 좋을 것 같습니다!
FE도 보안에서 자유롭지 않다는 거 다시 한 번 깨달았습니다. 잘 정리된 글 감사합니다 🙏
혹시 실무에서는 dangerouslySetInnerHTML 사용이 꼭 필요한 상황이 있었을까요? 저는 아직 실제로 써본 적은 없어서 사용하는 상황이 궁금하네요~!
react에서 에디터를 구현하고 그것을 보여줄 때 dangerouslySetInnerHTML 를 사용했던 기억이 있네요. side effect를 막는 처리를 했었는데 정확히 기억이 안나는군요 ㅎㅎ 재밌게 잘 읽었습니다.
CORS와 같은 브라우저 정책도 보안 정책인 것 같은데 추가 어떠신가영