...
helmet 모듈
helmet 모듈은 서버에서 다양한 HTTP 헤더를 자동 설정을 통해 서버 어플리케이션의 보안을 강화해주는 대표적인 노드 보안 모듈이다.
헬멧을 써서 내 머리를 보호하듯이, 내 웹서버를 외부의 공격으로부터 보호해준다.
helmet 사용법
사용하는 에디터의 터미널에서 아래와 같이 helmet 모듈을 설치해주고 간단하게 선언만 해주면 된다. (배포용 모듈)
> npm install helmet # 헬멧 설치
// helmet은 개발환경에서는 필요없는 모듈이니 배포환경에서만 활성
if (process.env.NODE_ENV === 'production') {
app.use(morgan('combined'));
app.use(helmet( { contentSecurityPolicy: false } )); // contentSecurityPolicy는 꽤 복잡한 설정이기 때문에 일단 꺼둔다. (후술)
} else {
app.use(morgan('dev'));
}
helmet 미들웨어 기능
Helmet은 Express의 미들웨어 모듈이며, Helmet 또한 여러 미들웨어 모듈을 합쳐 놓은 미들웨어 패키지 모듈이다.
정확히 말하자면, 익스프레스 기반 어플리케이션에서 HTTP response header를 설정하는 여러개의 작은 미들웨어 함수 유형 모음인 것이다.
각 미들웨어 함수들의 기능은 아래와 같다.
const express = require("express");
const helmet = require("helmet");
const app = express();
// 기본 설정 기능 사용
app.use(helmet());
// or
// 특정 세부 기능 하나하나 설정할때 사용
app.use(helmet.contentSecurityPolicy());
app.use(helmet.crossOriginEmbedderPolicy());
app.use(helmet.crossOriginOpenerPolicy());
app.use(helmet.crossOriginResourcePolicy());
app.use(helmet.dnsPrefetchControl());
app.use(helmet.expectCt());
app.use(helmet.frameguard());
app.use(helmet.hidePoweredBy());
app.use(helmet.hsts());
app.use(helmet.ieNoOpen());
app.use(helmet.noSniff());
app.use(helmet.originAgentCluster());
app.use(helmet.permittedCrossDomainPolicies());
app.use(helmet.referrerPolicy());
app.use(helmet.xssFilter());
모듈명 | 기능 |
contentSecurityPolicy (CSP) | 콘텐츠 보안 정책 설정 및 구성을 통해 의도하지 않은 내용이 페이지에 삽입되는 것을 방지 Content-Security-Policy 헤더를 설정해서 XSS나 교차사이트 인젝션등을 방지한다. 다른사이트의 script를 불러오는것도 막기때문에 헬멧 적용전 별도로 설정이 필요하다. |
crossOriginEmbedderPolicy | Cross-Origin-Embedder-Policy 헤더를 require-corp로 설정 |
crossOriginOpenerPolicy | Cross-Origin-Opener-Policy 헤더를 설정 |
crossOriginResourcePolicy | Cross-Origin-Resource-Policy 헤더를 설정 |
dnsPrefetchControl | 도메인이 미리로딩되는 Prefetch에 대해 컨트롤 하기위해 X-DNS-Prefetch-Control 헤더를 설정 대부분의 브라우저는 성능을 향상시키기 위해서 페이지의 링크에 대한 DNS 레코드(record)를 미리 추출(prefetch)한다. 이러한 방식으로써 사용자가 링크를 클릭할 때 대상에 대한 IP가 이미 알려져있다. 이로 인해 유발될 수 있는 상황은 DNS 서비스가 과도하게 사용되는 것 (큰 웹 사이트를 소유하고 있는 경우, 수백만 명이 방문하는 경우 등), 개인 정보 보호 문제 (도청자 한 명이 당신이 특정 페이지에 있다고 추론할 수 있음), 페이지 통계 변경 (일부 링크는 방문되지 않은 경우에도 방문된 것으로 나타날 수 있음) 이 있다. 즉, 보안 요구가 높은 경우에는 성능 저하를 감수하고서 DNS 프리페치를 비활성화할 수 있다. |
exprectCt | Expect-CT 헤더를 설정하여 SSL 인증서 오발급을 예방 |
frameguard | X-Frame-Options 헤더를 설정하여 클릭재킹의 공격을 방지 클릭재킹이란 사용자가 자신이 클릭하고 있다고 인지하는 것과 다른 것을 클릭하도록 하여 속이는 해킹 기법이다. 속이기 위해 보이지 않는 레이어에 보이지 않는 버튼을 만드는 방법이 있다. 서버가 제공한 페이지에 서버의 허락없이 악의 적인 페이지를 로드한 frame 혹은 iframe 태그를 넣을 수 있기 때문에. 악의적인 컨텍스트(context)에서 페이지에 클릭재킹을 실행할 수 있기 때문이다. 이러한 context에서, 해커는 서버가 최초에 제공한 페이지 위에다가 숨겨진 레이어를 넣을 수 있다. 그래서 숨겨진 버튼을 사용하여 나쁜 스크립트를 실행할 수도 있는 것이다. 따라서 iframe에 넣는 허용된 사이트만을 제한하는 개념이다. |
hidePoweredBy | 응답 헤더에 있는 X-Powerd-By에 서버 소프트웨어가 표기되는데 이를숨겨준다. 이 정보는 악의적으로 활용될 가능성이 높기에 헬멧을 통해서 제거해 주는 것이 좋다. |
hsts | 서버에 대한 안전한(SSL/TLS를 통한 HTTP) 연결을 적용하는 Strict-Transport-Security 헤더를 설정. 브라우저에게 HTTPS만을 통해서 사이트에 엑세스 할 수 있도록 요청 (기본 false) 사용자가 특정 사이트에 접속할 때 해당 사이트가 HTTPS를 지원하는지, 하지 않는지를 미리 모르는 경우가 대부분이다. 그렇기에 브라우저는 디폴트로 HTTP로 먼저 접속을 시도한다. 이때 HTTPS로 지원되는 사이트였다면 301Redirect나 302 Redirect를 응답하여 HTTPS로 다시 접속하도록 한다. 하지만 이때 해커가 중간자 공격을 하여, 중간에 프록시 서버를 두고, [나] ↔ [해커] 사이에서는 HTTP 통신을 하고 [해커] ↔ [웹사이트] 사이에선 HTTPS 통신을 한다면, 개인정보가 HTTP 프로토콜을 통해 해커에게로 전해지는 참사가 일어난다. 이러한 공격을 SSL Stripping이라고 하며 이런 공격을 방지하기 위해 HSTS를 설정한다. |
ieNoOpen | X-Download-Options 헤더를 설정하여 ie8 이상에서만 사용할 수 있도록 |
noSniff | X-Content-Type-Options 를 설정하여 선언된 콘텐츠 유형으로부터 벗어난 응답에 대한 브라우저의 MIME 스니핑을 방지 MIME이란 Multipurpose Internet Mail Extensions의 약자로 클라이언트에게 전송된 문서의 다양성을 알려주기 위한 포맷이다. 브라우저는 리소스를 내려받을 때 MIME 타입을 보고 동작하기에 정확한 설정이 중요하다. MIME 스니핑이란 브라우저가 특정 파일을 읽을 때 파일의 실제 내용과 Content-Type에 설정된 내용이 다르면 파일로 부터 형식을 추측하여 실행하는 것인데, 편리함을 위한 기능이지만 공격자에게 악용 될 가능성이 있다. |
originAgentCluster | Origin-Agent-Cluster 헤더를 설정하여 오리진간 문서를 별도 에이전트 클러스터로 분리 |
permittedCrossDomainPolicies | X-Permitted-Cross-Domain-Policies 헤더를 설정하여 크로스도메인 컨텐츠 정책을 설정 X-Permitted-Cross-Domain-Policies 헤더는 일부 클라이언트(대부분 Adobe 제품)에 도메인 간 콘텐츠 로드에 대한 도메인 정책을 처리한다. |
referrerPolicy | 참조 referrer 헤더를 숨김 |
xssFilter | X-XSS-Protection 헤더를 0으로 설정하여 XSS(Cross Site Scripting) 공격 스크립트를 비활성화여 예방 |
noCache 미들웨어는 (현재 Helmet 5에서 deprecated 됨)
SSL (Secure Socket Layer) 보안 소켓 계층
웹사이트와 브라우저 ( 혹은 두 서버 ) 사이에 전송된 데이터를 암호화하여 인터넷 연결을 유지하는 표준 기술.
이는 해커가 정보 및 금융 정보를 포함한 전송되는 모든 정보를 열람하거나 훔치는 것을 방지한다.
TLS (전송 계층 보안 Transport Layer Security, TLS)
TLS는 가장 최신 기술로 더 강력한 버전의 SSL 이라고 보면 된다.
그러나 SSL이 더 일반적으로 사용되는 용어이기에, 여전히 보안 인증서는 SSL이라 불린다.
XSS (Cross Site Scripting) 공격
사이트에 남몰래 스크립트를 삽입해 그릇된 스크립트 실행으로 공격을 하는 행위.
예를 들어 쿠키나 세션 토큰 등의 민감한 정보를 탈취.
Strict-Transport-Security
웹 사이트를 접속할 때 강제적으로 https 프로토콜로만 접속하게 하는 기능.
helmet default
기본적으로 helmet은 세부적인 여러 미들웨어 함수를 포함하고 있지만, 단순히 helmet()으로 호출하게 되면 default로 지원하는 모듈들로 설정된다.
// 기본 설정 기능 사용
app.use(helmet());
default 모듈 종류는 아래와 같다.
만일 default 미들웨어에서 몇개만 지우는 방식으로 사용하고 싶다면 다음과 같이 사용한다.
app.use(helmet({
frameguard: false
}))
CSP (contentSecurityPolicy)
CSP를 사용하면 웹사이트의 http응답에 CSP 헤더가 추가된다.
CSP 헤더가 존재할 경우, 브라우저는 CSP 헤더에 언급되지 않은 리소스들을 다음과 같이 로드하지 않게된다.
Helmet의 CSP 기본설정은 ‘self’ 즉, 자신의 웹사이트에 존재하는 리소스들만을 허용하기 때문이다.
그래서 내 웹서비스에 CDN 등의 외부 사이트의 소스를 이용할 경우, 다른 웹사이트에서 이미지 로드하는 경우, 심지어 인라인 스크립트로 자바스크립트 코드를 작성한 경우 다음과 같이 모두 에러를 발생시키게 된다.
미리 위에서 helmet() 옵션으로 contetnSecurityPolicy를 false로 해두라고 권고했는데, 왜냐하면 csp를 세세하게 설정하지 않으면 콘솔에서 마구 오류를 내뿜어 초보자 입장에선 혼란을 줄수 있기 때문에 그렇다.
CSP 는 신뢰할 수 있는 컨텐츠 소스의 화이트리스트를 정의하는 방식으로 작동한다.
웹 페이지에 필요한 각 리소스 유형(스크립트, 스타일시트, 글꼴, 프레임, 미디어 등)에 대해 구성할 수 있다.
사용 가능한 지침(directive)이 여러 개 있으므로, 웹 사이트 소유자는 세부적인 제어를 할 수 있다.
따라서 이를 해결하기 위해서는 웹 페이지가 사용할 신뢰할 수 있는 리소스들의 도메인을 CSP 헤더에 추가해야 한다.
contentSecurityPolicy 옵션 설정법은 다음과 같다.
const cspOptions = {
directives: {
// 헬멧 기본 옵션 가져오기
...helmet.contentSecurityPolicy.getDefaultDirectives(), // 기본 헬멧 설정 객체를 리턴하는 함수를 받아 전개 연산자로 삽입
/*
none : 어떳 것도 허용하지 않음
self : 현재 출처에서는 허용하지만 하위 도메인에서는 허용되지 않음
unsafe-inline : 인라인 자바스크립트, 인라인 스타일을 허용
unsafe-eval : eval과 같은 텍스트 자바스크립트 메커니즘을 허용
*/
// 구글 API 도메인과 인라인 스크립트, eval 스크립트를 허용
"script-src": ["'self'", "*.googleapis.com", "'unsafe-inline'", "'unsafe-eval'"],
// 다음과 카카오에서 이미지 소스를 허용
'img-src': ["'self'", 'data:', '*.daumcdn.net', '*.kakaocdn.net'],
// 소스에 https와 http 허용
"base-uri" : ["/", "http:"],
}
}
// Helmet의 모든 기능 사용. (contentSecurityPolicy에는 custom option 적용)
app.use(helmet({
contentSecurityPolicy: cspOptions,
}));
Helmet이 지원하는 이름 지정 스타일(naming style)은 defaultSrc 및 default-src 을 모두 허용한다. (camel, snake 표기법을 모두 지원)
kebab-cased | camel-cased | description |
base-uri | baseUri | document의 <base> 요소에 사용할 수 있는 URL을 제한 |
block-all-mixed-content | blockAllMixedContent | HTTPS를 사용하여 페이지가 로드될 때 HTTP를 사용하여 asset을 로드하는 것을 방지 |
child-src | childSrc | <frame>이나 <iframe>과 같은 요소를 사용하여 로드된 웹 작업자나 내포된 검색 컨텍스트에서 가능한 소스를 정의 |
connect-src | connectSrc | 스크립트 인터페이스를 사용하여 로드할 수 있는 URL 제한 |
default-src | defaultSrc | 다른 fetch directive들의 fallback으로 제공됨 |
font-src | fontSrc | @font-face를 사용하여 로드할 수 있는 소스를 지정 |
form-action | formAction | 지정된 컨텍스트에서 제출 대상으로 사용될 수 있는 URL을 제한 |
frame-ancestors | frameAncestors | <frame>, <object>, <embed> 혹은 <applet>을 사용하여 임베디드 될 페이지의 부모를 지정 |
frame-src | frameSrc | <frame>과 <iframe>과 같은 요소를 사용하여 내포된 검색 컨텍스트를 로드하는데 사용 가능한 소스를 지정 |
img-src | imgSrc | 가능한 이미지와 favicon 소스 지정 |
manifest-src | manifestSrc | 가능한 manifest 소스 지정 |
media-src | mediaSrc | <audio>, <video>, <track> 요소들을 사용하여 로드할 수 있는 가능한 소스를 지정 |
object-src | objectTypes | <object>, <embed>, 그리고 <applet> |
plugin-types | pluginTypes | 로드할 수 있는 리소스 타입들을 제한하여 document에 포할될 수 있는 플러그인 설정을 제한 |
prefetch-src | prefetchSrc | prefetch 혹은 prerendered될 수 있는 소스들을 지정 |
report-to | reportTo | SecurityPolicyViolationEvent 실행 |
report-uri | reportUri | 컨턴츠 보안 정책 위반 시도를 보고하도록 user agent에 지시. 보고서는 HTTP POST 요청을 통해 지정된 URI로 JSON 문서로 전송 |
require-sri-for | requireSriFor | 페이지의 스크립트나 스타일에 SRI 사용을 요청 |
sandbox | sandbox | <iframe> sandbox 속성과 유사하게 리소스 요청에 대한 샌드박스 활성화 |
script-src | scriptSrc | 가능한 javascript 소스 지정 |
style-src | styleSrc | 가능한 style 소스 자정 |
upgrade-insecure-requests | upgradeInsecureRequests | 사이트의 모든 불안정한 URL(HTTP)들을 보안 URL(HTTPS)로 교체한 것처럼 처리하도록 use agent에 지시 |
worker-src | workerSrc | 가능한 Worker, SharedWorker, ServiceWorker 소스 지정 |
보다 자세한 사용법은 다음 사이트를 참고하길 바란다.
helemt을 이용한 웹 정보보안 실전 예제
서버 소프트웨어 숨기기
먼저 적용 전의 Response Headers 사진을 살펴보면, Server와 X-Powered-By 헤더가 적나라하게 노출되어 서버에서 사용중인 웹 서버 소프트웨어의 종류를 알 수 있게 된다.
만약 nginx/1.15.5의 취약점 정보가 공개된다면, 해커가 취약점을 이용해 쉽게 우리의 서버에 침투할 수 있다.
다음은 helmet 적용 후의 Response Headers 사진이다.
Server와 X-Powered-By 헤더의 정보가 사라지면서, 해커에게 제공될 수 있었던 서버에 대한 주요 정보가 대부분 사라졌음을 알 수 있다.
app.use(helmet.hidePoweredBy())
헤더를 보지못한다고해서 아무도 취약점을 악용할 수 없게되는 것이 아니다.
공격자로 하여금 사이트에 잠재적으로 취약한 기술이 있는지 파악하기 약간 어렵게함으로써 공격의 전체적인 진행을 약간 늦추거나 게으른 해커를 막는 정도의 효과를 발휘한다.
또한 x-powered-by 헤더를 지우는 대신, 기존값을 다른 것으로 바꿀수도 있다 (낚시)
app.use (helmet.hidePoweredBy ({setTo : 'PHP 4.2.0'}))
사용자의 HTTPS 연결 유지하기
HTTPS로 접속했을 때 HTTP로 가지 않게 하기위해 HSTS(HTTP Strict Transport Security)라는 기능을 지원한다.
HSTS 는 프로토콜 다운그레이드 공격(protocol downgrade attacks)과 쿠키 하이재킹(cookie hijacking)로부터 웹사이트를 보호하는데 도움이되는 웹 보안 정책이다.
HTTPS를 통해 웹 사이트에 액세스할 수 있는 경우 사용자의 브라우저에게 안전하지 않은 HTTP를 사용하지 않도록 요청할 수 있다.
Strict-Transport-Security 헤더를 설정함으로써 브라우저에게 지정된 시간 내에 향후 요청에 대해 HTTPS를 사용하도록 지시한다.
즉, 이것은 초기 요청 이후에 오는 요청에 적용되므로 이후 요청에 대해서만 효과가 있다.
app.use(helmet.hsts({
maxAge: 90*24*60*60, // 90일동안 https 사용하도록
force: true, // 기본 설정을 무시
includeSubDomains: true,
preload: true
}));
XSS (교차 사이트 스크립팅) 방어
XSS(교차 사이트 스크립팅) 공격은 게시판이나 웹 메일 등에 자바스크립트와 같은 스크립트 코드를 삽입 해 개발자가 고려하지 않은 기능이 작동하게 하는 공격이다.
<!-- 자바스크립트 링크 -->
<a href="javascript:alert('XSS')">XSS</a>
<!-- 이벤트 속성 -->
<img src="#" onerror="alert('XSS')">
app.use(helmet.xssFilter()); // X-XSS-Protection 보안헤더 작성.
XSS 공격을 막는 방법은 다음과 같다.
- 유효성 검사, 긴 입력 불가
- html태그 무력화 (escape 처리)
- encodeURI 함수를 사용해 입력받은 URL이 안전한지 확인
- html 속성에 사용자가 쌍따옴표(")를 넣을 수 없는지 확인. 또한 DB에 넣기 전 검사
- HTTP헤더로 완화
추가로, XSS 공격을 완벽히 방어하기 위해선 sanitze-html 모듈도 같이 써주어야 한다.
- helmet은 src나 href, 인라인 자바스크립트 XSS 공격을 막는 용도로
- sanitize-html는 <script> 태그 작성 공격을 막는 용도로 쓴다.
클릭재킹 방어
클릭재킹은 사용자의 클릭을 가로채 다른 것을 누르게 하는 방식이다.
제한적인 X-Frame-Option을 보내서 브라우저가 더이상 이 프레임을 로드하지 않도록 해 대응할 수 있다.
// X-Frame-Option
app.use(helmet.frameguard("deny"));
X-Frame-Option
- deny : 프레임 내에 우리 사이트를 넣지 못하게 함
- sameorigin : 다른 사람이 프레임 내에 우리 사이트를 넣지 못하지만 우리 사이트에서는 허용
- allow-from : 지정한 사이트에서 프레임을 표시할 수 있음
MIME 스니핑 차단
MIME(Multipurpose Internet Mail Extensions) 타입이 없을 때, 혹은 클라이언트가 타입이 잘못 설정됐다고 판단한 어떤 다른 경우에, 브라우저들은 MIME 스니핑(sniffing)을 시도할 수도 있는데, 이는 리소스를 훑어보고 정확한 MIME 타입을 추측하는 것이다.
많은 브라우저에서 파일 형식이 자바스크립트용이 아니라면 실행하도록 허용하는데, 만약 굉장히 위험한 파일이 html과 유사하다면 이 파일을 html로 해석할 것이다.
따라서 MIME 스니핑을 X-Content-Type-Options 헤더를 nosniff라는 옵션으로 설정해 차단할 수 있다.
// Helmet을 사용한 HTTP 헤더를 설정
app.use(helmet.noSniff());
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.