https://github.com/Study-FE-Techbook/Modern-React-Deep-Dive
[14장] 웹사이트 보안을 위한 리액트와 웹페이지 보안 이슈
코드의 규모 증가 = 보안 취약점 노출 확률 증가
14.1 리액트에서 발생하는 크로스 사이트 스크립팅(XSS)
- 웹사이트 개발자가 아닌 제3자가 웹사이트에 악성 스크립트를 삽입해 실행할 수 있는 취약점
✨dangerouslySetInnerHTML prop
- 특정 브라우저 DOM의 innerHTML을 특정한 내용으로 교체할 수 있는 방법
const htmlContent = `<p>악</p>`
const MyComponent = ({ htmlContent }) => {
return (
<div
dangerouslySetInnerHTML={{ __html: htmlContent }}
/>
);
};
export default MyComponent;
- 오직 __html 을 키로 가지고 있는 개체만 인수로 받을 수 있고, 이 인수로 넘겨받은 문자열을 DOM에 그대로 표시하는 역할을 한다.
- 인수로 받는 문자열에는 제한이 없어 위험하다.
- 문자열 값은 한 번 더 검증이 필요하다.
✨useRef를 활용한 직접 삽입
- DOM에 직접 내용을 삽입할 수 있는 방법
- innerHTML에 보안 취약점이 있는 스크립트를 삽입하면 위와 동일한 문제가 발생한다.
import React, { useRef, useEffect } from 'react';
const MyComponent = ({ htmlContent }) => {
const divRef = useRef(null);
useEffect(() => {
if (divRef.current) {
divRef.current.innerHTML = htmlContent;
}
}, [htmlContent]);
return <div ref={divRef}></div>;
};
export default MyComponent;
✨리액트에서 XSS 문제를 피하는 방법
- 제3자가 삽입할 수 있는 HTML을 안전한 HTML 코드로 한 번 치환하는 것 (=새니타이즈 or 이스케이프)
const MyComponent = ({ htmlContent }) => {
const divRef = useRef(null);
useEffect(() => {
if (divRef.current) {
const cleanHtml = sanitizeHtml(htmlContent, {
allowedTags: ['b', 'i', 'em', 'strong', 'a'], // 허용할 태그를 명시
allowedAttributes: {
'a': ['href']
},
// 스타일 속성을 제거
allowedStyles: {},
// 스크립트 태그를 제거
disallowedTagsMode: 'discard'
});
divRef.current.innerHTML = cleanHtml;
}
}, [htmlContent]);
return <div ref={divRef}></div>;
};
export default MyComponent;
14.2 getServerSideProps와 서버 컴포넌트를 주의하자
- 서버에는 일반 사용자에게 노출되면 안 되는 정보들이 담겨 있기 때문에 클라이언트, 즉 브라우저에 정보를 내려줄 때는 조심해야 한다.
- getServerSideProps가 반환하는 props값은 모두 사용자의 HTML에 기록되고, 전역 변수로 등록되어 스크립트로 충분히 접근할 수 있는 보안 위협에 노출되는 값이 된다. (API 키, 사용자 세션 정보, 민감한 설정 값 등)
- 충분히 getServerSideProps에서 처리할 수 있는 리다이렉트가 클라이언트에서 실행되어 성능 측면에서도 손해를 본다. → getServerSideProps가 반환하는 값 또는 서버 컴포넌트가 클라이언트 컴포넌트에 반환하는 props는 반드시 필요한 값으로만 철저하게 제한되어야 한다.
export async function getServerSideProps(context) {
// 민감한 데이터는 서버에서만 사용하고 클라이언트로 전달하지 않는다.
const data = await fetchDataFromServer();
const safeData = {
id: data.id,
name: data.name,
// 클라이언트에 필요한 최소한의 데이터만 포함한다.
};
return {
props: { safeData },
};
}
function Page({ safeData }) {
return (
<div>
<h1>{safeData.name}</h1>
{/* 민감한 정보가 포함되지 않은 안전한 데이터를 렌더링 */}
</div>
);
}
export default Page;
14.3 <a> 태그의 값에 적절한 제한을 둬야 한다
- <a> 태그는 반드시 페이지 이동이 있을 때만 사용하자.
- 페이지 이동이 없어 어떠한 핸들러만 작동시키고 싶다면 button을 사용하자.
- href에 사용자가 입력한 주소를 넣을 수 있다면 이 또한 보안 이슈로 이어질 수 있다. → href 값 제한하기
- 피싱 사이트로 이동하는 것을 막기 위해 origin도 확인해 처리하자.
14.4 HTTP 보안 헤더 설정하기
✨Strict-Transport-Security
- HTTP의 Strict-Transport-Security 응답 헤더는 모든 사이트가 HTTPS를 통해 접근해야 하며, 만약 HTTP로 접근하는 경우 모든 시도는 HTTPS로 변경되게 한다.
✨X-XSS-Protection
- 비표준 기술, 현재 사파리와 구형 브라우저에 제공되는 기능
- 0: XSS 필터 비활성화
- 1: XSS 필터 활성화하고, 공격이 탐지되면 페이지 로드를 차단
- 1; mode=block: XSS 필터를 활성화하고, 공격이 탐지되면 페이지 로드를 차단
- 1; report=<reporting-URI>: XSS 필터를 활성화하고, 공격이 탐지되면 페이지 로드를 차단하며, 지정된 URI로 보고함.
✨X-Frame-Options
- 페이지를 frame, iframe, embed, object 내부에서 렌더링을 허용할지를 나타낼 수 있다.
- DENY: 모든 도메인에서 해당 페이지를 프레임 내에 렌더링하는 것을 금지
- X-Frame-Options: DENY
- SAMEORIGIN: 동일한 출처에서만 해당 페이지를 프레임 내에 렌더링할 수 있도록 허용
- X-Frame-Options: SAMEORIGIN
- ALLOW-FROM uri: 특정 출처(uri)에서만 해당 페이지를 프레임 내에 렌더링할 수 있도록 허용한다. 이 옵션은 최신 브라우저에서 지원되지 않는 경우가 있으므로, 사용 시 주의가 필요하다.
- X-Frame-Options: ALLOW-FROM <https://example.com/>
✨Permissions-Policy
- 웹사이트에서 사용할 수 있는 기능과 사용할 수 없는 기능을 명시적으로 선언하는 헤더
[XSS를 사용하는 예제 헤더]
server {
...
# 모든 위치 정보 접근 막기
# geolocation 기능을 비활성화하여, 페이지가 위치 정보에 접근하지 못하게 함
add_header Permissions-Policy "geolocation=()";
# 위치 정보는 페이지 자신과 특정 페이지에 대해서만 허용
# 카메라는 모든 출처에서 허용
# 화면 전체 모드(PIP)를 비활성화
# - geolocation: 현재 도메인과 example.com에서만 위치 정보 접근 허용
# - camera: 모든 출처에서 카메라 접근 허용
# - fullscreen: 화면 전체 모드 비활성화
add_header Permissions-Policy "geolocation=(self <https://example.com>), camera=(*), fullscreen=()";
...
}
✨X-Content-Type-Options
❓MIME(Multipurpose Internet Mail Extensions)
메시지의 콘텐츠가 어떤 형식인지 브라우저와 서버에게 알려주는 메타데이터 MIME 헤더는 이메일 메시지나 HTTP 응답의 메타데이터를 정의한다. Content-Type, Content-Disposition, Content-Encoding 등
✨Referrer-Policy
- 웹사이트가 다른 웹사이트로 요청을 보낼 때, 리퍼러(즉, 현재 페이지의 URL 정보)를 어떻게 전송할지를 제어하는 보안 기능
- 이 헤더를 사용하면 사이트가 자신의 URL 정보를 얼마나 공개할지를 설정할 수 있으며, 이를 통해 사용자 개인정보를 보호하고 보안 위험을 줄일 수 있다.
- scheme: HTTPS 프로토콜
- hostname: yceffort.kr이라는 호스트명 의미
- port: 443
Origin A | Origin B | "Same-origin" 또는 "cross-origin" |
https://www.example.com:443 | https://www.evil.com:443 | 교차 출처 (Cross-origin): 도메인이 다름 |
https://example.com:443 | https://www.example.com:443 | 교차 출처 (Cross-origin): 서브도메인이 다름 |
https://login.example.com:443 | https://www.example.com:443 | 교차 출처 (Cross-origin): 서브도메인이 다름 |
http://www.example.com:443 | https://www.example.com:443 | 교차 출처 (Cross-origin): 스킴(HTTP와 HTTPS)이 다름 |
https://www.example.com:443 | https://www.example.com:80 | 교차 출처 (Cross-origin): 포트 번호가 다름 |
https://www.example.com:443 | https://www.example.com:443 | 동일 출처 (Same-origin): 정확히 일치 |
https://www.example.com | https://www.example.com | 동일 출처 (Same-origin): 암시적 포트 번호(443)와 일치 |
✨Content-Security-Policy
- 콘텐트 보안 정책은 XSS 공격이나 데이터 삽입 공격과 같은 다양한 보안 위협을 막기 위해 설계됐다.
📍-src*
CSP에서 다양한 종류의 리소스 출처를 정의하는 데 사용된다.
Content-Security-Policy: connect-src 'self';
font-src 'self';
frame-src 'self';
img-src 'self';
manifest-src 'self';
media-src 'self';
object-src 'self';
script-src <https://example.com>;
style-src 'self';
worker-src 'self'
📍form-action
폼 양식으로 제출할 수 있는 URL을 제한할 수 있다.
Content-Security-Policy: form-action 'self' <https://forms.example.com>
Submit Form
// 이 폼은 자기 도메인과 <https://forms.example.com으로> 데이터를 제출할 수 있다.
✨보안 헤더 설정하기
📍Next.js Next.js 애플리케이션에서는 보안 헤더를 설정하는 데 next.config.js 파일을 사용한다. 이 파일에서 headers 함수를 활용하여 HTTP 응답 헤더를 설정할 수 있다.
// next.config.js
module.exports = {
async headers() {
return [
{
// 적용할 경로
source: '/(.*)', // 모든 경로에 적용
headers: [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' <https://trusted.cdn.com>; style-src 'self' 'unsafe-inline';",
},
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-XSS-Protection',
value: '1; mode=block',
},
{
key: 'Referrer-Policy',
value: 'no-referrer',
},
{
key: 'Permissions-Policy',
value: 'geolocation=(self), camera=(self)',
},
],
},
];
},
};
- Content-Security-Policy (CSP): XSS 및 데이터 삽입 공격 방지.
- Strict-Transport-Security: HTTPS를 강제.
- X-Content-Type-Options: MIME 타입 스니핑 방지.
- X-Frame-Options: 클릭재킹 방지.
- X-XSS-Protection: XSS 공격 방지.
- Referrer-Policy: 참조자 정보 제어.
- Permissions-Policy: 웹 API 접근 제어.
📍NGINX 서버 블록(server block) 또는 위치 블록(location block)에서 add_header 지시어를 사용하여 보안 헤더를 설정할 수 있다.
server {
listen 80;
server_name example.com;
# HTTP를 HTTPS로 리디렉션
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
# SSL 인증서 설정
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# 보안 헤더 설정
add_header Content-Security-Policy "default-src 'self'; script-src 'self' <https://trusted.cdn.com>; style-src 'self' 'unsafe-inline';";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "DENY";
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "no-referrer";
add_header Permissions-Policy "geolocation=(self), camera=(self)";
location / {
# 기본 설정
try_files $uri $uri/ =404;
}
}
✨보안 헤더 확인하기
Analyse your HTTP response headers
14.5 취약점이 있는 패키지의 사용을 피하자
Snyk Vulnerability Database | Snyk
14.6 OWASP Top 10
- Open Worldwide Application Security Project: 오픈소스 웹 애플리케이션 보안 프로젝트를 의미한다.
- 주로 웹에서 발생할 수 있는 정보 노출, 악성 스크립트, 보안 취약점 등을 연구하며, 주기적으로 10대 웹 애플리케이션 취약점을 공개하는데 이것을 OWASP Top 10이라고 한다.
참고
'📖 책 찢기 > 모던 리액트 Deep Dive' 카테고리의 다른 글
[모던 리액트 Deep Dive] 15장. 마치며 (0) | 2024.07.31 |
---|---|
[모던 리액트 Deep Dive] 13장. 웹페이지의 성능을 측정하는 다양한 방법 (0) | 2024.07.29 |
[모던 리액트 Deep Dive] 12장. 모든 웹 개발자가 관심을 가져야 할 핵심 지표 (0) | 2024.07.28 |
[모던 리액트 Deep Dive] 11장. Next.js 13과 리액트 18 (1) | 2024.07.27 |
[모던 리액트 Deep Dive] 10장. 리액트 17과 18의 변경 사항 살펴보기 (2) | 2024.07.26 |